Интерпретатор Сценариев C++

Всем привет. Я написал простой интерпретатор, конечно не конкурент 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++: ноль — самый высокий, чем выше значение приоритета, тем позже будет выполнен оператор.

    Порядок выполнения операторов с одинаковым приоритетом — слева направо.

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



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

  • Состав.

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

    $myStruct.fieldA = 1; $myStruct.fieldB = 2; $myStruct.fieldC = 3;

    ( обновление : добавлен библиотека основных функций , есть тип Struct с обычным доступом к полям через '.

    ') Теперь остается вопрос: как передать структуру функции.

    Здесь можно напрямую передать имя структуры, а в функцию взять конкретные поля:

    ir.addFunction("summFieldsOfMyStruct", [&ir](const vector<string>& args) ->string{ if (!args.empty()){ string& myStruct = args[0]; auto vars = ir.allVariables(); int fA = isNumber(vars[myStruct + ".

    fieldA"]) ? stoi(vars[myStruct + ".

    fieldA"]) : 0; int fB = isNumber(vars[myStruct + ".

    fieldB"]) ? stoi(vars[myStruct + ".

    fieldB"]) : 0; int fC = isNumber(vars[myStruct + ".

    fieldC"]) ? stoi(vars[myStruct + ".

    fieldC"]) : 0; return to_string(fA + fB + fC); } return "0"; }, 1);



    $s = summFieldsOfMyStruct(myStruct);

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

    Макрос может быть предопределен в основном коде:

    ir.setMacro("#myStruct", "myStruct;");

    В этом случае наличие макроса будет проверено на этапе разбора скрипта:

    $s = summFieldsOfMyStruct(#myStruct);

  • Множество.

    Для массива также не существует специального ключевого слова.

    Можно сделать то же, что и со структурой — создать переменную для каждого поля массива.

    ( обновление : добавлен библиотека основных функций , существует тип Vector (под капотом std::vector) с обычными методами: 'push_back', 'size', 'clear' и т.д., вызывая методы через '.

    ')

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

  • пока (состояние){тело}.

    Выполняет циклическую последовательность выражений (далее тело цикла) в зависимости от результата выполнения условия.

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

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

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



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

  • если (состояние){тело}.

    Выполняет последовательность выражений один раз в зависимости от результата условия.



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

  • еще {тело}.

    Выполняет последовательность выражений один раз, если предыдущее условие не выполнено.



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

  • еще если (состояние){тело}.

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



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

  • перерыв; .

    Прерывает текущий цикл.

    продолжать; .

    Перезапускает текущий цикл.



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

    Во всех управляющих структурах можно обойтись без фигурных скобок, если тело состоит из одного выражения:

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

  • #макрос имя {тело}.

    Декларация макроса.

    #имя; .

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



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

    Макросы можно размещать в параметрах функции:

    #macro myMc{ $c + $b }; myFunc(#myMc);

  • перейти к l_name; .

    Переместите метку вверх или вниз в скрипте.

    Должен быть единственным оператором в выражении.

    л_имя: .

    Маркер, к которому вы можете перейти.

    Метка должна начинаться с символа «l_» (el и подчеркивание) и заканчиваться символом «:».



    $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.: Я это уже давно написал, получилось не очень.

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

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

Наконец-то я нажал кнопку, может быть, кому-нибудь еще когда-нибудь пригодится.

обновление : добавлен библиотека основных функций Теги: #программирование #открытый исходный код #C++ #скрипты #скриптовые языки

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.