1 {********************************************************}
2 { TSsParser Component - Parser for Sakura Script }
4 { Copyright (c) 2001-2003 naruto/CANO-Lab }
5 { (c) 2001-2005 WinBottle Project }
6 {********************************************************}
13 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
18 // mtTag: Tag. (begins with \)
19 // mtMeta: Meta expression. (begins with %)
20 // mtTagErr: Seems to be a markup error
21 // mtStr: Other normal talk string
22 TSsMarkUpType = (mtTag, mtMeta, mtTagErr, mtStr);
24 // Intercepting the parsing
25 TSsParseEvent = procedure (Sender: TObject; const Script: string;
26 var Len: integer; var MarkType: TSsMarkUpType; var Extra: string) of object;
29 ESsParserError = class(Exception);
31 // Internally used private class
32 // Do not use this class outside this unit!
33 TSsMarkUp = class(TPersistent)
37 FMarkUpType: TSsMarkUpType;
40 procedure Assign(Source: TPersistent); override;
41 constructor Create(Pos: Integer; MarkUpType: TSsMarkUpType; Str: string;
44 property Pos: Integer read FPos; //This markup is Pos'th character in InputString
45 property MarkUpType: TSsMarkUpType read FMarkUpType;
46 property Str: string read FStr;
47 property Extra: string read FExtra write FExtra;
50 // TSsParseResult class
51 TSsParseResult = class(TObject)
55 function GetCount: Integer;
56 function GetExtra(Index: Integer): string;
57 function GetMarkUpType(Index: Integer): TSsMarkUpType;
58 function GetPosition(Index: Integer): Integer;
59 function GetStr(Index: Integer): string;
60 procedure SetExtra(Index: Integer; const Value: string);
61 function GetItem(Index: Integer): TSsMarkUp;
63 procedure Add(Pos: Integer; MarkUpType: TSsMarkUpType; Str: string;
66 property Item[Index: Integer]: TSsMarkUp read GetItem;
68 constructor Create(const InputString: string);
69 destructor Destroy; override;
70 function MarkUpAt(const Pos: Integer): Integer;
71 property Count: Integer read GetCount;
72 property MarkUpType[Index: Integer]: TSsMarkUpType read GetMarkUpType;
73 property Str[Index: Integer]: string read GetStr; default;
74 property Extra[Index: Integer]: string read GetExtra write SetExtra;
75 property Position[Index: Integer]: Integer read GetPosition;
76 property InputString: string read FInputString; // Cannot Write
80 // This class has been modified so that it can handle multiple parsing
81 // results, but this class is NOT thread-safe.
82 // If you want to parse scripts within more than one thread,
83 // separate parser instance should be prepared for each thread.
84 TSsParser = class(TComponent)
86 FTagPattern: TStrings; //SakuraScript tag pattern string
87 FMetaPattern: TStrings; //SakuraScript meta expression pattern string
88 FBuffList: TSsParseResult; //To implement deprecated properties
89 FLeaveEscape: boolean;
90 FEscapeInvalidMeta: boolean;
91 FOnParse: TSsParseEvent;
92 FPatternStudied: Boolean; // Whether pattern definition is optimized
93 FTagPatternTable: TStrings;
94 FTagPatternPointer: array[Char] of Integer;
95 FMetaPatternTable: TStrings;
96 FMetaPatternPointer: array[Char] of Integer;
97 procedure SetInputString(const Value: string);
98 function GetCount: integer;
99 function GetExtra(Index: integer): string;
100 function GetMarkUpType(Index: integer): TSsMarkUpType;
101 function GetStr(Index: integer): string;
102 procedure SetExtra(Index: integer; const Value: string);
103 procedure SetMetaPattern(const Value: TStrings);
104 procedure SetTagPattern(const Value: TStrings);
105 procedure SetOnParse(const Value: TSsParseEvent);
106 function GetFirstChar(const Str: string): string;
107 function GetPosition(Index: integer): integer;
108 function GetInputString: string;
110 function ChopFirstChar(var Str: string): string;
111 procedure BeginParse(ResultList: TSsParseResult);
114 constructor Create(AOwner: TComponent); override;
115 destructor Destroy; override;
116 function MatchP(PStr, PPattern: PChar): integer;
117 function Match(Str, Pattern: string): integer;
118 function GetParam(Tag: string; const Index: integer): string;
119 function EscapeParam(const Param: string): string;
120 function MarkUpAt(const Pos: integer): integer;
122 function Parse(const Script: string): TSsParseResult;
124 { These properties are deprecated. }
125 property Count: integer read GetCount;
126 property MarkUpType[Index: integer]: TSsMarkUpType read GetMarkUpType;
127 property Str[Index: integer]: string read GetStr; default;
128 property Extra[Index: integer]: string read GetExtra write SetExtra;
129 property Position[Index: integer]: integer read GetPosition;
131 property InputString: string read GetInputString write SetInputString;
133 // Script parsing patterns.
134 property TagPattern: TStrings read FTagPattern write SetTagPattern;
135 property MetaPattern: TStrings read FMetaPattern write SetMetaPattern;
136 // Whether to leave escape sequence "\\" and "\%" in mtStr elements
137 property LeaveEscape: boolean read FLeaveEscape write FLeaveEscape
139 // Replace '%' to '\%' if sequence follwing the '%' could not
140 // be parsed as a meta expression
141 property EscapeInvalidMeta: boolean read FEscapeInvalidMeta
142 write FEscapeInvalidMeta default false;
143 // Component users can intercept and handle part of the parsing using this event.
144 property OnParse: TSsParseEvent read FOnParse write SetOnParse;
152 CElementTypeError = '
\90³
\82µ
\82¢
\83G
\83\8c\83\81\83\93\83g
\83^
\83C
\83v
\82ð
\95Ô
\82µ
\82Ä
\82
\82¾
\82³
\82¢';
153 CTagPatternSyntaxError = 'TagPattern
\95\
\8bL
\83~
\83X %d
\8ds
\96Ú';
154 CMarkupIndexOutofRange = '
\83C
\83\93\83f
\83b
\83N
\83X
\82ª
\94Í
\88Í
\82ð
\82±
\82¦
\82Ä
\82¢
\82Ü
\82·';
157 // English error message
158 CElementTypeError = 'Returned element type is invalid in OnParse.';
159 CTagPatternSyntaxError = 'TagPattern syntax error at line %d.';
160 CMarkupIndexOutofRange = 'Markup index if out of range.';
166 RegisterComponents('Samples', [TSsParser]);
171 procedure TSsParser.BeginParse(ResultList: TSsParseResult);
172 var Str, Talk, T, Ex: string;
176 PHead, PStr, PTalk, PTail, Buf: PChar;
178 // Pattern study must be done the first time you parse.
179 if not FPatternStudied then
182 Str := ResultList.InputString; // The string to be parsed from now
183 // This is to avoid access violation if `Str` is terminated
184 // with DBCS leadbyte. (Such string is invalid from the beginning of course)
186 if Length(Str) = 0 then
188 PHead := PChar(Str); // const
191 // Length(Talk) <= Length(Str) * 2
192 // Because a script like '%%%' is converted to '\%\%\%' if EscapeInvalidMeta is on
193 SetLength(Talk, Length(Str) * 2 + 2);
194 PTalk := PChar(Talk);
195 while PStr^ <> #0 do begin
196 if PStr^ = '\' then begin
200 // Escaped sequence "\\"
214 end else if PStr^ = '%' then
216 // Escaped sequence "\%"
234 // Generate OnParser event
238 if Assigned(FOnParse) then begin
239 FOnParse(Self, string(PStr), Le, Mt, Ex);
241 if (Mt <> mtTag) and (Mt <> mtTagErr) then begin
242 raise ESsParserError.Create(CElementTypeError);
245 if Mt = mtTagErr then IsErr := true;
251 i := FTagPatternPointer[PStr^];
253 while (i < FTagPatternTable.Count) do begin
254 T := FTagPatternTable[i];
258 Le := MatchP(PStr, Buf);
265 if T[2] <> PStr^ then
270 if PChar(Talk) <> PTalk then begin
272 ResultList.Add(PTail-PHead+1, mtStr, string(PChar(Talk)));
273 PTalk := PChar(Talk);
276 // Parsed as a correct tag
277 T := Copy(string(PStr), 1, Le);
279 ResultList.Add(PStr-PHead+1, mtTagErr, T, Ex)
281 ResultList.Add(PStr-PHead+1, mtTag, T, Ex);
284 // Parsing failed; The character '\' and the next one character is
285 // marked as a tag error.
286 Inc(PStr); // Skip '\'
287 if PStr^ in LeadBytes then
289 T := '\' + Copy(string(PStr), 1, 2);
290 ResultList.Add(PStr-PChar(Str), mtTagErr, T);
295 ResultList.Add(PStr-PChar(Str), mtTagErr, T);
301 end else if (PStr^ = '%') then begin
303 if Assigned(FOnParse) then begin
304 FOnParse(Self, string(PStr), Le, Mt, Ex);
306 if Mt <> mtMeta then begin
307 raise ESsParserError.Create(CElementTypeError);
313 Inc(PStr); // Skip '%'
316 i := FMetaPatternPointer[PStr^];
318 while i < FMetaPatternTable.Count do
320 Le := MatchP(PStr, PChar(FMetaPatternTable[i]));
323 if FMetaPatternTable[i][1] <> PStr^ then
328 if Le > 0 then // Matched as valid meta string
330 if PChar(Talk) <> PTalk then
333 ResultList.Add(PTail-PHead+1, mtStr, string(PChar(Talk)));
334 PTalk := PChar(Talk);
336 T := Copy(string(PStr), 1, Le);
337 ResultList.Add(PStr-PChar(Str), mtMeta, '%' + T, Ex);
342 // in case this %??? sequence seems NOT to be a meta expression
343 if FEscapeInvalidMeta then
357 if PStr^ in LeadBytes then
360 Inc(PStr); Inc(PTalk);
362 Inc(PStr); Inc(PTalk);
366 Inc(PStr); Inc(PTalk);
370 if PTalk <> PChar(Talk) then
373 ResultList.Add(PTail-PHead+1, mtStr, string(PChar(Talk)));
377 function TSsParser.ChopFirstChar(var Str: string): string;
379 Result := GetFirstChar(Str);
380 Delete(Str, 1, Length(Result));
383 constructor TSsParser.Create(AOwner: TComponent);
386 FTagPattern := TStringList.Create;
387 FMetaPattern := TStringList.Create;
388 FTagPatternTable := TStringList.Create;
389 FMetaPatternTable := TStringList.Create;
390 FLeaveEscape := true;
393 destructor TSsParser.Destroy;
398 FTagPatternTable.Free;
399 FMetaPatternTable.Free;
400 if FBuffList <> nil then
404 function TSsParser.EscapeParam(const Param: string): string;
406 //StringReplace supports DBCS
407 Result := StringReplace(Param, '\', '\\', [rfReplaceAll]);
408 Result := StringReplace(Param, ']', '\]', [rfReplaceAll]);
411 function TSsParser.GetCount: integer;
413 if FBuffList <> nil then
414 Result := FBuffList.Count
419 function TSsParser.GetExtra(Index: integer): string;
421 if (Index >= 0) and (Index < FBuffList.Count) then
422 Result := FBuffList.Extra[Index]
427 function TSsParser.GetFirstChar(const Str: string): string;
429 // Get the first character of the given string. Supports DBCS
430 if Length(Str) <= 1 then begin
433 if Str[1] in LeadBytes then begin
434 Result := Str[1] + Str[2];
441 function TSsParser.GetInputString: string;
443 if FBuffList <> nil then
444 Result := FBuffList.InputString
449 function TSsParser.GetMarkUpType(Index: integer): TSsMarkUpType;
451 if (Index >= 0) and (Index <= FBuffList.Count-1) then
452 Result := FBuffList.MarkUpType[Index]
454 raise ESsParserError.Create(CMarkupIndexOutOfRange);
457 function TSsParser.GetParam(Tag: string;
458 const Index: integer): string;
459 var ParamCount: integer;
460 First, Param: string;
461 Escape, Inside: boolean;
463 if Index <= 0 then Exit;
468 First := ChopFirstChar(Tag);
471 if First = '\' then Param := Param + '\'
472 else if First = ']' then Param := Param + ']'
473 else Param := Param + '\' + First;
475 end else if First = '\' then
477 else if First = ']' then
481 Param := Param + First;
483 end else if First = '[' then begin
489 until (First = '') or ((ParamCount = Index) and not Inside);
490 if ((ParamCount = Index) and not Inside) then
496 function TSsParser.GetPosition(Index: integer): integer;
498 if (Index >= 0) and (Index < FBuffList.Count) then
499 Result := FBuffList.Position[Index]
504 function TSsParser.GetStr(Index: integer): string;
506 if (Index >= 0) and (Index < FBuffList.Count) then
507 Result := FBUffList[Index]
512 function TSsParser.MarkUpAt(const Pos: integer): Integer;
514 Result := FBuffList.MarkUpAt(Pos);
517 function TSsParser.Match(Str, Pattern: string): integer;
519 if (Length(Str) = 0) or (Length(Pattern) = 0) then
522 Result := MatchP(@Str[1], @Pattern[1]);
525 function TSsParser.MatchP(PStr, PPattern: PChar): integer;
526 var Matched, F, Escape: boolean;
530 while Matched and (PPattern^ <> #0) do begin
531 if PPattern^ = '%' then
555 if PStr^ in ['0' .. '9'] then
565 if PStr^ in ['0' .. '9'] then
567 while PStr^ in ['0' .. '9'] do
576 'b': //String enclosed by '[' and ']'. The content may be an empty string.
584 Escape := false; //After escape character
593 if PStr^ = '\' then Escape := true;
594 if PStr^ = ']' then F := true;
596 if PStr^ in LeadBytes then
605 until (PStr^ = #0) or F;
611 'c': // String which can be the argument content enclosed by '[' and ']'
614 if not (PStr^ = ']') then
620 else if PStr^ = ']' then
623 if PStr^ = '\' then Escape := true;
624 if PStr^ in LeadBytes then
639 if not (PStr^ in LeadBytes) then
644 end else Matched := false;
648 if (PStr^ in LeadBytes) then
653 end else Matched := false;
656 if (PStr^ in LeadBytes) then
679 if PStr^ <> PPattern^ then
689 if not Matched then Result := 0;
692 function TSsParser.Parse(const Script: string): TSsParseResult;
694 Result := TSsParseResult.Create(Script);
698 procedure TSsParser.SetExtra(Index: integer; const Value: string);
700 FBuffList.Extra[Index] := Value;
703 procedure TSsParser.SetInputString(const Value: string);
705 // Warning! This property is deprecated
706 // Use Parse() method instead.
707 if FBuffList <> nil then
709 FBuffList.FInputString := Value; // Access to private method outside the class!
712 FBuffList := TSsParseResult.Create(Value);
713 BeginParse(FBuffList);
716 procedure TSsParser.SetMetaPattern(const Value: TStrings);
718 FMetaPattern.Assign(Value);
719 FPatternStudied := false;
722 procedure TSsParser.SetOnParse(const Value: TSsParseEvent);
727 procedure TSsParser.SetTagPattern(const Value: TStrings);
729 FTagPattern.Assign(Value);
730 FPatternStudied := false;
733 procedure TSsParser.Study;
735 i, index, count: Integer;
738 // Pattern studying is done for reducing meaningless pattern matching.
739 FTagPatternTable.Clear;
740 for c := Low(Char) to High(Char) do
743 FTagPatternPointer[c] := -1;
744 for i := 0 to FTagPattern.Count-1 do
746 if Length(FTagPattern[i]) < 2 then
747 Continue; // invalid pattern
748 if not (FTagPattern[i][1] in ['\', '!']) then
749 raise ESsParserError.CreateFmt(CTagPatternSyntaxError, [i+1]);
750 if FTagPattern[i][2] = c then
753 index := FTagPatternTable.Add(FTagPattern[i]);
755 FTagPatternPointer[c] := index;
759 FMetaPatternTable.Clear;
760 for c := Low(Char) to High(Char) do
763 FMetaPatternPointer[c] := -1;
764 for i := 0 to FMetaPattern.Count-1 do
766 if Length(FMetaPattern[i]) < 1 then
767 Continue; // invalid pattern
768 if FMetaPattern[i][1] = c then
771 index := FMetaPatternTable.Add(FMetaPattern[i]);
773 FMetaPatternPointer[c] := index;
777 FPatternStudied := true;
782 procedure TSsMarkUp.Assign(Source: TPersistent);
786 if Source is TSsMarkUp then
788 Src := Source as TSsMarkUp;
790 FMarkUpType := Src.FMarkUpType;
792 FExtra := Src.FExtra;
797 constructor TSsMarkUp.Create(Pos: Integer;
798 MarkUpType: TSsMarkUpType; Str: string; Extra: string = '');
801 FMarkUpType := MarkUpType;
808 procedure TSsParseResult.Add(Pos: Integer;
809 MarkUpType: TSsMarkUpType; Str: string; Extra: string);
811 FList.Add(TSsMarkUp.Create(Pos, MarkUpType, Str, Extra));
814 procedure TSsParseResult.Clear;
819 constructor TSsParseResult.Create(const InputString: string);
821 FList := TObjectList.Create(true);
822 FInputString := InputString;
825 destructor TSsParseResult.Destroy;
831 function TSsParseResult.GetCount: Integer;
833 Result := FList.Count;
836 function TSsParseResult.GetExtra(Index: integer): string;
839 Result := Item[Index].Extra;
843 raise ESsParserError.Create(CMarkupIndexOutofRange);
848 function TSsParseResult.GetItem(Index: Integer): TSsMarkUp;
850 Result := FList[Index] as TSsMarkUp;
853 function TSsParseResult.GetMarkUpType(Index: integer): TSsMarkUpType;
856 Result := Item[Index].MarkUpType;
860 raise ESsParserError.Create(CMarkupIndexOutofRange);
865 function TSsParseResult.GetPosition(Index: integer): integer;
868 Result := Item[Index].Pos;
872 raise ESsParserError.Create(CMarkupIndexOutofRange);
877 function TSsParseResult.GetStr(Index: integer): string;
880 Result := Item[Index].Str;
884 raise ESsParserError.Create(CMarkupIndexOutofRange);
889 function TSsParseResult.MarkUpAt(const Pos: integer): integer;
890 var i, lo, hi: integer;
892 if FList.Count = 0 then
897 i := (hi-lo) div 2 + lo;
900 i := (hi-lo) div 2 + lo;
901 if (Item[i].Pos >= Pos) and
902 (Item[i+1].Pos < Pos) then
906 end else if Item[i].Pos > Pos then
915 procedure TSsParseResult.SetExtra(Index: Integer; const Value: string);
917 if (Index >= 0) and (Index < FList.Count) then
918 Item[Index].Extra := Value
920 raise ESsParserError.Create(CMarkupIndexOutOfRange);