unit ABSLexer;

{$I ABSVer.inc}

interface

uses classes, windows, sysutils, db,
{$IFDEF DEBUG_LOG}
 ABSDebug,
{$ENDIF}
 ABSVariant,
 ABSExcept,
 ABSConst;



const Lf: string = #$0A;       // binary mode line separator
const Cr: string = #$0D;       // text mode line separator
const Crlf: string = #$0D#$0A; // text mode line separator
const Tab: string = #$09; // <tab>
const Comment = '-'; // -- comment <Crlf>
const Comment1 = '#'; // # comment <Crlf>
const Dot = '.';
const Comma = ',';
const SemiColon = ';';
const Asterisk = '*';
const SingleQuote = '''';
const BackQuote = '`';
const DoubleQuote = '"';
const Space = ' ';
const LeftParenthesis = '(';
const RightParenthesis = ')';
const LeftSquareBracket = '[';
const RightSquareBracket = ']';
const Percent = '%';
const BackSlash = '\';

EABSTokenType: array [0..11] of string =
(
 'tktNone', 'tktString', 'tktQuotedString', 'tktBracketedString',
 'tktInt', 'tktFloat', 'tktReservedWord',
 'tktParameter', 'tktLeftParenthesis', 'tktRightParenthesis',
 'tktComma', 'tktDot'
);

type
 TTokenType = (tktNone, tktString, tktQuotedString, tktBracketedString,
 tktInt, tktFloat, tktReservedWord,
 tktParameter, tktLeftParenthesis, tktRightParenthesis,
 tktComma, tktDot);

 TTokenTypes = set of TTokenType;

 TToken = record
  TokenType:      TTokenType;
  ReservedWord:   TReservedWord;
  Text:           string;
  ParamValue:     TABSVariant;
  LineNum:        Integer; // number of line in script where token begins
  ColumnNum:      Integer; // number of column in script where token begins
 end;

 PToken = ^TToken;

 TSQLCommand = record
  Tokens:     array of TToken;
  NumTokens:  Integer;
  CurrentTokenNo: Integer;
 end;


////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLParam
//
////////////////////////////////////////////////////////////////////////////////

  TABSSQLParam = class (TABSVariant)
   public
    Name:  string;
  end;


////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLParams
//
////////////////////////////////////////////////////////////////////////////////
            
  TABSSQLParams = class (TObject)
   private
    FParamList: TList;
   private
    function GetCount: Integer;
    function GetValue(Index: Integer): TABSSQLParam;
    procedure SetValue(Index: Integer; Value: TABSSQLParam);
   public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    function AddCreated: TABSSQLParam;
    function GetParamByName(Name: string): TABSSQLParam;
   public
    property Count: Integer read GetCount;
    property Items[Index: Integer]: TABSSQLParam read GetValue write SetValue; default;
  end;//TABSSQLParams




 TABSLexer = class (TObject)
  private
   FSQL:              string;
   FSQLParams:        TABSSQLParams;
   FNoParams:         boolean;
  private
   procedure Parse;
  public
   NumCommands:       Integer;
   Commands:          array of TSQLCommand;
   CurrentCommandNo:  Integer;

   constructor Create(SQLScript: string; SQLParams: TABSSQLParams = nil; NoParams: Boolean = false);
   destructor Destroy; override;
   function Test(bGenerate: Boolean = true; bShowDetails: Boolean = true): string;
   procedure AddCommand;
   procedure AddToken(Token: TToken);

   // makes next command current
   function GetNextCommand: Boolean;
   // gets next token in current command
   function GetNextToken(var Token: TToken): Boolean;
   // gets current token in current command
   function GetCurrentToken(var Token: TToken): Boolean;
   // looks at next token
   function LookNextToken(var Token: TToken): Boolean;
   // gets current token No
   function GetCurrentTokenNo: integer;
   // sets current token No
   function SetCurrentTokenNo(TokenNo: integer; var Token: TToken): Boolean; overload;
   function SetCurrentTokenNo(TokenNo: integer): Boolean; overload;
   // gets first next token specified type
   function GetNextSpecifiedToken(var Token: TToken; TokenTypes: TTokenTypes): Boolean;
 end;

 // checks whether token is reserved word
 function IsReservedWord(Token: TToken; ReservedWord: TReservedWord=rwNone): Boolean;
 function FindReservedWord(s: string): Integer;


implementation

uses ABSTypes;

////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLParams
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSSQLParams.Create;
begin
  FParamList := TList.Create;
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSSQLParams.Destroy;
begin
  Clear;
  FParamList.Free;
end;//Destroy


//------------------------------------------------------------------------------
// return Count
//------------------------------------------------------------------------------
function TABSSQLParams.GetCount: Integer;
begin
  Result := FParamList.Count;
end;//GetCount


//------------------------------------------------------------------------------
// GetValue
//------------------------------------------------------------------------------
function TABSSQLParams.GetValue(Index: Integer): TABSSQLParam;
begin
  Result := TABSSQLParam(FParamList[Index]);
end;//GetValue


//------------------------------------------------------------------------------
// SetValue
//------------------------------------------------------------------------------
procedure TABSSQLParams.SetValue(Index: Integer; Value: TABSSQLParam);
begin
  TABSSQLParam(FParamList[Index]).Free;
  FParamList[Index] := Value;
end;//SetValue


//------------------------------------------------------------------------------
// AddCreated
//------------------------------------------------------------------------------
function TABSSQLParams.AddCreated: TABSSQLParam;
begin
  Result := TABSSQLParam.Create;
  FParamList.Add(Result);
end;//AddCreated


//------------------------------------------------------------------------------
// Clear
//------------------------------------------------------------------------------
procedure TABSSQLParams.Clear;
var i: Integer;
begin
  for i:=0 to Count-1 do
    TABSSQLParam(FParamList[i]).Free;
  FParamList.Clear;
end;//Clear


//------------------------------------------------------------------------------
// GetParamByName
//------------------------------------------------------------------------------
function TABSSQLParams.GetParamByName(Name: string): TABSSQLParam;
var i: Integer;
begin
  Result := nil;
  for i:=0 to Count-1 do
   if (UpperCase(items[i].Name) = UpperCase(Name)) then
    begin
     Result := items[i];
     Break;
    end;
end;//GetParamByName




////////////////////////////////////////////////////////////////////////////////
//
// TABSLexer
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Parse SQL script
//------------------------------------------------------------------------------
procedure TABSLexer.Parse;
var i,l:      Integer;
    line,command,token,column:  Integer;
    c,priorSymbol:   Char;
    numParenthesis,
    LeftParenthesisLine,
    LeftParenthesisColumn,
    RightParenthesisLine,
    RightParenthesisColumn,
    quoteLine,quoteColumn: Integer;
    bIsDelimiter:     boolean;
    bTokenStarted:    boolean;
    bCommandStarted:  boolean;
    bNewTokenStarted: boolean;
    bTokenFinished:   boolean;
    bQuoteNotClosed:  boolean;
    LastParamNo: Integer;

  function GetNextSymbol: Char;
  begin
   result := ' ';
    if (i < l-1) then
     result := PChar(PChar(FSQL)+i+1)^;
  end;


 function IsNewTokenStarted: Boolean;
 begin
  result := false;
  if (bTokenStarted) then
   begin
      if (c = '>') or (c = '<') or (c = '=') or (c = '(') or (c = ')') or
         (c = ',') or (c = '/') or (c = '*') or (c = ':') or (c = '?') or (c = '|') or
         ((c=Dot) and (Commands[command].Tokens[token].TokenType<>tktInt)) then
           result := true
      else
      if (Commands[command].Tokens[token].TokenType = tktDot) then
       begin
         if not ((c >= '0') and (c <= '9')) and (c <> space) then
          result := true;
       end
      else
      if (Commands[command].Tokens[token].TokenType = tktString) then
       begin
        if (c = '+') or (c = '-') then
         result := true;
       end
      else
      if (Commands[command].Tokens[token].TokenType = tktInt) then
       begin
        if (c = '+') and (UpperCase(priorSymbol) <> 'E') then
         result := true
        else
        if (c = '-') and (UpperCase(priorSymbol) <> 'E') then
         result := true;
       end; // Int or Float token
   end // some token already started
  else
   begin
    if (not bIsDelimiter) then
     result := true;
   end; // no token started
 end; // IsNewTokenStarted

 function IsTokenFinished: Boolean;
 begin
  result := false;
  if (bTokenStarted) then
   begin
    if (bIsDelimiter) then
     result := true;
   end;
 end; // IsTokenFinished

 procedure CreateCommand;
 begin
  inc(NumCommands);
  SetLength(Commands, NumCommands);
  command := NumCommands-1;
  Commands[command].NumTokens := 0;
  Commands[command].Tokens := nil;
  token := 0;
  bCommandStarted := true;
  numParenthesis := 0;
 end; // CreateCommand;

 procedure CloseToken;
 var i:         Integer;
     Param:     TABSSQLParam;
     ParamName: string;
//     oldSeparator: AnsiChar;
 begin
  if (not bTokenStarted) then
   Exit;
  // check int or float
  if (Commands[command].Tokens[token].TokenType = tktInt) then
   begin
    // check int or float
    if (Pos('.',Commands[command].Tokens[token].Text) > 0) then
     Commands[command].Tokens[token].TokenType := tktFloat
    else
    if (Pos('E',UpperCase(Commands[command].Tokens[token].Text)) > 0) then
     Commands[command].Tokens[token].TokenType := tktFloat;
   end;
  // String
  if (Commands[command].Tokens[token].TokenType = tktString) then
   begin
    // check for SQLParameter ( :name)
    if (not FNoParams) and (AnsiChar(Commands[command].Tokens[token].Text[1]) in [':', '?'] ) then
     begin
      Commands[command].Tokens[token].TokenType := tktParameter;
      if (Commands[command].Tokens[token].Text[1] = ':') then
       begin
        // param name without ':'
        ParamName := Copy(Commands[command].Tokens[token].Text, 2,
                         Length(Commands[command].Tokens[token].Text)-1);
        Commands[command].Tokens[token].Text := ParamName;
        // try to set param value
        if Assigned(FSQLParams) then
          Param := FSQLParams.GetParamByName(ParamName)
        else
          Param := nil;
       end
      else
       begin
        if Assigned(FSQLParams) then
         begin
          Param := FSQLParams[LastParamNo];
          Inc(LastParamNo);
          ParamName := 'Param' + IntToStr(LastParamNo);
          Commands[command].Tokens[token].Text := ParamName;
         end
        else
          Param := nil;
       end;

      if (Param <> nil) then
        begin
          // commented in 5.02 (to support NULL params of unknown type)
//          if (Param.DataType = bftUnknown) then
//            raise EABSException.Create(20271, ErrorAParameterOfUnknownType, [Param.Name]);
          Commands[command].Tokens[token].ParamValue := TABSVariant.Create;
          Commands[command].Tokens[token].ParamValue.Assign(Param);
        end
      else
        raise EABSException.Create(30358, ErrorGParameterValueNotFound, [ParamName]);
     end
    else
     begin
      // check for reserved word
      i := FindReservedWord(Commands[command].Tokens[token].Text);
      if (i > 0) then
       begin
        Commands[command].Tokens[token].ReservedWord := TReservedWord(i);
        Commands[command].Tokens[token].TokenType := tktReservedWord;
       end;
     end;
   end;// if String
  bTokenStarted := false;
 end; // CloseToken;

 procedure CreateToken;
 var NextSymbol: Char;
     quoteSymbol: Char;
 begin
  if (not bCommandStarted) then
   CreateCommand;
  bTokenStarted := true;
  inc(Commands[command].NumTokens);
  SetLength(Commands[command].Tokens,Commands[command].NumTokens);

  token := Commands[command].NumTokens - 1;
  Commands[command].Tokens[token].Text := c;
  Commands[command].Tokens[token].ColumnNum := column;
  Commands[command].Tokens[token].LineNum := line;
  Commands[command].Tokens[token].ReservedWord := rwNone;
  Commands[command].Tokens[token].ParamValue := nil;

  NextSymbol := GetNextSymbol;
  // here will be scanning quoted string to its end
  if (c = SingleQuote) or (c = BackQuote) or (c = DoubleQuote) or (c = LeftSquareBracket) or
     ((c=':') and (AnsiChar(NextSymbol) in [SingleQuote, DoubleQuote, LeftSquareBracket]))then
   begin
    Commands[command].Tokens[token].Text := '';
    case c of
      SingleQuote, DoubleQuote:
         Commands[command].Tokens[token].TokenType := tktQuotedString;
      BackQuote:
         Commands[command].Tokens[token].TokenType := tktString;
      LeftSquareBracket:
         Commands[command].Tokens[token].TokenType := tktBracketedString;
      ':':
         Commands[command].Tokens[token].TokenType := tktString;
    end;
    if (c = ':') then
      begin
        Commands[command].Tokens[token].Text := ':';
        if (NextSymbol <> LeftSquareBracket) then
         quoteSymbol := NextSymbol
        else
         quoteSymbol := RightSquareBracket;
        inc(i);
      end
    else
      if (c <> LeftSquareBracket) then
       quoteSymbol := c
      else
       quoteSymbol := RightSquareBracket;
    quoteLine := line;
    quoteColumn := column;
    bQuoteNotClosed := true;
    inc(i);
    while (i < l) do
     begin
      c := PChar(PChar(FSQL)+i)^;
      if ((AnsiChar(quoteSymbol) in [BackQuote]) and (c = BackSlash)) then
       begin
        Inc(i);
        if (not (i < l)) then
          Break;
        c := PChar(PChar(FSQL)+i)^;
       end;
      NextSymbol := GetNextSymbol;
      if (c = quoteSymbol) then
       begin
        if (NextSymbol = c) then
         begin
          Commands[command].Tokens[token].Text :=
            Commands[command].Tokens[token].Text + c;
          inc(i,2);
          inc(column,2);
          continue;
         end // ''
        else
         begin
          // end of QuotedStr
          bQuoteNotClosed := false;
          inc(column,1);
          break;
         end; // '
       end; // quote symbol

      Commands[command].Tokens[token].Text :=
         Commands[command].Tokens[token].Text + c;
      if (c = lf) then
       begin
        inc(line);
        column := 1;
       end
      else
       if (c <> cr) then
        inc(column);
      inc(i);
     end; // while
    CloseToken;
    if AnsiChar(quoteSymbol) in [BackQuote, RightSquareBracket] then
      Commands[command].Tokens[token].ReservedWord := rwNone;
   end
  else
  if (c = LeftParenthesis) then
   begin
    Commands[command].Tokens[token].TokenType := tktLeftParenthesis;
    CloseToken;
    inc(numParenthesis);
    LeftParenthesisLine := line;
    LeftParenthesisColumn := column;
   end
  else
  if (c = RightParenthesis) then
   begin
    dec(numParenthesis);
    RightParenthesisLine := line;
    RightParenthesisColumn := column;
    Commands[command].Tokens[token].TokenType := tktRightParenthesis;
    CloseToken;
   end
  else
  if (c = Dot) then //!!
   begin
    Commands[command].Tokens[token].TokenType := tktDot;
    //CloseToken;
   end
  else
  if (c = Comma) then
   begin
    Commands[command].Tokens[token].TokenType := tktComma;
    CloseToken;
   end
  else
  if ((c >= '0') and (c <= '9')) then
    Commands[command].Tokens[token].TokenType := tktInt
  else
   Commands[command].Tokens[token].TokenType := tktString;
  if (c = '-') or (c = '+') or (c = '*') or (c = '/') or (c = '=') then
   CloseToken;
  if (c = '|') then
   begin
    NextSymbol := GetNextSymbol;
    if (NextSymbol = '|') then
     begin
      Commands[command].Tokens[token].Text :=
        Commands[command].Tokens[token].Text + NextSymbol;
      inc(i);
      inc(column);
     end;
    CloseToken;
   end; // <
  if (c = '<') then
   begin
    NextSymbol := GetNextSymbol;
    if (NextSymbol = '=') or (NextSymbol = '>') then
     begin
      Commands[command].Tokens[token].Text :=
        Commands[command].Tokens[token].Text + NextSymbol;
      inc(i);
      inc(column);
     end;
    CloseToken;
   end; // <
  if (c = '>') then
   begin
    NextSymbol := GetNextSymbol;
    if (NextSymbol = '=') then
     begin
      Commands[command].Tokens[token].Text :=
        Commands[command].Tokens[token].Text + NextSymbol;
      inc(i);
      inc(column);
     end;
    CloseToken;
   end; // >
 end; // CreateToken;

 procedure CloseCommand;
 begin
  CloseToken;
  bCommandStarted := false;

  if (numParenthesis > 0) then
    raise EABSException.Create(30059, ErrorGMissingRightParenthesis,
                                  [LeftParenthesisLine,LeftParenthesisColumn]);
  if (numParenthesis < 0) then
    raise EABSException.Create(30060, ErrorGUnexpectedRightParenthesis,
                                  [LeftParenthesisLine,LeftParenthesisColumn]);
  priorSymbol := ' ';

  if (bQuoteNotClosed) then
    raise EABSException.Create(30061, ErrorGUnterminatedString,
                                                       [quoteLine,quoteColumn]);
 end; // CloseCommand;

 var NextSymbol: Char;
begin
 l := Length(FSQL);
 line := 1;
 column := 1;
 NumCommands := 0;
 Commands := nil;
 bTokenStarted := false;
 bQuoteNotClosed := false;
 bCommandStarted := false;
 command := 0;
 token := 0;
 priorSymbol := ' ';
 numParenthesis := 0;
 LeftParenthesisLine := 0;
 LeftParenthesisColumn := 0;
 RightParenthesisLine := 0;
 RightParenthesisColumn := 0;
 LastParamNo := 0;
 i := 0;
 while (i < l) do
  begin
   // get current character
   c := PChar(PChar(FSQL)+i)^;

   // end of the current comand
   if (c = ';') then
    begin
     CloseCommand;
     inc(i);
     continue;
    end;

   // check for comment
   if (c = '-') then
    begin
     NextSymbol := GetNextSymbol;
     if (NextSymbol = '-') then
      begin
       // seek for end of line
       inc(i);
       column := 1;
       inc(line);
       while (i < l) do
        begin
         c := PChar(PChar(FSQL)+i)^;
         if (c = lf) then
           break;
         inc(i);
        end;
       inc(i);
       continue;
      end;
    end; // comment --

   // check for comment
   if (c = '/') then
    begin
     NextSymbol := GetNextSymbol;
     if (NextSymbol = '*') then
      begin
       // commentary started
       inc(i,2);
       inc(column,2);
       while (i < l) do
        begin
          c := PChar(PChar(FSQL)+i)^;
          if (c = '*') then
           begin
             NextSymbol := GetNextSymbol;
             if (NextSymbol = '/') then
              begin
               inc(i,2);
               inc(column,2);
               break;
              end;
           end;
          if (c = lf) then
           begin
            inc(line);
            column := 1;
           end
          else
           if (c <> cr) then
            inc(column);
          inc(i);
        end; // while
       continue;
      end; // comment /* */ started
    end;


   // is this symbol a delimiter?
   bIsDelimiter := false;
   if (c = Tab) or (c = CR) or (c = LF) or (c = Space) then
    bIsDelimiter := true;

   bTokenFinished := IsTokenFinished;
   bNewTokenStarted := IsNewTokenStarted;
   if (bTokenFinished) or (bTokenStarted and bNewTokenStarted) then
    CloseToken;
   if (bNewTokenStarted) then
    CreateToken;

   // add current symbol to token
   if (not bNewTokenStarted) and (bTokenStarted) and (not bIsDelimiter) then
    begin
     // check for number
     if Commands[command].Tokens[token].TokenType in [tktInt,tktFloat] then
      if not ((c >= '0') and (c <= '9') or (c = Dot) or
              (UpperCase(c)='E') or (c = '+') or (c = '-') ) then
        raise EABSException.Create(30062, ErrorGInvalidNumericSymbol,
                                                              [c,line,column]);
      // check float type .22
      if (Commands[command].Tokens[token].TokenType = tktDot) and
         (c >= '0') and (c <= '9') then
       begin
        Commands[command].Tokens[token].TokenType := tktInt;
       end;
     // add current symbol to token
    Commands[command].Tokens[token].Text :=
      Commands[command].Tokens[token].Text + c;
    end;

   if (c = lf) then
    begin
     inc(line);
     column := 1;
    end
   else
    if (c <> cr) then
     inc(column);
   // skip delimiters
   if (bIsDelimiter) then
    begin
     if (bTokenStarted) then
      CloseToken;
    end;
   // next symbol
   priorSymbol := c;
   inc(i);
  end; // for all symbols in FSQL

 CloseCommand;
end; // Parse


//------------------------------------------------------------------------------
// create
//------------------------------------------------------------------------------
constructor TABSLexer.Create(SQLScript: string; SQLParams: TABSSQLParams; NoParams: Boolean);
var
  i: integer;
begin
  FSQL := SQLScript;
  FSQLParams := SQLParams;
  FNoParams := NoParams;
  NumCommands := 0;
  Parse;
  CurrentCommandNo := -1;
  for i := 0 to NumCommands-1 do
    Commands[i].CurrentTokenNo := 0;
end; // Create


//------------------------------------------------------------------------------
// destroy
//------------------------------------------------------------------------------
destructor TABSLexer.Destroy;
var i,j: integer;
begin
 for i:=0 to NumCommands - 1 do
  for j:=0 to  Length(Commands[i].Tokens)-1 do
   if (Commands[i].Tokens[j].ParamValue <> nil) then
     Commands[i].Tokens[j].ParamValue.Free;
end; // Destroy


//------------------------------------------------------------------------------
// test
//------------------------------------------------------------------------------
function TABSLexer.Test(bGenerate: Boolean = true; bShowDetails: Boolean = true): string;
var i,j: integer;
    s: string;
begin
 result := 'Number of commands: '+IntToStr(NumCommands);
 if (bGenerate) then
  begin
   // generate
  end;

 for i := 0 to NumCommands - 1 do
  begin
   result := result + Crlf+'Command #'+IntToStr(i+1)+':' + Crlf;
   for j := 0 to Commands[i].NumTokens - 1 do
    begin
     result := result + Commands[i].Tokens[j].Text + Crlf;
     if (bShowDetails) then
      begin
       if (Commands[i].Tokens[j].ReservedWord = rwNone) then
        s := 'ReservedWord = None'+Crlf
       else
        s :=  'ReservedWord = ' +
              ABSSQLReservedWords[Integer(Commands[i].Tokens[j].ReservedWord)] +
              Crlf;
        result := result +
                  'Type = ' +
                  EABSTokenType[Integer(Commands[i].Tokens[j].TokenType)] +
                  Crlf + s +
                  'Line = ' + IntToStr(Commands[i].Tokens[j].LineNum) + Crlf +
                  'Column = ' + IntToStr(Commands[i].Tokens[j].ColumnNum) +
                  Crlf + Crlf;
      end;
    end;

  end;
end; // Test


//------------------------------------------------------------------------------
// Add Empty Command
//------------------------------------------------------------------------------
procedure TABSLexer.AddCommand;
begin
  SetLength(Commands, Length(Commands)+1);
  NumCommands := Length(Commands);
  CurrentCommandNo := NumCommands - 1;
end;// AddCommand


//------------------------------------------------------------------------------
// AddToken
//------------------------------------------------------------------------------
procedure TABSLexer.AddToken(Token: TToken);
begin
 with Commands[CurrentCommandNo] do
   begin
     SetLength(Tokens, Length(Tokens)+1);
     NumTokens := Length(Tokens);
     CurrentTokenNo := NumTokens - 1;
     Tokens[CurrentTokenNo] := Token;
   end;
end;// AddToken


//------------------------------------------------------------------------------
// makes next command current
//------------------------------------------------------------------------------
function TABSLexer.GetNextCommand: Boolean;
begin
 Inc(CurrentCommandNo);
 Result := (CurrentCommandNo < NumCommands);
end;// GetNextCommand

//------------------------------------------------------------------------------
// gets next token in current command
//------------------------------------------------------------------------------
function TABSLexer.GetNextToken(var Token: TToken): Boolean;
begin
 with Commands[CurrentCommandNo] do
  begin
   Result := LookNextToken(Token);
//   if (Result) then
    Inc(CurrentTokenNo);
  end;
end;// GetNextToken


//------------------------------------------------------------------------------
// gets current token in current command
//------------------------------------------------------------------------------
function TABSLexer.GetCurrentToken(var Token: TToken): Boolean;
begin
 with Commands[CurrentCommandNo] do
  begin
   Result := (CurrentTokenNo < NumTokens);
   if (Result) then
    Token := Tokens[CurrentTokenNo]
   else
    Token.TokenType := tktNone;
  end;
end;// GetCurrentToken


//------------------------------------------------------------------------------
// looks at next token in current command
//------------------------------------------------------------------------------
function TABSLexer.LookNextToken(var Token: TToken): Boolean;
begin
 with Commands[CurrentCommandNo] do
  begin
   Result := (CurrentTokenNo+1 < NumTokens);
   if (Result) then
    Token := Tokens[CurrentTokenNo+1]
   else
     begin
       Token.TokenType := tktNone;
       Token.Text := '';
     end;
  end;
end;// LookNextToken


//------------------------------------------------------------------------------
// gets current token No
//------------------------------------------------------------------------------
function TABSLexer.GetCurrentTokenNo: integer;
begin
  Result := Commands[CurrentCommandNo].CurrentTokenNo;
end;// GetCurrentTokenNo


//------------------------------------------------------------------------------
// sets current token No
//------------------------------------------------------------------------------
function TABSLexer.SetCurrentTokenNo(TokenNo: integer; var Token: TToken): Boolean;
begin
 Commands[CurrentCommandNo].CurrentTokenNo := TokenNo;
 Result := GetCurrentToken(Token);
end;// SetCurrentTokenNo

function TABSLexer.SetCurrentTokenNo(TokenNo: integer): Boolean;
begin
 with Commands[CurrentCommandNo] do
  begin
   CurrentTokenNo := TokenNo;
   Result := (CurrentTokenNo < NumTokens);
  end;
end;


// checks whether token is reserved word
function IsReservedWord(Token: TToken; ReservedWord: TReservedWord=rwNone): Boolean;
begin
 Result := False;
 if (Token.TokenType = tktReservedWord) then
  if ((Token.ReservedWord = ReservedWord) or
      (ReservedWord = rwNone)) then
   Result := True;
end;

//------------------------------------------------------------------------------
// gets first next token specified type
//------------------------------------------------------------------------------
function TABSLexer.GetNextSpecifiedToken(var Token: TToken;
  TokenTypes: TTokenTypes): Boolean;
begin
 with Commands[CurrentCommandNo] do
  begin
   repeat
     Result := LookNextToken(Token);
     if (Result) then
      Inc(CurrentTokenNo);
   until (not Result) or (Token.TokenType in TokenTypes);
  end;
end;// GetNextSpecifiedToken

function FindReservedWord(s: string): Integer;
var i: integer;
begin
  result := -1;
  s := UpperCase(s);
  for i := 0 to ABSMaxSQLReservedWords do
    if (s = ABSSQLReservedWords[i]) then
     begin
      result := i;
      break;
     end;
end; // FindReservedWord


end.

