Интерпретатор скрипта на С++

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Всем привет.

Написал простой интерпретатор, конечно не конкурент lua, но тоже может пригодиться.
Кому интересно прошу.

Сразу пример, что получилось:

stringstream ss;
ss << "$a = 5;"
      "$b = 2;"
      "while($a > 1){"
      "  $a -= 1;"
      "  $b = summ($b, $a);"
      "  if($a < 4){"
      "    break;"
      "  }"
      "}"
      "$b";
string res = ir.cmd(ss.str()); // 9

Что хотелось


Идея была в том, чтобы полностью отделить функционал (пользовательские операторы и функции) от самого языка скрипта, ограничиться минимумом ключевых слов (всем известных), ну и сделать интерпретатор и язык компактным и удобным для использования.

Что получилось


Скриптовый язык вышел простой и ограниченный конечно.

Состоит из трех компонентов — переменные, выражения и функции, и нескольких базовых ключевых слов. Тип значений для всех компонентов — строка.

Нет жестко забитых функций и операторов, все добавляет программист перед запуском скрипта.

Как это работает


Первый этап — парсинг скрипта. На этом этапе идет синтаксический разбор текста скрипта: выделяются ключевые слова, операторы, функции.

Явно АСД не создается, но косвенно появляется (можно сказать в плоскости массива) в виде очереди операций, которые должны быть выполнены последовательно, то есть другими словами, все встречающиеся сущности попадают в массив операций сразу в нужном порядке выполнения.

Все ошибки написания скрипта находятся на этом этапе.

Второй этап — выполнение скрипта. Здесь идет проход по массиву операций, с последовательным выполнением каждой.

Внутри все построено на рекурсивном вызове функций и проверках условий вызова.

Основные компоненты скрипта:

  • Переменная. Любая последовательность символов в коде скрипта начинающаяся с '$', считается переменной.

    Переменные используются для хранения промежуточных результатов вычислений, и для передачи параметров в функции. Имеют глобальную область видимости.

    Объявляются и используются только в коде скрипта, сразу использовать без объявления можно (значение по умолчанию — пустая строка):

    $c = 5 + 6;
    summ($c, 6);
    

    Ко всем переменным в скрипте можно обращаться (и изменять их при необходимости) из основного кода, например, в функции:

    Intrerpreter ir;
    ir.addFunction("summScriptVars", [&ir](const vector<string>& args) ->string {
        int res = 0;
        for (auto& v : ir.allVariables()) {
          if (isNumber(v.second)) res += stoi(v.second);
        }
        return to_string(res);
      });
    
  • Выражение. Состоит из переменных, операторов и вызовов функций.

    Обязательно должно заканчиваться символом ';'.

    Может быть параметром функции, в этом случае его не нужно закрывать символом ';'.

    В выражениях могут использоваться скобочки для повышения приоритета операций над переменными. О приоритете ниже.

    $b = 4; 
    $c = 5 + $b + 3 - 7; 
    $a = $b * (3 + $c) + summ($a, $b, $c + 1);
    
  • Функция. Любые функции создаются на уровне основного кода, в скрипте только используются. Функция принимает массив параметров, возвращает строку как результат работы.
    Сначала функцию нужно определить и добавить в основном коде:

    Interpreter ir;
    ir.addFunction("summ", [](const vector<string>& args) ->string {
        int res = 0;
        for (auto& v : args) {
          if (isNumber(v)) res += stoi(v);
        }
        return to_string(res);
      });
    

    В скрипте функция вызывается по имени, параметры передаются в скобочках, как обычно:

    $b = summ($b, $a);
    

    Функция может принимать другие функции и выражения:

    $b = 1;
    $c = summ($b, summ($b + 5, $b + $b - 1), 4);
    $a = $c - summ($b, 3);
    
  • Оператор. Любая последовательность символов в коде скрипта, заранее определенная в основном коде, считается оператором.

    Сначала оператор нужно определить и добавить в основном коде:

     Interpreter ir;
     ir.addOperator("+", [](string& leftOpd, string& rightOpd) ->string {
        if (isNumber(leftOpd) && isNumber(rightOpd))
          return to_string(stoi(leftOpd) + stoi(rightOpd));
        else
          return leftOpd + rightOpd;
      }, 1);
     
      ir.addOperator("==", [](string& leftOpd, string& rightOpd) ->string {
        return leftOpd == rightOpd? "1" : "0";
      }, 2);
    
      ir.addOperator("=", [](string& leftOpd, string& rightOpd) ->string {
        leftOpd = rightOpd;
        return leftOpd;
      }, 17);
    

    При создании оператора помимо определения нужно задать приоритет.

    Приоритет работает так же как в С++: нулевой наивысший, далее чем больше значение приоритета, тем позже будет выполнен оператор. Порядок выполнения операторов с одинаковым приоритетом — слева направо.

    Операторы используются в выражениях.

    $c = 5 + 6;
    $b = 2;
    $a = $c + 5; 
    $c = summ($a + 5 / $b);
    


Теперь опишу остальные ключевые слова языка скрипта, в основном это управляющие конструкции.

  • while(condition){body}. Выполняет циклически последовательность выражений (далее, тело цикла) в зависимости от результата выполнения условия.

    Условие заключается в скобочки '()' и, как и в любом языке, рассчитывается на каждой итерации цикла.

    Условие считается выполненным, если результат расчета условия для численного значения не равен 0, для строкового значения — не пустая строка (численное значение — значит, что строка может быть преобразована в целое число).

    Тело цикла заключается в фигурные скобки '{}', и состоит из неограниченной последовательности выражений и управляющих конструкций (то есть в теле цикла могут быть другие циклы).

    $c = 1;
    $b = 4; 
    while($b > 0){
      $c *= $b; 
      $b -= 1;
    }
    

  • if(condition){body}. Выполняет однократно последовательность выражений в зависимости от результата выполнения условия.

    $c = 1;
    $b = 4; 
    if(($b - 4) == 0){
      $c = $b;
    }
    

  • else{body}. Выполняет однократно последовательность выражений, если не было выполнено предыдущее условие.

    $c = 1;
    $b = 4; 
    if(($b - 3) == 0){
      $c = $b;
    }
    else{
      $b = $c;
    }
    

  • elseif(condition){body}. Выполняет однократно последовательность выражений, если не было выполнено предыдущее условие и выполняется текущее условие.

    $c = 1;
    $b = 4; 
    if(($b = $b - 3) == 0){
      $c = $b;
    }
    elseif($c == summ($b)){
      $b = $c;
    }
    

  • break;. Выполняет прерывание текущего цикла.
    continue;. Начинает заново текущий цикл.

    $b = 4; 
    while($b > 0){
      $b = rand(10);
      if ($b == 3){
        continue;
      }
      if ($b == 2){
        break; 
      }
    }
    

  • #macro name{body}. Объявление макроса.

    #name;. Вставка тела макроса далее в коде.

    Под макросом здесь имеется в виду код, который повторяется много раз в скрипте, и можно его заменить именем.

    #macro myMc{
     $c = 1;
     $b = 4; 
    };
    $d = 5;
    #myMc;
    

  • goto l_name;. Перемещение на метку вверх или вниз по скрипту. Должен быть единственным оператором в выражении.

    l_name:. Метка, на которую можно переместиться.

    Метка обязательно должна начинаться с 'l_' (элл и нижнее подчеркивание) и заканчиваться ':'.

    $a = 5; 
    while($a > 0){
      $a -= 1;
      if ($a == 2){
        goto l_myLabel;
      }  
    }
    l_myLabel: $a;
    

    На метку можно перемещаться из основного кода, например, в функции скрипта вызвать специальную функцию 'gotoOnLabel' (это конечно грязный хак, специально для месье, которые знают..):

    Interpreter ir;
    ir.addFunction("myJump", [&ir](const vector<string>& args) ->string {
        if (!args.empty())
          ir.gotoOnLabel(args[0]);
        }
        return "";
      });
    


Как использовать и где может быть полезен


Предлагается использовать как код, то есть добавлять в свой проект файл исходного кода интерпретатора, он всего один. Заголовочный файл тоже единственный.

Может использоваться в простых случаях, когда не хочется подключать что-то внешнее, но нужно дать пользователю возможность интерактивно влиять на ход выполнения ПО.

Либо в случаях, когда не хочется давать пользователю в руки весь арсенал скриптовых языков, а ограничиться простым набором команд.

Еще можно попробовать построить RPC на его основе.

Что дальше, что планируется нового


Если коротко, то ничего.

Проект в ширину расти не будет, никаких новых ключевых слов, структур не планируется добавлять. Не хочу, чтобы он разбух и превратился в еще один птичий язык никому не нужный, потому что разбираться долго, что там наворочено, а здесь все пока прозрачно более-менее, можно быстро понять брать-нет.

Только поддержка, правка багов, возможно, стоит добавить несколько заранее определенных пользовательских функций, отдельно.

Распространяется свободно, лицензия MIT

Спасибо.

P.S.:

Я писал его ранее когда-то давно, там получилось не очень. Тут после одного письма пользователя, решил все это дело переписать по нормальному.

Вот думал, публиковать-нет статью на эту поделку, по сути тривиальный баян в принципе, и есть уже мастодонты всякие в этой нише.

Нажал все-таки кнопочку, может еще кому-то пригодится когда.
Источник: https://habr.com/ru/post/533998/


Интересные статьи

Интересные статьи

Как быстро определить, что на отдельно взятый сайт забили, и им никто не занимается? Если в подвале главной страницы в копирайте стоит не текущий год, а старый, то именно в этом году опека над са...
Как широко известно, с 1 января 2017 года наступает три важных события в жизни интернет-магазинов.
Получить трафик для интернет-магазина сегодня не проблема. Есть много каналов его привлечения: органическая выдача, контекстная реклама, контент-маркетинг, RTB-сети и т. д. Вопрос в том, как вы распор...
1С Битрикс: Управление сайтом (БУС) - CMS №1 в России по версии портала “Рейтинг Рунета” за 2018 год. На рынке c 2003 года. За это время БУС не стоял на месте, обрастал новой функциональностью...
На сегодняшний день у сервиса «Битрикс24» нет сотен гигабит трафика, нет огромного парка серверов (хотя и существующих, конечно, немало). Но для многих клиентов он является основным инструментом ...