Пишем интерпретатор на PascalABC.NET. Синтаксический анализатор. Часть 2. Абстрактное синтаксическое дерево (AST)
Этот текст - продолжение публикации https://teletype.in/@pascalabcnet/Parse2, в которой описывался парсер простого языка программирования Simple.
Чтобы код, приведенный по предыдущей ссылке, работал, нам необходим еще один модуль - ASTNodes. Это - модуль, содержащий узлы абстрактного синтаксического дерева программы AST, которое создается как результат парсинга.
Все узлы синтаксического дерева - наследники узла Node:
type Node = class public pos: Position; end;
Этот класс по-существу хранит только позицию начала конструкции pos, тип которой Position описан в модуле Common:
type Position = auto class auto property Line: integer; auto property Col: integer; end;
Следующие два важных класса - это ExprNode - предок всех выражений и StatementNode - предок всех операторов. Они не имеют собственных полей и нужны лишь для того чтобы мы от них наследовали конкретные узлы. Такие классы, которые нужны только для создания потомков, а экземпляры самих классов не создаются, называются абстрактными. В PascalABC.NET для абстрактных классов имеется специальное ключевое слово abstract:
type Node = abstract class public pos: Position; end; ExprNode = abstract class(Node) end; StatementNode = abstract class(Node) end;
Теперь рассмотрим несколько конкретных классов.
Класс BinOpNode сохраняет левый и правый операнд и знак операции:
BinOpNode = class(ExprNode) public Left,Right: ExprNode; Op: char; constructor (Left,Right: ExprNode; Op: char; pos: Position) := (Self.Left,Self.Right,Self.Op,Self.pos) := (Left,Right,Op,pos); end;
Классы IntNode и DoubleNode являются узлами, представляющими целые и вещественные константы в коде программы. Они хранят помимо позиции значение этой константы:
IntNode = class(ExprNode) public Value: integer; constructor(Value: integer; pos: Position) := (Self.Value,Self.pos) := (Value,pos); end; DoubleNode = class(ExprNode) public Value: real; constructor(Value: real; pos: Position) := (Self.Value,Self.pos) := (Value,pos); end;
Класс IdNode представляет идентификатор:
IdNode = class(ExprNode) public Name: string; constructor(name: string; pos: Position) := (Self.Name,Self.pos) := (name,pos); end;
Далее приведем код всех узлов используемого нами синтаксического дерева. Каждый узел хранит позицию в коде т.к. наследуется от узла Node, свои подузлы - наследники Node (если они есть) и дополнительные поля, связанные с конкретным узлом:
type StatementListNode = class(StatementNode) public lst: List<StatementNode> := new List<StatementNode>; procedure Add(st: StatementNode) := lst.Add(st); end; ExprListNode = class(Node) public lst: List<ExprNode> := new List<ExprNode>; procedure Add(ex: ExprNode) := lst.Add(ex); end; IntNode = class(ExprNode) public Value: integer; constructor(Value: integer; pos: Position) := (Self.Value,Self.pos) := (Value,pos); end; DoubleNode = class(ExprNode) public Value: real; constructor(Value: real; pos: Position) := (Self.Value,Self.pos) := (Value,pos); end; IdNode = class(ExprNode) public Name: string; constructor(name: string; pos: Position) := (Self.Name,Self.pos) := (name,pos); end; AssignNode = class(StatementNode) public Ident: IdNode; Expr: ExprNode; constructor(Ident: IdNode; Expr: ExprNode; pos: Position) := (Self.Ident,Self.Expr,Self.pos) := (Ident,Expr,pos); end; AssignPlusNode = class(StatementNode) public Ident: IdNode; Expr: ExprNode; constructor(Ident: IdNode; Expr: ExprNode; pos: Position) := (Self.Ident,Self.Expr,Self.pos) := (Ident,Expr,pos); end; IfNode = class(StatementNode) public Condition: ExprNode; ThenStat,ElseStat: StatementNode; constructor(Condition: ExprNode; ThenStat,ElseStat: StatementNode; pos: Position) := (Self.Condition,Self.ThenStat,Self.ElseStat,Self.pos) := (Condition,ThenStat,ElseStat,pos); end; WhileNode = class(StatementNode) public Condition: ExprNode; Stat: StatementNode; constructor(Condition: ExprNode; Stat: StatementNode; pos: Position) := (Self.Condition,Self.Stat,Self.pos) := (Condition,Stat,pos); end; ProcCallNode = class(StatementNode) public Name: IdNode; Pars: ExprListNode; constructor(Name: IdNode; Pars: ExprListNode; pos: Position) := (Self.Name,Self.Pars,Self.pos) := (Name,Pars,pos); end; FuncCallNode = class(ExprNode) public Name: IdNode; Pars: ExprListNode; constructor(Name: IdNode; Pars: ExprListNode; pos: Position) := (Self.Name,Self.Pars,Self.pos) := (Name,Pars,pos); end;
Если мы разместим все узлы в модуле ASTNodes и подключим его в модуле ParseUnit, то следующий код будет строить абстрактное синтаксическое дерево:
uses LexUnit,ParseUnit,Common; begin var text := 'i = 1; sum = 0; while i<100000000 do {sum += 1/i; i += 1}; print(sum)'; var lex := new Lexer(text); try var par := new Parser(lex); var progr := par.MainProgram; except on e: LexerException do OutputError('Lexer error:',e,lex.Lines); on e: SyntaxException do OutputError('Parser error:',e,lex.Lines); end; end.
Всего наше приложение занимает около 650 строк. Оно пока ничего не делает кроме построения AST - дерева. Однако именно AST-дерево - отправная точка для дальнейших преобразований.
В следующей статье мы напишем простой интерпретатор по синтаксическому дереву для нашего языка.