Написание простого DSL компилятора на Delphi (5. Фреймворк)

Перевод поста Writing a Simple DSL Compiler with Delphi (5. Framework)open in new window.

Эта статья представляет собой описание фреймворка используемого для проекта моего языка программирования. Если вы только начинаете читать эту серию, то я бы рекомендовал вам начать с этого постаopen in new window.

Сейчас у нас есть работающий парсерopen in new window который преобразует строку кода в абстрактное синтаксическое деревоopen in new window. Однако ещё не время писать о самой интересной части — компиляторе — сначала мы должны сделать интеграцию и тестирование.

Мой игрушечный компилятор использует очень простой фреймворк доступ к которому производится через интерфейс ISimpleDSLCompiler (модуль SimpleDSLCompiler). Уместная часть интерфейса показана ниже:

type
  ISimpleDSLCompiler = interface ['{7CF78EC7-023B-4571-B310-42873921B0BC}']
    function  Codegen: boolean;
    function  Compile(const code: string): boolean;
    function  Parse(const code: string): boolean;
    property AST: ISimpleDSLAST read GetAST;
    property Code: ISimpleDSLProgram read GetCode;
    property ASTFactory: TSimpleDSLASTFactory       read GetASTFactory write SetASTFactory;
    property CodegenFactory: TSimpleDSLCodegenFactory       read GetCodegenFactory write SetCodegenFactory;
    property ParserFactory: TSimpleDSLParserFactory       read GetParserFactory write SetParserFactory;
    property TokenizerFactory: TSimpleDSLTokenizerFactory       read GetTokenizerFactory write SetTokenizerFactory;
   end;
1
2
3
4
5
6
7
8
9
10
11
12

Фреймворк представляет функции для разбора входных данных (Parse), генерации исполняемого кода (Codegen) и оба эти действия в один шаг (Compile), но он понятия не имеет как это делать. Вся функциональность реализована снаружи — через токинизатор, парсер и движок генерации кода что который создаётся в фабричном методе (TokenizerFactory).

Чтобы сделать конфигурацию простой, TSimpleDSLCompiler.Create устанавливает фабрики по умолчанию, создавая типичные классы движков. Если вы хотите подключить свою собственную реализацию отдельного шага, вы можете сделать это установив подходящее свойство XXXFactory перед вызовом любой функции этого интерфейса. Мы будем использовать эту возможность для реализации "AST Dumper" в следующей части этого блога.

constructor TSimpleDSLCompiler.Create;
begin
  inherited Create;
  ASTFactory := CreateSimpleDSLAST;
  CodegenFactory := CreateSimpleDSLCodegen;
  ParserFactory := CreateSimpleDSLParser;
  TokenizerFactory := CreateSimpleDSLTokenizer;
end;
1
2
3
4
5
6
7
8

Давайте быстро взглянем на все три функции API. Самая важная — Compile, не делает ничего кроме вызова парсера и (если обрабатываемый код корректен) генератора кода. Здесь нет ничего особенного.

function TSimpleDSLCompiler.Compile(const code: string): boolean;
begin
  Result := Parse(code);
  if Result then
    Result := Codegen;
end;
1
2
3
4
5
6

Вторая — Parse, создаёт движки парсера и токинезатора, подготавливает AST (FAST) и вызывает метод парсера Parse. Большинство из этого просто обвязка, и вся реальная работа делается в parser.Parse.

function TSimpleDSLCompiler.Parse(const code: string): boolean;
var
  parser   : ISimpleDSLParser;
  tokenizer: ISimpleDSLTokenizer;
begin
  LastError := '';
  parser := ParserFactory();
  tokenizer := TokenizerFactory();
  FAST := ASTFactory();
  Result := parser.Parse(code, tokenizer, FAST);
  if not Result then begin
    FAST := nil;
    LastError := (parser as ISimpleDSLErrorInfo).ErrorInfo;
  end
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Последний — Codegen такой же простой. После нескольких проверок он создаёт движок генерации кода и вызывает его метод Generate передавая в него AST. Мы не рассматривали генератор кода, так что пока достаточно сказать, что генератор кода предоставляет одну функцию — Generate которая конвертирует ISimpleDSLAST в ISimpleDSLProgram.

function TSimpleDSLCompiler.Codegen: boolean;
var
  codegen  : ISimpleDSLCodegen;
begin
  LastError := '';
  if not assigned(FAST) then
    Exit(SetError('Nothing to do'))
  else begin
    codegen := CodegenFactory();
    Result := codegen.Generate(FAST, FCode);
    if not Result then begin
      FCode := nil;
      LastError := (codegen as ISimpleDSLErrorInfo).ErrorInfo;
    end;
  end;
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Всё это позволяет нам очень просто вызывать компилятор:

compiler := CreateSimpleDSLCompiler;
if not compiler.Compile(CMultiProcCode) then
  Writeln('Compilation/codegen error: ' +     (compiler as ISimpleDSLErrorInfo).ErrorInfo); 
1
2
3

В следующей части мы увидим как можно сохранить сгенерированное AST в текстовую форму заменой CodegenFactory.

Последниее изменение: 24.08.2023, 06:42:55