Ответ 1
Если ваш язык является обязательным /c -like, общий сценарий начинается с того, что верхняя иерархия разделяется на 2 супертипа:
- Выражение
- Заявление
Программа представляет собой список операторов, который является самим выражением.
Вероятно, вы захотите иметь один класс для типа оператора, который расширяет базовый класс оператора.
Типичный сценарий выглядит так:
- блок операторов (список операторов)
- ite (если тогда еще)
- for (цикл for с его списком инструкций инициализации, выражение проверки, инструкции инкремента и блок
- while (похоже, но только проверить выражение
- объявление переменной
- (включая + = - = ++ - вы можете обернуть все в один класс с помощью поля оператора, lval и rval)
- Функциональный вызов (void one)
Для выражений:
- Bop (двоичная операция, все, что имеет 2 операнда и 1 оператор i.e. + - */% | и && || == <
- Uop (унарная операция, все, что имеет 1 операнд и 1 оператор, т.е. ~!)
- Вызов функции (непустые)
- Условное выражение (exp? true val: false val)
Хорошо, что эти 2 абстракции (выражения и утверждения) заключаются в том, что во всех ваших классах вы будете иметь абстрактные типы и, например, сможете посетить AST с шаблоном посетителя.
Например, некоторые классы будут выглядеть так (псевдокод):
class Ite extends Statement {
Expression condition;
Statement ifBranch;
Statement elseBranch;
}
class Bop extends Expression {
BOperator operator; // +, -. * or whatever
Expression left; // Left operand
Expression right; // Right operand
}
class StatementBlock extends Statement {
List<Statement> statements;
}
class Assignment extends Statement {
AOperator assignOp; // = += -= etc.
LVal lvalue; // The lvalue cannot be an arbitrary expression, you will usually have a specific type for it
Expression rvalue; // Right value
}
Кроме того, вам нужно будет каким-то образом представить типы (для AST достаточно просто статических типов, если вы планируете реализовать и некоторые внешние серверы, вам понадобятся и некоторые динамические типы).
Статические типы обычно могут быть указаны с некоторыми перечислениями, если вы не планируете поддерживать массивы фиксированного размера, которым нужна информация о размере. Если вам нужны массивы фиксированного размера с размером, вы можете реализовать один класс для типа и иметь тип массива, содержащий дополнительную информацию о размере.
enum Type {
CHAR,
SHORT,
INT,
LONG,
FLOAT,
DOUBLE,
ARRAY
}
class Float extends StaticType {
final Type type = Type.FLOAT;
}
class Array extends StaticArray {
final Type type = Type.ARRAY;
int size;
}
Затем вы создадите экземпляр экземпляра StaticType для каждого типа в AST, например, когда пользователь объявит переменную. Вы также сможете использовать одну и ту же иерархию, если вы планируете также проводить статическую проверку типов в будущем.
Что касается запуска/интерпретации кода в форме AST, вам понадобится память, в которой будет храниться стек/куча, содержащая информацию о памяти во время выполнения. В этот момент вам нужно будет хранить значения вместе со своей информацией о типе.