Извините за сложное название, но это немного сложно объяснить только одним предложением.
Так что я пишу простой интерпретируемый язык, чтобы помочь с некоторыми вещами, которые я часто делаю. У меня настроен лексер, который подключается к генератору абстрактного синтаксического дерева.
Абстрактное синтаксическое дерево выплевывает выражения. (Который я передаю с помощью unique_ptrs). Есть несколько типов выражений, которые получены из этого базового класса, в том числе:
- Цифры
- Переменные
- Вызовы функций / прототипы
- Двоичные операции
и т.д.. Каждый производный класс содержит информацию, необходимую ему для этого выражения, то есть переменные содержат std :: string их идентификатора, двоичные операции содержат unique_ptrs слева и справа, а также символ оператора.
Теперь это работает отлично, и выражения анализируются так, как и должно быть.
This is what an AST would look like for 'x=y*6^(z-4)+5'
+--Assignment (=)--+
| |
Var (x) +--------BinOp (+)----+
| |
5 +------------BinOp (*)---+
| |
+---------BinOp (^)-------+ Var (y)
| |
Num (6) +------BinOp (-)-----+
| |
Var (z) Num (4)
Проблема возникает при попытке отделить AST от интерпретатора . Я хочу сохранить его изолированным на случай, если захочу обеспечить поддержку компиляции в будущем или что-то еще. Плюс AST уже становится достаточно сложным, и я не хочу добавлять к нему. Я только хочу, чтобы в AST была информация о том, как взять токенов и преобразовать их в правильном порядке в дерево выражений.
Теперь интерпретатор должен иметь возможность просматривать этот список выражений сверху вниз и рекурсивно оценивать каждое подвыражение, добавлять определения в память, оценивать константы, назначать определения их функциям и т. Д. Но каждый Оценка должна возвращать значение, чтобы я мог рекурсивно пройти по дереву выражений .
Например, выражение бинарной операции должно рекурсивно вычислять левую и правую стороны, а затем выполнить сложение двух сторон и вернуть его.
Теперь проблема в том, что AST возвращает указатели на базовый класс , Expr , а не на производные типы. Вызов getExpression возвращает следующее выражение независимо от его производного типа, что позволяет мне легко рекурсивно оценивать двоичные операции и т. Д. Чтобы интерпретатор мог получить информацию об этих выражениях (например, числовое значение или идентификатор), я бы в основном динамически приводить каждое выражение и проверять, работает ли оно, и мне придется делать это повторно. Другим способом было бы сделать что-то вроде шаблона Visitor - Expr вызывает интерпретатор и передает ему this , что позволяет интерпретатору иметь несколько определений для каждого производного типа. Но опять же, интерпретатор должен вернуть значение !
Вот почему я не могу использовать шаблон посетителя - мне нужно возвращать значения, которые бы полностью связывали AST с интерпретатором.
Я также не могу использовать шаблон стратегии, потому что каждая стратегия возвращает совершенно разные вещи. Например, стратегия интерпретатора будет слишком отличаться от стратегии LLVM.
Я в полной растерянности, что здесь делать. Одним из действительно глупых решений было бы буквально иметь перечисление каждого типа выражения в качестве члена базового класса expr, и интерпретатор мог бы проверить тип и затем сделать соответствующий тип-тип. Но это безобразно. Действительно некрасиво.
Какие у меня есть варианты? Спасибо!