FastReport_2022_VCL/LibD28/fs_iparser.pas
2024-01-01 16:13:08 +01:00

762 lines
17 KiB
ObjectPascal

{******************************************}
{ }
{ FastScript v1.9 }
{ Parser }
{ }
{ (c) 2003-2007 by Alexander Tzyganenko, }
{ Fast Reports Inc }
{ }
{******************************************}
//VCL uses section
{$IFNDEF FMX}
unit fs_iparser;
interface
{$i fs.inc}
uses
{$IFNDEF CROSS_COMPILE}
Windows,
{$ENDIF}
SysUtils, Classes;
//FMX uses
{$ELSE}
interface
{$i fs.inc}
uses
System.Types, FMX.Types, System.SysUtils, System.Classes;
{$ENDIF}
type
TfsIdentifierCharset = set of AnsiChar;
{ TfsParser parser the source text and return such elements as identifiers,
keywords, punctuation, strings and numbers. }
TfsParser = class(TObject)
private
FCaseSensitive: Boolean;
FCommentBlock1: String;
FCommentBlock11: String;
FCommentBlock12: String;
FCommentBlock2: String;
FCommentBlock21: String;
FCommentBlock22: String;
FCommentLine1: String;
FCommentLine2: String;
FHexSequence: String;
FIdentifierCharset: TfsIdentifierCharset;
FKeywords: TStrings;
FLastPosition: Integer;
FPosition: Integer;
FSize: Integer;
FSkiPChar: String;
FSkipEOL: Boolean;
FSkipSpace: Boolean;
FStringQuotes: String;
FText: String;
FUseY: Boolean;
FYList: TList;
FSpecStrChar: Boolean;
function DoDigitSequence: Boolean;
function DoHexDigitSequence: Boolean;
function DoScaleFactor: Boolean;
function DoUnsignedInteger: Boolean;
function DoUnsignedReal: Boolean;
procedure SetPosition(const Value: Integer);
procedure SetText(const Value: String);
function Ident: String;
procedure SetCommentBlock1(const Value: String);
procedure SetCommentBlock2(const Value: String);
public
constructor Create;
destructor Destroy; override;
procedure Clear;
procedure ConstructCharset(const s: String);
{ skip all #0..#31 symbols }
procedure SkipSpaces;
{ get EOL symbol }
function GetEOL: Boolean;
{ get any valid ident except keyword }
function GetIdent: String;
{ get any valid punctuation symbol like ,.;: }
function GetChar: String;
{ get any valid ident or keyword }
function GetWord: String;
{ get valid hex/int/float number }
function GetNumber: String;
{ get valid quoted/control string like 'It''s'#13#10'working' }
function GetString: String;
{ get FR-specific string - variable or db field like [main data."field 1"] }
function GetFRString: String;
{ get Y:X position }
function GetXYPosition: String;
{ get plain position from X:Y }
function GetPlainPosition(pt: TPoint): Integer;
{ is this keyword? }
function IsKeyWord(const s: String): Boolean;
// Language-dependent elements
// For Pascal:
// CommentLine1 := '//';
// CommentBlock1 := '{,}';
// CommentBlock2 := '(*,*)';
// HexSequence := '$'
// IdentifierCharset := ['_', '0'..'9', 'a'..'z', 'A'..'Z'];
// Keywords: 'begin','end', ...
// StringQuotes := ''''
property CaseSensitive: Boolean read FCaseSensitive write FCaseSensitive;
property CommentBlock1: String read FCommentBlock1 write SetCommentBlock1;
property CommentBlock2: String read FCommentBlock2 write SetCommentBlock2;
property CommentLine1: String read FCommentLine1 write FCommentLine1;
property CommentLine2: String read FCommentLine2 write FCommentLine2;
property HexSequence: String read FHexSequence write FHexSequence;
property IdentifierCharset: TfsIdentifierCharset read FIdentifierCharset
write FIdentifierCharset;
property Keywords: TStrings read FKeywords;
property SkiPChar: String read FSkiPChar write FSkiPChar;
property SkipEOL: Boolean read FSkipEOL write FSkipEOL;
property SkipSpace: Boolean read FSkipSpace write FSkipSpace;
property StringQuotes: String read FStringQuotes write FStringQuotes;
property SpecStrChar: Boolean read FSpecStrChar write FSpecStrChar;
property UseY: Boolean read FUseY write FUseY;
{ Current position }
property Position: Integer read FPosition write SetPosition;
{ Text to parse }
property Text: String read FText write SetText;
end;
implementation
{ TfsParser }
constructor TfsParser.Create;
begin
FKeywords := TStringList.Create;
TStringList(FKeywords).Sorted := True;
FYList := TList.Create;
FUseY := True;
Clear;
end;
destructor TfsParser.Destroy;
begin
FKeywords.Free;
FYList.Free;
inherited;
end;
procedure TfsParser.Clear;
begin
FKeywords.Clear;
FSpecStrChar := False;
FCommentLine1 := '//';
CommentBlock1 := '{,}';
CommentBlock2 := '(*,*)';
FHexSequence := '$';
FIdentifierCharset := ['_', '0'..'9', 'a'..'z', 'A'..'Z'];
FSkipChar := '';
FSkipEOL := True;
FStringQuotes := '''';
FSkipSpace := True;
end;
procedure TfsParser.SetCommentBlock1(const Value: String);
var
sl: TStringList;
begin
FCommentBlock1 := Value;
FCommentBlock11 := '';
FCommentBlock12 := '';
sl := TStringList.Create;
sl.CommaText := FCommentBlock1;
if sl.Count > 0 then
FCommentBlock11 := sl[0];
if sl.Count > 1 then
FCommentBlock12 := sl[1];
sl.Free;
end;
procedure TfsParser.SetCommentBlock2(const Value: String);
var
sl: TStringList;
begin
FCommentBlock2 := Value;
FCommentBlock21 := '';
FCommentBlock22 := '';
sl := TStringList.Create;
sl.CommaText := FCommentBlock2;
if sl.Count > 0 then
FCommentBlock21 := sl[0];
if sl.Count > 1 then
FCommentBlock22 := sl[1];
sl.Free;
end;
procedure TfsParser.SetPosition(const Value: Integer);
begin
FPosition := Value;
FLastPosition := Value;
end;
procedure TfsParser.SetText(const Value: String);
var
i: Integer;
begin
FText := Value + #0;
FLastPosition := 1;
FPosition := 1;
FSize := Length(Value);
if FUseY then
begin
FYList.Clear;
FYList.Add(TObject(0));
for i := 1 to FSize do
if FText[i] = #10 then
FYList.Add(TObject(i));
end;
end;
procedure TfsParser.ConstructCharset(const s: String);
var
i: Integer;
begin
FIdentifierCharset := [];
for i := 1 to Length(s) do
FIdentifierCharset := FIdentifierCharset + [s[i]];
end;
function TfsParser.GetEOL: Boolean;
begin
SkipSpaces;
{$IFDEF Delphi12}
if CharInSet(FText[FPosition], [#10, #13]) then
{$ELSE}
if FText[FPosition] in [#10, #13] then
{$ENDIF}
begin
Result := True;
{$IFDEF Delphi12}
while CharInSet(FText[FPosition], [#10, #13]) do
{$ELSE}
while FText[FPosition] in [#10, #13] do
{$ENDIF}
Inc(FPosition);
end
else
Result := False;
end;
procedure TfsParser.SkipSpaces;
var
s1, s2: String;
Flag, CLine: Boolean;
Spaces: set of AnsiChar;
begin
Spaces := [#0..#32];
if not FSkipEOL then
{$IFDEF Windows}
Spaces := Spaces - [#13];
{$ELSE}
Spaces := Spaces - [#10];
{$ENDIF}
{$IFDEF Delphi12}
while (FPosition <= FSize) and (CharInSet(FText[FPosition], Spaces)) do
{$ELSE}
while (FPosition <= FSize) and (FText[FPosition] in Spaces) do
{$ENDIF}
Inc(FPosition);
{ skip basic '_' }
if (FPosition <= FSize) and (FSkipChar <> '') and (FText[FPosition] = FSkipChar[1]) then
begin
Inc(FPosition);
GetEOL;
SkipSpaces;
end;
if FPosition < FSize then
begin
if FCommentLine1 <> '' then
s1 := Copy(FText, FPosition, Length(FCommentLine1)) else
s1 := ' ';
if FCommentLine2 <> '' then
s2 := Copy(FText, FPosition, Length(FCommentLine2)) else
s2 := ' ';
if (s1 = FCommentLine1) or (s2 = FCommentLine2) then
begin
CLine := (FPosition - 1 > 0) and (FText[FPosition - 1] <> #10) and not FSkipEOL;
while (FPosition <= FSize) and (FText[FPosition] <> #10) do
begin
if (FText[FPosition] = {$IFDEF LINUX}#10{$ELSE}#13{$ENDIF}) and CLine then break;
Inc(FPosition);
end;
SkipSpaces;
end
else
begin
Flag := False;
if FCommentBlock1 <> '' then
begin
s1 := Copy(FText, FPosition, Length(FCommentBlock11));
if s1 = FCommentBlock11 then
begin
Flag := True;
s2 := FCommentBlock12;
end;
end;
if not Flag and (FCommentBlock2 <> '') then
begin
s1 := Copy(FText, FPosition, Length(FCommentBlock21));
if s1 = FCommentBlock21 then
begin
Flag := True;
s2 := FCommentBlock22;
end;
end;
if Flag then
begin
Inc(FPosition, Length(s2));
while (FPosition <= FSize) and (Copy(FText, FPosition, Length(s2)) <> s2) do
Inc(FPosition);
Inc(FPosition, Length(s2));
SkipSpaces;
end;
end;
end;
FLastPosition := FPosition;
end;
function TfsParser.Ident: String;
begin
if FSkipSpace then
SkipSpaces;
{$IFDEF Delphi12}
if (CharInSet(FText[FPosition], FIdentifierCharset - ['0'..'9']))
or ((FText[FPosition] >= Char($007F)) and (FText[FPosition] <= Char($FFFF))) then
begin
while CharInSet(FText[FPosition], FIdentifierCharset)
or ((FText[FPosition] >= Char($007F)) and (FText[FPosition] <= Char($FFFF))) do
{$ELSE}
if (FText[FPosition] in FIdentifierCharset) and not (FText[FPosition] in ['0'..'9']) then
begin
while (FText[FPosition] in FIdentifierCharset) do
{$ENDIF}
Inc(FPosition);
Result := Copy(FText, FLastPosition, FPosition - FLastPosition);
end
else
Result := '';
end;
function TfsParser.IsKeyWord(const s: String): Boolean;
var
i: Integer;
begin
if FCaseSensitive then
begin
Result := False;
for i := 0 to FKeywords.Count - 1 do
begin
Result := FKeywords[i] = s;
if Result then break;
end;
end
else
Result := FKeywords.IndexOf(s) <> -1;
end;
function TfsParser.GetIdent: String;
begin
Result := Ident;
if IsKeyWord(Result) then
Result := '';
end;
function TfsParser.GetWord: String;
begin
Result := Ident;
end;
function TfsParser.GetChar: String;
begin
{$IFDEF Delphi12}
if CharInSet(FText[FPosition], ['!', '@', '#', '$', '%', '^', '&', '|', '\',
'.', ',', ':', ';', '?', '''', '"', '~', '`', '_', '[', ']', '{', '}',
'(', ')', '+', '-', '*', '/', '=', '<', '>']) then
{$ELSE}
if FText[FPosition] in ['!', '@', '#', '$', '%', '^', '&', '|', '\',
'.', ',', ':', ';', '?', '''', '"', '~', '`', '_', '[', ']', '{', '}',
'(', ')', '+', '-', '*', '/', '=', '<', '>'] then
{$ENDIF}
begin
Result := FText[FPosition];
Inc(FPosition);
end
else
Result := '';
end;
function TfsParser.GetString: String;
var
Flag: Boolean;
Str: String;
FError: Boolean;
FCpp: Boolean;
function DoQuotedString: Boolean;
var
i, j: Integer;
begin
Result := False;
i := FPosition;
if FText[FPosition] = FStringQuotes[1] then
begin
repeat
Inc(FPosition);
if FCpp and (FText[FPosition] = '\') then
begin
{$IFNDEF FPC}
{$IFDEF Delphi12}
case Lowercase(Char(FText[FPosition + 1]))[1] of
{$ELSE}
case Lowercase(FText[FPosition + 1])[1] of
{$ENDIF}
{$ELSE}
case Lowercase(FText[FPosition + 1]) of
{$ENDIF}
'n':
begin
Str := Str + #10;
Inc(FPosition);
end;
'r':
begin
Str := Str + #13;
Inc(FPosition);
end;
'x':
begin
Inc(FPosition, 2);
j := FPosition;
Result := DoHexDigitSequence;
if Result then
Str := Str + Chr(StrToInt('$' + Copy(FText, j, FPosition - j))) else
FPosition := j;
Dec(FPosition);
end
else
begin
Str := Str + FText[FPosition + 1];
Inc(FPosition);
end;
end;
end
else if FText[FPosition] = FStringQuotes[1] then
begin
if not FCpp and (FText[FPosition + 1] = FStringQuotes[1]) then
begin
Str := Str + FStringQuotes[1];
Inc(FPosition);
end
else
break
end
else
Str := Str + FText[FPosition];
{$IFDEF Delphi12}
until CharInSet(FText[FPosition], [#0..#31] - [#9]);
{$ELSE}
until FText[FPosition] in [#0..#31] - [#9];
{$ENDIF}
if FText[FPosition] = FStringQuotes[1] then
begin
Inc(FPosition);
Result := True;
end
else
FPosition := i;
end;
end;
function DoControlString: Boolean;
var
i: Integer;
begin
Result := False;
i := FPosition;
if FText[FPosition] = '#' then
begin
Inc(FPosition);
Result := DoUnsignedInteger;
if Result then
Str := Chr(StrToInt(Copy(FText, i + 1, FPosition - i - 1))) else
FPosition := i;
end;
end;
{$HINTS OFF}
begin
Result := '';
if FSkipSpace then
SkipSpaces;
Flag := True;
FError := False;
FCpp := {FStringQuotes = '"'} FSpecStrChar;
repeat
Str := '';
if DoQuotedString or DoControlString then
Result := Result + Str
else
begin
FError := Flag;
break;
end;
Flag := False;
until False;
if not FError then
Result := '''' + Result + '''';
end;
{$HINTS ON}
function TfsParser.DoDigitSequence: Boolean;
begin
Result := False;
{$IFDEF Delphi12}
if CharInSet(FText[FPosition], ['0'..'9']) then
begin
while CharInSet(FText[FPosition], ['0'..'9']) do
{$ELSE}
if FText[FPosition] in ['0'..'9'] then
begin
while FText[FPosition] in ['0'..'9'] do
{$ENDIF}
Inc(FPosition);
Result := True;
end;
end;
function TfsParser.DoHexDigitSequence: Boolean;
begin
Result := False;
{$IFDEF Delphi12}
if CharInSet(FText[FPosition], ['0'..'9', 'a'..'f', 'A'..'F']) then
begin
while CharInSet(FText[FPosition], ['0'..'9', 'a'..'f', 'A'..'F']) do
{$ELSE}
if FText[FPosition] in ['0'..'9', 'a'..'f', 'A'..'F'] then
begin
while FText[FPosition] in ['0'..'9', 'a'..'f', 'A'..'F'] do
{$ENDIF}
Inc(FPosition);
Result := True;
end;
end;
function TfsParser.DoUnsignedInteger: Boolean;
var
Pos1: Integer;
s: String;
begin
Pos1 := FPosition;
s := Copy(FText, FPosition, Length(FHexSequence));
if s = FHexSequence then
begin
Inc(FPosition, Length(s));
Result := DoHexDigitSequence;
end
else
Result := DoDigitSequence;
if not Result then
FPosition := Pos1;
end;
function TfsParser.DoUnsignedReal: Boolean;
var
Pos1, Pos2: Integer;
begin
Pos1 := FPosition;
Result := DoUnsignedInteger;
if Result then
begin
if FText[FPosition] = '.' then
begin
Inc(FPosition);
Result := DoDigitSequence;
end;
if Result then
begin
Pos2 := FPosition;
if not DoScaleFactor then
FPosition := Pos2;
end;
end;
if not Result then
FPosition := Pos1;
end;
function TfsParser.DoScaleFactor: Boolean;
begin
Result := False;
{$IFDEF Delphi12}
if CharInSet(FText[FPosition], ['e', 'E']) then
{$ELSE}
if FText[FPosition] in ['e', 'E'] then
{$ENDIF}
begin
Inc(FPosition);
{$IFDEF Delphi12}
if CharInSet(FText[FPosition], ['+', '-']) then
{$ELSE}
if FText[FPosition] in ['+', '-'] then
{$ENDIF}
Inc(FPosition);
Result := DoDigitSequence;
end;
end;
function TfsParser.GetNumber: String;
var
Pos1: Integer;
begin
Result := '';
if FSkipSpace then
SkipSpaces;
Pos1 := FPosition;
if DoUnsignedReal or DoUnsignedInteger then
Result := Copy(FText, FLastPosition, FPosition - FLastPosition) else
FPosition := Pos1;
if FHexSequence <> '$' then
while Pos(FHexSequence, Result) <> 0 do
begin
Pos1 := Pos(FHexSequence, Result);
Delete(Result, Pos1, Length(FHexSequence));
Insert('$', Result, Pos1);
end;
end;
function TfsParser.GetFRString: String;
var
i, c: Integer;
fl1, fl2: Boolean;
begin
Result := '';
i := FPosition;
fl1 := True;
fl2 := True;
c := 1;
Dec(FPosition);
repeat
Inc(FPosition);
{ if FText[FPosition] in [#10, #13] then
begin
FPosition := i;
break;
end;}
if fl1 and fl2 then
{$IFDEF Delphi12}
if CharInSet(FText[FPosition], ['<', '[']) then
{$ELSE}
if FText[FPosition] in ['<', '['] then
{$ENDIF}
Inc(c)
{$IFDEF Delphi12}
else if CharInSet(FText[FPosition], ['>', ']']) then
{$ELSE}
else if FText[FPosition] in ['>', ']'] then
{$ENDIF}
Dec(c);
if fl1 then
if FText[FPosition] = '"' then
fl2 := not fl2;
if fl2 then
if FText[FPosition] = '''' then
fl1 := not fl1;
until (c = 0) or (FPosition >= Length(FText));
Result := Copy(FText, i, FPosition - i);
end;
function TfsParser.GetXYPosition: String;
var
i, i0, i1, c, pos, X, Y: Integer;
begin
i0 := 0;
i1 := FYList.Count - 1;
while i0 <= i1 do
begin
i := (i0 + i1) div 2;
pos := Integer(FYList[i]);
if pos = FPosition then
c := 0
else if pos > FPosition then
c := 1
else
c := -1;
if c < 0 then
i0 := i + 1
else
begin
i1 := i - 1;
if c = 0 then
i0 := i;
end;
end;
X := 1;
Y := i0;
i := Integer(FYList[i0 - 1]) + 1;
while i < FPosition do
begin
Inc(i);
Inc(X);
end;
Result := IntToStr(Y) + ':' + IntToStr(X)
end;
function TfsParser.GetPlainPosition(pt: TPoint): Integer;
var
i: Integer;
begin
Result := -1;
i := pt.Y - 1;
if (i >= 0) and (i < FYList.Count) then
Result := Integer(FYList[i]) + pt.X;
end;
end.