123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- {
- This file is part of the Free Component Library
- JSON source lexical scanner
- Copyright (c) 2007 by Michael Van Canneyt [email protected]
- 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.
- **********************************************************************}
- {$mode objfpc}
- {$h+}
- unit jsonscanner;
- interface
- uses SysUtils, Classes;
- resourcestring
- SErrInvalidCharacter = 'Invalid character ''%s''';
- SErrOpenString = 'string exceeds end of line';
- type
- TJSONToken = (
- tkEOF,
- tkWhitespace,
- tkString,
- tkNumber,
- tkTrue,
- tkFalse,
- tkNull,
- // Simple (one-character) tokens
- tkComma, // ','
- tkColon, // ':'
- tkCurlyBraceOpen, // '{'
- tkCurlyBraceClose, // '}'
- tkSquaredBraceOpen, // '['
- tkSquaredBraceClose, // ']'
- tkUnknown
- );
- EScannerError = class(Exception);
- TJSONScanner = class
- private
- FSource : TStringList;
- FCurRow: Integer;
- FCurToken: TJSONToken;
- FCurTokenString: string;
- FCurLine: string;
- TokenStr: PChar;
- function GetCurColumn: Integer;
- protected
- procedure Error(const Msg: string);overload;
- procedure Error(const Msg: string; Args: array of Const);overload;
- function DoFetchToken: TJSONToken;
- public
- constructor Create(Source : TStream); overload;
- constructor Create(Source : String); overload;
- destructor Destroy; override;
- function FetchToken: TJSONToken;
- property CurLine: string read FCurLine;
- property CurRow: Integer read FCurRow;
- property CurColumn: Integer read GetCurColumn;
- property CurToken: TJSONToken read FCurToken;
- property CurTokenString: string read FCurTokenString;
- end;
- const
- TokenInfos: array[TJSONToken] of string = (
- 'EOF',
- 'Whitespace',
- 'String',
- 'Number',
- 'True',
- 'False',
- 'Null',
- ',',
- ':',
- '{',
- '}',
- '[',
- ']',
- ''
- );
- implementation
- constructor TJSONScanner.Create(Source : TStream);
- begin
- FSource:=TStringList.Create;
- FSource.LoadFromStream(Source);
- end;
- constructor TJSONScanner.Create(Source : String);
- begin
- FSource:=TStringList.Create;
- FSource.Text:=Source;
- end;
- destructor TJSONScanner.Destroy;
- begin
- FreeAndNil(FSource);
- Inherited;
- 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; Args: array of Const);
- begin
- raise EScannerError.CreateFmt(Msg, Args);
- end;
- function TJSONScanner.DoFetchToken: TJSONToken;
- function FetchLine: Boolean;
- begin
- Result:=FCurRow<FSource.Count;
- if Result then
- begin
- FCurLine:=FSource[FCurRow];
- TokenStr:=PChar(FCurLine);
- Inc(FCurRow);
- end
- else
- begin
- FCurLine:='';
- TokenStr:=nil;
- end;
- end;
- var
- TokenStart, CurPos: PChar;
- it : TJSONToken;
- I : Integer;
- OldLength, SectionLength, Index: Integer;
- S : String;
-
- begin
- if TokenStr = nil then
- if not FetchLine then
- begin
- Result := tkEOF;
- FCurToken := Result;
- exit;
- end;
- FCurTokenString := '';
- case TokenStr[0] of
- #0: // Empty line
- begin
- FetchLine;
- Result := tkWhitespace;
- end;
- #9, ' ':
- begin
- Result := tkWhitespace;
- repeat
- Inc(TokenStr);
- if TokenStr[0] = #0 then
- if not FetchLine then
- begin
- FCurToken := Result;
- exit;
- end;
- until not (TokenStr[0] in [#9, ' ']);
- end;
- '"':
- begin
- Inc(TokenStr);
- TokenStart := TokenStr;
- OldLength := 0;
- FCurTokenString := '';
- while not (TokenStr[0] in [#0,'"']) do
- begin
- if (TokenStr[0]='\') then
- begin
- // Save length
- SectionLength := TokenStr - TokenStart;
- Inc(TokenStr);
- // Read escaped token
- Case TokenStr[0] of
- '"' : S:='"';
- 't' : S:=#9;
- 'b' : S:=#8;
- 'n' : S:=#10;
- 'r' : S:=#13;
- 'f' : S:=#12;
- '\' : S:='\';
- '/' : S:='/';
- 'u' : begin
- S:='0000';
- For I:=1 to 4 do
- begin
- Inc(TokenStr);
- Case TokenStr[0] of
- '0'..'9','A'..'F','a'..'f' :
- S[i]:=Upcase(TokenStr[0]);
- else
- Error(SErrInvalidCharacter, [TokenStr[0]]);
- end;
- end;
- // Takes care of conversion...
- S:=WideChar(StrToInt('$'+S));
- end;
- #0 : Error(SErrOpenString);
- else
- Error(SErrInvalidCharacter, [TokenStr[0]]);
- end;
- SetLength(FCurTokenString, OldLength + SectionLength+1+Length(S));
- if SectionLength > 0 then
- Move(TokenStart^, FCurTokenString[OldLength + 1], SectionLength);
- Move(S[1],FCurTokenString[OldLength + SectionLength+1],Length(S));
- Inc(OldLength, SectionLength+Length(S));
- // Next char
- // Inc(TokenStr);
- TokenStart := TokenStr+1;
- end;
- if TokenStr[0] = #0 then
- Error(SErrOpenString);
- Inc(TokenStr);
- end;
- if TokenStr[0] = #0 then
- Error(SErrOpenString);
- SectionLength := TokenStr - TokenStart;
- SetLength(FCurTokenString, OldLength + SectionLength);
- if SectionLength > 0 then
- Move(TokenStart^, FCurTokenString[OldLength + 1], SectionLength);
- Inc(TokenStr);
- Result := tkString;
- end;
- ',':
- begin
- Inc(TokenStr);
- Result := tkComma;
- end;
- '0'..'9','-':
- begin
- TokenStart := TokenStr;
- while true do
- begin
- Inc(TokenStr);
- case TokenStr[0] of
- '.':
- begin
- if TokenStr[1] in ['0'..'9', 'e', 'E'] then
- begin
- Inc(TokenStr);
- repeat
- Inc(TokenStr);
- until not (TokenStr[0] in ['0'..'9', 'e', 'E','-','+']);
- end;
- break;
- end;
- '0'..'9': ;
- 'e', 'E':
- begin
- Inc(TokenStr);
- if TokenStr[0] in ['-','+'] then
- Inc(TokenStr);
- while TokenStr[0] in ['0'..'9'] do
- Inc(TokenStr);
- break;
- end;
- else
- break;
- end;
- end;
- SectionLength := TokenStr - TokenStart;
- SetLength(FCurTokenString, SectionLength);
- if SectionLength > 0 then
- Move(TokenStart^, FCurTokenString[1], SectionLength);
- Result := tkNumber;
- end;
- ':':
- begin
- Inc(TokenStr);
- Result := tkColon;
- end;
- '{':
- begin
- Inc(TokenStr);
- Result := tkCurlyBraceOpen;
- end;
- '}':
- begin
- Inc(TokenStr);
- Result := tkCurlyBraceClose;
- end;
- '[':
- begin
- Inc(TokenStr);
- Result := tkSquaredBraceOpen;
- end;
- ']':
- begin
- Inc(TokenStr);
- Result := tkSquaredBraceClose;
- end;
- 'T','t','F','f','N','n' :
- begin
- TokenStart := TokenStr;
- repeat
- Inc(TokenStr);
- until not (TokenStr[0] in ['A'..'Z', 'a'..'z', '0'..'9', '_']);
- SectionLength := TokenStr - TokenStart;
- SetLength(FCurTokenString, SectionLength);
- if SectionLength > 0 then
- Move(TokenStart^, FCurTokenString[1], SectionLength);
- for it := tkTrue to tkNull do
- if CompareText(CurTokenString, TokenInfos[it]) = 0 then
- begin
- Result := it;
- FCurToken := Result;
- exit;
- end;
- Error(SErrInvalidCharacter, [TokenStart[0]]);
- end;
- else
- Error(SErrInvalidCharacter, [TokenStr[0]]);
- end;
- FCurToken := Result;
- end;
- function TJSONScanner.GetCurColumn: Integer;
- begin
- Result := TokenStr - PChar(CurLine);
- end;
- end.
|