Русский
Русский
English
Статистика
Реклама

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

Всем привет.

Написал простой интерпретатор, конечно не конкурент 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.:

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

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

Нажал все-таки кнопочку, может еще кому-то пригодится когда.
Источник: habr.com
К списку статей
Опубликовано: 24.12.2020 20:04:36
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Open source

Программирование

C++

Скрипт

Скриптовые языки

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru