Всем привет. Я написал простой интерпретатор, конечно не конкурент 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++ #скрипты #скриптовые языки
-
Условный Рефлекс
19 Oct, 24 -
4 Шага Для Создания Профиля Кандидата
19 Oct, 24 -
Скромный Комплект Электронных Книг
19 Oct, 24 -
Развитие По: 2. Наследование
19 Oct, 24 -
Магнитные Ссылки В Firefox Под Linux
19 Oct, 24 -
Мой Бизнес - Онлайн Бухгалтерия Для Ип
19 Oct, 24