{
 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code 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.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.        *
 *                                                                         *
 ***************************************************************************

  Author: Mattias Gaertner

  Abstract:
    TCTConfigScriptEngine implements an interpreter for a simple scripting
    language, enough for configurations.

  Working:
    if, then, else, begin..end, ;
    ()
    boolean operators: not, and, or, xor
    operators: =, <>, >, <, <=, >=, +
    variables
    constants: decimal, $hex, &octal, %binary, 'string', #character
    functions: string(), integer(), int64(), defined(), undefined()
    procedures: undefine()
    assignments: :=, +=

  Not supported:
    - floats
    - types
    - objects
    - loops
    - custom functions
}
unit CodeToolsCfgScript;

{$mode objfpc}{$H+}
{$inline on}

{off $Define VerboseCTCfgScript}
{off $DEFINE CheckCTCfgVars}

interface

uses
  Classes, SysUtils, BasicCodeTools, AVL_Tree, KeywordFuncLists, FileProcs,
  typinfo, CodeToolsStrConsts;

type
  TCTCSValueType = (
    ctcsvNone,
    ctcsvString,
    ctcsvNumber
    );

  { TCTCfgScriptVariable }

  TCTCfgScriptVariable = record
    Name: PChar;
    ValueType: TCTCSValueType;
    case Integer of
    0: (StrStart: PChar; StrLen: integer);
    1: (Number: int64);
  end;
  PCTCfgScriptVariable = ^TCTCfgScriptVariable;


  { TCTCfgScriptVariables }

  TCTCfgScriptVariables = class
  private
    FItems: TAVLTree; // tree of PCTCfgScriptVariable sorted for name
    function GetValues(const Name: string): string;
    procedure SetValues(const Name: string; const AValue: string);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    function Equals(Vars: TCTCfgScriptVariables): boolean; reintroduce;
    procedure Assign(Source: TCTCfgScriptVariables); overload;
    procedure Assign(Source: TStrings); overload;
    procedure AddOverrides(Source: TCTCfgScriptVariables);
    procedure AddOverride(Source: PCTCfgScriptVariable);
    function GetVariable(const Name: PChar;
                      CreateIfNotExists: Boolean = false): PCTCfgScriptVariable;
    property Values[const Name: string]: string read GetValues write SetValues; default;
    procedure Undefine(Name: PChar);
    procedure Define(Name: PChar; const Value: string);
    function IsDefined(Name: PChar): boolean;
    property Tree: TAVLTree read FItems;
    procedure WriteDebugReport(const Title: string; const Prefix: string = '');
  end;
  PCTCfgScriptVariables = ^TCTCfgScriptVariables;

type
  TCTCfgScriptOperator = (
    ctcsoNone,
    ctcsoNot,
    ctcsoAnd,
    ctcsoOr,
    ctcsoXOr,
    ctcsoShL,
    ctcsoShR,
    ctcsoDiv,
    ctcsoMod,
    ctcsoPlus,
    ctcsoMinus,
    ctcsoMultiply,
    ctcsoDivide,
    ctcsoEqual,
    ctcsoNotEqual,
    ctcsoLowerThan,
    ctcsoLowerOrEqualThan,
    ctcsoGreaterThan,
    ctcsoGreaterOrEqualThan
    );
  TCTCfgScriptOperators = set of TCTCfgScriptOperator;
const
  CTCfgScriptOperatorLvl: array[TCTCfgScriptOperator] of integer = (
    0, //ctcsoNone,
    1, //ctcsoNot,
    1, //ctcsoAnd,
    2, //ctcsoOr,
    2, //ctcsoXOr,
    1, //ctcsoShL,
    1, //ctcsoShR,
    1, //ctcsoDiv,
    1, //ctcsoMod,
    2, //ctcsoPlus,
    2, //ctcsoMinus,
    1, //ctcsoMultiply,
    1, //ctcsoDivide,
    4, //ctcsoEqual,
    4, //ctcsoNotEqual,
    4, //ctcsoLowerThan,
    4, //ctcsoLowerOrEqualThan,
    4, //ctcsoGreaterThan,
    4  //ctcsoGreaterOrEqualThan
    );
type
  TCTCfgScriptStackItemType = (
    ctcssNone,
    ctcssStatement,
    ctcssBegin,
    ctcssIf,
    ctcssIfThen,
    ctcssIfElse,
    ctcssExpression,
    ctcssRoundBracketOpen,
    ctcssOperand,
    ctcssOperator,
    ctcssAssignment
    );
const
  ctcssAllStatementStarts = [ctcssNone,ctcssIfThen,ctcssIfElse,ctcssBegin];
type
  TCTCfgScriptStackItem = record
    Typ: TCTCfgScriptStackItemType;
    StartPos: PChar;
    Operand: TCTCfgScriptVariable;
  end;
  PCTCfgScriptStackItem = ^TCTCfgScriptStackItem;

type
  { TCTCfgScriptStack }

  TCTCfgScriptStack = class
  public
    Items: PCTCfgScriptStackItem;
    Top: integer; // current item, -1 = empty
    TopTyp: TCTCfgScriptStackItemType;
    Capacity: integer;
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    procedure Push(Typ: TCTCfgScriptStackItemType; const StartPos: PChar);
    procedure Pop(Count: integer = 1);
    procedure Delete(Index: integer);
    function TopItem: PCTCfgScriptStackItem;
    function TopItemOperand: PCTCfgScriptVariable;
    {$IFDEF CheckCTCfgVars}
    procedure CheckOperands;
    {$ENDIF}
  end;

  { TCTCfgScriptError }

  TCTCfgScriptError = class
  public
    Msg: string;
    Position: integer;
    Line: integer;
    Column: integer;
    constructor Create(const aMsg: string; aPos, aLine, aCol: integer);
    constructor Create(const aMsg: string);
  end;

  { TCTConfigScriptEngine }

  TCTConfigScriptEngine = class
  protected
    FVariables: TCTCfgScriptVariables;
    FStack: TCTCfgScriptStack;
    FErrors: TFPList; // list of TCTCfgScriptError
    function GetErrors(Index: integer): TCTCfgScriptError;
    procedure AddError(const aMsg: string; ErrorPos: PChar); overload;
    procedure AddError(const aMsg: string); overload;
    procedure PushNumberConstant;
    procedure PushBooleanValue(b: boolean);
    procedure PushNumberValue(const Number: int64);
    function RunDefined(Negate: boolean): boolean;
    function RunFunction: boolean;
    procedure PushStringConstant;
    procedure RunStatement(Skip: boolean);
    procedure RunBegin(Skip: boolean);
    procedure RunIf(Skip: boolean);
    procedure RunUndefine(Skip: boolean);
    procedure RunAssignment(Skip: boolean);
    function RunExpression: boolean; // if true the stack top has an operand
    function ExecuteStack(MaxLevel: integer): boolean;
    function GetOperatorLevel(P: PChar): integer;
    function IsKeyWord(P: PChar): boolean;
    function IsFunction(P: PChar): boolean;
    function IsCustomFunction({%H-}FunctionName: PChar): boolean; virtual;
    procedure RunCustomSimpleFunction({%H-}FunctionName: PChar; {%H-}Value: PCTCfgScriptVariable); virtual;
  public
    Src: PChar;
    AtomStart: PChar;
    SrcStart, SrcEnd: PChar;
    MaxErrorCount: integer;
    constructor Create;
    destructor Destroy; override;
    procedure ClearErrors;
    property Variables: TCTCfgScriptVariables read FVariables;
    function Execute(const Source: string; StopAfterErrors: integer = 1): boolean;// true if no errors
    function ErrorCount: integer;
    property Errors[Index: integer]: TCTCfgScriptError read GetErrors;
    function GetAtom: string;
    function GetAtomOrNothing: string;
    function GetAtom(P: PChar): string;
    function PosToLineCol(p: PChar; out Line, Column: integer): boolean;
    function PosToStr(p: PChar): string;
    function GetErrorStr(Index: integer): string;

    procedure WriteDebugReportStack(Title: string);
  end;

procedure RenameCTCSVariable(var Src: string; const OldName, NewName: string);

function CompareCTCSVariables(Var1, Var2: Pointer): integer;
function ComparePCharWithCTCSVariableName(Name, aVar: Pointer): integer;
function AreCTCSVariablesEqual(const V1, V2: PCTCfgScriptVariable): Boolean;
function AreCTCSVariablesExactEqual(const V1, V2: PCTCfgScriptVariable): Boolean;
function CompareCTCSVariables(const Left, Right: PCTCfgScriptVariable;
                           out Equal, LeftIsLowerThanRight: boolean): boolean;
function NewCTCSVariable: PCTCfgScriptVariable;
function NewCTCSVariable(CloneName: PChar): PCTCfgScriptVariable;
function CloneCTCSVariable(const V: PCTCfgScriptVariable): PCTCfgScriptVariable;
procedure FreeCTCSVariable(var V: PCTCfgScriptVariable);
procedure ClearCTCSVariable(const V: PCTCfgScriptVariable);
procedure SetCTCSVariableAsString(const V: PCTCfgScriptVariable; const s: string);
procedure SetCTCSVariableAsNumber(const V: PCTCfgScriptVariable; const i: int64);
procedure SetCTCSVariableValue(const Src, Dest: PCTCfgScriptVariable);
function GetCTCSVariableAsString(const V: PCTCfgScriptVariable): string;
procedure MakeCTCSVariableString(const V: PCTCfgScriptVariable);
procedure MakeCTCSVariableInt64(const V: PCTCfgScriptVariable);
procedure MakeCTCSVariableInteger(const V: PCTCfgScriptVariable);
procedure AddCTCSVariables(const AddVar, SumVar: PCTCfgScriptVariable);
function CTCSNumberEqualsString(const Number: int64; const P: PChar): boolean; inline;
function CTCSVariableIsTrue(const V: PCTCfgScriptVariable): boolean; inline;
function CTCSVariableIsFalse(const V: PCTCfgScriptVariable): boolean;

function CTCSStringToNumber(P: PChar; out Number: int64): boolean;
function AtomToCTCfgOperator(p: PChar): TCTCfgScriptOperator;

procedure CheckCTCSVariable(const V: PCTCfgScriptVariable);
function dbgs(const t: TCTCfgScriptStackItemType): string; overload;
function dbgs(const t: TCTCSValueType): string; overload;
function dbgs(const t: TCTCfgScriptOperator): string; overload;
function dbgs(const V: PCTCfgScriptVariable): string; overload;


implementation

procedure RenameCTCSVariable(var Src: string; const OldName, NewName: string);
var
  p: PChar;
  AtomStart: PChar;
  SrcPos: PtrUInt;
begin
  if (Src='') or (OldName='') or (not IsValidIdent(OldName))
  or (NewName='') then exit;
  p:=PChar(Src);
  //debugln(['RenameCTCSVariable START ',dbgstr(Src)]);
  repeat
    ReadRawNextPascalAtom(p,AtomStart,nil,false,true);
    if (p=AtomStart) then break;
    if IsIdentStartChar[AtomStart^]
    and (CompareIdentifierPtrs(PChar(OldName),AtomStart)=0)
    then begin
      SrcPos:=PtrUInt(AtomStart-PChar(Src))+1;

      Src:=copy(Src,1,SrcPos-1)+NewName+copy(Src,SrcPos+PtrUInt(length(OldName)),length(Src));
      p:=@Src[SrcPos]+length(NewName);
    end;
  until false;
  //debugln(['RenameCTCSVariable END ',dbgstr(Src)]);
end;

function CompareCTCSVariables(Var1, Var2: Pointer): integer;
var
  v1: PCTCfgScriptVariable absolute Var1;
  v2: PCTCfgScriptVariable absolute Var2;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(v1);
  CheckCTCSVariable(v2);
  {$ENDIF}
  Result:=CompareIdentifiers(v1^.Name,v2^.Name);
end;

function ComparePCharWithCTCSVariableName(Name, aVar: Pointer): integer;
var
  n: PChar absolute Name;
  v: PCTCfgScriptVariable absolute aVar;
begin
  {$IFDEF CheckCTCfgVars}CheckCTCSVariable(v);{$ENDIF}
  Result:=CompareIdentifiers(n,v^.Name);
end;

function AreCTCSVariablesEqual(const V1, V2: PCTCfgScriptVariable): Boolean;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(v1);
  CheckCTCSVariable(v2);
  {$ENDIF}
  Result:=false;
  case V1^.ValueType of
  ctcsvNone:
    exit; // invalid is never equal to anything
  ctcsvString:
    case V2^.ValueType of
    ctcsvNone: exit;
    ctcsvString:
      if (V1^.StrLen<>V2^.StrLen)
          or ((V1^.StrStart<>nil)
              and (not CompareMem(V1^.StrStart,V2^.StrStart,V1^.StrLen)))
      then exit;
    ctcsvNumber:
      if not CTCSNumberEqualsString(V2^.Number,V1^.StrStart) then exit;
    end;
  ctcsvNumber:
    case V2^.ValueType of
    ctcsvNone: exit;
    ctcsvString:
      if not CTCSNumberEqualsString(V1^.Number,V2^.StrStart) then exit;
    ctcsvNumber:
      if V1^.Number<>V2^.Number then exit;
    end;
  end;
  Result:=true;
end;

function AreCTCSVariablesExactEqual(const V1, V2: PCTCfgScriptVariable
  ): Boolean;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(v1);
  CheckCTCSVariable(v2);
  {$ENDIF}
  Result:=false;
  if V1^.ValueType<>V2^.ValueType then exit;
  case V1^.ValueType of
  ctcsvNone: ;
  ctcsvString: if (V1^.StrLen<>V2^.StrLen)
                 or ((V1^.StrStart<>nil)
                     and (not CompareMem(V1^.StrStart,V2^.StrStart,V1^.StrLen)))
               then exit;
  ctcsvNumber: if V1^.Number<>V2^.Number then exit;
  end;
  Result:=true;
end;

function CompareCTCSVariables(const Left, Right: PCTCfgScriptVariable; out
  Equal, LeftIsLowerThanRight: boolean): boolean;
{ Rules:
    If one of the values is invalid, return false
    If both are numbers, compare as numbers
    Otherwise compare as string alphabetically case sensitive A<B, A<AA
}

  procedure CompareNumberWithString(Number: int64; p: PChar);
  var
    i: Integer;
    Cnt: integer;
    s: array[0..30] of char;
  begin
    if p=nil then begin
      Equal:=false;
      LeftIsLowerThanRight:=false;
      exit;
    end;
    // convert number to decimal string
    if Number=0 then begin
      Cnt:=1;
      s[0]:='0';
    end else begin
      Cnt:=0;
      if Number<0 then begin
        Cnt:=1;
        s[0]:='-';
        Number:=-Number;
      end;
      while Number>0 do begin
        s[Cnt]:=chr(Number mod 10+ord('0'));
        inc(Cnt);
        Number:=Number div 10;
      end;
    end;
    for i:=0 to Cnt-1 do begin
      if p^<>s[i] then begin
        Equal:=false;
        LeftIsLowerThanRight:=s[i]<p^;
        exit;
      end;
      inc(p);
    end;
    if p^=#0 then begin
      Equal:=true;
      LeftIsLowerThanRight:=false;
    end else begin
      Equal:=False;
      LeftIsLowerThanRight:=true;
    end;
  end;

var
  V1: PChar;
  V2: PChar;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(Left);
  CheckCTCSVariable(Right);
  {$ENDIF}
  //debugln(['CompareCTCSVariables START Left=',dbgs(Left),' Right=',dbgs(Right)]);
  Result:=false;
  Equal:=false;
  LeftIsLowerThanRight:=false;
  case Left^.ValueType of
  ctcsvNone:
    exit; // invalid is never equal to anything
  ctcsvString:
    case Right^.ValueType of
    ctcsvNone: exit;
    ctcsvString:
      begin
        // compare two strings
        V1:=Left^.StrStart;
        V2:=Right^.StrStart;
        if V1=nil then begin
          if V2=nil then begin
            Equal:=true;
            LeftIsLowerThanRight:=false;
          end else begin
            Equal:=False;
            LeftIsLowerThanRight:=true; // left is shorter than right
          end;
        end else begin
          if V2=nil then begin
            Equal:=False;
            LeftIsLowerThanRight:=false; // left is longer than right
          end else begin
            repeat
              if V1^=V2^ then begin
                if V1^=#0 then begin
                  Equal:=true;
                  LeftIsLowerThanRight:=false;
                  break;
                end else begin
                  inc(V1);
                  inc(V2);
                end;
              end else begin
                Equal:=false;
                LeftIsLowerThanRight:=V1^<V2^;
                break;
              end;
            until false;
          end;
        end;
      end;
    ctcsvNumber:
      begin
        CompareNumberWithString(Right^.Number,Left^.StrStart);
        LeftIsLowerThanRight:=not LeftIsLowerThanRight;
      end;
    end;
  ctcsvNumber:
    case Right^.ValueType of
    ctcsvNone: exit;
    ctcsvString:
      CompareNumberWithString(Left^.Number,Right^.StrStart);
    ctcsvNumber:
      begin
        Equal:=Left^.Number=Right^.Number;
        LeftIsLowerThanRight:=Left^.Number<Right^.Number;
      end;
    end;
  end;
  Result:=true;
end;

function NewCTCSVariable: PCTCfgScriptVariable;
begin
  New(Result);
  FillByte(Result^,SizeOf(TCTCfgScriptVariable),0);
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(Result);
  {$ENDIF}
end;

function NewCTCSVariable(CloneName: PChar): PCTCfgScriptVariable;
var
  l: LongInt;
begin
  Result:=NewCTCSVariable();
  l:=GetIdentLen(CloneName);
  if l>0 then begin
    Result^.Name:=GetMem(l+1);
    System.Move(CloneName^,Result^.Name^,l);
    Result^.Name[l]:=#0;
  end;
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(Result);
  {$ENDIF}
end;

function CloneCTCSVariable(const V: PCTCfgScriptVariable): PCTCfgScriptVariable;
var
  l: LongInt;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(v);
  {$ENDIF}
  Result:=NewCTCSVariable(V^.Name);
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(Result);
  {$ENDIF}
  Result^.ValueType:=V^.ValueType;
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(Result);
  {$ENDIF}
  case V^.ValueType of
  ctcsvNone: ;
  ctcsvString:
    begin
      l:=V^.StrLen;
      if l>0 then begin
        Result^.StrLen:=l;
        Result^.StrStart:=GetMem(l+1);
        System.Move(V^.StrStart^,Result^.StrStart^,l);
        Result^.StrStart[l]:=#0;
      end;
    end;
  ctcsvNumber:
    Result^.Number:=V^.Number;
  end;
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(Result);
  {$ENDIF}
end;

procedure SetCTCSVariableValue(const Src, Dest: PCTCfgScriptVariable);
var
  l: LongInt;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(Src);
  CheckCTCSVariable(Dest);
  {$ENDIF}
  if Src=Dest then exit;
  case Src^.ValueType of
  ctcsvNone:
    ClearCTCSVariable(Dest);
  ctcsvString:
    begin
      if Dest^.ValueType<>ctcsvString then begin
        Dest^.ValueType:=ctcsvString;
        Dest^.StrStart:=nil;
      end;
      l:=Src^.StrLen;
      Dest^.StrLen:=l;
      if l>0 then begin
        ReAllocMem(Dest^.StrStart,l+1);
        System.Move(Src^.StrStart^,Dest^.StrStart^,l);
        Dest^.StrStart[l]:=#0;
      end else
        ReAllocMem(Dest^.StrStart,0);
    end;
  ctcsvNumber:
    begin
      case Dest^.ValueType of
      ctcsvNone:
        Dest^.ValueType:=ctcsvNumber;
      ctcsvString:
        begin
          Dest^.ValueType:=ctcsvNumber;
          if Dest^.StrStart<>nil then
            Freemem(Dest^.StrStart);
        end;
      ctcsvNumber: ;
      end;
      Dest^.Number:=Src^.Number;
    end;
  end;
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(Src);
  CheckCTCSVariable(Dest);
  {$ENDIF}
end;

procedure FreeCTCSVariable(var V: PCTCfgScriptVariable);
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(v);
  {$ENDIF}
  ClearCTCSVariable(V);
  ReAllocMem(V^.Name,0);
  Dispose(V);
end;

procedure ClearCTCSVariable(const V: PCTCfgScriptVariable);
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(v);
  {$ENDIF}
  if V^.ValueType=ctcsvString then
    ReAllocMem(V^.StrStart,0);
  V^.ValueType:=ctcsvNone;
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(v);
  {$ENDIF}
end;

procedure MakeCTCSVariableString(const V: PCTCfgScriptVariable);
var
  s: String;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(V);
  {$ENDIF}
  case V^.ValueType of
  ctcsvNone:
    begin
      V^.StrLen:=0;
      V^.StrStart:=nil;
      V^.ValueType:=ctcsvString;
    end;
  ctcsvString: ;
  ctcsvNumber:
    begin
      s:=IntToStr(V^.Number);
      V^.StrLen:=length(s);
      V^.StrStart:=GetMem(length(s)+1);
      System.Move(s[1],V^.StrStart^,length(s)+1);
      V^.ValueType:=ctcsvString;
    end;
  end;
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(V);
  {$ENDIF}
end;

procedure MakeCTCSVariableInt64(const V: PCTCfgScriptVariable);
var
  i: Int64;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(V);
  {$ENDIF}
  case V^.ValueType of
  ctcsvNone:
    begin
      V^.Number:=0;
      V^.ValueType:=ctcsvNumber;
    end;
  ctcsvString:
    begin
      if V^.StrStart<>nil then begin
        i:=StrToInt64Def(V^.StrStart,0);
        FreeMem(V^.StrStart);
        V^.Number:=i;
      end else
        V^.Number:=0;
      V^.ValueType:=ctcsvNumber;
    end;
  ctcsvNumber: ;
  end;
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(V);
  {$ENDIF}
end;

procedure MakeCTCSVariableInteger(const V: PCTCfgScriptVariable);
var
  i: integer;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(V);
  {$ENDIF}
  case V^.ValueType of
  ctcsvNone:
    begin
      V^.Number:=0;
      V^.ValueType:=ctcsvNumber;
    end;
  ctcsvString:
    begin
      if V^.StrStart<>nil then begin
        i:=StrToIntDef(V^.StrStart,0);
        FreeMem(V^.StrStart);
        V^.Number:=i;
      end else
        V^.Number:=0;
      V^.ValueType:=ctcsvNumber;
    end;
  ctcsvNumber: ;
  end;
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(V);
  {$ENDIF}
end;

procedure AddCTCSVariables(const AddVar, SumVar: PCTCfgScriptVariable);
{ If one of them is none, then save in sum the other value
  If both are numbers, add them.
  Otherwise concatenate as strings.
}
var
  OldLen: LongInt;
  s: String;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(AddVar);
  CheckCTCSVariable(SumVar);
  {$ENDIF}
  case SumVar^.ValueType of
  ctcsvNone:
    SetCTCSVariableValue(AddVar,SumVar);
  ctcsvString:
    case AddVar^.ValueType of
    ctcsvNone:
      ;
    ctcsvString:
      if AddVar^.StrLen>0 then begin
        // append
        OldLen:=SumVar^.StrLen;
        SumVar^.StrLen+=AddVar^.StrLen;
        ReAllocMem(SumVar^.StrStart,SumVar^.StrLen+1);
        System.Move(AddVar^.StrStart^,SumVar^.StrStart[OldLen],AddVar^.StrLen+1);
      end;
    ctcsvNumber:
      begin
        // append as string
        s:=IntToStr(AddVar^.Number);
        OldLen:=SumVar^.StrLen;
        SumVar^.StrLen+=length(s);
        ReAllocMem(SumVar^.StrStart,SumVar^.StrLen+1);
        System.Move(s[1],SumVar^.StrStart[OldLen],length(s)+1);
      end;
    end;
  ctcsvNumber:
    case AddVar^.ValueType of
    ctcsvNone:
      ;
    ctcsvString:
      begin
        // convert SumVar from number to string and append
        s:=IntToStr(SumVar^.Number);
        SumVar^.ValueType:=ctcsvString;
        SumVar^.StrLen:=length(s)+AddVar^.StrLen;
        SumVar^.StrStart:=GetMem(SumVar^.StrLen+1);
        System.Move(s[1],SumVar^.StrStart^,length(s));
        if AddVar^.StrStart<>nil then
          System.Move(AddVar^.StrStart^,SumVar^.StrStart[length(s)],AddVar^.StrLen+1)
        else
          SumVar^.StrStart[SumVar^.StrLen]:=#0;
      end;
    ctcsvNumber:
      try
        SumVar^.Number+=AddVar^.Number;
      except
      end;
    end;
  end;
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(AddVar);
  CheckCTCSVariable(SumVar);
  {$ENDIF}
end;

function CTCSNumberEqualsString(const Number: int64; const P: PChar): boolean;
var
  n: int64;
begin
  Result:=CTCSStringToNumber(P,n) and (n=Number);
end;

function CTCSStringToNumber(P: PChar; out Number: int64): boolean;
var
  n: int64;
  Negated: Boolean;
begin
  Result:=false;
  if (P=nil) or (P^=#0) then exit;
  try
    n:=0;
    if p^='-' then begin
      Negated:=true;
      inc(p);
    end else
      Negated:=false;
    if p^='$' then begin
      // hex
      repeat
        case p^ of
        '0'..'9': n:=n*16+Ord(p^)-Ord('0');
        'a'..'f': n:=n*16+Ord(p^)-Ord('a')+10;
        'A'..'F': n:=n*16+Ord(p^)-Ord('A')+10;
        #0: break;
        else exit;
        end;
        inc(p);
      until false;
    end else if p^='%' then begin
      // binary
      repeat
        case p^ of
        '0': n:=n*2;
        '1': n:=n*2+1;
        #0: break;
        else exit;
        end;
        inc(p);
      until false;
    end else begin
      // decimal
      repeat
        case p^ of
        '0'..'9': n:=n*10+Ord(p^)-Ord('0');
        #0: break;
        else exit;
        end;
        inc(p);
      until false;
    end;
    if Negated then n:=-n;
  except
    exit;
  end;
  Number:=n;
  Result:=true;
end;

function CTCSVariableIsTrue(const V: PCTCfgScriptVariable): boolean;
begin
  Result:=not CTCSVariableIsFalse(V);
end;

function CTCSVariableIsFalse(const V: PCTCfgScriptVariable): boolean;
begin
  case V^.ValueType of
  ctcsvNone:
    Result:=false;
  ctcsvString:
    Result:=(V^.StrLen=1) and (V^.StrStart^='0');
  ctcsvNumber:
    Result:=V^.Number=0;
  end;
end;

function AtomToCTCfgOperator(p: PChar): TCTCfgScriptOperator;
begin
  Result:=ctcsoNone;
  case UpChars[p^] of
  'A':
    if CompareIdentifiers('and',p)=0 then Result:=ctcsoAnd;
  'D':
    if CompareIdentifiers('div',p)=0 then Result:=ctcsoDiv;
  'M':
    if CompareIdentifiers('mod',p)=0 then Result:=ctcsoMod;
  'N':
    if CompareIdentifiers('not',p)=0 then Result:=ctcsoNot;
  'O':
    if CompareIdentifiers('or',p)=0 then Result:=ctcsoOr;
  'S':
    case UpChars[p[1]] of
    'H':
      case UpChars[p[2]] of
      'L': if CompareIdentifiers('shl',p)=0 then Result:=ctcsoShL;
      'R': if CompareIdentifiers('shr',p)=0 then Result:=ctcsoShR;
      end;
    end;
  'X':
    if CompareIdentifiers('xor',p)=0 then Result:=ctcsoXOr;
  '=':
    Result:=ctcsoEqual;
  '<':
    case p[1] of
    '>': Result:=ctcsoNotEqual;
    '=': Result:=ctcsoLowerOrEqualThan;
    else { < lower than } Result:=ctcsoLowerThan;
    end;
  '>':
    case p[1] of
    '=': Result:=ctcsoGreaterOrEqualThan;
    else { > greater than } Result:=ctcsoGreaterThan;
    end;
  '*':
    case p[1] of
    '*': ;
    '=': ;
    else { * multiply } Result:=ctcsoMultiply;
    end;
  '/':
    case p[1] of
    '/': ;
    '=': ;
    else { / divide } Result:=ctcsoDivide;
    end;
  '+':
    case p[1] of
    '=': ;
    else { + plus } Result:=ctcsoPlus;
    end;
  '-':
    case p[1] of
    '=': ;
    else { - minus } Result:=ctcsoMinus;
    end;
  ':':
    case p[1] of
    '=': ;
    else { : colon } ;
    end;
  end;
end;

procedure CheckCTCSVariable(const V: PCTCfgScriptVariable);
begin
  if V=nil then
    RaiseCatchableException('');
  if (V^.Name<>nil) and (strlen(V^.Name)>255) then
    RaiseCatchableException('');
  case V^.ValueType of
  ctcsvNone: ;
  ctcsvString:
    begin
      if V^.StrLen=0 then begin
        if V^.StrStart<>nil then
          RaiseCatchableException('');
      end else begin
        if V^.StrStart=nil then
          RaiseCatchableException('');
        if strlen(V^.StrStart)<>V^.StrLen then
          RaiseCatchableException('');
      end;
    end;
  ctcsvNumber: ;
  end;
end;

function dbgs(const t: TCTCfgScriptStackItemType): string;
begin
  Result:=GetEnumName(typeinfo(t),ord(t));
end;

function dbgs(const t: TCTCSValueType): string;
begin
  Result:=GetEnumName(typeinfo(t),ord(t));
end;

function dbgs(const t: TCTCfgScriptOperator): string;
begin
  Result:=GetEnumName(typeinfo(t),ord(t));
end;

function dbgs(const V: PCTCfgScriptVariable): string;
var
  l: Integer;
begin
  Result:=GetIdentifier(V^.Name)+':';
  case V^.ValueType of
  ctcsvNone:
    Result:=Result+'none';
  ctcsvString:
    begin
      Result:=Result+'string=';
      l:=length(Result);
      if V^.StrLen>0 then begin
        SetLength(Result,l+V^.StrLen);
        System.Move(V^.StrStart^,Result[l+1],V^.StrLen);
      end;
    end;
  ctcsvNumber:
    Result:=Result+'int64='+IntToStr(V^.Number);
  end;
end;

function GetCTCSVariableAsString(const V: PCTCfgScriptVariable): string;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(V);
  {$ENDIF}
  case V^.ValueType of
  ctcsvNone: Result:='';
  ctcsvString:
    begin
      SetLength(Result,V^.StrLen);
      if Result<>'' then
        System.Move(V^.StrStart^,Result[1],length(Result));
    end;
  ctcsvNumber: Result:=IntToStr(V^.Number);
  else Result:='';
  end;
end;

procedure SetCTCSVariableAsString(const V: PCTCfgScriptVariable; const s: string);
var
  l: Integer;
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(v);
  {$ENDIF}
  if V^.ValueType<>ctcsvString then begin
    V^.ValueType:=ctcsvString;
    V^.StrLen:=0;
    V^.StrStart:=nil;
  end;
  l:=length(s);
  V^.StrLen:=l;
  if l>0 then begin
    ReAllocMem(V^.StrStart,l+1);
    System.Move(s[1],V^.StrStart^,l+1); // +1 for the #0
  end else
    ReAllocMem(V^.StrStart,0);
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(v);
  {$ENDIF}
end;

procedure SetCTCSVariableAsNumber(const V: PCTCfgScriptVariable; const i: int64);
begin
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(v);
  {$ENDIF}
  if (V^.ValueType=ctcsvString) and (V^.StrStart<>nil) then
    Freemem(V^.StrStart);
  V^.ValueType:=ctcsvNumber;
  V^.Number:=i;
  {$IFDEF CheckCTCfgVars}
  CheckCTCSVariable(v);
  {$ENDIF}
end;

{ TCTCfgScriptVariables }

function TCTCfgScriptVariables.GetValues(const Name: string): string;
var
  v: PCTCfgScriptVariable;
begin
  if Name='' then
    exit('');
  v:=GetVariable(PChar(Name));
  if v=nil then
    exit('');
  Result:=GetCTCSVariableAsString(v);
end;

procedure TCTCfgScriptVariables.SetValues(const Name: string;
  const AValue: string);
var
  v: PCTCfgScriptVariable;
begin
  if Name='' then
    exit;
  v:=GetVariable(PChar(Name),true);
  SetCTCSVariableAsString(v,AValue);
end;

constructor TCTCfgScriptVariables.Create;
begin
  FItems:=TAVLTree.Create(@CompareCTCSVariables);
end;

destructor TCTCfgScriptVariables.Destroy;
begin
  Clear;
  FreeAndNil(FItems);
  inherited Destroy;
end;

procedure TCTCfgScriptVariables.Clear;
var
  Node: TAVLTreeNode;
  Item: PCTCfgScriptVariable;
begin
  Node:=FItems.FindLowest;
  while Node<>nil do begin
    Item:=PCTCfgScriptVariable(Node.Data);
    FreeCTCSVariable(Item);
    Node:=FItems.FindSuccessor(Node);
  end;
  FItems.Clear;
end;

function TCTCfgScriptVariables.Equals(Vars: TCTCfgScriptVariables): boolean;
var
  Node1: TAVLTreeNode;
  Node2: TAVLTreeNode;
  Item1: PCTCfgScriptVariable;
  Item2: PCTCfgScriptVariable;
begin
  Result:=false;
  if Vars=nil then exit;
  if FItems.Count<>Vars.FItems.Count then exit;
  Node1:=FItems.FindLowest;
  Node2:=Vars.FItems.FindLowest;
  while Node1<>nil do begin
    Item1:=PCTCfgScriptVariable(Node1.Data);
    Item2:=PCTCfgScriptVariable(Node2.Data);
    if CompareIdentifiers(Item1^.Name,Item2^.Name)<>0 then exit;
    if Item1^.ValueType<>Item2^.ValueType then exit;
    case Item1^.ValueType of
    ctcsvNone: ;
    ctcsvString: if (Item1^.StrLen<>Item2^.StrLen)
                   or ((Item1^.StrStart<>nil)
                       and (not CompareMem(Item1^.StrStart,Item2^.StrStart,Item1^.StrLen)))
                 then exit;
    ctcsvNumber: if Item1^.Number<>Item2^.Number then exit;
    end;
    Node1:=FItems.FindSuccessor(Node1);
    Node2:=Vars.FItems.FindSuccessor(Node2);
  end;
  Result:=true;
end;

procedure TCTCfgScriptVariables.Assign(Source: TCTCfgScriptVariables);
var
  Node: TAVLTreeNode;
  Item: PCTCfgScriptVariable;
  NewItem: PCTCfgScriptVariable;
begin
  if Self=Source then exit;
  Clear;
  Node:=Source.FItems.FindLowest;
  while Node<>nil do begin
    Item:=PCTCfgScriptVariable(Node.Data);
    NewItem:=CloneCTCSVariable(Item);
    FItems.Add(NewItem);
    Node:=Source.FItems.FindSuccessor(Node);
  end;
end;

procedure TCTCfgScriptVariables.Assign(Source: TStrings);
var
  Name: string;
  Value: string;
  i: Integer;
begin
  Clear;
  for i:=0 to Source.Count-1 do begin
    Name:=Source.Names[i];
    if (Name='') or not IsValidIdent(Name) then continue;
    Value:=Source.ValueFromIndex[i];
    Define(PChar(Name),Value);
  end;
end;

procedure TCTCfgScriptVariables.AddOverrides(Source: TCTCfgScriptVariables);
var
  Item: PCTCfgScriptVariable;
  Node: TAVLTreeNode;
begin
  Node:=Source.FItems.FindLowest;
  while Node<>nil do begin
    Item:=PCTCfgScriptVariable(Node.Data);
    AddOverride(Item);
    Node:=Source.FItems.FindSuccessor(Node);
  end;
end;

procedure TCTCfgScriptVariables.AddOverride(Source: PCTCfgScriptVariable);
var
  Node: TAVLTreeNode;
  Item: PCTCfgScriptVariable;
begin
  Node:=FItems.Find(Source);
  if Node<>nil then begin
    Item:=PCTCfgScriptVariable(Node.Data);
    SetCTCSVariableValue(Source,Item);
  end else begin
    Item:=CloneCTCSVariable(Source);
    FItems.Add(Item);
  end;
end;

function TCTCfgScriptVariables.GetVariable(const Name: PChar;
  CreateIfNotExists: Boolean): PCTCfgScriptVariable;
var
  Node: TAVLTreeNode;
begin
  Node:=FItems.FindKey(Name,@ComparePCharWithCTCSVariableName);
  if Node<>nil then
    Result:=PCTCfgScriptVariable(Node.Data)
  else if CreateIfNotExists then begin
    Result:=NewCTCSVariable(Name);
    FItems.Add(Result);
  end else
    Result:=nil;
end;

procedure TCTCfgScriptVariables.Undefine(Name: PChar);
var
  Node: TAVLTreeNode;
  Item: PCTCfgScriptVariable;
begin
  Node:=FItems.FindKey(Name,@ComparePCharWithCTCSVariableName);
  if Node=nil then exit;
  Item:=PCTCfgScriptVariable(Node.Data);
  FreeCTCSVariable(Item);
  FItems.Delete(Node);
end;

procedure TCTCfgScriptVariables.Define(Name: PChar; const Value: string);

  function IsNumber: boolean;
  var
    p: PChar;
  begin
    if Value='' then exit(false);
    p:=PChar(Value);
    if p^='-' then inc(p);
    while (p^ in ['0'..'9']) do inc(p);
    Result:=(p^=#0) and (p-PChar(Value)=length(Value));
  end;

var
  V: PCTCfgScriptVariable;
  i: Int64;
begin
  V:=GetVariable(Name,true);
  if Value='' then
    ClearCTCSVariable(V)
  else if IsNumber and TryStrToInt64(Value,i) then
    SetCTCSVariableAsNumber(V,i)
  else
    SetCTCSVariableAsString(V,Value);
end;

function TCTCfgScriptVariables.IsDefined(Name: PChar): boolean;
begin
  Result:=GetVariable(Name)<>nil;
end;

procedure TCTCfgScriptVariables.WriteDebugReport(const Title: string;
  const Prefix: string);
var
  Node: TAVLTreeNode;
  V: PCTCfgScriptVariable;
begin
  debugln([Prefix,'TCTCfgScriptVariables.WriteDebugReport Count=',Tree.Count,': ',Title]);
  Node:=FItems.FindLowest;
  while Node<>nil do begin
    V:=PCTCfgScriptVariable(Node.Data);
    debugln([Prefix,'  ',dbgs(V)]);
    Node:=FItems.FindSuccessor(Node);
  end;
end;

{ TCTConfigScriptEngine }

function TCTConfigScriptEngine.GetErrors(Index: integer): TCTCfgScriptError;
begin
  Result:=TCTCfgScriptError(FErrors[Index]);
end;

procedure TCTConfigScriptEngine.AddError(const aMsg: string; ErrorPos: PChar);
var
  Err: TCTCfgScriptError;
  Position: Integer;
  Line: Integer;
  Column: Integer;
begin
  {$IFDEF VerboseCTCfgScript}
  WriteDebugReportStack('ERROR: '+aMsg);
  {$ENDIF}
  Position:=-1;
  Line:=0;
  Column:=0;
  if (ErrorPos<>nil) then begin
    Position:=ErrorPos-Src;
    PosToLineCol(ErrorPos,Line,Column);
  end;
  Err:=TCTCfgScriptError.Create(aMsg,Position,Line,Column);
  FErrors.Add(Err);
  if ErrorCount>=MaxErrorCount then
    raise Exception.Create(GetErrorStr(ErrorCount-1));
end;

procedure TCTConfigScriptEngine.AddError(const aMsg: string);
begin
  AddError(aMsg,AtomStart);
end;

procedure TCTConfigScriptEngine.RunStatement(Skip: boolean);
{ Examples:
    begin..
    if...
    variable:=
}

  procedure ErrorUnexpectedAtom;
  begin
    AddError(Format(ctsExpectedSemicolonOfStatementButFound, [GetAtomOrNothing]))
  end;

var
  Handled: Boolean;
  StartTop: LongInt;
begin
  {$IFDEF VerboseCTCfgScript}
  debugln(['TCTConfigScriptEngine.RunStatement Atom=',GetAtom]);
  {$ENDIF}
  StartTop:=FStack.Top;
  case AtomStart^ of
  #0: ;
  ';': ; // empty statement
  'a'..'z','A'..'Z':
    begin
      // identifier or keyword
      Handled:=false;
      case UpChars[AtomStart^] of
      'B':
        if CompareIdentifiers('BEGIN',AtomStart)=0 then begin
          Handled:=true;
          RunBegin(Skip);
        end;
      'I':
        if CompareIdentifiers('IF',AtomStart)=0 then begin
          Handled:=true;
          RunIf(Skip);
        end;
      'U':
        if CompareIdentifiers('Undefine',AtomStart)=0 then begin
          Handled:=true;
          RunUndefine(Skip);
        end;
      end;
      if (not Handled) then begin
        if IsKeyWord(AtomStart) then begin
          AddError(Format(ctsUnexpectedKeyword2, [GetAtom]));
        end else if IsFunction(AtomStart) then begin
          if not RunFunction then exit;
        end else begin
          // parse assignment
          RunAssignment(Skip);
        end;
      end;
    end;
  else
    ErrorUnexpectedAtom;
  end;
  // clean up stack
  while FStack.Top>StartTop do FStack.Pop;
end;

procedure TCTConfigScriptEngine.RunBegin(Skip: boolean);
{ Examples:
    begin
    end
    begin
      statement statement
    end
}
var
  BeginStart: PChar;
  StartTop: LongInt;

  procedure ErrorMissingEnd;
  begin
    //debugln(['ErrorMissingEnd BeginStart=',BeginStart]);
    AddError(Format(ctsBeginAtWithoutEnd, [PosToStr(BeginStart)]));
  end;

begin
  BeginStart:=AtomStart;
  StartTop:=FStack.Top;
  FStack.Push(ctcssBegin,AtomStart);
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  repeat
    if (AtomStart^=#0) then begin
      ErrorMissingEnd;
      break;
    end else if CompareIdentifiers('END',AtomStart)=0 then begin
      FStack.Pop;
      ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
      break;
    end else if AtomStart^=';' then begin
      // skip
      ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
    end else begin
      RunStatement(Skip);
    end;
  until false;
  // clean up stack (recover from errors)
  while FStack.Top>StartTop do FStack.Pop;
end;

procedure TCTConfigScriptEngine.RunIf(Skip: boolean);
{ Examples:
    if expression then statement else statement
}
var
  IfStart: PChar;
  ExprIsTrue: Boolean;
  StartTop: LongInt;
begin
  IfStart:=AtomStart;
  StartTop:=FStack.Top;
  FStack.Push(ctcssIf,IfStart);
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  ExprIsTrue:=false;
  if RunExpression then begin
    ExprIsTrue:=CTCSVariableIsTrue(FStack.TopItemOperand);
    FStack.Pop;
  end;
  {$IFDEF VerboseCTCfgScript}
  debugln(['TCTConfigScriptEngine.RunIf expression=',ExprIsTrue]);
  {$ENDIF}

  // read then
  if CompareIdentifiers(AtomStart,'then')<>0 then
    AddError(Format(ctsThenExpectedButFound, [GetAtomOrNothing]));
  // then statement
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  RunStatement(Skip or not ExprIsTrue);
  if CompareIdentifiers(AtomStart,'else')=0 then begin
    // else statement
    ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
    RunStatement(Skip or ExprIsTrue);
  end;
  // clean up stack
  while FStack.Top>StartTop do FStack.Pop;
end;

procedure TCTConfigScriptEngine.RunUndefine(Skip: boolean);
var
  VarStart: PChar;
begin
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  if AtomStart^<>'(' then begin
    AddError(Format(ctsExpectedButFound, [GetAtomOrNothing]));
    exit;
  end;
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  if (not IsIdentStartChar[AtomStart^]) or IsKeyWord(AtomStart) then begin
    AddError(Format(ctsExpectedIdentifierButFound, [GetAtomOrNothing]));
    exit;
  end;
  VarStart:=AtomStart;
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  if AtomStart^<>')' then begin
    AddError(Format(ctsExpectedButFound2, [GetAtomOrNothing]));
    exit;
  end;
  if not Skip then
    Variables.Undefine(VarStart);
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
end;

procedure TCTConfigScriptEngine.RunAssignment(Skip: boolean);
{ Examples:
    a:=3;
}
var
  VarStart: PChar;
  Variable: PCTCfgScriptVariable;
  OperatorStart: PChar;
  StartTop: LongInt;
begin
  VarStart:=AtomStart;
  {$IFDEF VerboseCTCfgScript}
  debugln(['TCTConfigScriptEngine.RunAssignment ',GetIdentifier(VarStart)]);
  {$ENDIF}
  StartTop:=FStack.Top;
  FStack.Push(ctcssAssignment,VarStart);
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  {$IFDEF VerboseCTCfgScript}
  debugln(['TCTConfigScriptEngine.RunAssignment Operator=',GetAtom]);
  {$ENDIF}
  // read := or +=
  if AtomStart^=#0 then begin
    AddError(ctsMissing);
    exit;
  end;
  OperatorStart:=AtomStart;
  if (not (AtomStart^ in [':','+'])) or (AtomStart[1]<>'=') then begin
    AddError(Format(ctsExpectedButFound3, [GetAtom]));
    exit;
  end;
  // read expression
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  if RunExpression and (not Skip) then begin
    Variable:=Variables.GetVariable(VarStart,true);
    {$IFDEF VerboseCTCfgScript}
    debugln(['TCTConfigScriptEngine.RunAssignment BEFORE ',GetIdentifier(VarStart),'=(Old=',dbgs(Variable),') ',GetAtom(OperatorStart),' ',dbgs(FStack.TopItemOperand)]);
    {$ENDIF}
    case OperatorStart^ of
    ':': // :=
      SetCTCSVariableValue(FStack.TopItemOperand,Variable);
    '+': // +=
      AddCTCSVariables(FStack.TopItemOperand,Variable);
    end;

    {$IFDEF VerboseCTCfgScript}
    debugln(['TCTConfigScriptEngine.RunAssignment AFTER ',GetIdentifier(VarStart),' = ',dbgs(Variable),' Atom=',GetAtom]);
    {$ENDIF}
  end;
  // clean up stack
  while FStack.Top>StartTop do FStack.Pop;
end;

procedure TCTConfigScriptEngine.PushNumberValue(const Number: int64);
var
  Operand: PCTCfgScriptVariable;
begin
  FStack.Push(ctcssOperand,AtomStart);
  Operand:=FStack.TopItemOperand;
  Operand^.ValueType:=ctcsvNumber;
  Operand^.Number:=Number;
  ExecuteStack(1);
end;

function TCTConfigScriptEngine.RunDefined(Negate: boolean): boolean;
var
  VarStart: PChar;
  b: Boolean;
begin
  Result:=false;
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  if AtomStart^<>'(' then begin
    AddError(Format(ctsExpectedButFound, [GetAtomOrNothing]));
    exit;
  end;
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  if (not IsIdentStartChar[AtomStart^]) or IsKeyWord(AtomStart) then begin
    AddError(Format(ctsExpectedIdentifierButFound, [GetAtomOrNothing]));
    exit;
  end;
  VarStart:=AtomStart;
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  if AtomStart^<>')' then begin
    AddError(Format(ctsExpectedButFound2, [GetAtomOrNothing]));
    exit;
  end;
  b:=Variables.GetVariable(VarStart)<>nil;
  if Negate then b:=not b;
  PushBooleanValue(b);
  Result:=true;
end;

function TCTConfigScriptEngine.RunFunction: boolean;
var
  StartTop: LongInt;
  Value: TCTCfgScriptVariable;
  FunctionName: PChar;
begin
  Result:=false;
  FunctionName:=AtomStart;
  StartTop:=FStack.Top;
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  if AtomStart^<>'(' then begin
    AddError(Format(ctsExpectedButFound, [GetAtomOrNothing]));
    exit;
  end;
  ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);

  FStack.Push(ctcssRoundBracketOpen,AtomStart);
  FillByte(Value{%H-},SizeOf(Value),0);
  if RunExpression then
    SetCTCSVariableValue(FStack.TopItemOperand,@Value);
  if AtomStart^<>')' then begin
    AddError(Format(ctsExpectedButFound2, [GetAtomOrNothing]));
    exit;
  end;

  // clean up stack
  while FStack.Top>StartTop do FStack.Pop;

  // execute function
  {$IFDEF VerboseCTCfgScript}
  debugln(['TCTConfigScriptEngine.RunFunction FunctionName="',GetAtom(FunctionName),'" Parameter=',dbgs(PCTCfgScriptVariable(@Value))]);
  {$ENDIF}
  case UpChars[FunctionName^] of
  'I':
    if CompareIdentifiers(FunctionName,'int64')=0 then
      MakeCTCSVariableInt64(@Value)
    else if CompareIdentifiers(FunctionName,'integer')=0 then
      MakeCTCSVariableInteger(@Value);
  'S':
    if CompareIdentifiers(FunctionName,'string')=0 then
      MakeCTCSVariableString(@Value);
  else
    RunCustomSimpleFunction(FunctionName,@Value);
  end;

  // put result on stack as operand
  {$IFDEF VerboseCTCfgScript}
  debugln(['TCTConfigScriptEngine.RunFunction FunctionName="',GetAtom(FunctionName),'" Result=',dbgs(PCTCfgScriptVariable(@Value))]);
  {$ENDIF}
  FStack.Push(ctcssOperand,FunctionName);
  SetCTCSVariableValue(@Value,FStack.TopItemOperand);

  ClearCTCSVariable(@Value);

  Result:=true;
end;

procedure TCTConfigScriptEngine.PushStringConstant;
var
  Operand: PCTCfgScriptVariable;

  procedure Add(p: PChar; Count: integer);
  var
    OldLen: LongInt;
    NewLen: Integer;
  begin
    if Count=0 then exit;
    OldLen:=Operand^.StrLen;
    NewLen:=OldLen+Count;
    ReAllocMem(Operand^.StrStart,NewLen+1);
    System.Move(p^,Operand^.StrStart[OldLen],Count);
    Operand^.StrLen:=NewLen;
    Operand^.StrStart[NewLen]:=#0;
  end;

var
  p: PChar;
  StartPos: PChar;
  i: Integer;
  c: char;
begin
  FStack.Push(ctcssOperand,AtomStart);
  Operand:=FStack.TopItemOperand;
  Operand^.ValueType:=ctcsvString;
  Operand^.StrLen:=0;
  Operand^.StrStart:=nil;
  p:=AtomStart;
  while true do begin
    case p^ of
    #0:
      break;
    '#':
      begin
        inc(p);
        StartPos:=p;
        i:=0;
        while (p^ in ['0'..'9']) do begin
          i:=i*10+ord(p^)-ord('0');
          if (i>255) then begin
            AddError(ctsCharacterConstantOutOfRange);
            while (p^ in ['0'..'9']) do inc(p);
            break;
          end;
          inc(p);
        end;
        c:=chr(i);
        Add(@c,1);
      end;
    '''':
      begin
        inc(p);
        StartPos:=p;
        while not (p^ in ['''',#10,#13,#0]) do
          inc(p);
        if p^<>'''' then
          AddError('missing end apostrophe of string constant');
        Add(StartPos,p-StartPos);
        if p^='''' then
          inc(p);
      end;
    else
      break;
    end;
  end;
  ExecuteStack(1);
end;

procedure TCTConfigScriptEngine.PushNumberConstant;
var
  Item: PCTCfgScriptStackItem;
  p: PChar;
  Number: int64;
  l: integer;
  c: Char;
begin
  FStack.Push(ctcssOperand,AtomStart);
  Item:=FStack.TopItem;
  p:=AtomStart;
  c:=p^;
  if not (c in ['0'..'9']) then inc(p);
  Number:=0;
  try
    while true do begin
      case c of
      '%':
        case p^ of
        '0': Number:=Number*2;
        '1': Number:=Number*2+1;
        else break;
        end;
      '&':
        case p^ of
        '0'..'7': Number:=Number*8+ord(p^)-ord('0');
        else break;
        end;
      '$':
        case p^ of
        '0'..'9': Number:=Number*16+ord(p^)-ord('0');
        'a'..'f': Number:=Number*16+ord(p^)-ord('a')+10;
        'A'..'F': Number:=Number*16+ord(p^)-ord('A')+10;
        else break;
        end;
      else
        // decimal or float
        case p^ of
        '0'..'9': Number:=Number*10+ord(p^)-ord('0');
        else break;
        end;
      end;
      inc(p);
    end;
  except
    p:=AtomStart;
  end;
  if p=Src then begin
    // a number
    Item^.Operand.ValueType:=ctcsvNumber;
    Item^.Operand.Number:=Number;
  end else begin
    // string constant
    Item^.Operand.ValueType:=ctcsvString;
    l:=Src-AtomStart;
    Item^.Operand.StrLen:=l;
    Item^.Operand.StrStart:=GetMem(l+1);
    System.Move(AtomStart^,Item^.Operand.StrStart^,l);
    Item^.Operand.StrStart[l]:=#0;
  end;
  ExecuteStack(1);
end;

procedure TCTConfigScriptEngine.PushBooleanValue(b: boolean);
var
  Operand: PCTCfgScriptVariable;
begin
  FStack.Push(ctcssOperand,AtomStart);
  Operand:=FStack.TopItemOperand;
  Operand^.ValueType:=ctcsvNumber;
  if b then
    Operand^.Number:=1
  else
    Operand^.Number:=0;
  ExecuteStack(1);
end;

function TCTConfigScriptEngine.RunExpression: boolean;
{ Examples:
    A   is false if A=0 or A='0'
    defined(A)
    (A)
    unary operators:

    binary operators:

}
  function OperandAllowed: boolean;
  begin
    case FStack.TopTyp of
    ctcssExpression,ctcssOperator,ctcssRoundBracketOpen:
      Result:=true;
    else
      {$IFDEF VerboseCTCfgScript}
      debugln(['TCTConfigScriptEngine.RunExpression.OperandAllowed no']);
      {$ENDIF}
      AddError(Format(ctsOperatorExpectedButFound, [GetAtom]));
      Result:=false;
    end;
  end;

  function BinaryOperatorAllowed: boolean;
  begin
    case FStack.TopTyp of
    ctcssOperand:
      Result:=true;
    else
      {$IFDEF VerboseCTCfgScript}
      debugln(['TCTConfigScriptEngine.RunExpression.BinaryOperatorAllowed no']);
      {$ENDIF}
      AddError(Format(ctsOperandExpectedButFound, [GetAtom]));
      Result:=false;
    end;
  end;

  function PushBinaryOperator: boolean;
  begin
    Result:=BinaryOperatorAllowed;
    if not Result then begin
      RunExpression:=false;
      exit;
    end;
    ExecuteStack(GetOperatorLevel(AtomStart));
    FStack.Push(ctcssOperator,AtomStart);
  end;

var
  ExprStart: PChar;
  Handled: Boolean;
  Item: PCTCfgScriptStackItem;
  StartTop: LongInt;
  v: PCTCfgScriptVariable;
begin
  Result:=true;
  ExprStart:=AtomStart;
  StartTop:=FStack.Top;
  FStack.Push(ctcssExpression,ExprStart);
  while true do begin
    {$IFDEF VerboseCTCfgScript}
    debugln(['TCTConfigScriptEngine.RunExpression Atom=',GetAtom]);
    {$ENDIF}
    case AtomStart^ of
    #0:
      break;
    '(':
      begin
        if not OperandAllowed then break;
        FStack.Push(ctcssRoundBracketOpen,AtomStart);
      end;
    ')':
      begin
        ExecuteStack(5);
        if FStack.TopTyp=ctcssRoundBracketOpen then begin
          // empty ()
          AddError(Format(ctsOperandExpectedButFound2, [GetAtom]));
          Result:=false;
          break;
        end else if (FStack.TopTyp=ctcssOperand)
        and (FStack.Top>0) and (FStack.Items[FStack.Top-1].Typ=ctcssRoundBracketOpen)
        then begin
          FStack.Delete(FStack.Top-1);
        end else
          break;
      end;
    '=':
      if not PushBinaryOperator then break;
    '<':
      if (Src-AtomStart=1) or (AtomStart[1] in ['=','>']) then begin
        if not PushBinaryOperator then break;
      end else begin
        AddError(Format(ctsInvalidOperator, [GetAtom]));
        Result:=false;
        break;
      end;
    '>':
      if (Src-AtomStart=1) or (AtomStart[1] in ['=']) then begin
        if not PushBinaryOperator then break;
      end else begin
        AddError(Format(ctsInvalidOperator, [GetAtom]));
        Result:=false;
        break;
      end;
    '+':
      if (Src-AtomStart=1) then begin
        if not PushBinaryOperator then break;
      end else begin
        AddError(Format(ctsInvalidOperator, [GetAtom]));
        Result:=false;
        break;
      end;
    'a'..'z','A'..'Z':
      begin
        // a keyword or an identifier

        {$IFDEF VerboseCTCfgScript}
        debugln(['TCTConfigScriptEngine.RunExpression StackTop=',dbgs(FStack.TopTyp),' Atom=',GetAtom]);
        {$ENDIF}
        // execute
        Handled:=false;
        case UpChars[AtomStart^] of
        'A':
          if CompareIdentifiers('and',AtomStart)=0 then begin
            Handled:=true;
            if not PushBinaryOperator then break;
          end;
        'D':
          case UpChars[AtomStart[1]] of
          'E':
            if CompareIdentifiers('defined',AtomStart)=0 then begin
              Handled:=true;
              if not OperandAllowed then break;
              if not RunDefined(false) then break;
            end;
          'I':
            if CompareIdentifiers('div',AtomStart)=0 then begin
              Handled:=true;
              if not PushBinaryOperator then break;
            end;
          end;
        'E':
          case UpChars[AtomStart[1]] of
          'L':
            if CompareIdentifiers('else',AtomStart)=0 then
              break;
          'N':
            if CompareIdentifiers('end',AtomStart)=0 then
              break;
          end;
        'F':
          if CompareIdentifiers('false',AtomStart)=0 then begin
            Handled:=true;
            if not OperandAllowed then break;
            PushBooleanValue(false);
          end;
        'M':
          if CompareIdentifiers('mod',AtomStart)=0 then begin
            Handled:=true;
            if not PushBinaryOperator then break;
          end;
        'N':
          if CompareIdentifiers('not',AtomStart)=0 then begin
            Handled:=true;
            if not OperandAllowed then break;
            // Note: no execute, "not" is unary operator for the next operand
            FStack.Push(ctcssOperator,AtomStart);
          end;
        'O':
          if CompareIdentifiers('or',AtomStart)=0 then begin
            Handled:=true;
            if not PushBinaryOperator then break;
          end;
        'T':
          case UpChars[AtomStart[1]] of
          'H':
            if CompareIdentifiers('then',AtomStart)=0 then begin
              break;
            end;
          'R':
            if CompareIdentifiers('true',AtomStart)=0 then begin
              Handled:=true;
              if not OperandAllowed then break;
              PushBooleanValue(true);
            end;
          end;
        'U':
          if CompareIdentifiers('undefined',AtomStart)=0 then begin
            Handled:=true;
            if not OperandAllowed then break;
            if not RunDefined(true) then break;
          end;
        'X':
          if CompareIdentifiers('xor',AtomStart)=0 then begin
            Handled:=true;
            if not PushBinaryOperator then break;
          end;
        end;
        if (not Handled) and IsKeyWord(AtomStart) then begin
          AddError(Format(ctsUnexpectedKeyword2, [GetAtom]));
          Result:=false;
          break;
        end;
        if (not Handled) then begin
          if not OperandAllowed then break;
          {$IFDEF VerboseCTCfgScript}
          debugln(['TCTConfigScriptEngine.RunExpression ',GetAtom(AtomStart),' ',IsFunction(AtomStart)]);
          {$ENDIF}
          if IsFunction(AtomStart) then begin
            // a function
            if not RunFunction then begin
              Result:=false;
              break;
            end;
          end else begin
            // a variable
            FStack.Push(ctcssOperand,AtomStart);
            Item:=FStack.TopItem;
            v:=Variables.GetVariable(AtomStart);
            if v<>nil then begin
              SetCTCSVariableValue(v,@Item^.Operand);
            end;
          end;
          ExecuteStack(1);
        end;
      end;
    '#','''':
      begin
        if not OperandAllowed then break;
        PushStringConstant;
      end;
    '0'..'9','$','%','&':
      begin
        // float, decimal, hex, octal, binary constant
        if not OperandAllowed then break;
        PushNumberConstant;
      end;
    else
      if FStack.TopTyp in [ctcssOperator,ctcssRoundBracketOpen]
      then begin
        AddError(Format(ctsOperandExpectedButFound2, [GetAtom]));
        Result:=false;
      end;
      break;
    end;
    ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
  end;

  if Result then begin
    if not ExecuteStack(10) then
      Result:=false;
    if FStack.Top=StartTop+1 then begin
      // empty expression
      AddError(Format(ctsOperandExpectedButFound2, [GetAtom]));
    end else if (FStack.TopTyp<>ctcssOperand) or (FStack.Top<>StartTop+2) then begin
      // unfinished expression
      if FStack.TopTyp in [ctcssOperator,ctcssRoundBracketOpen]
      then
        AddError(Format(ctsOperandExpectedButFound2, [GetAtom]))
      else
        AddError(Format(ctsOperatorExpectedButFound2, [GetAtom]));
      Result:=false;
    end
    else if Result then begin
      // success
      // delete ctcssExpression and keep the operand
      FStack.Delete(FStack.Top-1);
      Item:=FStack.TopItem;
      inc(StartTop);
      {$IFDEF VerboseCTCfgScript}
      debugln(['TCTConfigScriptEngine.RunExpression Result="',dbgs(PCTCfgScriptVariable(@Item^.Operand)),'" ']);
      {$ENDIF}
    end;
  end;

  // clean up stack
  while (FStack.Top>StartTop) do FStack.Pop;
end;

function TCTConfigScriptEngine.IsKeyWord(P: PChar): boolean;
begin
  Result:=false;
  if p=nil then exit;
  case UpChars[p^] of
  'A':
    if CompareIdentifiers('and',p)=0 then exit(true);
  'B':
    if CompareIdentifiers('begin',p)=0 then exit(true);
  'C':
    if CompareIdentifiers('case',p)=0 then exit(true);
  'D':
    case UpChars[p[1]] of
    'E':
      if CompareIdentifiers('defined',p)=0 then exit(true);
    'I':
      if CompareIdentifiers('div',p)=0 then exit(true);
    end;
  'E':
    case UpChars[p[1]] of
    'L':
      if CompareIdentifiers('else',p)=0 then exit(true);
    'N':
      if CompareIdentifiers('end',p)=0 then exit(true);
    end;
  'F':
    case UpChars[p[1]] of
    'A':
      if CompareIdentifiers('false',p)=0 then exit(true);
    'U':
      if CompareIdentifiers('function',p)=0 then exit(true);
    end;
  'I':
    case UpChars[p[1]] of
    'F':
      if CompareIdentifiers('if',p)=0 then exit(true);
    'N':
      if (CompareIdentifiers('in',p)=0) then exit(true)
    end;
  'M':
    if CompareIdentifiers('mod',p)=0 then exit(true);
  'N':
    if CompareIdentifiers('not',p)=0 then exit(true);
  'O':
    case UpChars[p[1]] of
    'F':
      if CompareIdentifiers('of',p)=0 then exit(true);
    'R':
      if CompareIdentifiers('or',p)=0 then exit(true);
    end;
  'P':
    if CompareIdentifiers('procedure',p)=0 then exit(true);
  'S':
    case UpChars[p[1]] of
    'H':
      case UpChars[p[2]] of
      'L':
        if CompareIdentifiers('shl',p)=0 then exit(true);
      'R':
        if CompareIdentifiers('shr',p)=0 then exit(true);
      end;
    end;
  'T':
    case UpChars[p[1]] of
    'H':
      if CompareIdentifiers('then',p)=0 then exit(true);
    'R':
      if CompareIdentifiers('true',p)=0 then exit(true);
    end;
  'X':
    if CompareIdentifiers('xor',p)=0 then exit(true);
  'U':
    if CompareIdentifiers('undefined',p)=0 then exit(true);
  end;
end;

function TCTConfigScriptEngine.ExecuteStack(MaxLevel: integer): boolean;
{ execute all operators on stack with level <= maxlevel
}
var
  OperatorItem: PCTCfgScriptStackItem;
  Typ: TCTCfgScriptOperator;
  OperandItem: PCTCfgScriptStackItem;
  b: Boolean;
  LeftOperandItem: PCTCfgScriptStackItem;
  OperandsEqual: boolean;
  LeftIsLowerThanRight: boolean;

  procedure ErrorInvalidOperator;
  begin
    raise Exception.Create('TCTConfigScriptEngine.ExecuteStack invalid operator: '+GetAtom(OperatorItem^.StartPos));
  end;

begin
  Result:=true;
  repeat
    {$IFDEF VerboseCTCfgScript}
    WriteDebugReportStack('ExecuteStack MaxLevel='+dbgs(MaxLevel));
    {$ENDIF}
    if (FStack.TopTyp<>ctcssOperand) or (FStack.Top<=0) then
      exit;
    OperatorItem:=@FStack.Items[FStack.Top-1];
    if (OperatorItem^.Typ<>ctcssOperator)
    or (GetOperatorLevel(OperatorItem^.StartPos)>MaxLevel) then
      exit;
    OperandItem:=FStack.TopItem;

    // execute operator
    Typ:=AtomToCTCfgOperator(OperatorItem^.StartPos);
    {$IFDEF VerboseCTCfgScript}
    debugln(['TCTConfigScriptEngine.ExecuteStack execute operator "',GetAtom(OperatorItem^.StartPos),'" Typ=',dbgs(Typ)]);
    {$ENDIF}
    case Typ of

    ctcsoNot:
      begin
        b:=CTCSVariableIsTrue(@OperandItem^.Operand);
        FStack.Pop(2);
        PushBooleanValue(not b);
      end;

    ctcsoAnd,ctcsoOr,ctcsoXOr:
      begin
        b:=CTCSVariableIsTrue(@OperandItem^.Operand);
        FStack.Pop(2);
        if (FStack.Top>=0) then begin
          OperandItem:=FStack.TopItem;
          case Typ of
          ctcsoAnd: b:=b and CTCSVariableIsTrue(@OperandItem^.Operand);
          ctcsoOr:  b:=b or CTCSVariableIsTrue(@OperandItem^.Operand);
          ctcsoXOr: b:=b xor CTCSVariableIsTrue(@OperandItem^.Operand);
          end;
          FStack.Pop;
        end;
        PushBooleanValue(b);
      end;

    ctcsoEqual, ctcsoNotEqual, ctcsoLowerThan, ctcsoLowerOrEqualThan,
    ctcsoGreaterThan, ctcsoGreaterOrEqualThan:
      begin
        b:=false;
        if (FStack.Top>=2) then begin
          LeftOperandItem:=@FStack.Items[FStack.Top-2];
          if not CompareCTCSVariables(@LeftOperandItem^.Operand,@OperandItem^.Operand,
                               OperandsEqual,LeftIsLowerThanRight)
          then begin
            b:=false;
          end else begin
            case Typ of
            ctcsoEqual:
              b:=OperandsEqual;
            ctcsoNotEqual:
              b:=not OperandsEqual;
            ctcsoLowerThan:
              b:=(not OperandsEqual) and LeftIsLowerThanRight;
            ctcsoLowerOrEqualThan:
              b:=OperandsEqual or LeftIsLowerThanRight;
            ctcsoGreaterThan:
              b:=(not OperandsEqual) and not LeftIsLowerThanRight;
            ctcsoGreaterOrEqualThan:
              b:=OperandsEqual or not LeftIsLowerThanRight;
            end;
          end;
          {$IFDEF VerboseCTCfgScript}
          debugln(['TCTConfigScriptEngine.ExecuteStack ',GetCTCSVariableAsString(@LeftOperandItem^.Operand),' ',GetCTCSVariableAsString(@OperandItem^.Operand),' Equal=',OperandsEqual,' LT=',LeftIsLowerThanRight,' Result=',Result]);
          {$ENDIF}
          FStack.Pop(3);
        end else begin
          FStack.Pop(2);
        end;
        PushBooleanValue(b);
      end;

    ctcsoPlus:
      begin
        if (FStack.Top>=2) then begin
          LeftOperandItem:=@FStack.Items[FStack.Top-2];
          // add right operand to left operand on stack
          AddCTCSVariables(@OperandItem^.Operand,@LeftOperandItem^.Operand);
          // remove right operand and +
          FStack.Pop(2);
        end else begin
          // unary operator
          // just remove the +
          FStack.Delete(FStack.Top-1);
        end;
      end;

    else
      ErrorInvalidOperator;
    end;
  until false;
end;

function TCTConfigScriptEngine.GetOperatorLevel(P: PChar): integer;
begin
  Result:=CTCfgScriptOperatorLvl[AtomToCTCfgOperator(P)];
end;

function TCTConfigScriptEngine.IsFunction(p: PChar): boolean;
begin
  Result:=false;
  if (p=nil) or (not IsIdentStartChar[p^]) then exit;
  case UpChars[p^] of
  'I':
    if (CompareIdentifiers(p,'integer')=0)
    or (CompareIdentifiers(p,'int64')=0)
    then exit(true);
  'S':
    if CompareIdentifiers(p,'string')=0 then exit(true);
  end;
  Result:=IsCustomFunction(p);
end;

function TCTConfigScriptEngine.IsCustomFunction(FunctionName: PChar): boolean;
begin
  Result:=false;
end;

procedure TCTConfigScriptEngine.RunCustomSimpleFunction(FunctionName: PChar;
  Value: PCTCfgScriptVariable);
begin

end;

constructor TCTConfigScriptEngine.Create;
begin
  FVariables:=TCTCfgScriptVariables.Create;
  FStack:=TCTCfgScriptStack.Create;
  FErrors:=TFPList.Create;
end;

destructor TCTConfigScriptEngine.Destroy;
begin
  ClearErrors;
  FreeAndNil(FErrors);
  FreeAndNil(FVariables);
  FreeAndNil(FStack);
  inherited Destroy;
end;

procedure TCTConfigScriptEngine.ClearErrors;
var
  i: Integer;
begin
  for i:=0 to FErrors.Count-1 do
    TObject(FErrors[i]).Free;
  FErrors.Clear;
end;

function TCTConfigScriptEngine.Execute(const Source: string;
  StopAfterErrors: integer): boolean;

  procedure ExpectedSemicolon;
  begin
    AddError(Format(ctsExpectedSemicolonOfStatementButFound, [GetAtomOrNothing]))
  end;

var
  Err: TCTCfgScriptError;
begin
  FStack.Clear;
  ClearErrors;
  MaxErrorCount:=StopAfterErrors;
  SrcStart:=#0;
  SrcEnd:=SrcStart;
  Src:=SrcStart;
  AtomStart:=SrcStart;

  if Source='' then exit(true);

  SrcStart:=PChar(Source);
  SrcEnd:=SrcStart+length(Source);
  Src:=SrcStart;
  AtomStart:=Src;

  try
    // execute all statements
    ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
    while Src^<>#0 do begin
      RunStatement(false);
      if not (AtomStart^ in [#0,';']) then
        ExpectedSemicolon;
      ReadRawNextPascalAtom(Src,AtomStart,nil,false,true);
    end;
  except
    on E: Exception do begin
      // too many errors
      if ErrorCount=0 then begin
        Err:=TCTCfgScriptError.Create(E.Message);
        FErrors.Add(Err);
      end;
    end;
  end;
  Result:=ErrorCount=0;
end;

function TCTConfigScriptEngine.ErrorCount: integer;
begin
  Result:=FErrors.Count;
end;

function TCTConfigScriptEngine.GetAtom: string;
begin
  if (AtomStart=nil) or (AtomStart>Src) then
    exit('');
  SetLength(Result,Src-AtomStart);
  if Result<>'' then
    System.Move(AtomStart^,Result[1],length(Result));
end;

function TCTConfigScriptEngine.GetAtomOrNothing: string;
begin
  if (AtomStart=nil) or (AtomStart>Src) then
    Result:='nothing'
  else begin
    SetLength(Result,Src-AtomStart);
    if Result<>'' then
      System.Move(AtomStart^,Result[1],length(Result));
  end;
end;

function TCTConfigScriptEngine.GetAtom(P: PChar): string;
var
  StartPos: PChar;
begin
  if P=nil then
    exit('');
  ReadRawNextPascalAtom(P,StartPos,nil,false,true);
  SetLength(Result,p-StartPos);
  if Result<>'' then
    System.Move(StartPos^,Result[1],length(Result));
end;

function TCTConfigScriptEngine.PosToLineCol(p: PChar; out Line, Column: integer
  ): boolean;
var
  run: PChar;
begin
  Line:=1;
  Column:=1;
  if (p<SrcStart) or (p>SrcEnd) then exit(false);
  run:=SrcStart;
  while run<p do begin
    if Run^ in [#10,#13] then begin
      inc(Line);
      Column:=1;
      if (Run[1] in [#10,#13]) and (Run^<>Run[1]) then
        inc(Run,2)
      else
        inc(Run);
    end else begin
      inc(Run);
      inc(Column);
    end;
  end;
  Result:=true;
end;

function TCTConfigScriptEngine.PosToStr(p: PChar): string;
var
  Line: integer;
  Column: integer;
begin
  if PosToLineCol(p,Line,Column) then
    Result:='('+IntToStr(Line)+','+IntToStr(Column)+')'
  else
    Result:='';
end;

function TCTConfigScriptEngine.GetErrorStr(Index: integer): string;
var
  Err: TCTCfgScriptError;
begin
  Err:=Errors[Index];
  Result:='Error: ';
  if Err.Line>0 then
    Result:=Result+'('+IntToStr(Err.Line)+','+IntToStr(Err.Column)+') ';
  Result:=Result+Err.Msg;
end;

procedure TCTConfigScriptEngine.WriteDebugReportStack(Title: string);
var
  i: Integer;
  Item: PCTCfgScriptStackItem;
begin
  debugln(['TCTConfigScriptEngine.WriteDebugReportStack FStack.Top=',FStack.Top,' ',Title]);
  for i:=0 to FStack.Top do begin
    dbgout(GetIndentStr(i*2+2));
    Item:=@FStack.Items[i];
    dbgout(dbgs(Item^.Typ),' StartPos=',GetAtom(Item^.StartPos));
    if Item^.Typ=ctcssOperator then
      dbgout(' level='+dbgs(GetOperatorLevel(Item^.StartPos)));
    if Item^.Typ=ctcssOperand then
      dbgout(' ',dbgs(PCTCfgScriptVariable(@Item^.Operand)));
    debugln;
  end;
end;

{ TCTCfgScriptStack }

constructor TCTCfgScriptStack.Create;
begin
  Top:=-1;
end;

destructor TCTCfgScriptStack.Destroy;
begin
  Clear;
  inherited Destroy;
end;

procedure TCTCfgScriptStack.Clear;
var
  i: Integer;
  Item: PCTCfgScriptStackItem;
begin
  for i:=0 to Top do begin
    Item:=@Items[i];
    ClearCTCSVariable(@Item^.Operand);
    if Item^.Operand.Name<>nil then
      ReAllocMem(Item^.Operand.Name,0);
  end;
  Top:=-1;
  TopTyp:=ctcssNone;
  Capacity:=0;
  ReAllocMem(Items,0);
end;

procedure TCTCfgScriptStack.Push(Typ: TCTCfgScriptStackItemType;
  const StartPos: PChar);
var
  OldCapacity: LongInt;
  Item: PCTCfgScriptStackItem;
begin
  inc(Top);
  if Top>=Capacity then begin
    OldCapacity:=Capacity;
    if Capacity<10 then
      Capacity:=10
    else
      Capacity:=Capacity*2;
    ReAllocMem(Items,Capacity*SizeOf(TCTCfgScriptStackItem));
    FillByte(Items[OldCapacity],(Capacity-OldCapacity)*SizeOf(TCTCfgScriptStackItem),0);
  end;
  Item:=@Items[Top];
  Item^.Typ:=Typ;
  Item^.StartPos:=StartPos;
  TopTyp:=Typ;
  {$IFDEF CheckCTCfgVars}
  CheckOperands;
  {$ENDIF}
end;

procedure TCTCfgScriptStack.Pop(Count: integer);

  procedure RaiseTooManyPop;
  begin
    raise Exception.Create('TCTCfgScriptStack.Pop too many pop');
  end;

var
  Item: PCTCfgScriptStackItem;
begin
  if Top<Count-1 then
    RaiseTooManyPop;
  while Count>0 do begin
    Item:=@Items[Top];
    ClearCTCSVariable(@Item^.Operand);
    if Item^.Operand.Name<>nil then
      ReAllocMem(Item^.Operand.Name,0);
    dec(Top);
    if Top>=0 then
      TopTyp:=Items[Top].Typ
    else
      TopTyp:=ctcssNone;
    dec(Count);
  end;
  {$IFDEF CheckCTCfgVars}
  CheckOperands;
  {$ENDIF}
end;

procedure TCTCfgScriptStack.Delete(Index: integer);
var
  Item: PCTCfgScriptStackItem;
begin
  if (Index<0) or (Index>Top) then exit;
  Item:=@Items[Index];
  ClearCTCSVariable(@Item^.Operand);
  if Item^.Operand.Name<>nil then
    ReAllocMem(Item^.Operand.Name,0);
  if Index<Top then begin
    System.Move(Items[Index+1],Items[Index],SizeOf(TCTCfgScriptStackItem)*(Top-Index));
    Item:=@Items[Top];
    Item^.Typ:=ctcssNone;
    FillByte(Item^.Operand,SizeOf(Item^.Operand),0);
  end;
  dec(Top);
  {$IFDEF CheckCTCfgVars}
  CheckOperands;
  {$ENDIF}
end;

function TCTCfgScriptStack.TopItem: PCTCfgScriptStackItem;
begin
  if Top<0 then
    Result:=nil
  else
    Result:=@Items[Top];
end;

function TCTCfgScriptStack.TopItemOperand: PCTCfgScriptVariable;
begin
  if Top<0 then
    Result:=nil
  else
    Result:=@Items[Top].Operand;
end;

{$IFDEF CheckCTCfgVars}
procedure TCTCfgScriptStack.CheckOperands;
var
  i: Integer;
begin
  for i:=0 to Top do
    CheckCTCSVariable(@Items[Top].Operand);
end;
{$ENDIF}

{ TCTCfgScriptError }

constructor TCTCfgScriptError.Create(const aMsg: string; aPos, aLine,
  aCol: integer);
begin
  Msg:=aMsg;
  Position:=aPos;
  Line:=aLine;
  Column:=aCol;
end;

constructor TCTCfgScriptError.Create(const aMsg: string);
begin
  Msg:=aMsg;
  Position:=-1;
  Line:=0;
  Column:=0;
end;

end.

