Пишем интерпретатор на PascalABC.NET. Синтаксический анализатор. Часть 1. Базовый класс ParserBase
Этот текст - продолжение публикации https://teletype.in/@pascalabcnet/Lex4, в которой описывалось, как постороить лексический анализатор языка программирования.
Следующая статья - https://teletype.in/@pascalabcnet/Parse2
Приступим к написанию синтаксического анализатора (парсера) простого языка программирования.
Описание языка
Наш язык будет содержать операторы присваивания, выражения с арифметическими и логическими операциями, условный оператор, оператор 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 проверяет, что тип текушего токена совпадает с одним из данных типов, в случае успеха возвращает текущий токен и продвигается к следующему, а иначе выбрасывает ошибку.