Проект компилятора / интерпретатора: должны ли встроенные методы иметь свой собственный узел или должна использоваться таблица поиска? - PullRequest
1 голос
/ 13 апреля 2020

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

Одним из примеров метода, который я реализую, является * Метод 1003 *, который выводит на консоль, точно так же, как метод python print() и Java System.out.println().

Однако до меня дошло, что существует несколько способов реализации эти встроенные методы. Я уверен, что есть еще много, но я определил 2 жизнеспособных пути достижения этого, и я пытаюсь установить sh, какой путь является лучшей практикой. Для контекста ниже представлены различные слои, которые я использую в моем интерпретаторе, который свободно основан на https://www.geeksforgeeks.org/introduction-of-compiler-design/ и других учебных пособиях, с которыми я сталкивался.

  1. Lexer
  2. Parser
  3. Semanti c Анализатор
  4. Интерпретатор / генератор кода

1. Создание узла AST для каждого отдельного встроенного метода.

Этот метод влечет за собой программирование синтаксического анализатора для генерации узла для каждого отдельного метода. Это означает, что для каждого метода будет существовать уникальный узел. Например:

Анализатор будет искать узел, когда в лексере будет обнаружен токен TPRINT.

print : TPRINT TLPAREN expr TRPAREN {$$ = new Print($3);}
      ;

И вот как выглядит класс печати. ​​

class Print : public Node {
public:
    virtual VariableValue visit_Semantic(SemanticAnalyzer* analyzer) override;
    virtual VariableValue visit_Interpreter(Interpreter* interpreter) override;
    Node* value;
    Print(Node* cvalue) {
        value = cvalue;
    }
}

Оттуда я определяю методы visit_Semantic и visit_interpreter и посещаю их, используя рекурсию из верхнего узла.

Я могу вспомнить пару преимуществ / недостатков использования этого метода :

Преимущества

  • Когда генератор кода обходит дерево и посещает метод visit_interpreter узла Print, он может сразу же выполнить ответ непосредственно, как он запрограммирован в нем. методы посещения.

Недостатки

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

2. Создание узла Generi c AST для узла вызова метода, а затем с помощью таблицы поиска определить, какой метод вызывается.

Это включает создание одного узла c узла MethodCall и грамматики. чтобы определить, был ли вызван метод, с некоторым уникальным идентификатором , например строкой для метода, на который он ссылается. Затем, когда вызывается метод MethodCall visit_Interpreter или visit_Semantic, он ищет в таблице код для выполнения.

methcall : TIDENTIFIER TLPAREN call_params TRPAREN {$$ = new MethodCall($1->c_str(), $3);}
         ;

MethodCall Node. Здесь уникальный идентификатор std::string methodName:

class MethodCall : public Node {
public:
    virtual VariableValue visit_Semantic(SemanticAnalyzer* analyzer) override;
    virtual VariableValue visit_Interpreter(Interpreter* interpreter) override;
    std::string methodName;
    ExprList *params;
    MethodCall(std::string cmethodName, ExprList *cparams) {
        params = cparams;
        methodName = cmethodName;
    }
};

Преимущества:

  • Один универсальный c грамматика / узел для всех вызовов метода. Это делает его более читабельным

Недостатки:

  • В какой-то момент уникальный идентификатор std::string methodName необходимо сравнить в таблице поиска, чтобы определить ответ на него. Это не так эффективно, как непосредственное программирование в ответ на методы посещения узла.

Какая практика является лучшим способом обработки методов в компиляторе / интерпретаторе? Существуют ли разные методы, которые лучше, или есть какие-то другие недостатки / преимущества, которые мне не хватает?

Я довольно новичок в разработке компиляторов / интерпретаторов, поэтому, пожалуйста, попросите меня уточнить, есть ли у меня некоторая терминология неправильно.

Ответы [ 2 ]

2 голосов
/ 06 мая 2020

Насколько я понимаю, вы должны где-то разделить вещи на методы. Вопрос в том, хотите ли вы реализовать это как часть определения синтаксического анализатора (решение 1) или вы хотите реализовать это на стороне C ++ (решение 2).

Лично я бы предпочел оставить определение синтаксического анализатора просто и переместите эту логику c в сторону C ++, то есть решение 2.

С точки зрения времени выполнения решения 2, я бы не стал сильно беспокоиться об этом. Но, в конце концов, это зависит от того, как часто вызывается этот метод и сколько у вас идентификаторов. Наличие всего лишь нескольких идентификаторов отличается от сравнения, скажем, сотен строк способом «иначе, если».

Вы могли бы сначала реализовать это простым прямым способом, то есть сопоставить строковые идентификаторы с помощью «еще, если» и посмотрите, не столкнетесь ли вы с проблемами во время выполнения.

Если у вас возникнут проблемы во время выполнения, вы можете использовать функцию ha sh. «Хардкорным» способом было бы реализовать оптимальную функцию ha sh самостоятельно и проверить оптимальность функции ha sh в автономном режиме, поскольку вы знаете набор строковых идентификаторов. Но для вашего приложения это, вероятно, было бы излишним или для академических целей c, и я бы рекомендовал просто использовать unordered_map из STL (который использует хеширование под капотом, см. Также Как std :: unordered_map реализован ) для сопоставления ваших строковых идентификаторов с индексными номерами, чтобы вы могли реализовать свою таблицу переходов с эффективной switch операцией над этими индексными номерами.

1 голос
/ 27 апреля 2020

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

...