-
Notifications
You must be signed in to change notification settings - Fork 5
/
utokenizer.pas
508 lines (473 loc) · 22.1 KB
/
utokenizer.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
(******************************************************************************)
(* utokenizer.pas ??.??.???? *)
(* *)
(* Version : 0.06 *)
(* *)
(* Author : Uwe Schächterle (Corpsman) *)
(* *)
(* Support : www.Corpsman.de *)
(* *)
(* Description : <Module_description> *)
(* *)
(* License : See the file license.md, located under: *)
(* https://github.com/PascalCorpsman/Software_Licenses/blob/main/license.md *)
(* for details about the license. *)
(* *)
(* It is not allowed to change or remove this text from any *)
(* source file of the project. *)
(* *)
(* Warranty : There is no warranty, neither in correctness of the *)
(* implementation, nor anything other that could happen *)
(* or go wrong, use at your own risk. *)
(* *)
(* Known Issues: none *)
(* *)
(* History : 0.01 - Initial version *)
(* 0.02 - Conversion to Unicode, Linux, Removed "UseUnderLinux" *)
(* inserted Compilerswitches, to do that. *)
(* 0.03 - Bugfix, Operatoren die Präfixe von anderen Operatoren *)
(* sind müssen "sortiert" betrachtet werden. *)
(* 0.04 - Rule String Erkennung, *)
(* Wenn AddRule(a,b) gemacht wurde und im String Steht *)
(* ...a...ba..b.. *)
(* Wird wird das token *)
(* a...b...b *)
(* ausgegeben *)
(* 0.05 - Anpassungen für 64-Bit Systeme *)
(* 0.06 - Fix Bug (mit workaround), letztes Token nicht sauber *)
(* geparst. *)
(* *)
(******************************************************************************)
Unit utokenizer;
{$MODE ObjFPC}{$H+}
Interface
Uses Sysutils;
Type
// Unsere Token Structur, hier könnten natürlich noch jede menge anderer INformationen stehn
// Bei einem Compiler sind ja auch informationen über den Token , wie Typ usw nötig.
TToken = Record
Value: String; // Der Token ansich
Line: PtrInt; // Die zeile in der der token steht (wird auch als Pointer Missbraucht, muss daher PtrInt sein !)
End;
// Unser Typ zum Speichern einer Regel
TRule = Record
BeginChar: String; // Die zeichenkette die einen Token einleitet
EndChar: String; // Die ZeichenKette die einen Token beendet
End;
// Pointer auf Array of Token
TTokenarray = Array Of TToken;
// Der Eigentliche tokenizer
TTokenizer = Class
private
FOperators: Array Of String; // Auflistung aller Operatoren ( Wenn sie gefunden werden trennen sie den alten Token und fügen diesen und sich selbst in die Tokenliste ein )
Fseperators: Array Of String; // Auflistung aller Trennsymole die selbst aber nicht als Token aufgeführt werden.
FRules: Array Of Trule; // Auflistung aller token die Übergeordnet sind, wie z.b. Strings
Fscanlength: Integer; // Legt fest wie viele Zeichen der Parser im Vorraus Einliest.
FCaseSensitive: Boolean; // Wenn True dann wird Groß Klein schreibung berücksichtigt.
public
Property CaseSensitive: Boolean read FCaseSensitive write FCaseSensitive;
Constructor Create;
Destructor Destroy; override;
Procedure AddOperator(Value: String);
Procedure AddSeperator(Value: String);
Procedure AddRule(BeginChar, EndChar: String);
Procedure ClearRules;
Procedure ClearOperators;
Procedure ClearSeperators;
Function Scan(Value: String): TTokenArray; // Zerlegt den Text in Tokens
End;
(*
ACHTUNG Tokens wird verändert !!
Die Eingabe Tokenliste sollte aus TUnCommenter stammen und entsprechend mit Lineseperatoren versehen sein !!
Damit diese Funktion Funktioniert, mus Lineseperator als Operator geadded sein !!!
*)
Function NormalTokensToLineInfoTokens(Var Tokens: TTokenarray; Lineseperator: Char): TTokenarray; // Rechnet die Tokenliste von scan in eine mit entsprechenden Zeileninformationen um ( dazu mus aber numberline vom uncommenter an sein !!)
Function PtrToPtrInt(p: Pointer): Ptrint Inline;
Function PtrIntToPtr(p: PtrInt): Pointer Inline;
Implementation
Function PtrIntToPtr(p: PtrInt): Pointer Inline;
Begin
result := {%H-} Pointer(p); // Kommt hier immer noch ein Hinweis, dann hat die Codeformatierung zwischen } und P ein Leerzeichen gemacht, das darf nicht sein.
End;
Function PtrToPtrInt(p: Pointer): Ptrint Inline;
Begin
result := {%H-} Ptrint(p); // Kommt hier immer noch ein Hinweis, dann hat die Codeformatierung zwischen } und P ein Leerzeichen gemacht, das darf nicht sein.
End;
Function Max(v1, v2: integer): integer;
Begin
If V1 > v2 Then
result := v1
Else
result := v2;
End;
Constructor TTokenizer.Create;
Begin
Inherited create;
setlength(Foperators, 0);
setlength(Fseperators, 0);
setlength(FRules, 0);
FCaseSensitive := false;
End;
Destructor TTokenizer.Destroy;
Begin
// Inherited destroy; // Braucht net da von Tobject abgeleitet
setlength(FRules, 0);
setlength(Foperators, 0);
setlength(Fseperators, 0);
End;
Procedure TTokenizer.Addoperator(Value: String);
Var
i, j: Integer;
b: Boolean;
s: String;
Begin
(* Erst mal Checken ob der Operator bereits Existiert *)
For i := 0 To High(Foperators) Do Begin
If FOperators[i] = Value Then exit;
End;
setlength(Foperators, high(Foperators) + 2);
Foperators[high(Foperators)] := Value;
Fscanlength := max(Fscanlength, length(Value));
(*
Wenn ein bereits "geaddeter" Operator ein Präfix des zu addenden Operators ist
dann wird der nun geaddete Operator nicht erkannt.
=> deswegen sortieren wir die Operatoren entsprechend um.
*)
b := True;
i := -1;
While b Do Begin
inc(i);
b := i < High(Foperators);
For j := i + 1 To High(Foperators) Do Begin
If (pos(Foperators[i], Foperators[j]) = 1) Then Begin
s := Foperators[j];
Foperators[j] := Foperators[i];
Foperators[i] := s;
i := -1;
b := true;
break;
End;
End;
End;
End;
Procedure TTokenizer.Addseperator(Value: String);
Begin
setlength(Fseperators, high(Fseperators) + 2);
Fseperators[high(Fseperators)] := Value;
Fscanlength := max(Fscanlength, length(Value));
End;
Procedure TTokenizer.AddRule(BeginChar, EndChar: String);
Begin
// ohne Version 0.04
// Fscanlength := max(Fscanlength, length(BeginChar)); // Merken des Längsten Einführungszeichens unserer Tocken
// Fscanlength := max(Fscanlength, length(EndChar)); // Merken des Längsten Beendenzeichens unserer Tocken
// mit Version 0.04
Fscanlength := max(Fscanlength, length(BeginChar) + length(EndChar)); // Die Maximale Länge der Tokens wird größer, durch das Vorrauslesen.
setlength(Frules, high(frules) + 2); // Übernehmen in die Rules Liste
Frules[high(frules)].BeginChar := BeginChar;
Frules[high(frules)].EndChar := EndChar;
End;
Procedure TTokenizer.ClearRules;
Begin
setlength(Frules, 0); // Wieder Löschen der Regeln
End;
Procedure TTokenizer.ClearOperators;
Begin
setlength(Foperators, 0);
End;
Procedure TTokenizer.ClearSeperators;
Begin
setlength(Fseperators, 0);
End;
Function TTokenizer.scan(Value: String): TTokenArray;
Var
erg: TTokenArray; // tmp Variable für die gefundenen Tokens
token: String; // Speichern der zeichen Kette des Tokens der Gerade gelesen wird.
akt: String; // Die Aktuell gelesenen Zeichen ( Formatiert )
akttmp: String; // Die Aktuell gelesenen Zeichen ( Un Formatiert )
ueber: integer; // Anzahl der im Nächsten Schritt zu überlesenden Zeichen.
i2: integer; // Zählvariable
i: integer; // Zählvariable
b, bb: Boolean; // Schmiermerker
fall: Boolean; // ist true wenn gerade ein Token geschrieben wurde.
incase: Integer; // -1 wenn wir uns normal im Lesen befinden <>-1 wenn wir in einer Rule sind.
Begin
erg := Nil;
(*
* Bugfix 0.05: Der Parser macht irgend einen Mist, Und erkennt das letzte Token nicht sauber
* Wenn man aber lauter Separatoren anfügt und als letztes dann einen Operator, dann gehts.
*)
If high(Fseperators) <> -1 Then Begin
For i := 0 To (Fscanlength Div length(Fseperators[0])) + 1 Do Begin
value := value + Fseperators[0];
End;
End;
If high(FOperators) <> -1 Then Begin
value := value + FOperators[0];
End;
(*
* Bugfix 0.05: Ende
*)
setlength(erg, 0); // Initialisieren
If (High(Frules) <> -1) Or (High(Foperators) <> -1) Or (High(Fseperators) <> -1) Then Begin // Wenn es überhaupt was zu tun gibt.
incase := -1; // Initialisieren
// Die ersten Paar Zeichen können auf einen Schlag eingelesen werden.
If Length(Value) > Fscanlength Then Begin
akt := copy(value, 1, Fscanlength - 1);
delete(value, 1, Fscanlength - 1);
End
Else Begin
akt := value;
Value := '';
End;
token := ''; // Initialisieren
ueber := 0; // Initialisieren
// Zwischenspeichern des eingelesenen Textes Unformatiert
akttmp := akt;
If Not FCaseSensitive Then akt := lowercase(akt); // Formatierung für nicht Case Sensitiv
While Length(Value) <> 0 Do Begin // Arbeiten so lange es noch einen Zu Lesenden text gibt.
fall := false;
While (Length(Value) <> 0) And (Length(akt) < Fscanlength) Do Begin
// Weiterlesen im Text.
If Length(Value) <> 0 Then Begin
If FCaseSensitive Then // Wenn Casesensitive
akt := akt + Value[1] // Dann wird Akttmp eigentlich sinnlos.
Else
akt := akt + lowercase(Value[1]);
akttmp := akttmp + Value[1]; // Mitziehen von Akttmp
delete(Value, 1, 1); // Löschen des bereits eingelesenen Textes
End;
End;
//********************************************
If Incase = -1 Then Begin
For i := 0 To High(Foperators) Do Begin
// schaun ob der Beginn unseres Tokens überhaupt geht.
If length(akt) >= Length(Foperators[i]) Then Begin
b := true; // Initialisieren
i2 := 1; // Initialisieren
While b And (i2 <= length(Foperators[i])) Do Begin // Solange der gelesene Text mit dem Tocken übereinstimmt, lesen
If FCaseSensitive Then Begin // wieder die Case Sensitiv sache
If akt[i2] <> Foperators[i][i2] Then b := false; // Wenn die Zeicehn nicht Gleich sind
End
Else Begin
If akt[i2] <> lowercase(Foperators[i][i2]) Then b := false; // Wenn die Zeichen nicht Gleich sind
End;
inc(i2); // Weiterzählen auf den nächsten Char
End;
If b Then Begin // Wenn wir tatsächlich einen Token erkannt haben
If Length(Token) <> 0 Then Begin
setlength(Erg, high(erg) + 3);
erg[high(erg) - 1].Value := token;
erg[high(erg)].Value := Foperators[i];
End
Else Begin
setlength(Erg, high(erg) + 2);
erg[high(erg)].Value := Foperators[i];
End;
Token := '';
ueber := Length(Foperators[i]) - 1;
fall := true;
break; // Raus.
End;
End;
End;
For i := 0 To High(fseperators) Do Begin
// schaun ob der Beginn unseres Tokens überhaupt geht.
If length(akt) >= Length(fseperators[i]) Then Begin
b := true; // Initialisieren
i2 := 1; // Initialisieren
While b And (i2 <= length(fseperators[i])) Do Begin // Solange der gelesene Text mit dem Tocken übereinstimmt, lesen
If FCaseSensitive Then Begin // wieder die Case Sensitiv sache
If akt[i2] <> fseperators[i][i2] Then b := false; // Wenn die Zeicehn nicht Gleich sind
End
Else Begin
If akt[i2] <> lowercase(fseperators[i][i2]) Then b := false; // Wenn die Zeicehn nicht Gleich sind
End;
inc(i2); // Weiterzählen auf den nächsten Char
End;
If b Then Begin // Wenn wir tatsächlich einen Token erkannt haben
If Length(token) <> 0 Then Begin
setlength(Erg, high(erg) + 2);
erg[high(erg)].Value := token;
Token := '';
End;
ueber := Length(Fseperators[i]) - 1;
fall := true;
break; // Raus.
End;
End;
End;
For i := 0 To High(Frules) Do
// schaun ob der Beginn unseres Tokens überhaupt geht.
If length(akt) >= Length(Frules[i].BeginChar) Then Begin
b := true; // Initialisieren
i2 := 1; // Initialisieren
While b And (i2 <= length(Frules[i].BeginChar)) Do Begin // Solange der gelesene Text mit dem Tocken übereinstimmt, lesen
If FCaseSensitive Then Begin // wieder die Case Sensitiv sache
If akt[i2] <> Frules[i].BeginChar[i2] Then b := false; // Wenn die Zeicehn nicht Gleich sind
End
Else Begin
If akt[i2] <> lowercase(Frules[i].BeginChar[i2]) Then b := false; // Wenn die Zeicehn nicht Gleich sind
End;
inc(i2); // Weiterzählen auf den nächsten Char
End;
If b Then Begin // Wenn wir tatsächlich einen Token erkannt haben
incase := i; // Merken welcher Token es war
ueber := Length(Frules[i].BeginChar) - 1;
// Übernehmen des bis dahin entstandenen Tokens
If Length(Token) <> 0 Then Begin
setlength(erg, high(erg) + 2);
erg[high(erg)].Value := Token;
End;
Token := Frules[i].BeginChar; // Der Begin unseres tokens mus nu übernommen werde.
fall := True;
break; // Wenn wir ein Token gefunden haben können wir natürlich keine weiteren mehr Finden.
End;
End;
End
Else Begin // Wenn wir in einer Rule sind
i := incase;
// schaun ob das Ende unseres Tokens überhaupt geht.
If (length(akt) >= Length(Frules[i].EndChar)) Then Begin
b := true; // Initialisieren
i2 := 1; // Initialisieren
While b And (i2 <= length(Frules[i].EndChar)) Do Begin // Solange der gelesene Text mit dem Tocken übereinstimmt, lesen
If FCaseSensitive Then Begin // wieder die Case Sensitiv sache
If akt[i2] <> Frules[i].EndChar[i2] Then b := false; // Wenn die Zeicehn nicht Gleich sind
End
Else Begin
If akt[i2] <> lowercase(Frules[i].EndChar[i2]) Then b := false; // Wenn die Zeicehn nicht Gleich sind
End;
inc(i2); // Weiterzählen auf den nächsten Char
End;
If b Then Begin // Wenn wir tatsächlich einen Token erkannt haben
// --------------------- Neuer Code Version 0.04 -------------- Anfang
(*
Wir dürfen nur dann aus der Regel Raus, wenn nach dem End Token nicht
sofort ein Start Token Kommt !!
*)
i2 := 1;
bb := true;
While bb And (i2 <= length(Frules[i].BeginChar)) Do Begin
If i2 + length(Frules[i].EndChar) > length(akt) Then
Raise exception.create('Error in Tokenizer (Scan, end rule detection ) , please kontakt the Programmer !!!');
If FCaseSensitive Then Begin // wieder die Case Sensitiv sache
If akt[i2 + length(Frules[i].EndChar)] <> Frules[i].BeginChar[i2] Then bb := false; // Wenn die Zeicehn nicht Gleich sind
End
Else Begin
If akt[i2 + length(Frules[i].EndChar)] <> lowercase(Frules[i].BeginChar[i2]) Then bb := false; // Wenn die Zeichen nicht Gleich sind
End;
inc(i2); // Weiterzählen auf den nächsten Char
End;
// Wenn sofort wieder ein Beginnchar gefunden wurde dann
// Dürfen wir die Rule niht beenden lassen !!
If bb Then b := false;
// Die Rule Endet Tatsächlich Jetzt
If b Then Begin
Token := Token + Frules[i].EndChar; // Den Token Vervollständigen
setlength(erg, high(erg) + 2); // Den token ins Ergebniss anhängen
erg[high(erg)].Value := Token; // Den token ins Ergebniss anhängen
Token := ''; // den neuen Token initialisieren.
incase := -1; // zurücksetzen des Tokenmerkers
fall := true; // Merken der Fallenden Flanke.
Ueber := Length(Frules[i].EndChar) - 1; // Berechen der zu Überlesenden zeichen
End
Else Begin
// Die Rule Endet noch nicht es geht noch weiter
Token := Token + Frules[i].EndChar; // Den Token Vervollständeigen
Ueber := Length(Frules[i].EndChar) + Length(Frules[i].BeginChar) - 1; // Berechen der zu Überlesenden zeichen
fall := true; // Merken der Fallenden Flanke = Verhindern das Token erweitert wird, das haben wir ja schon gemacht.
End;
// --------------------- Neuer Code Version 0.04 -------------- Ende
{
// ALTER Code der ohne Version 0.04 ist !!
Token := Token + Frules[i].EndChar; // Den Token Vervollständeigen
setlength(erg, high(erg) + 2); // Den token ins Ergebniss anhängen
erg[high(erg)].Value := Token; // Den token ins Ergebniss anhängen
Token := ''; // den neuen Token initialisieren.
incase := -1; // zurücksetzen des Tokenmerkers
fall := true; // Merken der Fallenden Flanke.
Ueber := Length(Frules[i].EndChar) - 1; // Berechen der zu überlesenden zeichen
//}
End;
End;
End;
// Wenn nicht gerade ein Token gefunden wurde dann lesen wir den Aktuellen text in unsere Tokenvariable ein.
If Not fall Then Token := Token + akttmp[1];
// Falls Zeichen Überlesen werden müssen machen wir das nun
If Ueber <> 0 Then Begin
Delete(akt, 1, ueber); // Der Witz ist das Akt immer Länger oder gleich lang wie Ueber + 1 ist !!
Delete(akttmp, 1, ueber); // Der Witz ist das Akt immer Länger oder gleich lang wie Ueber + 1 ist !!
Ueber := 0; // Zurücksetzen
End;
// Weiterlesen im Code , durch löschen des 1. elementes des Aktuell gelesenen Codes
delete(akt, 1, 1);
delete(akttmp, 1, 1);
End;
End;
(*
Ist das Letze Zeichen ein Seperator wird dies oben nicht erkannt ...
*)
For i := 0 To High(Fseperators) Do
If Akt = Fseperators[i] Then
akt := '';
// Wenn am schlus was Übrig ist mus das noch geadet werden
If (length(Token) <> 0) Or (length(akt) <> 0) Then Begin
setlength(erg, high(erg) + 2); // Den token ins Ergebniss anhängen
erg[high(erg)].Value := Token + akt; // Den token ins Ergebniss anhängen
End;
Result := erg; // Zurückgeben des Ergebnisses.
(*
* Bugfix 0.05: (siehe erste Zeilen von Scan)
*)
setlength(result, high(result));
End;
(*
Diese Funktion Konvertiert eine Tokenliste
Die Tokenliste mus derart sein das immer wieder
Lineseperator dann Linenummer kommt
Klar ist das somit die Allerletzte token eine Linenumber ist.
Auch klar ist, das Lineseperator ein Oparator des Tokenizers sein mus !
*)
Function NormalTokensToLineInfoTokens(Var Tokens: TTokenarray;
Lineseperator: Char): TTokenarray;
Var
n, rlc, i: integer;
Begin
result := Nil;
rlc := 0;
// Gab es überhaupt irgendwas zum Parsen ?
If High(tokens) <> -1 Then Begin
// Einlesen der Zeilennummern aus den Tokens
n := -1;
For i := High(tokens) Downto 1 Do Begin
(*
Wird hier eine Access violation geworfen, so entspricht die Tokenliste nicht den Oben genannten definitionen.
*)
If tokens[i - 1].value = Lineseperator Then Begin
n := strtoint(tokens[i].value);
inc(rlc, 2);
End;
If n <> -1 Then
tokens[i].Line := n;
End;
tokens[0].line := n; // Beim 1. Token mus das Manuel gemacht werden.
// Übernehmen der Daten in die Globale Tokenliste
Setlength(result, High(tokens) + 1 - rlc);
n := 0;
i := 0;
While i <= High(tokens) Do Begin
If tokens[i].value <> Lineseperator Then Begin
Result[n] := tokens[i];
inc(n);
End
Else Begin
inc(i);
End;
inc(i);
End;
End
Else
setlength(result, 0);
End;
End.