Search code examples
delphimathcomponentsdelphi-prismexpression-evaluation

Delphi Prism: Replacement for TMathparser class for evaluating complex expressions?


In Delphi, I use a component called TMathparser to evaluate an expression to get an answer. I am trying to get it to work in Delphi Prism and it is not working out too well. In fact, there is just too many errors. So, I was wondering if there is something similar that will work with Delphi Prism.

Thanks,


Solution

  • I am posting the class in the hope that it might help others.

    As pointed out by David, I think I have to post this here or I will have to completely remove my answer:

    {==========================================================================}
    { Expression Evaluator v1.4 for Delphi                                     }
    { (16 & 32 bits)                                                           }
    {                                                                          }
    { Copyright © 1997 by BitSoft Development, L.L.C.                          }
    { All rights reserved                                                      }
    {                                                                          }
    { Web:     http://www.bitsoft.com                                          }
    { E-mail:  info@bitsoft.com                                                }
    { Support: tech-support@bitsoft.com                                        }
    {--------------------------------------------------------------------------}
    { Portions Copyright © 1992 by Borland International, Inc.                 }
    { All rights reserved                                                      }
    {--------------------------------------------------------------------------}
    { This file is distributed as freeware and without warranties of any kind. }
    { You can use it in your own applications at your own risk.                }
    { See the License Agreement for more information.                          }
    {==========================================================================}
    

    Here is the modified version of Mathparser Class for Prism:

    namespace MathParserClass;
    
    interface
    
    uses
      System.Collections.Generic,
      System.Collections.*,
      System.Text;
    
    type
        TExtendedWrapper = class(Object)
        public
        MyNumber: Extended;
        constructor;
        end;
    
    type
      TGetVarEvent = procedure(Sender : System.Object; VarName : string; var
        Value : Extended; var Found : Boolean) of object;
    
      TParseErrorEvent = procedure(Sender : System.Object; ParseError : Integer)
        of object;
    
    const
      ParserStackSize = 15;
      MaxFuncNameLen = 5;
      ExpLimit = 11356;
      SqrLimit = 1E2466;
      MaxExpLen = 4;
      TotalErrors = 7;
      ErrParserStack = 1;
      ErrBadRange = 2;
      ErrExpression = 3;
      ErrOperator = 4;
      ErrOpenParen = 5;
      ErrOpCloseParen = 6;
      ErrInvalidNum = 7;
    
    type
      ErrorRange = 0..TotalErrors;
    
      TokenTypes = (Plus, Minus, Times, Divide, Expo, OParen, CParen, Num,
                    Func, EOL, Bad, ERR, Modu, tmp);
    
      TokenRec = record
        State : Byte;
        Value : Extended;
        FuncName : String;
      end; { TokenRec }
    
    type
      MathParser = class(System.Object)
      private
        { Private declarations }   //moved to public
          FInput : string;     //was private
          FOnGetVar : TGetVarEvent; //was private
          FOnParseError : TParseErrorEvent; //was private
    
      protected
          CurrToken : TokenRec;   //was protected begin
          MathError : Boolean;
          Stack : array[1..ParserStackSize] of TokenRec;
          StackTop : Integer;//0..ParserStackSize;
          TokenError : ErrorRange;
          TokenLen : Word;
          TokenType : TokenTypes;
          method GotoState(Production : Word) : Word;
          method IsFunc(S : String) : Boolean;
          method IsVar(var Value : Extended) : Boolean;
          method NextToken : TokenTypes;
          method Push(Token : TokenRec);
          method Pop(var Token : TokenRec);
          method Reduce(Reduction : Word);
          method Shift(State : Word);
                                          //was protected end
    
          public
          { Public declarations }
          Queue: Queue;   //not on-> on now
          Queue2: Queue;   //not on-> on now
          QueueHR: Queue;
          Position : Word; { Public declarations moved above}
          ParseError : Boolean;  { Public declarations moved above}
          ParseValue : Extended; { Public declarations moved above}
          TempToken : TokenRec;
          constructor;
          procedure Parse;
          property OnGetVar : TGetVarEvent read FOnGetVar write FOnGetVar;
          property OnParseError : TParseErrorEvent read FOnParseError write FOnParseError;
          property ParseString : string read FInput write FInput;
      end;
    
    
    
    var
     FirstTimeThru, SecondTimeThru : Boolean;
     FirstTimeThruHR, SecondTimeThruHR : Boolean;
     FirstTimeThru3, SecondTimeThru3 : Boolean;
     FirstTimeThru4, SecondTimeThru4 : Boolean;
     icnt, icnt2, icnt3, icnt4, timecount : integer;
     NetAmount, NetAmount3, RunningTotalForMinute:extended;
     PrevToken, PrevToken3, PrevToken4, CurrentToken :extended;
     NetAmountHR, RunningTotalForHour, PrevTokenHR:extended;
     CurrentTokenHR,LastResultMin, LastResultHr:extended;
     toggleMin, toggleHr : boolean;
     kk,jj, m : integer;
    
    implementation
    const
      Letters : set of Char = ['A'..'Z', 'a'..'z'];
      Numbers : set of Char = ['0'..'9'];
    
    constructor MathParser;
    begin
      { defaults }
      FInput := '';
      FirstTimeThru := true;
      SecondTimeThru := false;
      FirstTimeThruHR := true;
      SecondTimeThruHR := false;
      FirstTimeThru3 := true;
      SecondTimeThru3 := false;
      FirstTimeThru4 := true;
      SecondTimeThru4 := false;
      toggleMin := true;
      toggleHr := true;
      //TempToken.Value := 0.0;
      RunningTotalForMinute := 0.0;
      RunningTotalForHOUR := 0.0;
      kk:=1;
      jj:=1;
      m:=0;
      Queue := new Queue; //need this here
      Queue2 := new Queue; //need this here
      QueueHR := new Queue; //need this here
      timecount := 0;
    end;
    
    method MathParser.GotoState(Production : Word) : Word;
    { Finds the new state based on the just-completed production and the
       top state. }
    var
      State : Word;
    begin
         //GotoState := 0;
         Result:=0;
      State := Stack[StackTop].State;
      if (Production <= 3) then
      begin
        case State of
          0 : Result:=1; //GotoState := 1;
          9 : Result:=19; //GotoState := 19;
          20 : Result:=28; //GotoState := 28;
        end; { case }
      end
      else if Production <= 6 then
      begin
        case State of
          0, 9, 20 : Result:=2; //GotoState := 2;
          12 : Result:=21; //GotoState := 21;
          13 : Result:=22; //GotoState := 22;
        end; { case }
      end
      else if (Production <= 8) or (Production = 100) then
      begin
        case State of
          0, 9, 12, 13, 20 : Result:=3; //GotoState := 3;
          14 : Result := 23; //GotoState := 23;
          15 : Result := 24; //GotoState := 24;
          16 : Result := 25; //GotoState := 25;
          40 : Result := 80; //GotoState := 80;
        end; { case }
      end
      else if Production <= 10 then
      begin
        case State of
          0, 9, 12..16, 20, 40 : Result := 4; //GotoState := 4;
        end; { case }
      end
      else if Production <= 12 then
      begin
        case State of
          0, 9, 12..16, 20, 40 : Result := 6; //GotoState := 6;
          5 : Result := 17; //GotoState := 17;
        end; { case }
      end
      else begin
        case State of
          0, 5, 9, 12..16, 20, 40 : Result:=8; //GotoState := 8;
        end; { case }
      end;
    end; { GotoState }
    
    method MathParser.IsFunc(S : String) : Boolean;
    { Checks to see if the parser is about to read a function }
    var
      P, SLen : Word;
      FuncName : string;
    begin
      P := Position;
      FuncName := '';
    
      while (P < Length(FInput)) do
      begin
        if (FInput[P] in ['A'..'Z', 'a'..'z', '0'..'9','_']) then
        begin
          FuncName := FuncName + FInput[P];
        end
        else
          break;
        Inc(P);
      end; { while }
    
      if FuncName.ToUpper = S then begin
               SLen := Length(S);
               CurrToken.FuncName := FInput.Substring(Position,SLen).ToUpper; 
               Inc(Position, SLen);
               Result:=true;//IsFunc := True;
             end { if }
        else Result:=false;//IsFunc := False;
    end; { IsFunc }
    
    method MathParser.IsVar(var Value : Extended) : Boolean;
    var
      VarName : string;
      VarFound : Boolean;
    begin
      VarFound := False;
      VarName := '';
    
      while (Position < Length(FInput)) do
      begin
        if (FInput[Position] in ['A'..'Z','a'..'z', '0'..'9', '_']) then
        begin
          VarName := VarName + FInput[Position];
        end
        else
          break;
        Inc(Position);
      end; { while }
    
      //if Assigned(FOnGetVar) then 
      //  FOnGetVar(Self, VarName, var Value, var VarFound);
      //If you notice above lines are commented out, for some reason the event assigned to it
      //did not fire. So, I called the method, which is defined in another namespace or file,
      //directly. It works fine. In your expression if you have a variable, this method 
      //varifies that it exists and that it can turn it to a value. It is totally upto you
      //how you define this method. It is very important to have if you are going to have
      //variables in your expression.
      MathParserGetVar(self,VarName,var Value,var VarFound);
      //IsVar := VarFound;
      Result := VarFound;
    end; { IsVar }
    
    method MathParser.NextToken : TokenTypes;
    { Gets the next Token from the Input stream }
    var
      NumString : string;
      TLen, NumLen : Word;
      Check : Integer;
      Ch : Char;
      Decimal : Boolean;
      tmpVar : Double;
      tmpstr:String;
    begin
         Result:=TokenTypes.tmp;
       while (Position < Length(FInput)) do
       begin
        if (FInput[Position] = ' ') then
         Inc(Position)
        else
          break;
       end;
    
       TokenLen := Position;
       if Position >= Length(FInput) then
       begin
         result:=TokenTypes.EOL;
         TokenLen := 0;
         Exit;
       end; { if }
    
       tmpstr:=FInput.Substring(Position,1).ToUpper;
       ch:=char(tmpstr[0]);
       if Ch in ['!'] then
       begin
          Result:=TokenTypes.ERR;
          TokenLen := 0;
          Exit;
       end; { if }
       if Ch in ['0'..'9', '.'] then
       begin
         NumString := '';
         TLen := Position;
         Decimal := False;
    
         while (TLen < Length(FInput)) do
         begin
               if ((FInput[TLen] in ['0'..'9']) or ((FInput[TLen] = '.') and (not Decimal))) then
               begin
                NumString := NumString + FInput[TLen];
                if Ch = '.' then
                  Decimal := True;
               end
               else
                 break;
               Inc(TLen);
         end; { while }
    
         if (TLen = 2) and (Ch = '.') then
         begin
           Result:=TokenTypes.BAD;
           TokenLen := 0;
           Exit;
         end; { if }
    
         if (TLen < Length(FInput)) then
         begin
          tmpStr := FInput.Substring(TLen,1).ToUpper;
          ch := char(tmpStr[0]);
          if (Ch in ['E']) then
          begin
           NumString := NumString + 'E';
           Inc(TLen);
           if FInput[TLen] in ['+', '-'] then
           begin
             NumString := NumString + FInput[TLen];
             Inc(TLen);
           end; { if }
           NumLen := 1;
           while (TLen <= Length(FInput)) and (NumLen <= MaxExpLen) do
           begin
             if (FInput[TLen] in ['0'..'9']) then
              NumString := NumString + FInput[TLen]
             else
              break;
             Inc(NumLen);
             Inc(TLen);
           end; { while }
          end;
         end; { if }
    
    
         if NumString[0] = '.' then
           NumString := '0' + NumString;
         if Double.TryParse(NumString, out tmpvar)=true then
         begin
            Check:=0;
            CurrToken.Value:=tmpVar;
         end
         else
            Check:=1;
    
         if Check <> 0 then
           begin
             MathError := True;
             TokenError := ErrInvalidNum;
             Inc(Position, NumString.Length-1);
           end { if }
         else
           begin
             Inc(Position, NumString.Length);
             TokenLen := Position - TokenLen;
             Result:=TokenTypes.NUM;
           end; { else }
         Exit;
       end { if }
       else if Ch in Letters then
       begin
         if IsFunc('ABS') or
            IsFunc('ATAN') or
            IsFunc('COS') or
            IsFunc('EXP') or
            IsFunc('LN') or
            IsFunc('ROUND') or
            IsFunc('SIN') or
            IsFunc('SQRT') or
            IsFunc('SQR') or
            IsFunc('TRUNC')
             then
         begin
           Result:=TokenTypes.FUNC;
           TokenLen := Position - TokenLen;
           Exit;
         end; { if }
         if IsFunc('MOD') then
         begin
           Result:=TokenTypes.MODU;
           TokenLen := Position - TokenLen;
           Exit;
         end; { if }
         if IsVar(var CurrToken.Value)
           then begin
                  Result:=TokenTypes.NUM;
                  TokenLen := Position - TokenLen;
                  Exit;
                end { if }
           else begin
                  Result:=TokenTypes.BAD;
                  TokenLen := 0;
                  Exit;
                end; { else }
       end { if }
       else begin
         case Ch of
           '+' : Result := TokenTypes.PLUS;
           '-' : Result := TokenTypes.MINUS;
           '*' : Result := TokenTypes.TIMES;
           '/' : Result := TokenTypes.DIVIDE;
           '^' : Result := TokenTypes.EXPO;
           '(' : Result := TokenTypes.OPAREN;
           ')' : Result := TokenTypes.CPAREN;
           else begin
             Result:=TokenTypes.BAD;
             TokenLen := 0;
             Exit;
           end; { case else }
         end; { case }
         Inc(Position);
         TokenLen := Position - TokenLen;
         Exit;
       end; { else if }
    end; { NextToken }
    
    procedure MathParser.Pop(var Token : TokenRec);
    { Pops the top Token off of the stack }
    begin
      Token := Stack[StackTop];
      StackTop:=StackTop-1;
    end; { Pop }
    
    procedure MathParser.Push(Token : TokenRec);
    { Pushes a new Token onto the stack }
    begin
      if StackTop = ParserStackSize then
        TokenError := ErrParserStack
      else begin
        StackTop:=StackTop+1;
        Stack[StackTop] := Token;
      end; { else }
    end; { Push }
    
    procedure MathParser.Parse;
    { Parses an input stream }
    var
      FirstToken : TokenRec;
      Accepted : Boolean;
    begin
      Position := 0;
      StackTop := 0;
      TokenError := 0;
      MathError := False;
      ParseError := False;
      Accepted := False;
      FirstToken.State := 0;
      FirstToken.Value := 0;
      Push(FirstToken);
      TokenType := NextToken;
      repeat
        case Stack[StackTop].State of
          0, 9, 12..16, 20, 40 : begin
            if TokenType = TokenTypes.NUM then
              Shift(10)
            else if TokenType = TokenTypes.FUNC then
              Shift(11)
            else if TokenType = TokenTypes.MINUS then
              Shift(5)
            else if TokenType = TokenTypes.OPAREN then
              Shift(9)
            else if TokenType = TokenTypes.ERR then
              begin
                 MathError := True;
                 Accepted := True;
              end { else if }
            else begin
              TokenError := ErrExpression;
              Dec(Position, TokenLen);
            end; { else }
          end; { case of }
          1 : begin
            if TokenType = TokenTypes.EOL then
              Accepted := True
            else if TokenType = TokenTypes.PLUS then
              Shift(12)
            else if TokenType = TokenTypes.MINUS then
              Shift(13)
            else begin
              TokenError := ErrOperator;
              Dec(Position, TokenLen);
            end; { else }
          end; { case of }
          2 : begin
            if TokenType = TokenTypes.TIMES then
              Shift(14)
            else if TokenType = TokenTypes.DIVIDE then
              Shift(15)
            else
              Reduce(3);
          end; { case of }
          3 : begin
           if TokenType = TokenTypes.MODU then
             Shift(40)
           else
             Reduce(6);
          end; { case of }
          4 : begin
           if TokenType = TokenTypes.EXPO then
             Shift(16)
           else
             Reduce(8);
          end; { case of }
          5 : begin
            if TokenType = TokenTypes.NUM then
              Shift(10)
            else if TokenType = TokenTypes.FUNC then
              Shift(11)
            else if TokenType = TokenTypes.OPAREN then
              Shift(9)
            else
              begin
                TokenError := ErrExpression;
                Dec(Position, TokenLen);
              end; { else }
          end; { case of }
          6 : Reduce(10);
          7 : Reduce(13);
          8 : Reduce(12);
          10 : Reduce(15);
          11 : begin
            if TokenType = TokenTypes.OPAREN then
              Shift(20)
            else
              begin
                TokenError := ErrOpenParen;
                Dec(Position, TokenLen);
              end; { else }
          end; { case of }
          17 : Reduce(9);
          18 : raise Exception('Bad token state');
          19 : begin
            if TokenType = TokenTypes.PLUS then
              Shift(12)
            else if TokenType = TokenTypes.MINUS then
              Shift(13)
            else if TokenType = TokenTypes.CPAREN then
              Shift(27)
            else
              begin
                TokenError := ErrOpCloseParen;
                Dec(Position, TokenLen);
              end;
          end; { case of }
          21 : begin
            if TokenType = TokenTypes.TIMES then
              Shift(14)
            else if TokenType = TokenTypes.DIVIDE then
              Shift(15)
            else
              Reduce(1);
          end; { case of }
          22 : begin
            if TokenType = TokenTypes.TIMES then
              Shift(14)
            else if TokenType = TokenTypes.DIVIDE then
              Shift(15)
            else
              Reduce(2);
          end; { case of }
          23 : Reduce(4);
          24 : Reduce(5);
          25 : Reduce(7);
          26 : Reduce(11);
          27 : Reduce(14);
          28 : begin
            if TokenType = TokenTypes.PLUS then
              Shift(12)
            else if TokenType = TokenTypes.MINUS then
              Shift(13)
            else if TokenType = TokenTypes.CPAREN then
              Shift(29)
            else
              begin
                TokenError := ErrOpCloseParen;
                Dec(Position, TokenLen);
              end; { else }
          end; { case of }
          29 : Reduce(16);
          80 : Reduce(100);
        end; { case }
      until Accepted or (TokenError <> 0);
      if TokenError <> 0 then
      begin
          if TokenError = ErrBadRange then
            Dec(Position, TokenLen);
          if Assigned(FOnParseError)
            then FOnParseError(Self, TokenError);
      end; { if }
    
      if MathError or (TokenError <> 0) then
      begin
        ParseError := True;
        ParseValue := 0;
        Exit;
      end; { if }
      ParseError := False;
      ParseValue := Stack[StackTop].Value;
    end; { Parse }
    
    procedure MathParser.Reduce(Reduction : Word);
    { Completes a reduction }
    var
      Token1, Token2 : TokenRec;
    begin
    
      case Reduction of
        1 : begin
          Pop(var Token1);
          Pop(var Token2);
          Pop(var Token2);
          CurrToken.Value := Token1.Value + Token2.Value;
        end;
        2 : begin
          Pop(var Token1);
          Pop(var Token2);
          Pop(var Token2);
          CurrToken.Value := Token2.Value - Token1.Value;
        end;
        4 : begin
          Pop(var Token1);
          Pop(var Token2);
          Pop(var Token2);
          CurrToken.Value := Token1.Value * Token2.Value;
        end;
        5 : begin
          Pop(var Token1);
          Pop(var Token2);
          Pop(var Token2);
          if Token1.Value = 0 then
            MathError := True
          else
            CurrToken.Value := Token2.Value / Token1.Value;
        end;
    
        { MOD operator }
        100 : begin
          Pop(var Token1);
          Pop(var Token2);
          Pop(var Token2);
          if Token1.Value = 0 then
            MathError := True
          else
            CurrToken.Value := int32(math.Round(Token2.Value)) mod int32(math.Round(Token1.Value));
        end;
    
        7 : begin
          Pop(var Token1);
          Pop(var Token2);
          Pop(var Token2);
          if Token2.Value <= 0 then
            MathError := True
          else if (Token1.Value * math.Log(Token2.Value) < -ExpLimit) or
                  (Token1.Value * math.Log(Token2.Value) > ExpLimit) then
            MathError := True
          else
            CurrToken.Value := math.Exp(Token1.Value * math.log(Token2.Value));
        end;
        9 : begin
          Pop(var Token1);
          Pop(var Token2);
          CurrToken.Value := -Token1.Value;
        end;
        //11 : raise Exception('Invalid reduction');
        //13 : raise Exception('Invalid reduction');
        14 : begin
          Pop(var Token1);
          Pop(var CurrToken);
          Pop(var Token1);
        end;
        16 : begin
          Pop(var Token1);
          Pop(var CurrToken);
          Pop(var Token1);
          Pop(var Token1);
    
          if Token1.FuncName = 'ABS' then
            CurrToken.Value := math.Abs(CurrToken.Value)
          else if Token1.FuncName = 'ATAN' then
            CurrToken.Value := math.Atan(CurrToken.Value)
          else if Token1.FuncName = 'COS' then
          begin
             if (CurrToken.Value < -9E18) or (CurrToken.Value > 9E18) then
                MathError := True
             else
                CurrToken.Value := math.Cos(CurrToken.Value)
          end 
          else if Token1.FuncName = 'EXP' then
          begin
            if (CurrToken.Value < -ExpLimit) or (CurrToken.Value > ExpLimit) then
              MathError := True
            else
              CurrToken.Value := math.Exp(CurrToken.Value);
          end
          else if Token1.FuncName = 'LN' then
          begin
            if CurrToken.Value <= 0 then
              MathError := True
            else
              CurrToken.Value := Math.Log(CurrToken.Value);
          end
          else if Token1.FuncName = 'ROUND' then
          begin
            if (CurrToken.Value < -1E9) or (CurrToken.Value > 1E9) then
              MathError := True
            else
              CurrToken.Value := math.Round(CurrToken.Value);
          end
          else if Token1.FuncName = 'SIN' then
          begin
             if (CurrToken.Value < -9E18) or (CurrToken.Value > 9E18) then
                MathError := True
             else
                CurrToken.Value := math.Sin(CurrToken.Value)
          end 
          else if Token1.FuncName = 'SQRT' then
          begin
            if CurrToken.Value < 0 then
              MathError := True
            else
              CurrToken.Value := math.Sqrt(CurrToken.Value);
          end
          else if Token1.FuncName = 'SQR' then
          begin
            if (CurrToken.Value < -1000000) or (CurrToken.Value > 1000000) then
              MathError := True
            else
              CurrToken.Value := (CurrToken.Value*CurrToken.Value);
          end
          else if Token1.FuncName = 'TRUNC' then
          begin
            if (CurrToken.Value < -1E9) or (CurrToken.Value > 1E9) then
              MathError := True
            else
              CurrToken.Value := math.Truncate(CurrToken.Value);
          end;
        end;
        3, 6, 8, 10, 12, 15 : Pop(var CurrToken);
      end; { case }
      CurrToken.State := GotoState(Reduction);
      Push(CurrToken);
    end; { Reduce }
    
    procedure MathParser.Shift(State : Word);
    { Shifts a Token onto the stack }
    begin
      CurrToken.State := State;
      Push(CurrToken);
      TokenType := NextToken;
    end; { Shift }
    
    constructor TExtendedWrapper;
    begin
    end;
    
    end.
    

    Here is the implementation detail for method MathParseronGetVar:

    method YourClass.MathParserGetVar(sender: Object; VarName: String; var Value: Extended; var VarFound: Boolean);
    var
      theSig:TSignal;
    begin
      theSig := FindSignal(VarName); //My variables are linked to external devices. Yours could simply two dimensional arraylist with variable and its value.
      if theSig <> nil then
      begin
        Value := theSig.AsReal;
        VarFound := true;
      end
      else
      begin
        VarFound := false;
      end;
    end;
    

    Here is how one would use MathParser class. By the way this class will easily handle complex expression.

      var  theparser := new Mathparser;
      with theparser do
      begin
        ParseString := '(COS((33*5))*TAN(X))+SQRT(100)';
        Parse;
        if not ParseError then
          Edit2.Text := string.Format('{0}',ParseValue)
        else
          Edit2.Text := '#Error';
      end;
    

    I think you may still need to modify Mathparser to work with your program, but it would be very simple.