Mega Code Archive

 
Categories / Delphi / Strings
 

Evaluating an expression in a string

Title: Evaluating an expression in a string Question: I need to resolve expressions like "a + 10 * (25 / b)" for a numeric result, or "(a = b) & (a 0)" Answer: Ever needed to add some scripting functionallity to your delphi application? For example, you could add a condition attribute to an xml tag and interpret if the data the tag represents should be used in some way. Here is a complete unit that takes an infix notation, translates it into a postfix notation and calculates and returns the result. This can be used to write your own little calculator as well. The code even supports the use of variables and parenthesis in the infix notation. Example: "(MYVAR * 2) / 5" or "(MYVAR = 10)|((MYVAR = 50) & (MYVAR = 100))" Just use the following function in your code: function EvaluateExpression(AInfix : string; out ResultType : TEvalResultType; out AResult : integer; AVars : IEvalVariableList = nil) : boolean; IEvalVariableList is an interface that you can implement in your own variable classes. There is however a ready class to handle variables "TEvalVariables". Enjoy! Let me know if you make the code better in any way. ------------------ unit EvalExpressions; // // Condition evaluator v1.0 // Written by Magnus Flysj 2008 // magnus@flysjo.com // interface uses Classes, SysUtils, Windows; type TEvalTokenType = (ttNone,ttOperator,ttOperand); TEvalOperator = (oNONE,oLP,oRP,oOR,oXOR,oAND,oEQ,oNEQ, oGT,oLT,oGET,oLET,oADD,oSUB,oDIV,oMUL); TEvalOperandType = (otUnknown,otConst,otVar); TEvalResultType = (rtBool,rtOrdinal); IEvalVariableList = interface ['{A7244BC1-BEB9-4CAB-8F98-C287ECCE71F5}'] function HasVariable(AVarName : string) : boolean; function GetValue(AVarName : string) : integer; procedure SetValue(AVarName : string; AValue : integer); end; TEvalVarItem = class(TCollectionItem) private FVarName : string; FVarValue : integer; public property VarName : string read FVarName write FVarName; property VarValue : integer read FVarValue write FVarValue; end; TEvalVariables = class(TCollection,IEvalVariableList) constructor Create; private function GetVarItem(AIdx : integer) : TEvalVarItem; function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; public function FindVarItemByName(AVarName : string) : TEvalVarItem; function HasVariable(AVarName : string) : boolean; function GetValue(AVarName : string) : integer; procedure SetValue(AVarName : string; AValue : integer); property VarItem[AIdx : integer] : TEvalVarItem read GetVarItem; property Variable[AVarName : string] : integer read GetValue write SetValue; default; end; // Base class for tokens TEvalToken = class(TCollectionItem) private function GetTokenType : TEvalTokenType; virtual; abstract; public function ToString : string; virtual; abstract; property TokenType : TEvalTokenType read GetTokenType; end; // Used by operators TEvalTokenOperator = class(TEvalToken) private FOperator : TEvalOperator; function GetTokenType : TEvalTokenType; override; public function ToString : string; override; property Operator : TEvalOperator read FOperator write FOperator; end; // Operand base class TEvalTokenOperand = class(TEvalToken) private function GetTokenType : TEvalTokenType; override; function GetOperandType : TEvalOperandType; virtual; abstract; public function IsConst : boolean; function IsVar : boolean; end; // Used by constant operands TEvalTokenConstOperand = class(TEvalTokenOperand) private FConstVal : integer; function GetOperandType : TEvalOperandType; override; public function ToString : string; override; property ConstVal : integer read FConstVal write FConstVal; end; // Used by variable operands TEvalTokenVarOperand = class(TEvalTokenOperand) private FVarName : string; function GetOperandType : TEvalOperandType; override; public function ToString : string; override; property VarName : string read FVarName write FVarName; end; TEvalExpression = class(TCollection) constructor Create; private function GetToken(AIdx : integer) : TEvalToken; function AddOperator(AOperator : TEvalOperator) : TEvalTokenOperator; function AddOperand(AOperand : string) : TEvalTokenOperand; public function ToString : string; function ParseInfix(AIn : string) : boolean; function Evaluate(out ResultType : TEvalResultType; out AResult : integer; AVars : IEvalVariableList = nil) : boolean; property Token[AIdx : integer] : TEvalToken read GetToken; default; end; function InfixToPostfixNotation(AInfix : string) : string; function EvaluateExpression(AInfix : string; out ResultType : TEvalResultType; out AResult : integer; AVars : IEvalVariableList = nil) : boolean; implementation const // List of valid operators, if changed, update enum TEvalOperator as well cOperators = '()&|=+-*/^'; type TEvalResultCalcItem = record Value : integer; ValueType : TEvalResultType; end; TEvalOrdinalStack = class(TObject) constructor Create; destructor Destroy; override; private FData : array of TEvalResultCalcItem; public function IsEmpty : boolean; function Count : integer; function Top : TEvalResultCalcItem; procedure Push(AItem : TEvalResultCalcItem); overload; procedure Push(AValue : integer; AType : TEvalResultType); overload; function Pop : TEvalResultCalcItem; end; TEvalOperatorStack = class(TObject) constructor Create; destructor Destroy; override; private FData : Array of TEvalOperator; public function IsEmpty : boolean; function Count : integer; function Top : TEvalOperator; procedure Push(AOp : TEvalOperator); function Pop : TEvalOperator; end; TEvalOperandStack = class(TObject) constructor Create; destructor Destroy; override; private FData : TStringList; public procedure Push(AOp : string); function Pop : string; end; function OperatorTokenToEnum(AToken : string) : TEvalOperator; begin if SameText(AToken,'(') then result := oLP else if SameText(AToken,')') then result := oRP else if SameText(AToken,'=') then result := oEQ else if SameText(AToken,'') then result := oNEQ else if SameText(AToken,'') then result := oGT else if SameText(AToken,'') then result := oLT else if SameText(AToken,'=') then result := oGET else if SameText(AToken,'=') then result := oLET else if SameText(AToken,'|') then result := oOR else if SameText(AToken,'&') then result := oAND else if SameText(AToken,'+') then result := oADD else if SameText(AToken,'-') then result := oSUB else if SameText(AToken,'*') then result := oMUL else if SameText(AToken,'/') then result := oDIV else if SameText(AToken,'^') then result := oXOR else result := oNone; end; function EnumToOperatorToken(AEnum : TEvalOperator) : string; begin case AEnum of oLP: result := '('; oRP: result := ')'; oEQ: result := '='; oNEQ: result := ''; oGT: result := ''; oLT: result := ''; oGET: result := '='; oLET: result := '='; oOR: result := '|'; oAND: result := '&'; oADD: result := '+'; oSUB: result := '-'; oMUL: result := '*'; oDIV: result := '/'; oXOR: result := '^'; else result := ''; end; end; function OperatorPriority(AOp : TEvalOperator) : integer; // Higher value means higher priority begin case AOp of oNone: result := 0; oLP: result := 0; oRP: result := 0; oOR: result := 4; oXOR: result := 5; oAND: result := 6; oEQ: result := 7; oNEQ: result := 7; oGT: result := 8; oLT: result := 8; oGET: result := 8; oLET: result := 8; oADD: result := 10; oSUB: result := 10; oDIV: result := 11; oMUL: result := 11; else result := 0; end; end; function OperatorGraterEqualPrio(AOpL,AOpR : TEvalOperator) : boolean; begin result := (OperatorPriority(AOpL) = OperatorPriority(AOpR)); end; function IsInt(AStr : string) : boolean; var lp0 : integer; begin AStr := Trim(AStr); if (AStr '') then begin result := true; for lp0 := 1 to Length(AStr) do begin if not (AStr[lp0] in [#$30..#$39]) then begin result := false; break; end; end; end else result := false; end; function InfixToPostfixNotation(AInfix : string) : string; var AEval : TEvalExpression; begin AEval := TEvalExpression.Create; try AEval.ParseInfix(AInfix); result := AEval.ToString; finally AEval.Free; end; end; function EvaluateExpression(AInfix : string; out ResultType : TEvalResultType; out AResult : integer; AVars : IEvalVariableList = nil) : boolean; var AEval : TEvalExpression; begin AEval := TEvalExpression.Create; try AEval.ParseInfix(AInfix); result := AEval.Evaluate(ResultType,AResult,AVars); finally AEval.Free; end; end; {TEvalOrdinalStack} function MakeResultCalcItem(AValue : integer; AType : TEvalResultType) : TEvalResultCalcItem; begin result.Value := AValue; result.ValueType := AType; end; constructor TEvalOrdinalStack.Create; begin inherited Create; SetLength(FData,0); end; destructor TEvalOrdinalStack.Destroy; begin Finalize(FData); inherited Destroy; end; function TEvalOrdinalStack.IsEmpty : boolean; begin result := (Length(FData) = 0); end; function TEvalOrdinalStack.Count : integer; begin result := Length(FData); end; function TEvalOrdinalStack.Top : TEvalResultCalcItem; var ASize : integer; begin ASize := Length(FData); if (ASize 0) then begin result := FData[ASize-1]; end else result := MakeResultCalcItem(0,rtBool); end; procedure TEvalOrdinalStack.Push(AItem : TEvalResultCalcItem); var ANewSize : integer; begin ANewSize := Length(FData)+1; SetLength(FData,ANewSize); Move(AItem,FData[ANewSize-1],SizeOf(TEvalResultCalcItem)); end; procedure TEvalOrdinalStack.Push(AValue : integer; AType : TEvalResultType); var ANewSize : integer; AItem : TEvalResultCalcItem; begin ANewSize := Length(FData)+1; SetLength(FData,ANewSize); AItem.Value := AValue; AItem.ValueType := AType; Move(AItem,FData[ANewSize-1],SizeOf(TEvalResultCalcItem)); end; function TEvalOrdinalStack.Pop : TEvalResultCalcItem; var ASize : integer; begin ASize := Length(FData); if ASize 0 then begin Move(FData[ASize-1],result,SizeOf(TEvalResultCalcItem)); SetLength(FData,ASize-1); end else result := MakeResultCalcItem(0,rtBool); end; {TEvalOperatorStack} constructor TEvalOperatorStack.Create; begin inherited Create; SetLength(FData,0); end; destructor TEvalOperatorStack.Destroy; begin Finalize(FData); inherited Destroy; end; function TEvalOperatorStack.IsEmpty : boolean; begin result := (Length(FData) = 0); end; function TEvalOperatorStack.Count : integer; begin result := Length(FData); end; function TEvalOperatorStack.Top : TEvalOperator; var ASize : integer; begin ASize := Length(FData); if (ASize 0) then begin result := FData[ASize-1]; end else result := oNone; end; procedure TEvalOperatorStack.Push(AOp : TEvalOperator); var ANewSize : integer; begin ANewSize := Length(FData)+1; SetLength(FData,ANewSize); Move(AOp,FData[ANewSize-1],SizeOf(TEvalOperator)); end; function TEvalOperatorStack.Pop : TEvalOperator; var ASize : integer; begin ASize := Length(FData); if ASize 0 then begin Move(FData[ASize-1],result,SizeOf(TEvalOperator)); SetLength(FData,ASize-1); end else result := oLP; end; {TEvalOperandStack} constructor TEvalOperandStack.Create; begin inherited Create; FData := TStringList.Create; end; destructor TEvalOperandStack.Destroy; begin FData.Free; inherited Destroy; end; procedure TEvalOperandStack.Push(AOp : string); begin FData.Add(AOp); end; function TEvalOperandStack.Pop : string; begin result := ''; if (FData.Count 0) then begin result := FData[FData.Count-1]; FData.Delete(FData.Count-1); end; end; {TEvalVariables} constructor TEvalVariables.Create; begin inherited Create(TEvalVarItem); end; function TEvalVariables.GetVarItem(AIdx : integer) : TEvalVarItem; begin if (AIdx = 0) and (AIdx Count) then begin result := TEvalVarItem(Items[AIdx]); end else result := nil; end; function TEvalVariables.QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; begin if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE; end; function TEvalVariables._AddRef: Integer; stdcall; begin result := -1; end; function TEvalVariables._Release: Integer; stdcall; begin result := -1; end; function TEvalVariables.FindVarItemByName(AVarName : string) : TEvalVarItem; var lp0 : integer; begin result := nil; for lp0 := 0 to Count-1 do begin if SameText(VarItem[lp0].FVarName,AVarName) then begin result := VarItem[lp0]; break; end; end; end; function TEvalVariables.HasVariable(AVarName : string) : boolean; begin result := Assigned(FindVarItemByName(AVarName)); end; function TEvalVariables.GetValue(AVarName : string) : integer; var AItem : TEvalVarItem; begin result := 0; AItem := FindVarItemByName(AVarName); if Assigned(AItem) then begin result := AItem.FVarValue; end; end; procedure TEvalVariables.SetValue(AVarName : string; AValue : integer); var AItem : TEvalVarItem; begin AItem := FindVarItemByName(AVarName); if (AItem = nil) then begin AItem := TEvalVarItem.Create(self); AItem.VarName := AVarName; end; if Assigned(AItem) then AItem.FVarValue := AValue; end; {TEvalTokenOperator} function TEvalTokenOperator.GetTokenType : TEvalTokenType; begin result := ttOperator; end; function TEvalTokenOperator.ToString : string; begin result := EnumToOperatorToken(FOperator); end; {TEvalTokenOperand} function TEvalTokenOperand.GetTokenType : TEvalTokenType; begin result := ttOperand; end; function TEvalTokenOperand.IsConst : boolean; begin result := (GetOperandType = otConst); end; function TEvalTokenOperand.IsVar : boolean; begin result := (GetOperandType = otVar); end; {TEvalTokenConstOperand} function TEvalTokenConstOperand.GetOperandType : TEvalOperandType; begin result := otConst; end; function TEvalTokenConstOperand.ToString : string; begin result := IntToStr(FConstVal); end; {TEvalTokenVarOperand} function TEvalTokenVarOperand.GetOperandType : TEvalOperandType; begin result := otVar; end; function TEvalTokenVarOperand.ToString : string; begin result := FVarName; end; {TEvalExpression} constructor TEvalExpression.Create; begin inherited Create(TEvalToken); end; function TEvalExpression.GetToken(AIdx : integer) : TEvalToken; begin if (AIdx = 0) and (AIdx Count) then begin result := TEvalToken(Items[AIdx]); end else result := nil; end; function TEvalExpression.AddOperator(AOperator : TEvalOperator) : TEvalTokenOperator; begin result := TEvalTokenOperator.Create(self); result.FOperator := AOperator; end; function TEvalExpression.AddOperand(AOperand : string) : TEvalTokenOperand; begin result := nil; if (AOperand '') then begin if IsInt(AOperand) then begin result := TEvalTokenConstOperand.Create(self); TEvalTokenConstOperand(result).FConstVal := StrToIntDef(AOperand,0); end else begin result := TEvalTokenVarOperand.Create(self); TEvalTokenVarOperand(result).FVarName := AOperand; end; end; end; function TEvalExpression.ToString : string; var lp0 : integer; begin result := ''; for lp0 := 0 to Count-1 do begin result := result + Token[lp0].ToString + #32; end; result := trim(result); end; function TEvalExpression.ParseInfix(AIn : string) : boolean; var AStack : TEvalOperatorStack; AOperator,APrevOperator : TEvalOperator; lp0,stl : integer; AToken : string; ATokenType : TEvalTokenType; function ReadToken : boolean; var AEox : boolean; begin result := false; AToken := ''; ATokenType := ttNone; AEox := (lp0 stl); if not AEox then begin repeat if (Pos(AIn[lp0],cOperators) 0) then begin // Operator if (ATokenType = ttOperand) then begin if (AToken '') then result := true; break; end else begin if (ATokenType = ttOperator) then begin if ((AToken = '') and ((AIn[lp0] = '=') or (AIn[lp0] = ''))) or ((AToken = '') and (AIn[lp0] = '=')) then begin // Multi char operator ATokenType := ttOperator; AToken := AToken + AIn[lp0]; end else begin if (AToken '') then result := true; break; end; end else begin ATokenType := ttOperator; AToken := AToken + AIn[lp0]; end; end; end else begin // Operand case AIn[lp0] of #9,#32: begin if (ATokenType ttNone) then begin if (AToken '') then result := true; break; end; end; else if (ATokenType = ttOperator) then begin if (AToken '') then result := true; break; end else begin ATokenType := ttOperand; AToken := AToken + AIn[lp0]; end; end; end; inc(lp0); AEox := (lp0 stl); if AEox and (AToken '') then result := true; until (AEox); end; end; begin result := false; AStack := TEvalOperatorStack.Create; try Clear; stl := Length(AIn); if (stl 0) then begin lp0 := 1; while ReadToken do begin case ATokenType of ttOperator: begin AOperator := OperatorTokenToEnum(AToken); case AOperator of oNone: ; // Ignore oLP: AStack.Push(AOperator); oRP: begin while not AStack.IsEmpty and (AStack.Top oLP) do begin APrevOperator := AStack.Pop; AddOperator(APrevOperator); end; if not AStack.IsEmpty then AStack.Pop; end; else while not AStack.IsEmpty and (AStack.Top oLP) do begin if OperatorGraterEqualPrio(AStack.Top,AOperator) then begin APrevOperator := AStack.Pop; AddOperator(APrevOperator); end else break; end; AStack.Push(AOperator); end; end; ttOperand: begin AddOperand(AToken); end; end; end; while not AStack.IsEmpty do begin APrevOperator := AStack.Pop; AddOperator(APrevOperator); end; result := true; end; finally AStack.Free; end; end; function TEvalExpression.Evaluate(out ResultType : TEvalResultType; out AResult : integer; AVars : IEvalVariableList = nil) : boolean; var ATokenIdx : integer; AIntStack : TEvalOrdinalStack; ATempVal,AValLeft,AValRight : TEvalResultCalcItem; AVarName : string; begin result := false; AResult := 0; ResultType := rtBool; AIntStack := TEvalOrdinalStack.Create; try for ATokenIdx := 0 to Count-1 do begin case Token[ATokenIdx].TokenType of ttOperator: begin if not (TEvalTokenOperator(Token[ATokenIdx]).FOperator in [oNONE,oLP,oRP]) then begin if (AIntStack.Count = 2) then begin ATempVal := MakeResultCalcItem(0,rtBool); AValRight := AIntStack.Pop; AValLeft := AIntStack.Pop; case TEvalTokenOperator(Token[ATokenIdx]).FOperator of oOR: begin if (AValLeft.ValueType = AValRight.ValueType) then begin ATempVal.ValueType := AValLeft.ValueType; if ATempVal.ValueType = rtBool then begin if (AValLeft.Value 0) or (AValRight.Value 0) then ATempVal.Value := 1; end else begin ATempVal.Value := (AValLeft.Value or AValRight.Value); end; AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of same type') end; end; oXOR: begin if (AValLeft.ValueType = AValRight.ValueType) then begin ATempVal.ValueType := AValLeft.ValueType; if ATempVal.ValueType = rtBool then begin if (AValLeft.Value 0) xor (AValRight.Value 0) then ATempVal.Value := 1; end else begin ATempVal.Value := (AValLeft.Value xor AValRight.Value); end; AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of same type') end; end; oAND: begin if (AValLeft.ValueType = AValRight.ValueType) then begin ATempVal.ValueType := AValLeft.ValueType; if ATempVal.ValueType = rtBool then begin if (AValLeft.Value 0) and (AValRight.Value 0) then ATempVal.Value := 1; end else begin ATempVal.Value := (AValLeft.Value and AValRight.Value); end; AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of same type') end; end; oEQ: begin if (AValLeft.ValueType = AValRight.ValueType) then begin ATempVal.ValueType := rtBool; if (AValLeft.Value = AValRight.Value) then ATempVal.Value := 1; AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of same type') end; end; oNEQ: begin if (AValLeft.ValueType = AValRight.ValueType) then begin ATempVal.ValueType := rtBool; if (AValLeft.Value AValRight.Value) then ATempVal.Value := 1; AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of same type') end; end; oGT: begin if (AValLeft.ValueType = AValRight.ValueType) then begin ATempVal.ValueType := rtBool; if (AValLeft.Value AValRight.Value) then ATempVal.Value := 1; AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of same type') end; end; oLT: begin if (AValLeft.ValueType = AValRight.ValueType) then begin ATempVal.ValueType := rtBool; if (AValLeft.Value AValRight.Value) then ATempVal.Value := 1; AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of same type') end; end; oGET: begin if (AValLeft.ValueType = AValRight.ValueType) then begin ATempVal.ValueType := rtBool; if (AValLeft.Value = AValRight.Value) then ATempVal.Value := 1; AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of same type') end; end; oLET: begin if (AValLeft.ValueType = AValRight.ValueType) then begin ATempVal.ValueType := rtBool; if (AValLeft.Value = AValRight.Value) then ATempVal.Value := 1; AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of same type') end; end; oADD: begin if (AValLeft.ValueType = rtOrdinal) and (AValRight.ValueType = rtOrdinal) then begin ATempVal.ValueType := rtOrdinal; ATempVal.Value := (AValLeft.Value + AValRight.Value); AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of type ordinal') end; end; oSUB: begin if (AValLeft.ValueType = rtOrdinal) and (AValRight.ValueType = rtOrdinal) then begin ATempVal.ValueType := rtOrdinal; ATempVal.Value := (AValLeft.Value - AValRight.Value); AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of type ordinal') end; end; oDIV: begin if (AValLeft.ValueType = rtOrdinal) and (AValRight.ValueType = rtOrdinal) then begin ATempVal.ValueType := rtOrdinal; if AValRight.Value 0 then begin ATempVal.Value := (AValLeft.Value div AValRight.Value); AIntStack.Push(ATempVal); end else begin raise Exception.Create('Can not divide by zero') end; end else begin raise Exception.Create('Incorrect operand, must be of type ordinal') end; end; oMUL: begin if (AValLeft.ValueType = rtOrdinal) and (AValRight.ValueType = rtOrdinal) then begin ATempVal.ValueType := rtOrdinal; ATempVal.Value := (AValLeft.Value * AValRight.Value); AIntStack.Push(ATempVal); end else begin raise Exception.Create('Incorrect operand, must be of type ordinal') end; end; end; end else raise Exception.Create('Missing operand for operator '+EnumToOperatorToken(TEvalTokenOperator(Token[ATokenIdx]).FOperator)); end; end; ttOperand: begin if (Token[ATokenIdx] is TEvalTokenVarOperand) then begin AVarName := TEvalTokenVarOperand(Token[ATokenIdx]).VarName; if Assigned(AVars) then begin if AVars.HasVariable(AVarName) then begin AIntStack.Push(AVars.GetValue(AVarName),rtOrdinal); end else raise Exception.Create('Variable '+AVarName+' is not set'); end else raise Exception.Create('Variables not allowed'); end else if (Token[ATokenIdx] is TEvalTokenConstOperand) then begin ATempVal.Value := TEvalTokenConstOperand(Token[ATokenIdx]).FConstVal; ATempVal.ValueType := rtOrdinal; AIntStack.Push(ATempVal); end else raise Exception.Create('Unknown operand'); end; end; end; ATempVal := AIntStack.Pop; AResult := ATempVal.Value; ResultType := ATempVal.ValueType; result := (AIntStack.IsEmpty); finally AIntStack.Free; end; end; end.