source: branches/generator/UBuilder.pas

Last change on this file was 137, checked in by chronos, 6 years ago
  • Added: Lookup tables can be defined and their usage set from grammer rules.
File size: 15.7 KB
Line 
1unit UBuilder;
2
3{$mode delphi}
4
5interface
6
7uses
8 Classes, SysUtils, UGrammer, StrUtils;
9
10type
11
12 { TBuilder }
13
14 TBuilder = class
15 private
16 LocalFunctions: string;
17 LocalFunctionIndex: Integer;
18 function Indent(Count: Integer): string;
19 function GetItemString(Item: TRuleItem; Required: Boolean; IndentLevel: Integer): string;
20 function StringText(const Text: string; Escaped: Boolean): string;
21 procedure BuildMain(FileName: string);
22 procedure BuildParser(FileName: string);
23 function BuildParserItems(Items: TRuleItems; Required: Boolean; IndentLevel: Integer): string;
24 procedure BuildSource(FileName: string);
25 procedure BuildTokenizer(FileName: string);
26 function BuildTokenizerItems(SourceFile: TStrings; Items: TRuleItems): string;
27 public
28 Grammer: TGrammer;
29 procedure BuildCompiler;
30 end;
31
32
33const
34 BooleanText: array[Boolean] of string = ('False', 'True');
35
36implementation
37
38function TBuilder.Indent(Count: Integer): string;
39begin
40 Result := DupeString(' ', Count);
41end;
42
43function TBuilder.GetItemString(Item: TRuleItem; Required: Boolean; IndentLevel: Integer): string;
44begin
45 Result := '';
46 case Item.RuleItemType of
47 ritTerminal: Result := 'Expect(''' +
48 StringText(Item.Terminal, Item.EscapedStrings) + ''', Required and ' + BooleanText[Required] + ')';
49 ritNonTerminal: Result := 'Parse' + Item.NonTerminal.Name + '(Required and ' + BooleanText[Required] + ')';
50 ritTerminalRange: Result := 'ExpectRange(''' +
51 StringText(Item.TerminalFrom, Item.EscapedStrings) + ''', ''' +
52 StringText(Item.TerminalTo, Item.EscapedStrings) + ''', Required and ' + BooleanText[Required] + ')';
53 ritSubItems: begin
54 LocalFunctions := LocalFunctions + 'function ParseSubitems' + IntToStr(LocalFunctionIndex) + '(Required: Boolean = False): Boolean;' + LineEnding;
55 LocalFunctions := LocalFunctions + 'var' + LineEnding;
56 LocalFunctions := LocalFunctions + ' OldPosition: TPosition;' + LineEnding;
57 LocalFunctions := LocalFunctions + 'begin' + LineEnding;
58 LocalFunctions := LocalFunctions + ' Result := True;' + LineEnding;
59 LocalFunctions := LocalFunctions + BuildParserItems(Item.SubItems, Required, 1);
60 LocalFunctions := LocalFunctions + 'end;' + LineEnding + LineEnding;
61
62 Result := Result + 'ParseSubitems' + IntToStr(LocalFunctionIndex) + '(Required and ' + BooleanText[Required] + ')';
63 Inc(LocalFunctionIndex);
64 end;
65 end;
66end;
67
68function TBuilder.StringText(const Text: string; Escaped: Boolean): string;
69begin
70 Result := Text;
71 Result := StringReplace(Result, '''', '''''', [rfReplaceAll]);
72 if Escaped then begin
73 Result := StringReplace(Result, '\n', '''#10''', [rfReplaceAll]);
74 Result := StringReplace(Result, '\r', '''#13''', [rfReplaceAll]);
75 Result := StringReplace(Result, '\t', '''#9''', [rfReplaceAll]);
76 Result := StringReplace(Result, '\\', '\', [rfReplaceAll]);
77 end;
78end;
79
80procedure TBuilder.BuildParser(FileName: string);
81var
82 ParserFile: TStringList;
83 Rule: TRule;
84 FunctionBody: string;
85begin
86 ParserFile := TStringList.Create;
87 with ParserFile do begin
88 Add('unit Parser;');
89 Add('');
90 Add('{$MODE Delphi}');
91 Add('');
92 Add('interface');
93 Add('');
94 Add('uses');
95 Add(' SysUtils, Tokenizer, Types;');
96 Add('');
97 Add('type');
98 Add(' TPosition = record');
99 Add(' Index: Integer;');
100 Add(' Pos: TPoint;');
101 Add(' procedure Calc(Text: string);');
102 Add(' end;');
103 Add('');
104 Add(' TParser = class');
105 Add(' Tokenizer: TTokenizer;');
106 Add(' Content: string;');
107 Add(' FileName: string;');
108 Add(' Position: TPosition;');
109 Add(' procedure Error(Text: string);');
110 Add(' function Expect(Text: string; Required: Boolean = False): Boolean;');
111 Add(' function ExpectRange(CharFrom, CharTo: Char; Required: Boolean = False): Boolean;');
112 for Rule in Grammer.Rules do
113 //if Rule.Level = rlParser then
114 Add(' function Parse' + Rule.Name + '(Required: Boolean = False): Boolean;');
115 Add(' constructor Create;');
116 Add(' destructor Destroy; override;');
117 Add(' end;');
118 Add('');
119 Add('');
120 Add('implementation');
121 Add('');
122 Add('procedure TPosition.Calc(Text: string);');
123 Add('var');
124 Add(' I: Integer;');
125 Add('begin');
126 Add(' for I := 1 to Length(Text) do begin');
127 Add(' Inc(Pos.X);');
128 Add(' Inc(Index);');
129 Add(' if Text[I] = LineEnding then begin');
130 Add(' Pos.X := 1;');
131 Add(' Inc(Pos.Y);');
132 Add(' end;');
133 Add(' end;');
134 Add('end;');
135 Add('');
136 Add('constructor TParser.Create;');
137 Add('begin');
138 Add(' Position.Index := 1;');
139 Add(' Position.Pos := Point(1, 1);');
140 Add(' Tokenizer := TTokenizer.Create;');
141 Add('end;');
142 Add('');
143 Add('destructor TParser.Destroy;');
144 Add('begin');
145 Add(' FreeAndNil(Tokenizer);');
146 Add('end;');
147 Add('');
148 Add('procedure TParser.Error(Text: string);');
149 Add('begin');
150 Add(' WriteLn(FileName + ''('' + IntToStr(Position.Pos.X) + '','' + IntToStr(Position.Pos.Y) + '') Error: '' + Text);');
151 Add('end;');
152 Add('');
153 Add('function TParser.Expect(Text: string; Required: Boolean = False): Boolean;');
154 Add('var');
155 Add(' ReadText: string;');
156 Add(' StartPos: TPosition;');
157 Add('begin');
158 Add(' StartPos := Position;');
159 Add(' ReadText := Copy(Content, Position.Index, Length(Text));');
160 Add(' Position.Calc(Text);');
161 Add(' Result := Text = ReadText;');
162 Add(' if not Result then begin');
163 Add(' Position := StartPos;');
164 Add(' if Required then Error(''Expected "'' + Text + ''" but found "'' + ReadText + ''".'');');
165 Add(' end else WriteLn(''Expect: '' + Text);');
166 Add('end;');
167 Add('');
168 Add('function TParser.ExpectRange(CharFrom, CharTo: char; Required: Boolean = False): Boolean;');
169 Add('var');
170 Add(' ReadChar: Char;');
171 Add(' StartPos: TPosition;');
172 Add('begin');
173 Add(' StartPos := Position;');
174 Add(' ReadChar := Content[Position.Index];');
175 Add(' Position.Calc(Content[Position.Index]);');
176 Add(' Result := (ReadChar >= CharFrom) and (ReadChar <= CharTo);');
177 Add(' if not Result then begin');
178 Add(' Position := StartPos;');
179 Add(' if Required then Error(''Expected "'' + CharFrom + ''" to "'' + CharTo + ''" but found "'' + ReadChar + ''".'');');
180 Add(' end else WriteLn(''ExpectRange: '' + CharFrom + ''..'' + CharTo + '': '' + ReadChar);');
181 Add('end;');
182 Add('');
183 for Rule in Grammer.Rules do
184 //if Rule.Level = rlParser then
185 begin
186 LocalFunctionIndex := 1;
187 LocalFunctions := '';
188 FunctionBody := BuildParserItems(Rule.Items, True, 1);
189 Text := Text + 'function TParser.Parse' + Rule.Name + '(Required: Boolean = False): Boolean;' + LineEnding;
190 Text := Text + LocalFunctions;
191 Text := Text + 'var' + LineBreak;
192 Text := Text + ' OldPosition: TPosition;' + LineEnding;
193 Text := Text + 'begin' + LineEnding;
194 //Text := Text + ' WriteLn(''Rule check: ' + Rule.Name + ''');';
195 Text := Text + ' Result := True;' + LineEnding;
196 Text := Text + FunctionBody;
197 Text := Text + ' if Result then WriteLn(''Rule: ' + Rule.Name + ''');';
198 Text := Text + 'end;' + LineEnding + LineEnding;
199 end;
200 Add('');
201 Add('end.');
202 SaveToFile(FileName);
203 end;
204 FreeAndNil(ParserFile);
205end;
206
207function TBuilder.BuildParserItems(Items: TRuleItems; Required: Boolean; IndentLevel: Integer): string;
208var
209 I: Integer;
210 Item: TRuleItem;
211 ItemRequired: Boolean;
212 Line: string;
213begin
214 Result := Indent(IndentLevel) + 'OldPosition := Position;' + LineEnding;
215 I := 0;
216 for Item in Items do begin
217 ItemRequired := not Item.Optional and Required;
218 Line := '';
219 case Items.RuleType of
220 rtOr: begin
221 Line := Line + Indent(IndentLevel);
222 if I > 0 then Line := Line + 'else ';
223 Line := Line + 'if ' + GetItemString(Item, False, IndentLevel) +
224 ' then Exit';
225 end;
226 rtAnd: begin
227 if not Item.Optional then
228 Line := Line + Indent(IndentLevel) + 'Result := Result and ';
229 if Item.Repetitive then begin
230 if not Item.Optional then
231 Line := Line + Indent(IndentLevel) + GetItemString(Item, ItemRequired, IndentLevel) + ';' + LineEnding;
232 Line := Line + Indent(IndentLevel) + 'repeat' + LineEnding;
233 Inc(IndentLevel);
234 Line := Line + Indent(IndentLevel) + 'if not ';
235 ItemRequired := False;
236 end else
237 if Item.Optional then Line := Line + Indent(IndentLevel);
238 Line := Line + GetItemString(Item, ItemRequired, IndentLevel);
239 if Item.Repetitive then begin
240 Dec(IndentLevel);
241 Line := Line + ' then Break;' + LineEnding +
242 Indent(IndentLevel) + 'until False';
243 end;
244 Line := Line + ';' + LineEnding +
245 Indent(IndentLevel) + 'if not Result then begin' + LineEnding +
246 Indent(IndentLevel + 1) + 'Position := OldPosition;' + LineEnding +
247 Indent(IndentLevel + 1) + 'Exit;' + LineEnding +
248 Indent(IndentLevel) + 'end;';
249
250 // Check white spaces
251 if Assigned(Items.Grammer.WhiteSpaceRule) and Items.WithWhiteSpeaces
252 then begin
253 Line := Line + Indent(IndentLevel) + 'repeat' + LineEnding;
254 Inc(IndentLevel);
255 Line := Line + Indent(IndentLevel) + 'if not Parse' +
256 Items.Grammer.WhiteSpaceRule.Name + '(False)' +
257 ' then Break;' + LineEnding;
258 Dec(IndentLevel);
259 Line := Line + Indent(IndentLevel) + 'until False;' + LineEnding;
260 end;
261 end;
262 end;
263 Result := Result + Line + LineEnding;
264 Inc(I);
265 end;
266 case Items.RuleType of
267 rtOr: begin
268 Result := Result + Indent(IndentLevel) + 'else begin' + LineEnding;
269 Result := Result + Indent(IndentLevel) + ' if Required then Error(''Unexpected token'');' + LineEnding;
270 Result := Result + Indent(IndentLevel) + ' Result := False;' + LineEnding;
271 Result := Result + Indent(IndentLevel) + 'end;' + LineEnding;
272 end;
273 end;
274end;
275
276procedure TBuilder.BuildMain(FileName: string);
277var
278 ParserFile: TStringList;
279begin
280 ParserFile := TStringList.Create;
281 with ParserFile do begin
282 Add('program Compiler;');
283 Add('');
284 Add('{$MODE Delphi}');
285 Add('');
286 Add('uses');
287 Add(' Source, Parser, Tokenizer, SysUtils;');
288 Add('');
289 Add('procedure Compile(FileName: string);');
290 Add('var');
291 Add(' SourceFile: file of Char;');
292 Add(' Parser: TParser;');
293 Add(' I: Integer;');
294 Add('begin');
295 Add(' AssignFile(SourceFile, FileName);');
296 Add(' Reset(SourceFile);');
297 Add(' Parser := TParser.Create;');
298 Add(' Parser.FileName := ExtractFileName(FileName);');
299 Add(' SetLength(Parser.Content, FileSize(SourceFile));');
300 Add(' I := 1;');
301 Add(' while not Eof(SourceFile) do begin');
302 Add(' Read(SourceFile, Parser.Content[I]);');
303 Add(' Inc(I);');
304 Add(' end;');
305 Add(' CloseFile(SourceFile);');
306 if Assigned(Grammer.TopRule) then
307 Add(' Parser.Parse' + Grammer.TopRule.Name + '(True);');
308 Add(' FreeAndNil(Parser);');
309 Add('end;');
310 Add('');
311 Add('begin');
312 Add(' if ParamCount > 0 then');
313 Add(' Compile(ParamStr(1))');
314 Add(' else WriteLn(''File name not specified as parameter.'');');
315 Add('end.');
316 SaveToFile(FileName);
317 end;
318 FreeAndNil(ParserFile);
319end;
320
321procedure TBuilder.BuildSource(FileName: string);
322var
323 Rule: TRule;
324 Item: TRuleItem;
325 SourceFile: TStringList;
326 TypeSectionStarted: Boolean;
327 I: Integer;
328begin
329 SourceFile := TStringList.Create;
330 with SourceFile do begin
331 Add('unit Source;');
332 Add('');
333 Add('{$MODE Delphi}');
334 Add('');
335 Add('interface');
336 Add('');
337 Add('uses');
338 Add(' fgl;');
339 Add('');
340 TypeSectionStarted := False;
341 for Rule in Grammer.Rules do
342 //if Rule.CreateSourceNode then
343 begin
344 if not TypeSectionStarted then begin
345 Add('type');
346 TypeSectionStarted := True;
347 end;
348 Add(' T' + Rule.Name + ' = class;');
349 end;
350 Add('');
351 for Rule in Grammer.Rules do
352 //if Rule.CreateSourceNode then
353 begin
354 Add(' T' + Rule.Name + ' = class');
355 I := 1;
356 for Item in Rule.Items do begin
357 if Item.RuleItemType = ritNonTerminal then
358 if Item.Repetitive then
359 Add(' ' + Item.NonTerminal.Name + IntToStr(I) + ': TFPGObjectList<T' + Item.NonTerminal.Name + '>;')
360 else Add(' ' + Item.NonTerminal.Name + IntToStr(I) + ': T' + Item.NonTerminal.Name + ';');
361 Inc(I);
362 end;
363 Add(' end;' + LineEnding);
364 end;
365 Add('');
366 Add('implementation');
367 Add('');
368
369 Add('end.');
370
371 SaveToFile(FileName);
372 end;
373 FreeAndNil(SourceFile);
374end;
375
376function TBuilder.BuildTokenizerItems(SourceFile: TStrings; Items: TRuleItems): string;
377var
378 Item: TRuleItem;
379 Line: string;
380begin
381 Result := '';
382 Line := '';
383 with SourceFile do begin
384 for Item in Items do begin
385 case Item.RuleItemType of
386 ritTerminal: Line := Line + '(Value = ''' + StringText(Item.Terminal, Item.EscapedStrings) + ''')';
387 ritNonTerminal: Line := Line + 'Is' + Item.NonTerminalName + '(Value)';
388 ritTerminalRange: Line := Line + '((Value > ''' + StringText(Item.TerminalFrom, Item.EscapedStrings) + ''') and (Value < ''' + StringText(Item.TerminalTo, Item.EscapedStrings) + '''))';
389 ritSubItems: Line := Line + '(' + BuildTokenizerItems(SourceFile, Item.SubItems) + ')';
390 end;
391 if Item <> Items[Items.Count - 1] then
392 Line := Line + ' ' + RuleTypeText[Items.RuleType] + ' ';
393 if Length(Line) > 80 then begin
394 Result := Result + Line + LineEnding;
395 Line := '';
396 end;
397 end;
398 Result := Result + Line;
399 end;
400end;
401
402procedure TBuilder.BuildTokenizer(FileName: string);
403var
404 Rule: TRule;
405 SourceFile: TStringList;
406begin
407 SourceFile := TStringList.Create;
408 with SourceFile do begin
409 Add('unit Tokenizer;');
410 Add('');
411 Add('{$MODE Delphi}');
412 Add('');
413 Add('interface');
414 Add('');
415 Add('uses');
416 Add(' fgl;');
417 Add('');
418 Add('type');
419 Add(' TTokenizer = class');
420 Add(' function ReadNext: string;');
421 Add(' function Expect(Text: string): Boolean;');
422 for Rule in Grammer.Rules do
423 if (Rule.Level = rlTokenizer) then begin
424 Add(' function Is' + Rule.Name + '(Value: Char): Boolean;');
425 end;
426 Add(' end;');
427 Add('');
428 Add('implementation');
429 Add('');
430 Add('function TTokenizer.ReadNext: string;');
431 Add('begin');
432 Add('end;');
433 Add('');
434 Add('function TTokenizer.Expect(Text: string): Boolean;');
435 Add('begin');
436 Add('end;');
437
438
439 for Rule in Grammer.Rules do
440 if Rule.Level = rlTokenizer then begin
441 Add('function TTokenizer.Is' + Rule.Name + '(Value: Char): Boolean;');
442 Add('begin');
443 Add(' Result := ' + BuildTokenizerItems(SourceFile, Rule.Items) + ';');
444 Add('end;' + LineEnding);
445 end;
446 Add('end.');
447
448 SaveToFile(FileName);
449 end;
450 FreeAndNil(SourceFile);
451end;
452
453procedure TBuilder.BuildCompiler;
454var
455 OutputDir: string;
456begin
457 OutputDir := 'Generated';
458 ForceDirectories(OutputDir);
459
460 BuildMain(OutputDir + DirectorySeparator + 'Compiler.pas');
461 BuildSource(OutputDir + DirectorySeparator + 'Source.pas');
462 BuildParser(OutputDir + DirectorySeparator + 'Parser.pas');
463 BuildTokenizer(OutputDir + DirectorySeparator + 'Tokenizer.pas');
464end;
465
466
467end.
468
Note: See TracBrowser for help on using the repository browser.