{ /*************************************************************************** * * * 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. * * * ***************************************************************************/ Author: Mattias Gaertner Abstract: Defines class TExpressionEvaluator Used by Code Tools for compiler directives. For example $IF expression. This class stores variables (case sensitive) of type string. Boolean values are '0' for false and true else (except empty '' which is invalid). The function Eval evaluates expressions and understands the operators AND, OR, XOR, NOT, (, ), =, <, >, <=, >=, <> defined() } unit ExprEval; {$ifdef FPC}{$mode objfpc}{$endif}{$H+} {$I codetools.inc} interface uses {$IFDEF MEM_CHECK} MemCheck, {$ENDIF} Classes, SysUtils, KeyWordFuncLists, FileProcs; const ExternalMacroStart = '#'; type TOnValuesChanged = procedure of object; TOnGetSameString = procedure(var s: string) of object; ArrayOfAnsiString = ^AnsiString; { TExpressionEvaluator } TExpressionEvaluator = class private FChangeStamp: integer; FNames, FValues: ArrayOfAnsiString; // always sorted in FNames and FNames uppercase FCount: integer; FCapacity: integer; Expr: string; CurPos, Max, AtomStart, AtomEnd, PriorAtomStart, ErrorPos: integer; FOnChange: TOnValuesChanged; function ReadTilEndBracket:boolean; function CompAtom(const UpperCaseTag:string): boolean; function ReadNextAtom:boolean; function EvalAtPos:string; function CompareValues(const v1, v2: string): integer; function GetVariables(const Name: string): string; procedure SetVariables(const Name: string; const Value: string); function IndexOfName(const VarName: string; InsertPos: boolean): integer; procedure Expand; public property Variables[const Name: string]: string read GetVariables write SetVariables; default; property Count: integer read FCount; procedure Undefine(const Name: string); function IsDefined(const Name: string): boolean; function Equals(AnExpressionEvaluator: TExpressionEvaluator): boolean; reintroduce; procedure Assign(SourceExpressionEvaluator: TExpressionEvaluator); procedure AssignTo(SL: TStringList); function Eval(const Expression: string):string; property ErrorPosition:integer read ErrorPos; property OnChange: TOnValuesChanged read FOnChange write FOnChange; function Items(Index: integer): string; function Names(Index: integer): string; function Values(Index: integer): string; procedure Append(const Variable, Value: string); procedure Prepend(const Variable, Value: string); procedure Clear; function AsString: string; constructor Create; destructor Destroy; override; procedure RemoveDoubles(OnGetSameString: TOnGetSameString); procedure ConsistencyCheck; procedure WriteDebugReport; function CalcMemSize(WithNamesAndValues: boolean = true; Original: TExpressionEvaluator = nil): PtrUInt; property ChangeStamp: integer read FChangeStamp; procedure IncreaseChangeStamp; inline; end; { TExpressionSolver Checks if expression is always true 1, always false 0, or something } TExpressionSolver = class public //Defines: TStringToStringTree; //Undefines: TStringToStringTree; ErrorMsg: string; // last error message ErrorPos: integer;// last error position constructor Create; destructor Destroy; override; function Solve(const Expr: string; out ExprResult: string): boolean; function Solve(const Src: string; StartPos, EndPos: integer; out ExprResult: string): boolean; end; implementation var IsWordChar, IsIdentifierChar, IsNumberBeginChar, IsNumberChar: array[#0..#255] of boolean; procedure InternalInit; var c:char; begin for c:=#0 to #255 do begin IsWordChar[c]:=(c in ['a'..'z','A'..'Z','_']); IsNumberBeginChar[c]:=(c in ['0'..'9','$','%']); IsNumberChar[c]:=(c in ['0'..'9','.','E','e']); IsIdentifierChar[c]:=(c in ['a'..'z','A'..'Z','_','0'..'9']); end; end; { TBooleanVariables } procedure TExpressionEvaluator.Clear; var i: integer; begin if FCount=0 then exit; for i:=0 to FCount-1 do begin FNames[i]:=''; FValues[i]:=''; end; FCount:=0; if FNames<>nil then begin FreeMem(FNames); FNames:=nil; end; if FValues<>nil then begin FreeMem(FValues); FValues:=nil; end; FCapacity:=0; IncreaseChangeStamp; end; function TExpressionEvaluator.CompareValues(const v1, v2: string): integer; // -1 : v1v2 var len1,len2,a:integer; c1: Char; c2: Char; ValPos1: Integer; ValPos2: Integer; begin len1:=length(v1); len2:=length(v2); ValPos1:=1; ValPos2:=1; if (len1>1) and (v1[ValPos1]='''') then begin inc(ValPos1); dec(Len1,2); end; if (len2>1) and (v2[ValPos2]='''') then begin inc(ValPos2); dec(Len2,2); end; if len1len2 then Result:=1 else begin for a:=1 to len1 do begin c1:=v1[ValPos1]; c2:=v2[ValPos2]; if c1c2 then begin Result:=1; exit; end; inc(ValPos1); inc(ValPos2); end; Result:=0; end; end; function TExpressionEvaluator.CompAtom( const UpperCaseTag: string): boolean; // compare uppercase tag with case insensitive atom var a,len:integer; begin if (AtomEnd>Max+1) then begin Result:=false; exit; end; len:=AtomEnd-AtomStart; if length(UpperCaseTag)<>len then begin Result:=false; exit; end; for a:=1 to len do begin if (UpChars[Expr[AtomStart+a-1]]<>UpperCaseTag[a]) then begin Result:=false; exit; end; end; Result:=true; end; constructor TExpressionEvaluator.Create; begin inherited Create; FValues:=nil; FNames:=nil; FCount:=0; end; destructor TExpressionEvaluator.Destroy; begin Clear; inherited Destroy; end; procedure TExpressionEvaluator.RemoveDoubles(OnGetSameString: TOnGetSameString); var i: Integer; begin for i:=0 to FCount-1 do begin OnGetSameString(FNames[i]); OnGetSameString(FValues[i]); end; end; function TExpressionEvaluator.Eval(const Expression: string): string; // 1 = true // 0 = syntax error // -1 = false var s:string; begin Expr:=Expression; Max:=length(expr); CurPos:=1; AtomStart:=-1; AtomEnd:=-1; PriorAtomStart:=-1; ErrorPos:=-1; s:=EvalAtPos; if ErrorPos>=0 then begin // error Result:=''; exit; end; Result:=s; end; function TExpressionEvaluator.Items(Index: integer): string; begin Result:=FNames[Index]+'='+FValues[Index]; end; function TExpressionEvaluator.Names(Index: integer): string; begin Result:=FNames[Index]; end; function TExpressionEvaluator.Values(Index: integer): string; begin Result:=FValues[Index]; end; procedure TExpressionEvaluator.Append(const Variable, Value: string); begin Variables[Variable]:=Variables[Variable]+Value; end; procedure TExpressionEvaluator.Prepend(const Variable, Value: string); begin Variables[Variable]:=Value+Variables[Variable]; end; function TExpressionEvaluator.EvalAtPos: string; var r: string; // current result c,o1,o2: char; OldPos: integer; AtomCount: Integer; HasBracket: Boolean; begin Result:=''; AtomCount:=0; repeat if (not ReadNextAtom) then exit; inc(AtomCount); c:=Expr[AtomStart]; if IsWordChar[c] then begin // identifier or keyword if (CompAtom('AND')) then begin if (Result='') then ErrorPos:=CurPos else if (Result<>'0') then begin // true AND ... Result:=EvalAtPos(); if ErrorPos>=0 then exit; if (Result='') then ErrorPos:=CurPos; end; exit; end else if (CompAtom('OR')) then begin if (Result='0') then begin // false OR ... Result:=EvalAtPos(); if ErrorPos>=0 then exit; if (Result='') then ErrorPos:=CurPos; end else if (AtomCount<=1) then ErrorPos:=CurPos; exit; end else if (CompAtom('XOR')) then begin if (Result='') then begin ErrorPos:=CurPos; exit; end; r:=Result; // true/false XOR ... Result:=EvalAtPos(); if ErrorPos>=0 then exit; if (Result='') then begin ErrorPos:=CurPos; exit; end; if (r='0') then begin if (Result='0') then Result:='0' else Result:='1'; end else begin if (Result='0') then Result:='1' else Result:='0'; end; exit; end else if (CompAtom('NOT')) then begin Result:=EvalAtPos(); if ErrorPos>=0 then exit; // Note: for Delphi compatibility: "IF not UndefinedVariable" is valid if (Result='0') then Result:='1' else Result:='0'; exit; end else if (CompAtom('DEFINED')) then begin // read DEFINED(identifier) or defined identifier if (Result<>'') or (not ReadNextAtom) then begin ErrorPos:=CurPos; exit; end; HasBracket:=CompAtom('('); if HasBracket and (not ReadNextAtom) then begin ErrorPos:=CurPos; exit; end; if IsDefined(copy(Expr,AtomStart,AtomEnd-AtomStart)) then Result:='1' else Result:='0'; if HasBracket then begin if (not ReadNextAtom) or (not CompAtom(')')) then begin ErrorPos:=CurPos; exit; end; end; end else if (CompAtom('DECLARED')) then begin // read DECLARED(identifier) if (Result<>'') or (not ReadNextAtom) or (CompAtom('(')=false) or (not ReadNextAtom) then begin ErrorPos:=CurPos; exit; end; if CompAtom('UNICODESTRING') then begin if IsDefined('FPC_HAS_UNICODESTRING') then Result:='1' else Result:='0'; end else begin Result:='0';// this can only be answered by a real compiler end; if (not ReadNextAtom) or (not CompAtom(')')) then begin ErrorPos:=CurPos; exit; end; end else if (CompAtom('UNDEFINED')) then begin // read UNDEFINED(identifier) or undefined identifier if (Result<>'') or (not ReadNextAtom) then begin ErrorPos:=CurPos; exit; end; HasBracket:=CompAtom('('); if HasBracket and (not ReadNextAtom) then begin ErrorPos:=CurPos; exit; end; Result:=Variables[copy(Expr,AtomStart,AtomEnd-AtomStart)]; if Result<>'' then Result:='0' else Result:='1'; if HasBracket then begin if (not ReadNextAtom) or (not CompAtom(')')) then begin ErrorPos:=CurPos; exit; end; end; end else begin // Identifier if (Result<>'') then begin ErrorPos:=CurPos; exit; end else Result:=Variables[copy(Expr,AtomStart,AtomEnd-AtomStart)]; end; end else if IsNumberBeginChar[c] then begin // number if (Result<>'') then begin ErrorPos:=CurPos; exit; end else Result:=copy(Expr,AtomStart,AtomEnd-AtomStart); end else if c='''' then begin Result:=copy(Expr,AtomStart+1,AtomEnd-AtomStart-2); end else begin // operator case c of ')':exit; '(':begin OldPos:=AtomStart; // eval in brackets Result:=EvalAtPos(); if ErrorPos>=0 then exit; // go behind brackets CurPos:=OldPos; if (not ReadTilEndBracket) then exit; inc(CurPos); end; '=','>','<':begin o1:=c; if AtomEnd=AtomStart+1 then begin r:=EvalAtPos(); if ErrorPos>=0 then exit; case o1 of '=':if CompareValues(Result,r)=0 then Result:='1' else Result:='0'; '>':if CompareValues(Result,r)=1 then Result:='1' else Result:='0'; '<':if CompareValues(Result,r)=-1 then Result:='1' else Result:='0'; end; end else begin o2:=Expr[AtomStart+1]; r:=EvalAtPos(); if ErrorPos>=0 then exit; if o1='<' then begin if o2='>' then begin if CompareValues(Result,r)<>0 then Result:='1' else Result:='0'; end else if o2='=' then begin if CompareValues(Result,r)<=0 then Result:='1' else Result:='0'; end else ErrorPos:=AtomStart; end else if o1='>' then begin if o2='=' then begin if CompareValues(Result,r)>=0 then Result:='1' else Result:='0'; end else ErrorPos:=AtomStart; end else ErrorPos:=AtomStart; end; exit; end; '!': begin Result:=EvalAtPos(); if ErrorPos>=0 then exit; if (Result='0') then Result:='1' else if (Result='') then ErrorPos:=CurPos else Result:='0'; exit; end; else begin ErrorPos:=CurPos; end; end; end; until (ErrorPos>=0); end; procedure TExpressionEvaluator.Expand; var NewSize: integer; begin FCapacity:=(FCapacity shl 1)+10; NewSize:=SizeOf(AnsiString)*FCapacity; ReAllocMem(FValues,NewSize); ReAllocMem(FNames,NewSize); end; function TExpressionEvaluator.IndexOfName( const VarName: string; InsertPos: boolean): integer; var l,r,m, cmp: integer; begin if FCount=0 then begin if InsertPos then Result:=0 else Result:=-1; exit; end; l:=0; r:=FCount-1; m:=0; while l<=r do begin m:=(l+r) shr 1; cmp:=CompareText(VarName,FNames[m]); if cmp>0 then l:=m+1 else if cmp<0 then r:=m-1 else begin Result:=m; exit; end; end; if InsertPos then begin if CompareText(VarName,FNames[m])>0 then inc(m); Result:=m; end else begin Result:=-1; end; end; function TExpressionEvaluator.GetVariables(const Name: string): string; var i: integer; begin i:=IndexOfName(Name,false); if (i>=0) then Result:=FValues[i] else Result:=''; end; function TExpressionEvaluator.IsDefined(const Name: string): boolean; var i: integer; begin i:=IndexOfName(Name,false); Result:=(i>=0); end; function TExpressionEvaluator.ReadNextAtom: boolean; var c,o1,o2:char; begin PriorAtomStart:=AtomStart; while (CurPos<=Max) do begin c:=Expr[CurPos]; if (c<=' ') then inc(CurPos) else if IsWordChar[c] then begin // Identifier AtomStart:=CurPos; repeat inc(CurPos); until (CurPos>Max) or (not IsIdentifierChar[Expr[CurPos]]); AtomEnd:=CurPos; Result:=true; exit; end else if IsNumberBeginChar[c] then begin // Number AtomStart:=CurPos; repeat inc(CurPos); until (CurPos>Max) or (IsNumberChar[Expr[CurPos]]=false); AtomEnd:=CurPos; Result:=true; exit; end else if c='''' then begin // string AtomStart:=CurPos; repeat inc(CurPos); if Expr[CurPos]='''' then begin inc(CurPos); AtomEnd:=CurPos; Result:=true; exit; end; if CurPos>Max then begin AtomEnd:=CurPos; Result:=false; exit; end; until (CurPos>Max); end else begin // Symbol AtomStart:=CurPos; inc(CurPos); if (CurPos<=Max) then begin o1:=c; o2:=Expr[CurPos]; if ((o2='=') and ((o1='<') or (o1='>'))) or ((o1='<') and (o2='>')) then inc(CurPos); end; AtomEnd:=CurPos; Result:=true; exit; end; end; Result:=false; end; function TExpressionEvaluator.ReadTilEndBracket: boolean; // true = end bracket found // false = not found var lvl:integer; begin lvl:=0; while (CurPos<=Max) do begin if (Expr[CurPos]='(') then inc(lvl) else if (Expr[CurPos]=')') then begin dec(lvl); if (lvl=0) then begin Result:=true; exit; end else if (lvl<0) then begin ErrorPos:=CurPos; Result:=true; exit; end; end; inc(CurPos); end; Result:=false; end; procedure TExpressionEvaluator.Assign( SourceExpressionEvaluator: TExpressionEvaluator); var i, Size: integer; begin Clear; if SourceExpressionEvaluator<>nil then begin FCount:=SourceExpressionEvaluator.Count; Size:=SizeOf(AnsiString) * FCount; if Size>0 then begin GetMem(FNames,Size); FillByte(Pointer(FNames)^,Size,0); GetMem(FValues,Size); FillByte(Pointer(FValues)^,Size,0); FCapacity:=FCount; for i:=0 to FCount-1 do begin FNames[i]:=SourceExpressionEvaluator.FNames[i]; FValues[i]:=SourceExpressionEvaluator.FValues[i]; end; end; IncreaseChangeStamp; end; if Assigned(FOnChange) then FOnChange; end; procedure TExpressionEvaluator.SetVariables(const Name: string; const Value: string); var i: integer; Size: Integer; begin i:=IndexOfName(Name,true); if (i>=0) and (i replace value if FValues[i]<>Value then begin FValues[i]:=Value; IncreaseChangeStamp; end; end else begin // new variable if FCount=FCapacity then Expand; if i<0 then i:=0; if i=0) then begin FNames[i]:=''; FValues[i]:=''; dec(FCount); if FCount>i then begin Size:=SizeOf(AnsiString)*(FCount-i); System.Move(PPointer(FNames)[i+1],PPointer(FNames)[i],Size); System.Move(PPointer(FValues)[i+1],PPointer(FValues)[i],Size); end; end; end; function TExpressionEvaluator.Equals( AnExpressionEvaluator: TExpressionEvaluator): boolean; var i: integer; begin if (AnExpressionEvaluator=nil) or (AnExpressionEvaluator.Count<>FCount) then begin Result:=false; exit; end; for i:=0 to FCount-1 do begin if (FNames[i]<>AnExpressionEvaluator.FNames[i]) or (FValues[i]<>AnExpressionEvaluator.FValues[i]) then begin Result:=false; exit; end; end; Result:=true; end; procedure TExpressionEvaluator.AssignTo(SL: TStringList); var i: integer; begin if SL=nil then exit; SL.Clear; for i:=0 to FCount-1 do SL.Add(FNames[i]+'='+FValues[i]); end; function TExpressionEvaluator.AsString: string; var TxtLen, i, p: integer; begin TxtLen:=FCount*3; for i:=0 to FCount-1 do inc(TxtLen,length(FNames[i])+length(FValues[i])); Setlength(Result,TxtLen); p:=1; for i:=0 to FCount-1 do begin Move(FNames[i][1],Result[p],length(FNames[i])); inc(p,length(FNames[i])); Result[p]:=' '; inc(p); if length(FValues[i])>0 then begin Move(FValues[i][1],Result[p],length(FValues[i])); inc(p,length(FValues[i])); end; Result[p]:=#13; inc(p); Result[p]:=#10; inc(p); end; end; procedure TExpressionEvaluator.ConsistencyCheck; // 0 = ok var i: integer; begin if FCapacity<0 then RaiseCatchableException(''); if FCapacitynil) then RaiseCatchableException(''); if (FNames=nil) xor (FValues=nil) then RaiseCatchableException(''); for i:=0 to FCount-1 do begin if not IsUpperCaseStr(FNames[i]) then RaiseCatchableException(''); if (i>0) and (FNames[i-1]=FNames[i]) then RaiseCatchableException(''); if (i>0) and (CompareText(FNames[i-1],FNames[i])>0) then RaiseCatchableException(''); end; end; procedure TExpressionEvaluator.WriteDebugReport; begin DebugLn('[TExpressionEvaluator.WriteDebugReport] '); ConsistencyCheck; end; function TExpressionEvaluator.CalcMemSize(WithNamesAndValues: boolean; Original: TExpressionEvaluator): PtrUInt; var i: Integer; j: LongInt; begin Result:=PtrUInt(InstanceSize) +MemSizeString(Expr) +SizeOf(Pointer)*PtrUInt(FCount)*2; if WithNamesAndValues then begin for i:=0 to FCount-1 do begin if Original<>nil then begin j:=Original.IndexOfName(FNames[i],false); if j>=0 then begin if Pointer(FNames[i])=Pointer(Original.FNames[j]) then continue; end; end; inc(Result,MemSizeString(FNames[i])); inc(Result,MemSizeString(FValues[i])); end; end; end; procedure TExpressionEvaluator.IncreaseChangeStamp; begin if FChangeStamp '' true = nonzero, false = zero defined(name) sizeof(type) unary operators: not, ! binary operators: = <> >= <= > < and or xor shl shr round brackets () } var AtomStart: LongInt; SrcPos: LongInt; function AtomIs(const s: shortstring): boolean; var len: Integer; i: Integer; begin len:=length(s); if (len<>SrcPos-AtomStart) then exit(false); if SrcPos>EndPos then exit(false); for i:=1 to len do if Src[AtomStart+i-1]<>s[i] then exit(false); Result:=true; end; begin if StartPos>=EndPos then begin ExprResult:=''; exit(true); end; SrcPos:=StartPos; AtomStart:=SrcPos; //ReadNextCAtom(Source,SrcPos,AtomStart); if AtomIs('!') then begin end; end; initialization InternalInit; end.