12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022 |
- {
- This file is part of the Free Component Library
- Copyright (c) 2010-2014 by the Free Pascal development team
- SQL source lexical scanner
- 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 fpsqlscanner;
- interface
- uses SysUtils, Classes, Contnrs;
- resourcestring
- SErrInvalidCharacter = 'Invalid character ''%s''';
- SErrOpenString = 'string exceeds end of line';
- SErrIncludeFileNotFound = 'Could not find include file ''%s''';
- SErrIfXXXNestingLimitReached = 'Nesting of $IFxxx too deep';
- SErrInvalidPPElse = '$ELSE without matching $IFxxx';
- SErrInvalidPPEndif = '$ENDIF without matching $IFxxx';
- SInvalidHexadecimalNumber = 'Invalid hexadecimal number';
- SErrInvalidNonEqual = 'SyntaxError: != or !== expected';
- SBarExpected = '| character expected';
- type
- TSQLToken = (tsqlUnknown,
- // Specials
- tsqlEOF,tsqlWhiteSpace,
- tsqlString {string literal},
- tsqlIdentifier {a table etc name},
- tsqlSymbolString {a string containing symbols/punctuation marks; only rarely used - e.g. in SET TERM ^ ;},
- tsqlIntegerNumber,tsqlFloatNumber,tsqlComment,
- tsqlBraceOpen,tsqlBraceClose,tsqlSquareBraceOpen,tsqlSquareBraceClose,
- tsqlPlaceHolder {question mark},
- tsqlCOMMA,tsqlCOLON,tsqlDOT,tsqlSEMICOLON,tsqlTerminator,
- tsqlGT,tsqlLT,tsqlPLUS,tsqlMINUS,tsqlMUL,tsqlDIV,tsqlConcatenate,
- tsqlEQ,tsqlGE,tsqlLE,tsqlNE,
- { Reserved words/keywords start here. They must be last }
- { Note: if adding before tsqlALL or after tsqlWHEN please update FirstKeyword/LastKeyword }
- tsqlALL, tsqlAND, tsqlANY, tsqlASC, tsqlASCENDING, tsqlAVG, tsqlALTER, tsqlAdd, tsqlActive, tsqlAction, tsqlAs,tsqlAt, tsqlAuto, tsqlAfter,tsqlAdmin,
- tsqlBETWEEN, tsqlBinary, tsqlBY, tsqlBLOB, tsqlBegin, tsqlBefore,
- tsqlCASE, tsqlCOLLATE, tsqlCONTAINING, tsqlCOUNT, tsqlCREATE, tsqlCOLUMN, tsqlCONSTRAINT, tsqlChar,tsqlCHARACTER, tsqlCHECK, tsqlComputed,tsqlCASCADE, tsqlCast, tsqlCommit,tsqlConnect,tsqlCache,tsqlConditional,tsqlCString,
- tsqlDESC, tsqlDESCENDING, tsqlDISTINCT, tsqlDEFAULT, tsqlDELETE, tsqlDO, tsqlDouble, tsqlDECLARE, tsqlDROP, tsqlDomain, tsqlDecimal, tsqlDate,tsqlDatabase,
- tsqlESCAPE, tsqlEXISTS, tsqlELSE, tsqlException, tsqlExternal, tsqlExecute, tsqlEnd,tsqlExit,tsqlEntrypoint,tsqlExtract,
- tsqlFIRST, tsqlFROM, tsqlFULL, tsqlFOREIGN, tsqlFOR, tsqlFUNCTION, tsqlFLOAT, tsqlFile,tsqlFreeIt,
- tsqlGenerator, tsqlGROUP, tsqlGenID,tsqlGDSCODE,tsqlGrant,
- tsqlHAVING,
- tsqlIF, tsqlIN, tsqlINNER, tsqlINSERT, tsqlINT, tsqlINTEGER, tsqlINTO, tsqlIS, tsqlINDEX, tsqlInactive,
- tsqlJOIN,
- tsqlKEY,
- tsqlLEFT, tsqlLIKE, tsqlLIMIT, tsqlLength,
- tsqlMAX, tsqlMIN, tsqlMERGE, tsqlManual, tsqlModuleName,
- tsqlNOT, tsqlNULL, tsqlNUMERIC , tsqlNChar, tsqlNATIONAL,tsqlNO, tsqlNatural,
- tsqlOFF {not an FB reserved word; used in isql scripts}, tsqlOFFSET, tsqlON, tsqlOR, tsqlORDER, tsqlOUTER, tsqlOption,
- tsqlPrecision, tsqlPRIMARY, tsqlProcedure, tsqlPosition, tsqlPlan, tsqlPassword, tsqlPage,tsqlPages,tsqlPageSize,tsqlPostEvent,tsqlPrivileges,tsqlPublic,
- tsqlRIGHT, tsqlROLE, tsqlReferences, tsqlRollBack, tsqlRelease, tsqlretain, tsqlReturningValues,tsqlReturns, tsqlrevoke,
- tsqlSELECT, tsqlSET, tsqlSINGULAR, tsqlSOME, tsqlSTARTING, tsqlSUM, tsqlSKIP,tsqlSUBTYPE,tsqlSize,tsqlSegment, tsqlSORT, tsqlSnapShot,tsqlSchema,tsqlShadow,tsqlSuspend,tsqlSQLCode,tsqlSmallint,
- tSQLTABLE, tsqlText, tsqlTrigger, tsqlTime, tsqlTimeStamp, tsqlType, tsqlTo, tsqlTop, tsqlTransaction, tsqlThen,
- tsqlUNION, tsqlUPDATE, tsqlUPPER, tsqlUNIQUE, tsqlUSER,
- tsqlValue, tsqlVALUES, tsqlVARIABLE, tsqlVIEW, tsqlVARCHAR,TSQLVARYING,
- tsqlWHERE, tsqlWITH, tsqlWHILE, tsqlWork, tsqlWhen,tsqlSequence,tsqlRestart,tsqlrecreate,tsqlterm
- );
- TSQLTokens = set of TSQLToken;
- const
- FirstKeyword = tsqlAll;
- LastKeyWord = tsqlTerm;
- sqlComparisons = [tsqleq,tsqlGE,tsqlLE,tsqlNE,tsqlGT,tsqlLT,tsqlIn,tsqlIS,
- tsqlbetween,tsqlLike,tsqlContaining,tsqlStarting,tsqlNOT];
- sqlInvertableComparisons = [tsqlLike,tsqlContaining,tsqlStarting,tsqlIN,tsqlIS, tsqlBetween];
- // Strings that represent tokens in TSQLToken
- TokenInfos: array[TSQLToken] of string = ('unknown',
- // Specials
- 'EOF','whitespace',
- 'String',
- 'identifier',
- 'symbol string',
- 'integer number','float number', 'comment',
- '(',')', '[',']',
- '?',',',':','.',';','',
- '>','<',
- '+','-','*','/','||',
- '=','>=','<=','<>',
- // Identifiers last:
- 'ALL', 'AND', 'ANY', 'ASC', 'ASCENDING', 'AVG', 'ALTER', 'ADD','ACTIVE','ACTION', 'AS', 'AT', 'AUTO', 'AFTER', 'ADMIN',
- 'BETWEEN', 'BINARY', 'BY', 'BLOB','BEGIN', 'BEFORE',
- 'CASE', 'COLLATE', 'CONTAINING', 'COUNT', 'CREATE', 'COLUMN', 'CONSTRAINT', 'CHAR','CHARACTER','CHECK', 'COMPUTED','CASCADE','CAST', 'COMMIT', 'CONNECT', 'CACHE','CONDITIONAL', 'CSTRING',
- 'DESC', 'DESCENDING', 'DISTINCT', 'DEFAULT', 'DELETE', 'DO', 'DOUBLE', 'DECLARE', 'DROP', 'DOMAIN', 'DECIMAL', 'DATE','DATABASE',
- 'ESCAPE', 'EXISTS', 'ELSE', 'EXCEPTION', 'EXTERNAL','EXECUTE', 'END','EXIT','ENTRY_POINT','EXTRACT',
- 'FIRST', 'FROM', 'FULL','FOREIGN', 'FOR', 'FUNCTION', 'FLOAT','FILE', 'FREE_IT',
- 'GENERATOR', 'GROUP', 'GEN_ID','GDSCODE','GRANT',
- 'HAVING',
- 'IF', 'IN', 'INNER', 'INSERT', 'INT', 'INTEGER', 'INTO', 'IS', 'INDEX', 'INACTIVE',
- 'JOIN',
- 'KEY',
- 'LEFT', 'LIKE', 'LIMIT', 'LENGTH',
- 'MAX', 'MIN', 'MERGE', 'MANUAL', 'MODULE_NAME',
- 'NOT', 'NULL', 'NUMERIC','NCHAR','NATIONAL', 'NO', 'NATURAL',
- 'OFF', 'OFFSET', 'ON', 'OR', 'ORDER', 'OUTER', 'OPTION',
- 'PRECISION', 'PRIMARY', 'PROCEDURE','POSITION','PLAN', 'PASSWORD','PAGE','PAGES','PAGE_SIZE','POST_EVENT','PRIVILEGES','PUBLIC',
- 'RIGHT', 'ROLE', 'REFERENCES', 'ROLLBACK','RELEASE', 'RETAIN', 'RETURNING_VALUES', 'RETURNS','REVOKE',
- 'SELECT', 'SET', 'SINGULAR', 'SOME', 'STARTING', 'SUM', 'SKIP','SUB_TYPE', 'SIZE', 'SEGMENT', 'SORT', 'SNAPSHOT','SCHEMA','SHADOW','SUSPEND','SQLCODE','SMALLINT',
- 'TABLE', 'TEXT', 'TRIGGER', 'TIME', 'TIMESTAMP', 'TYPE', 'TO', 'TOP', 'TRANSACTION', 'THEN',
- 'UNION', 'UPDATE', 'UPPER', 'UNIQUE', 'USER',
- 'VALUE','VALUES','VARIABLE', 'VIEW','VARCHAR','VARYING',
- 'WHERE', 'WITH', 'WHILE','WORK','WHEN','SEQUENCE','RESTART','RECREATE','TERM'
- );
- Type
- TLineReader = class
- public
- function IsEOF: Boolean; virtual; abstract;
- function ReadLine: string; virtual; abstract;
- end;
- { TStreamLineReader }
- TStreamLineReader = class(TLineReader)
- private
- FStream : TStream;
- Buffer : Array[0..1024] of Byte;
- FBufPos,
- FBufLen : Integer;
- procedure FillBuffer;
- public
- Constructor Create(AStream : TStream);
- function IsEOF: Boolean; override;
- function ReadLine: string; override;
- end;
- TFileLineReader = class(TLineReader)
- private
- FTextFile: Text;
- FileOpened: Boolean;
- public
- constructor Create(const AFilename: string);
- destructor Destroy; override;
- function IsEOF: Boolean; override;
- function ReadLine: string; override;
- end;
- ESQLScannerError = class(Exception);
- { TSQLScanner }
- TSQLScannerOption = (soReturnComments,
- soReturnWhiteSpace,
- soBackslashEscapes,
- soNoDoubleDelimIsChar,
- soDoubleQuoteStringLiteral, // Default: single quote is string literal
- soSingleQuoteIdentifier, // Default: double quote is identifier. Ignored if soDoubleQuoteStringLiteral is not specified
- soBackQuoteIdentifier, // Default: double quote is identifier
- soSquareBracketsIdentifier // Default: square brackets are not supported. (Enable for MSSQL support.)
- );
- TSQLScannerOptions = Set of TSQLScannerOption;
- TSQLScanner = class
- private
- FAlternateTerminator: String;
- FOptions: TSQLScannerOptions;
- FSourceFile: TLineReader;
- FSourceFilename: string;
- FCurRow: Integer;
- FCurToken: TSQLToken;
- FCurTokenString: string;
- FCurTokenRow: Integer;
- FCurTokenColumn: Integer;
- FCurLine: string;
- TokenStr: PChar;
- FSourceStream : TStream;
- FOwnSourceFile : Boolean;
- FKeyWords : TFPHashList;
- FExclude : TStringList;
- function CommentDiv: TSQLToken;
- // Used to parse out an identifier/name and store it in the list of identifiers
- function DoIdentifier : TSQLToken;
- // Used to parse out a string containing symbols
- function DoSymbolString : TSQLToken;
- function DoMultiLineComment: TSQLToken;
- function DoNumericLiteral: TSQLToken;
- function DoSingleLineComment: TSQLToken;
- function DoStringLiteral: TSQLToken;
- function DoWhiteSpace: TSQLToken;
- // Reads a new line into TokenStr and returns true
- // If no new lines, returns false
- function FetchLine: Boolean;
- function GetCurColumn: Integer;
- function GetExcludeKeywords: TStrings;
- function ReadUnicodeEscape: WideChar;
- procedure SetExcludeKeywords(const AValue: TStrings);
- procedure Setoptions(const AValue: TSQLScannerOptions);
- procedure ClearKeywords(Sender: TObject);
- protected
- Procedure BuildKeyWords; virtual;
- procedure Error(const Msg: string);overload;
- procedure Error(const Msg: string; Args: array of Const);overload;
- public
- constructor Create(ALineReader: TLineReader);
- constructor Create(AStream : TStream);
- destructor Destroy; override;
- procedure OpenFile(const AFilename: string);
- Function FetchToken: TSQLToken;
- Function IsEndOfLine : Boolean;
- Property Options : TSQLScannerOptions Read FOptions Write Setoptions;
- property SourceFile: TLineReader read FSourceFile;
- property CurFilename: string read FSourceFilename;
- property CurLine: string read FCurLine;
- property CurRow: Integer read FCurRow;
- property CurColumn: Integer read GetCurColumn;
- property CurToken: TSQLToken read FCurToken;
- property CurTokenString: string read FCurTokenString;
- Property CurTokenRow : Integer Read FCurTokenRow;
- Property CurTokenColumn : Integer Read FCurTokenColumn;
- Property ExcludeKeywords : TStrings Read GetExcludeKeywords Write SetExcludeKeywords;
- Property AlternateTerminator : String Read FAlternateTerminator Write FAlternateTerminator;
- end;
- implementation
- Var
- // Keeps track of identifiers used
- IdentifierTokens : array[FirstKeyword..LastKeyWord] of TSQLToken;
- IdentifierTokensOK : Boolean;
- Resourcestring
- SErrUnknownToken = 'Unknown token: %s';
- Procedure BuildIdentifierTokens;
- Var
- T : TSQLToken;
- begin
- For T:=FirstKeyword to LastKeyWord do
- IdentifierTokens[T]:=T;
- IdentifierTokensOK:=True;
- end;
- constructor TFileLineReader.Create(const AFilename: string);
- begin
- inherited Create;
- Assign(FTextFile, AFilename);
- Reset(FTextFile);
- FileOpened := true;
- end;
- destructor TFileLineReader.Destroy;
- begin
- if FileOpened then
- Close(FTextFile);
- inherited Destroy;
- end;
- function TFileLineReader.IsEOF: Boolean;
- begin
- Result := EOF(FTextFile);
- end;
- function TFileLineReader.ReadLine: string;
- begin
- ReadLn(FTextFile, Result);
- end;
- constructor TSQLScanner.Create(ALineReader: TLineReader);
- begin
- inherited Create;
- FSourceFile := ALineReader;
- FKeywords:=TFPHashList.Create;
- end;
- constructor TSQLScanner.Create(AStream: TStream);
- begin
- FSourceStream:=ASTream;
- FOwnSourceFile:=True;
- Create(TStreamLineReader.Create(AStream));
- end;
- destructor TSQLScanner.Destroy;
- begin
- If FOwnSourceFile then
- FSourceFile.Free;
- FreeAndNil(FKeywords);
- inherited Destroy;
- end;
- procedure TSQLScanner.OpenFile(const AFilename: string);
- begin
- FSourceFile := TFileLineReader.Create(AFilename);
- FOwnSourceFile:=True;
- FSourceFilename := AFilename;
- end;
- procedure TSQLScanner.Error(const Msg: string);
- begin
- raise ESQLScannerError.Create(Msg);
- end;
- procedure TSQLScanner.Error(const Msg: string; Args: array of Const);
- begin
- raise ESQLScannerError.CreateFmt(Msg, Args);
- end;
- function TSQLScanner.FetchLine: Boolean;
- begin
- if FSourceFile.IsEOF then
- begin
- FCurLine := '';
- TokenStr := nil;
- Result := false;
- end else
- begin
- FCurLine := FSourceFile.ReadLine;
- TokenStr := PChar(CurLine);
- Result := true;
- Inc(FCurRow);
- end;
- end;
- function TSQLScanner.DoWhiteSpace : TSQLToken;
- begin
- Result:=tsqlWhitespace;
- repeat
- Inc(TokenStr);
- if TokenStr[0] = #0 then
- if not FetchLine then
- begin
- FCurToken := Result;
- exit;
- end;
- until not (TokenStr[0] in [#9, ' ']);
- end;
- function TSQLScanner.DoSingleLineComment : TSQLToken;
- Var
- TokenStart : PChar;
- Len : Integer;
- begin
- Inc(TokenStr);
- TokenStart := TokenStr;
- while TokenStr[0] <> #0 do
- Inc(TokenStr);
- Len:=TokenStr-TokenStart;
- SetLength(FCurTokenString, Len);
- if (Len>0) then
- Move(TokenStart^,FCurTokenString[1],Len);
- Result := tsqlComment;
- end;
- function TSQLScanner.DoMultiLineComment : TSQLToken;
- Var
- TokenStart : PChar;
- Len,OLen : Integer;
- PrevToken : Char;
- begin
- Inc(TokenStr);
- TokenStart := TokenStr;
- FCurTokenString := '';
- OLen:= 0;
- PrevToken:=#0;
- while Not ((TokenStr[0]='/') and (PrevToken='*')) do
- begin
- if (TokenStr[0]=#0) then
- begin
- Len:=TokenStr-TokenStart+1;
- SetLength(FCurTokenString,OLen+Len);
- if Len>1 then
- Move(TokenStart^,FCurTokenString[OLen+1],Len-1);
- Inc(OLen,Len);
- FCurTokenString[OLen]:=#10;
- if not FetchLine then
- begin
- Result := tsqlEOF;
- FCurToken := Result;
- exit;
- end;
- TokenStart := TokenStr;
- PrevToken:=#0;
- end
- else
- begin
- PrevToken:=TokenStr[0];
- Inc(TokenStr);
- end;
- end;
- Len:=TokenStr-TokenStart-1; // -1 for *
- SetLength(FCurTokenString, Olen+Len);
- if (Len>0) then
- begin
- Move(TokenStart^, FCurTokenString[Olen + 1], Len);
- end;
- If TokenStr[0]<>#0 then
- Inc(TokenStr);
- Result := tsqlComment;
- end;
- function TSQLScanner.CommentDiv : TSQLToken;
- begin
- FCurTokenString := '';
- Inc(TokenStr);
- if (TokenStr[0]='*') then
- Result:=DoMultiLineComment
- else
- Result:=tsqlDiv;
- end;
- Function TSQLScanner.ReadUnicodeEscape : WideChar;
- Var
- S : String;
- I : Integer;
- 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... This needs improvement !!
- Result:=WideChar(StrToInt('$'+S));
- end;
- procedure TSQLScanner.SetExcludeKeywords(const AValue: TStrings);
- begin
- With ExcludeKeywords do
- begin
- Clear;
- AddStrings(AValue);
- end;
- end;
- procedure TSQLScanner.Setoptions(const AValue: TSQLScannerOptions);
- Const
- F = [soDoubleQuoteStringLiteral,soSingleQuoteIdentifier];
- begin
- FOptions:=AValue;
- if ((Foptions * F) = [soSingleQuoteIdentifier]) then
- Exclude(FOptions,soSingleQuoteIdentifier);
- end;
- procedure TSQLScanner.BuildKeyWords;
- Var
- I : TSQLToken;
- begin
- If Not IdentifierTokensOK then
- BuildIdentifierTokens;
- If FKeywords.Count>0 then
- FKeywords.Clear;
- for I:=FirstKeyword to LastKeyword do
- if (not Assigned(FExclude)) or (FExclude.IndexOf(TokenInfos[I])=-1) then
- FKeywords.Add(TokenInfos[I],@IdentifierTokens[i]);
- end;
- function TSQLScanner.DoStringLiteral: TSQLToken;
- Var
- Delim : Char;
- TokenStart : PChar;
- Len,OLen : Integer;
- S : UnicodeString;
- Procedure AppendBufToTokenString(DoNextToken : Boolean);
- begin
- SetLength(FCurTokenString, OLen + Len+Length(S));
- if Len > 0 then
- Move(TokenStart^, FCurTokenString[OLen + 1], Len);
- If Length(S)>0 then
- Move(S[1],FCurTokenString[OLen + Len+1],Length(S));
- Inc(OLen, Len+Length(S));
- If DoNextToken then
- Inc(TokenStr);
- TokenStart := TokenStr+1;
- end;
- Function CheckTokenBuf : Boolean;
- begin
- Result:=(TokenStr[0]<>#0);
- If Not Result then
- begin
- S:='';
- Len:=TokenStr-TokenStart;
- AppendBufToTokenString(False);
- Result:=FetchLine;
- TokenStart:=TokenStr;
- end;
- end;
- begin
- Delim:=TokenStr[0];
- if Delim='[' then
- Delim:=']';
- Inc(TokenStr);
- TokenStart := TokenStr;
- OLen := 0;
- FCurTokenString := '';
- while not (TokenStr[0]=#0) do
- begin
- If (TokenStr[0]=Delim) then
- begin
- if (not (soNoDoubleDelimIsChar in options)) and (TokenStr[1]=Delim) then
- begin
- S:=Delim;
- Len := TokenStr - TokenStart;
- AppendBufToTokenString(True);
- end
- else
- Break;
- end
- else if (TokenStr[0]='\') and (soBackSlashEscapes in Options) then
- begin
- // Save length
- Len := TokenStr - TokenStart;
- Inc(TokenStr);
- if not CheckTokenBuf then
- Error(SErrOpenString);
- // Read escaped token
- Case TokenStr[0] of
- '"' : S:='"';
- '''' : S:='''';
- 't' : S:=#9;
- 'b' : S:=#8;
- 'n' : S:=#10;
- 'r' : S:=#13;
- 'f' : S:=#12;
- '\' : S:='\';
- '/' : S:='/';
- 'u' : begin
- S:=ReadUniCodeEscape;
- end;
- else
- Error(SErrInvalidCharacter, [TokenStr[0]]);
- end;
- AppendBufToTokenString(False);
- end;
- Inc(TokenStr);
- if not CheckTokenBuf then
- Error(SErrOpenString);
- end;
- if Not CheckTokenBuf then
- Error(SErrOpenString);
- S:='';
- Len := TokenStr - TokenStart;
- AppendBufToTokenString(True);
- Result := tsqlString;
- end;
- function TSQLScanner.DoNumericLiteral :TSQLToken;
- Var
- TokenStart : PChar;
- Len : Integer;
- isFloat : boolean;
- begin
- TokenStart := TokenStr;
- IsFloat:=False;
- while true do
- begin
- Inc(TokenStr);
- case TokenStr[0] of
- 'x':
- If (TokenStart[0]='0') and ((TokenStr-TokenStart)=1) then
- begin
- Inc(TokenStr);
- while Upcase(TokenStr[0]) in ['0'..'9','A'..'F'] do
- Inc(TokenStr);
- end
- else
- Error(SInvalidHexadecimalNumber);
- '.':
- begin
- isfloat:=true;
- 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
- isFloat:=true;
- Inc(TokenStr);
- if TokenStr[0] in ['-','+'] then
- Inc(TokenStr);
- while TokenStr[0] in ['0'..'9'] do
- Inc(TokenStr);
- break;
- end;
- else
- break;
- end;
- end;
- Len:=TokenStr-TokenStart;
- Setlength(FCurTokenString, Len);
- if (Len>0) then
- Move(TokenStart^,FCurTokenString[1],Len);
- If IsFloat then
- Result := tsqlFloatNumber
- else
- Result:=tsqlIntegerNumber;
- end;
- function TSQLScanner.DoIdentifier : TSQLToken;
- Var
- TokenStart:PChar;
- Len : Integer;
- {I : TSQLToken;}
- S : ShortString;
- P : ^TSQLToken;
- begin
- Result:=tsqlIdentifier;
- TokenStart := TokenStr;
- repeat
- Inc(TokenStr);
- If (TokenStr[0]='\') and (TokenStr[1]='u') then
- until not (TokenStr[0] in ['A'..'Z', 'a'..'z', '0'..'9', '_','$']);
- Len:=(TokenStr-TokenStart);
- SetLength(FCurTokenString,Len);
- if Len > 0 then
- Move(TokenStart^,FCurTokenString[1],Len);
- S:=UpperCase(FCurTokenString);
- // Check if this is a keyword or identifier
- // to do: Optimize this!
- If FKeyWords.Count=0 then
- BuildKeyWords;
- P:=FKeyWords.Find(S);
- If (P<>Nil) then
- Result:=P^ //keyword found
- else if (AlternateTerminator<>'') and (S=AlternateTerminator) then
- Result:=tsqlTerminator;
- { I:=FirstKeyword;
- While (Result=tsqlIdentifier) and (I<=Lastkeyword) do
- begin
- if (S=TokenInfos[i]) then
- begin
- Result := i;
- FCurToken := Result;
- exit;
- end;
- I:=Succ(I);
- end;}
- end;
- function TSQLScanner.DoSymbolString : TSQLToken;
- Var
- Len : Integer;
- P : ^TSQLToken;
- TokenStart : PChar;
- begin
- Result:=tsqlUnknown;
- // Get "word" finalized by end of string, space/tab/line ending.
- TokenStart:=TokenStr;
- repeat
- Inc(TokenStr);
- until (TokenStr[0] in [#0, #9, #10, #13, ' ']);
- Len:=(TokenStr-TokenStart);
- if Len > 0 then
- begin
- result:=tsqlSymbolString;
- SetLength(FCurTokenString,Len);
- Move(TokenStart^,FCurTokenString[1],Len);
- if (AlternateTerminator<>'') and (CurtokenString=AlternateTerminator) then
- Exit(tsqlTerminator);
- // Check if this is a keyword or identifier/literal
- // Probably not (due to naming rules) but it doesn't hurt
- If FKeyWords.Count=0 then
- BuildKeyWords;
- P:=FKeyWords.Find(FCurTokenString); //case-sensitive search
- If (P<>Nil) then
- Result:=P^; //keyword found, just in case
- end;
- end;
- function TSQLScanner.FetchToken: TSQLToken;
- begin
- Repeat
- if TokenStr = nil then
- if not FetchLine then
- begin
- Result := tsqlEOF;
- FCurToken := Result;
- exit;
- end;
- FCurTokenRow:=CurRow;
- FCurTokenColumn:=CurColumn;
- FCurTokenString := '';
- case TokenStr[0] of
- #0: // Empty line
- begin
- FetchLine;
- Result := tsqlWhitespace;
- end;
- '/' :
- Result:=CommentDiv;
- #9, ' ',#10,#13:
- Result := DoWhiteSpace;
- '''':
- begin
- Result:=DoStringLiteral;
- if (soSingleQuoteIdentifier in Options) then
- result:=tsqlIdentifier;
- end;
- '"':
- begin
- Result:=DoStringLiteral;
- If (soDoubleQuoteStringLiteral in options) then
- Result:=tsqlString
- else
- Result:=tsqlIdentifier;
- end;
- '`':
- begin
- Result:=DoStringLiteral;
- If (soBackQuoteIdentifier in options) then
- Result:=tsqlIdentifier
- else
- Error(SErrUnknownToken,['`']);
- end;
- '0'..'9':
- Result:=DoNumericLiteral;
- '?':
- begin
- Inc(TokenStr);
- Result:=tsqlPlaceHolder;
- end;
- '!':
- begin
- Inc(TokenStr);
- If TokenStr[0]='>' then
- Result:=tsqlLE
- else if (TokenStr[0]='<') then
- Result:=tsqlGE
- else if (TokenStr[0]='=') then
- Result:=tsqlNE
- else
- Result:=tsqlUnknown;
- Inc(TokenStr);
- end;
- '|':
- begin
- Inc(TokenStr);
- If Tokenstr[0]='|' then
- begin
- Inc(TokenStr);
- Result := tsqlConcatenate
- end
- else
- Error(SBarExpected);
- end;
- '(':
- begin
- Inc(TokenStr);
- Result := tsqlBraceOpen;
- end;
- ')':
- begin
- Inc(TokenStr);
- Result := tsqlBraceClose;
- end;
- '[':
- begin
- If (soSquareBracketsIdentifier in options) then
- begin
- Result:=DoStringLiteral;
- Result:=tsqlIdentifier;
- end
- Else
- begin
- Inc(TokenStr);
- Result := tsqlSquareBraceOpen;
- end;
- end;
- ']':
- begin
- Inc(TokenStr);
- Result := tsqlSquareBraceClose;
- end;
- '*':
- begin
- Inc(TokenStr);
- Result := tsqlMul;
- end;
- '+':
- begin
- Inc(TokenStr);
- Result := tsqlPlus;
- end;
- ',':
- begin
- Inc(TokenStr);
- Result := tsqlComma;
- end;
- '-':
- begin
- Inc(TokenStr);
- If (TokenStr[0]='-') then
- begin
- Inc(TokenStr);
- Result:=DoSingleLineComment
- end
- else if (TokenStr[0] in ['0'..'9']) then
- begin
- Result:=DoNumericLiteral;
- If (Result in [tsqlIntegerNumber,tsqlFloatNumber]) then
- FCurTokenString:='-'+FCurTokenString;
- end
- else
- Result := tsqlMinus;
- end;
- '.':
- begin
- Inc(TokenStr);
- Result := tsqlDot;
- end;
- ':':
- begin
- Inc(TokenStr);
- Result := tsqlColon;
- end;
- ';':
- begin
- Inc(TokenStr);
- Result := tsqlSemicolon;
- end;
- '<':
- begin
- Inc(TokenStr);
- if TokenStr[0] = '>' then
- begin
- Inc(TokenStr);
- Result := tsqlNE;
- end
- else if (TokenStr[0] = '=') then
- begin
- Inc(TokenStr);
- Result := tsqlLE;
- end
- else
- Result := tsqlLT;
- end;
- '=':
- begin
- Inc(TokenStr);
- Result := tsqleQ;
- end;
- '>':
- begin
- Inc(TokenStr);
- if TokenStr[0] = '=' then
- begin
- Inc(TokenStr);
- Result:=tsqlGE;
- end
- else
- Result := tsqlGT;
- end;
- 'a'..'z',
- 'A'..'Z', '_':
- Result:=DoIdentifier;
- else
- // Symbol of some sort
- Result:=DoSymbolString;
- //Error(SErrUnknownToken,[TokenStr[0]]);
- end; // Case
- Until (Not (Result in [tsqlComment,tsqlWhitespace])) or
- ((Result=tsqlComment) and (soReturnComments in options)) or
- ((Result=tsqlWhiteSpace) and (soReturnWhiteSpace in Options));
- FCurToken:=Result;
- end;
- function TSQLScanner.IsEndOfLine: Boolean;
- begin
- Result:=(TokenStr=Nil) or (TokenStr[0] in [#0,#10,#13]);
- end;
- function TSQLScanner.GetCurColumn: Integer;
- begin
- Result := TokenStr - PChar(FCurLine) + 1;
- end;
- Procedure TSQLScanner.ClearKeywords(Sender : TObject);
- begin
- If Assigned(FKeywords) then
- FKeywords.Clear;
- end;
- function TSQLScanner.GetExcludeKeywords: TStrings;
- begin
- If FExclude=Nil then
- begin
- FExclude:=TStringList.Create;
- FExclude.Duplicates:=dupIgnore;
- FExclude.Sorted:=true;
- FExclude.OnChange:=@ClearKeywords;
- end;
- Result:=FExclude;
- end;
- { TStreamLineReader }
- constructor TStreamLineReader.Create(AStream: TStream);
- begin
- FStream:=AStream;
- FBufPos:=0;
- FBufLen:=0;
- end;
- function TStreamLineReader.IsEOF: Boolean;
- begin
- Result:=(FBufPos>=FBufLen);
- If Result then
- begin
- FillBuffer;
- Result:=(FBufLen=0);
- end;
- end;
- procedure TStreamLineReader.FillBuffer;
- begin
- FBufLen:=FStream.Read(Buffer,SizeOf(Buffer)-1);
- Buffer[FBufLen]:=0;
- FBufPos:=0;
- end;
- function TStreamLineReader.ReadLine: string;
- Var
- FPos,OLen,Len: Integer;
- PRun : PByte;
- begin
- FPos:=FBufPos;
- Result:='';
- Repeat
- PRun:=@Buffer[FBufPos];
- While (FBufPos<FBufLen) and Not (PRun^ in [10,13]) do
- begin
- Inc(PRun);
- Inc(FBufPos);
- end;
- If (FBufPos=FBufLen) then
- begin
- Len:=FBufPos-FPos;
- If (Len>0) then
- begin
- Olen:=Length(Result);
- SetLength(Result,OLen+Len);
- Move(Buffer[FPos],Result[OLen+1],Len);
- end;
- FillBuffer;
- FPos:=FBufPos;
- end;
- until (FBufPos=FBufLen) or (PRun^ in [10,13]);
- Len:=FBufPos-FPos+1;
- If (Len>0) then
- begin
- Olen:=Length(Result);
- SetLength(Result,OLen+Len);
- Move(Buffer[FPos],Result[OLen+1],Len);
- end;
- If (PRun^ in [10,13]) and (FBufPos<FBufLen) then
- begin
- Inc(FBufPos);
- // Check #13#10
- If (PRun^=13) then
- begin
- If (FBufPos=FBufLen) then
- FillBuffer;
- If (FBufPos<FBufLen) and (Buffer[FBufpos]=10) then
- begin
- Inc(FBufPos);
- Result:=Result+#10;
- end;
- end;
- end;
- end;
- end.
|