{ *************************************************************************** * * * 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 . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, 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, TypInfo, AVL_Tree, // Codetools BasicCodeTools, KeywordFuncLists, FileProcs, CodeToolsStrConsts; type ECodeToolCfgScript = class(Exception); 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 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 (CompareIdentifiers(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 A0 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]': 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 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 ECodeToolCfgScript.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 ECodeToolCfgScript.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 (pSrcEnd) then exit(false); run:=SrcStart; while 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 ECodeToolCfgScript.Create('TCTCfgScriptStack.Pop too many pop'); end; var Item: PCTCfgScriptStackItem; begin if Top0 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