mirror of https://github.com/red-prig/fpPS4.git
569 lines
14 KiB
Plaintext
569 lines
14 KiB
Plaintext
{
|
|
This file is part of the Free Component Library
|
|
|
|
JSON source lexical scanner
|
|
Copyright (c) 2007 by Michael Van Canneyt michael@freepascal.org
|
|
|
|
See the file COPYING.FPC, included in this distribution,
|
|
for details about the copyright.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
**********************************************************************}
|
|
//modifed by Red_prig
|
|
{$mode objfpc}
|
|
{$h+}
|
|
|
|
{$ifdef fpc}
|
|
{$define UsePChar}
|
|
{$endif}
|
|
|
|
unit Ujsonscanner;
|
|
|
|
interface
|
|
|
|
uses SysUtils, Classes,bufstream,Ufpjson;
|
|
|
|
resourcestring
|
|
SErrInvalidCharacter = 'Invalid character at line %d, pos %d: ''%s''';
|
|
SUnterminatedComment = 'Unterminated comment at line %d, pos %d: ''%s''';
|
|
SErrOpenString = 'string exceeds end of line %d';
|
|
|
|
type
|
|
|
|
TJSONToken = (
|
|
tkEOF,
|
|
tkWhitespace,
|
|
tkString,
|
|
tkNumber,
|
|
tkTrue,
|
|
tkFalse,
|
|
tkNull,
|
|
// Simple (one-character) tokens
|
|
tkComma, // ','
|
|
tkColon, // ':'
|
|
tkCurlyBraceOpen, // '{'
|
|
tkCurlyBraceClose, // '}'
|
|
tkSquaredBraceOpen, // '['
|
|
tkSquaredBraceClose, // ']'
|
|
tkIdentifier, // Any Javascript identifier
|
|
tkComment,
|
|
tkUnknown
|
|
);
|
|
|
|
EScannerError = class(EParserError);
|
|
|
|
TJSONOption = (joUTF8,joStrict,joComments,joIgnoreTrailingComma);
|
|
TJSONOptions = set of TJSONOption;
|
|
|
|
Const
|
|
DefaultOptions = [joUTF8];
|
|
|
|
Type
|
|
|
|
TJSONSInFlag=Set of (ifFreeSource,ifPrevChar,ifEOF);
|
|
|
|
{ TJSONScanner }
|
|
|
|
TJSONScanner = class
|
|
private
|
|
FSource:TStream;
|
|
FInFlag:TJSONSInFlag;
|
|
|
|
FCurRow,FCurColumn: Integer;
|
|
FCurToken: TJSONToken;
|
|
FCurTokenString: RawBytestring;
|
|
FCurChar:AnsiChar;
|
|
|
|
FOptions : TJSONOptions;
|
|
|
|
FAddStr:TUtf8AddStr;
|
|
|
|
function GetO(AIndex: TJSONOption): Boolean;
|
|
procedure SetO(AIndex: TJSONOption; AValue: Boolean);
|
|
protected
|
|
procedure Error(const Msg: string);overload;
|
|
procedure Error(const Msg: string;
|
|
Const Args: array of {$ifdef pas2js}jsvalue{$else}const{$endif});overload;
|
|
function DoFetchToken: TJSONToken; inline;
|
|
public
|
|
{$ifdef fpc}
|
|
constructor Create(Source : TStream; AUseUTF8 : Boolean = True); overload; deprecated 'use options form instead';
|
|
constructor Create(const Source : RawByteString; AUseUTF8 : Boolean = True); overload; deprecated 'use options form instead';
|
|
constructor Create(Source: TStream; AOptions: TJSONOptions); overload;
|
|
{$endif}
|
|
constructor Create(const Source: RawByteString; AOptions: TJSONOptions); overload;
|
|
destructor Destroy; override;
|
|
function FetchToken: TJSONToken;
|
|
|
|
function NextChar:Boolean;
|
|
Procedure StepPrev; inline;
|
|
|
|
//property CurLine: RawBytestring read FCurLine;
|
|
property CurRow: Integer read FCurRow;
|
|
property CurColumn: Integer read FCurColumn;
|
|
|
|
property CurToken: TJSONToken read FCurToken;
|
|
property CurTokenString: RawBytestring read FCurTokenString;
|
|
// Use strict JSON: " for strings, object members are strings, not identifiers
|
|
Property Strict : Boolean Index joStrict Read GetO Write SetO ; deprecated 'use options instead';
|
|
// if set to TRUE, then strings will be converted to UTF8 ansistrings, not system codepage ansistrings.
|
|
Property UseUTF8 : Boolean index joUTF8 Read GetO Write SetO; deprecated 'Use options instead';
|
|
// Parsing options
|
|
Property Options : TJSONOptions Read FOptions Write FOptions;
|
|
end;
|
|
|
|
const
|
|
TokenInfos: array[TJSONToken] of RawBytestring = (
|
|
'EOF',
|
|
'Whitespace',
|
|
'String',
|
|
'Number',
|
|
'True',
|
|
'False',
|
|
'Null',
|
|
',',
|
|
':',
|
|
'{',
|
|
'}',
|
|
'[',
|
|
']',
|
|
'identifier',
|
|
'comment',
|
|
''
|
|
);
|
|
|
|
|
|
implementation
|
|
|
|
{$ifdef fpc}
|
|
constructor TJSONScanner.Create(Source : TStream; AUseUTF8 : Boolean = True);
|
|
|
|
Var
|
|
O : TJSONOptions;
|
|
|
|
begin
|
|
O:=DefaultOptions;
|
|
if AUseUTF8 then
|
|
Include(O,joUTF8)
|
|
else
|
|
Exclude(O,joUTF8);
|
|
Create(Source,O);
|
|
end;
|
|
|
|
constructor TJSONScanner.Create(const Source : RawByteString; AUseUTF8 : Boolean = True);
|
|
Var
|
|
O : TJSONOptions;
|
|
|
|
begin
|
|
O:=DefaultOptions;
|
|
if AUseUTF8 then
|
|
Include(O,joUTF8)
|
|
else
|
|
Exclude(O,joUTF8);
|
|
Create(Source,O);
|
|
end;
|
|
|
|
constructor TJSONScanner.Create(Source: TStream; AOptions: TJSONOptions);
|
|
begin
|
|
FAddStr:=Default(TUtf8AddStr);
|
|
if Source.InheritsFrom(THandleStream) then
|
|
begin
|
|
FSource:=TReadBufStream.Create(Source,4*1024);
|
|
FInFlag:=[ifFreeSource];
|
|
end else
|
|
begin
|
|
FSource:=Source;
|
|
end;
|
|
|
|
FOptions:=AOptions;
|
|
end;
|
|
{$endif}
|
|
|
|
constructor TJSONScanner.Create(const Source: RawByteString; AOptions: TJSONOptions);
|
|
begin
|
|
FAddStr:=Default(TUtf8AddStr);
|
|
FSource:=TStringStream.Create(Source);
|
|
FInFlag:=[ifFreeSource];
|
|
FOptions:=AOptions;
|
|
end;
|
|
|
|
destructor TJSONScanner.Destroy;
|
|
begin
|
|
FAddStr.Free;
|
|
if ifFreeSource in FInFlag then
|
|
begin
|
|
FreeAndNil(FSource);
|
|
end;
|
|
Inherited;
|
|
end;
|
|
|
|
function TJSONScanner.NextChar:Boolean;
|
|
begin
|
|
if ifEOF in FInFlag then
|
|
begin
|
|
Result:=False;
|
|
end;
|
|
|
|
if ifPrevChar in FInFlag then
|
|
begin
|
|
Result:=True;
|
|
Exclude(FInFlag,ifPrevChar);
|
|
end else
|
|
begin
|
|
Result:=FSource.Read(FCurChar,1)=1;
|
|
if Result then
|
|
begin
|
|
Inc(FCurColumn);
|
|
end else
|
|
begin
|
|
FCurToken:=tkEOF;
|
|
Include(FInFlag,ifEOF);
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
Procedure TJSONScanner.StepPrev; inline;
|
|
begin
|
|
Include(FInFlag,ifPrevChar);
|
|
end;
|
|
|
|
function TJSONScanner.FetchToken: TJSONToken;
|
|
begin
|
|
Result:=DoFetchToken;
|
|
end;
|
|
|
|
procedure TJSONScanner.Error(const Msg: string);
|
|
begin
|
|
raise EScannerError.Create(Msg);
|
|
end;
|
|
|
|
procedure TJSONScanner.Error(const Msg: string;
|
|
const Args: array of {$ifdef pas2js}jsvalue{$else}const{$endif});
|
|
begin
|
|
raise EScannerError.CreateFmt(Msg, Args);
|
|
end;
|
|
|
|
function TJSONScanner.DoFetchToken: TJSONToken;
|
|
|
|
function CheckNextLine:Boolean; inline;
|
|
begin
|
|
Result:=False;
|
|
if not NextChar then Exit;
|
|
case FCurChar of
|
|
#13:begin //next line
|
|
Inc(FCurRow);
|
|
if not NextChar then Exit;
|
|
FCurColumn:=0;
|
|
case FCurChar of
|
|
#10:FCurColumn:=0;
|
|
else
|
|
StepPrev;
|
|
end;
|
|
end;
|
|
#10:begin //next line
|
|
Inc(FCurRow);
|
|
if not NextChar then Exit;
|
|
FCurColumn:=0;
|
|
case FCurChar of
|
|
#13:FCurColumn:=0;
|
|
else
|
|
StepPrev;
|
|
end;
|
|
end;
|
|
end;
|
|
Result:=True;
|
|
end;
|
|
|
|
var
|
|
it : TJSONToken;
|
|
I : Integer;
|
|
tstart,tcol, u2: Integer;
|
|
C , c2: char;
|
|
|
|
|
|
begin
|
|
|
|
if not NextChar then Exit(tkEOF);
|
|
|
|
if (FCurRow=0) then
|
|
begin
|
|
FCurRow:=1;
|
|
if (FCurColumn=1) then
|
|
begin
|
|
if (FCurChar=#$EF) then
|
|
begin
|
|
if not NextChar then Exit(tkEOF);
|
|
if (FCurChar=#$BB) then
|
|
begin
|
|
if not NextChar then Exit(tkEOF);
|
|
if (FCurChar=#$BF) then
|
|
begin
|
|
if not NextChar then Exit(tkEOF);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
FCurColumn:=1;
|
|
end;
|
|
|
|
FCurTokenString := '';
|
|
|
|
case FCurChar of
|
|
#13:begin //next line
|
|
Result := tkWhitespace;
|
|
Inc(FCurRow);
|
|
if not NextChar then Exit;
|
|
FCurColumn:=0;
|
|
case FCurChar of
|
|
#10:FCurColumn:=0;
|
|
else
|
|
StepPrev;
|
|
end;
|
|
end;
|
|
#10:begin //next line
|
|
Result := tkWhitespace;
|
|
Inc(FCurRow);
|
|
if not NextChar then Exit;
|
|
FCurColumn:=0;
|
|
case FCurChar of
|
|
#13:FCurColumn:=0;
|
|
else
|
|
StepPrev;
|
|
end;
|
|
end;
|
|
#0,#9, ' ':
|
|
begin
|
|
Result := tkWhitespace;
|
|
repeat
|
|
if not CheckNextLine then Exit;
|
|
until not (FCurChar in [#0,#9, ' ']);
|
|
StepPrev;
|
|
end;
|
|
'"','''':
|
|
begin
|
|
C:=FCurChar;
|
|
If (C='''') and (joStrict in Options) then
|
|
Error(SErrInvalidCharacter, [CurRow,CurColumn,FCurChar]);
|
|
if not NextChar then Exit(tkEOF);
|
|
FAddStr.Reset;
|
|
while not (FCurChar in [#0,#13,#10,C]) do
|
|
begin
|
|
if (FCurChar='\') then
|
|
begin
|
|
if not NextChar then
|
|
begin
|
|
Error(SErrOpenString,[FCurRow]);
|
|
Exit(tkEOF);
|
|
end;
|
|
Case FCurChar of
|
|
't' : FAddStr.AddChar(#9);
|
|
'b' : FAddStr.AddChar(#8);
|
|
'n' : FAddStr.AddChar(#10);
|
|
'r' : FAddStr.AddChar(#13);
|
|
'f' : FAddStr.AddChar(#12);
|
|
'u' : begin
|
|
u2:=0;
|
|
For I:=1 to 4 do
|
|
begin
|
|
if not NextChar then Exit(tkEOF);
|
|
c2:=FCurChar;
|
|
Case c2 of
|
|
'0'..'9': u2:=u2*16+ord(c2)-ord('0');
|
|
'A'..'F': u2:=u2*16+ord(c2)-ord('A')+10;
|
|
'a'..'f': u2:=u2*16+ord(c2)-ord('a')+10;
|
|
else
|
|
Error(SErrInvalidCharacter, [CurRow,CurColumn,FCurChar]);
|
|
end;
|
|
end;
|
|
FAddStr.AddWideChar(WideChar(u2));
|
|
end;
|
|
else
|
|
FAddStr.AddChar(FCurChar);
|
|
end;
|
|
end else
|
|
begin
|
|
FAddStr.AddChar(FCurChar);
|
|
end;
|
|
if not NextChar then Exit(tkEOF);
|
|
end;
|
|
if (FCurChar in [#0,#13,#10]) then
|
|
Error(SErrOpenString,[FCurRow]);
|
|
|
|
Result:=tkString;
|
|
FCurTokenString:=FAddStr.GetStr;
|
|
end;
|
|
',':
|
|
begin
|
|
Result := tkComma;
|
|
end;
|
|
'0'..'9','.','-':
|
|
begin
|
|
FAddStr.Reset;
|
|
FAddStr.AddChar(FCurChar);
|
|
while NextChar do
|
|
begin
|
|
|
|
case FCurChar of
|
|
'.':
|
|
begin
|
|
FAddStr.AddChar(FCurChar);
|
|
if not NextChar then Break;
|
|
//FAddStr.AddChar(FCurChar);
|
|
if FCurChar in ['0'..'9', 'e', 'E'] then
|
|
begin
|
|
//if not NextChar then Break;
|
|
repeat
|
|
FAddStr.AddChar(FCurChar);
|
|
if not NextChar then Break;
|
|
until not (FCurChar in ['0'..'9', 'e', 'E','-','+']);
|
|
end;
|
|
StepPrev;
|
|
break;
|
|
end;
|
|
'0'..'9':FAddStr.AddChar(FCurChar);
|
|
'e', 'E':
|
|
begin
|
|
FAddStr.AddChar(FCurChar);
|
|
if not NextChar then Break;
|
|
FAddStr.AddChar(FCurChar);
|
|
if FCurChar in ['-','+'] then
|
|
begin
|
|
if not NextChar then Break;
|
|
end;
|
|
while FCurChar in ['0'..'9'] do
|
|
begin
|
|
FAddStr.AddChar(FCurChar);
|
|
if not NextChar then Break;
|
|
end;
|
|
StepPrev;
|
|
break;
|
|
end;
|
|
else
|
|
if not (FCurChar in [#0,'}',']',',',#9,' ',#13,#10]) then
|
|
Error(SErrInvalidCharacter, [CurRow,CurColumn,FCurChar]);
|
|
StepPrev;
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
FCurTokenString:=FAddStr.GetStr;
|
|
If (FCurTokenString[1]='.') then
|
|
FCurTokenString:='0'+FCurTokenString;
|
|
Result := tkNumber;
|
|
end;
|
|
':':
|
|
begin
|
|
Result := tkColon;
|
|
end;
|
|
'{':
|
|
begin
|
|
Result := tkCurlyBraceOpen;
|
|
end;
|
|
'}':
|
|
begin
|
|
Result := tkCurlyBraceClose;
|
|
end;
|
|
'[':
|
|
begin
|
|
Result := tkSquaredBraceOpen;
|
|
end;
|
|
']':
|
|
begin
|
|
Result := tkSquaredBraceClose;
|
|
end;
|
|
'/' :
|
|
begin
|
|
if Not (joComments in Options) then
|
|
Error(SErrInvalidCharacter, [CurRow,CurCOlumn,FCurChar]);
|
|
FAddStr.Reset;
|
|
FAddStr.AddChar(FCurChar);
|
|
Case FCurChar of
|
|
'/' : begin
|
|
i:=FCurRow;
|
|
While (i=FCurRow) do
|
|
begin
|
|
if not CheckNextLine then Break;
|
|
FAddStr.AddChar(FCurChar);
|
|
end;
|
|
FCurTokenString:=FAddStr.GetStr;
|
|
end;
|
|
'*' :
|
|
begin
|
|
|
|
if not CheckNextLine then
|
|
begin
|
|
Error(SUnterminatedComment, [CurRow,CurCOlumn,FCurChar]);
|
|
Exit(tkEOF);
|
|
end;
|
|
|
|
repeat
|
|
FAddStr.AddChar(FCurChar);
|
|
c:=FCurChar;
|
|
if not CheckNextLine then
|
|
begin
|
|
Error(SUnterminatedComment, [CurRow,CurCOlumn,FCurChar]);
|
|
Break;
|
|
end;
|
|
until (c='*') and (FCurChar='/');
|
|
|
|
FCurTokenString:=FAddStr.GetStr;
|
|
end;
|
|
else
|
|
Error(SErrInvalidCharacter, [CurRow,CurCOlumn,FCurChar]);
|
|
end;
|
|
Result:=tkComment;
|
|
end;
|
|
'a'..'z','A'..'Z','_':
|
|
begin
|
|
FAddStr.Reset;
|
|
|
|
tStart:=CurRow;
|
|
tcol :=CurColumn;
|
|
|
|
repeat
|
|
FAddStr.AddChar(FCurChar);
|
|
if not NextChar then Break;
|
|
until not (FCurChar in ['A'..'Z', 'a'..'z', '0'..'9', '_']);
|
|
StepPrev;
|
|
|
|
FCurTokenString:=FAddStr.GetStr;
|
|
|
|
for it := tkTrue to tkNull do
|
|
if CompareText(FCurTokenString, TokenInfos[it]) = 0 then
|
|
begin
|
|
Result := it;
|
|
FCurToken := Result;
|
|
exit;
|
|
end;
|
|
|
|
if (joStrict in Options) then
|
|
Error(SErrInvalidCharacter, [tStart,tcol,FCurTokenString])
|
|
else
|
|
Result:=tkIdentifier;
|
|
end;
|
|
else
|
|
Error(SErrInvalidCharacter, [CurRow,CurColumn,FCurChar]);
|
|
end;
|
|
|
|
FCurToken:=Result;
|
|
end;
|
|
|
|
function TJSONScanner.GetO(AIndex: TJSONOption): Boolean;
|
|
begin
|
|
Result:=AIndex in FOptions;
|
|
end;
|
|
|
|
procedure TJSONScanner.SetO(AIndex: TJSONOption; AValue: Boolean);
|
|
begin
|
|
If AValue then
|
|
Include(Foptions,AIndex)
|
|
else
|
|
Exclude(Foptions,AIndex)
|
|
end;
|
|
|
|
end.
|