Пишем интерпретатор на PascalABC.NET. Синтаксический анализатор. Часть 1. Базовый класс ParserBase
Этот текст - продолжение публикации https://teletype.in/@pascalabcnet/Lex4, в которой описывалось, как постороить лексический анализатор языка программирования.
Приступим к написанию синтаксического анализатора (парсера) простого языка программирования.
Описание языка
Наш язык будет содержать операторы присваивания, выражения с арифметическими и логическими операциями, условный оператор, оператор while, составной оператор и оператор вывода (точнее, оператор вызова стандартной функции вывода). Вот - простейшая программа на нашем языке:
i = 1; sum = 0; n = 100000000; while i<100000000 do { sum += 1/i; i += 1 }; Print(sum)
Операторы будем разделять точкой с запятой, составной оператор будем заключать в операторные скобки в стиле C.
В данной статье опишем класс базового парсера, от которого будут наследоваться классы парсеров конкретных языков. Этот класс будет инициализироваться лексером и содержать ряд сервисных методов, которые сможет вызывать любой конкретный парсер.
Код класса ParserBase
type ParserBase = class private lex: Lexer; protected curToken: Token; public constructor (lex: Lexer); begin Self.lex := lex; Advance; end; /// Вернуть текущий токен и перейти к следующему function Advance: Token; begin Result := curToken; curToken := lex.NextToken; end; /// Проверить, что тип текушего токена совпадает с одним из данных типов function At(params types: array of TokenType): boolean; begin Result := types.Any(typ -> PeekToken.typ = typ); end; procedure Check(params types: array of TokenType); begin if not At(types) then ExpectedError(types) end; /// Проверить, что тип текушего токена совпадает с одним из данных типов /// В случае успеха перейти к следующему токену function IsMatch(params types: array of TokenType): boolean; begin Result := At(types); if Result then Advance; end; // Проверить, что тип текушего токена совпадает с одним из данных типов // В случае успеха вернуть текущий токен и продвинуться к следующему, // а иначе выбросить ошибку function Requires(params types: array of TokenType): Token; begin if At(types) then Result := Advance else ExpectedError(types) end; /// Мы - в конце файла function IsAtEnd: boolean := PeekToken.typ = TokenType.Eof; /// Мы - в конце файла function PeekToken := curToken; function CurrentToken := curToken; procedure ExpectedError(params types: array of TokenType) := SyntaxError(#39;{types.JoinToString('' или '')} ожидалось, но {PeekToken.Typ} найдено',PeekToken.Pos); end;
Методы класса ParserBase
Рассмотрим каждый метод по отдельности.
В конструкторе ParserBase инициализируется лексер, представленный полем класса, и осуществляется пропуск первой лексемы вызовом метода Advance.
Метод Advance возвращает текущую лексему, находящуюся в поле curToken и считывает в поле curToken следующую лексему вызовом метода lex.NextToken.
Метод CurrentToken возвращает текущую прочитанную лексему, хранящуюся в переменной curToken. Метод PeekToken является его синонимом.
Метод IsAtEnd возвращает ситуацию конца файла, что означает, что в данный момент мы находимся на токене Eof:
function IsAtEnd := PeekToken.typ = TokenType.Eof;
Метод At возвращает True если тип текущего токена совпадает с одним из типов - параметров At. Например,
At(TokenType.Plus,TokenType.Minus)
возвращает True только если мы находимся на токене + или -.
Самыми интересными являются методы Check, IsMatch и Requires, вызывающие метод At и немного отличающиеся по функциональности.
Метод Check проверяет, что тип текущего токена совпадает с одним из данных типов и в случае неуспеха выбрасывает ошибку.
Метод IsMatch проверяет проверяет, что тип текущего токена совпадает с одним из данных типов и в случае успеха возвращает True и переходит к следующему токену, а при неуспехе возвращает False.
Метод Requires проверяет, что тип текушего токена совпадает с одним из данных типов, в случае успеха возвращает текущий токен и продвигается к следующему, а иначе выбрасывает ошибку.