Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
fpc-src / usr / share / fpcsrc / 3.0.0 / packages / symbolic / src / parsexpr.inc
Size: Mime:
{
    $ id:                                                       $
    Copyright (c) 2000 by Marco van de Voort(marco@freepascal.org)
     member of the Free Pascal development team

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright. (LGPL)

    Implementation of Infix to parsetree/RPN converter based on principles
    copied from a RPN constant expression evaluator by Trai Tran
    (PD, from SWAG.)

    Parsetree to infix and parsetree to RPN/infix conversion
     by Marco v/d Voort
    OOP interface and vast improvements by Marco v/d Voort

    This program 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.

 **********************************************************************

Problems:
- -x^12 is -(x^12) or (-x)^12 ? (FIXED: Chose to do it as in FPC)
- No errorhandling. (will be rewritten to use classes and exceptions first)
  (this is partially done now)

Original comments:

---------------------------------------------------------------------------
THAI TRAN

I've netmailed you the full-featured version (800 lines!) that will do
Functions, exponentiation, factorials, and has all the bells and whistles,
but I thought you might want to take a look at a simple version so you can
understand the algorithm.

This one only works With +, -, *, /, (, and ).  I wrote it quickly, so it
makes extensive use of global Variables and has no error checking; Use at
your own risk.

Algorithm to convert infix to postfix (RPN) notation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parse through the entire expression getting each token (number, arithmetic
operation, left or right parenthesis).  For each token, if it is:
 1. an operand (number)        Send it to the RPN calculator
 2. a left parenthesis         Push it onto the operation stack
 3. a right parenthesis        Pop operators off stack and send to RPN
                               calculator Until the a left parenthesis is
                               on top of the stack.  Pop it also, but don't
                               send it to the calculator.
 4. an operator                While the stack is not empty, pop operators
                               off the stack and send them to the RPN
                               calculator Until you reach one With a higher
                               precedence than the current operator (Note:
                               a left parenthesis has the least precendence).
                               Then push the current operator onto the stack.

This will convert (4+5)*6/(2-3) to 4 5 + 6 * 2 3 - /

Algorithm For RPN calculator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Note:  this Uses a different stack from the one described above.

In RPN, if an operand (a number) is entered, it is just pushed onto the
stack.  For binary arithmetic operators (+, -, *, /, and ^), the top two
operands are popped off the stack, operated on, and the result pushed back
onto the stack.  if everything has gone correctly, at the end, the answer
should be at the top of the stack.

Released to Public Domain by Thai Tran (if that matters).
---------------------------------------------------------------------------
MvdV: It does for me. My routines might end up in either FPC or Jedi, and
      anything except LGPL and PD is unacceptable. :-)

Modifications: (starting to get so big that the original is hardly
               recognisable)
- OOP. Mainly to allow symbolic TExpression class to have custom parsers.
- Working with pnode stack instead of reals. Pnodes can be any expression,
   see inteface unit symbolic. (creating a parsetree)
- Support for functions(one or two parameter arguments), which weren't in the
   short Swag version. Most MATH functions are supported.
- Can make a difference between the minus of (-x) and the one in (x-y).
   The first is converted to function minus(x);
- power operator
- Faculty operator
- Conversions back to RPN and infix.
- Removing of excess parentheses.
}

type {Tokens generated by the parser. Anything else is a constant or variable}
 ParseOperation=(padd,psub,pmul,pdvd,ppow,pfacul,pleft,pright,
                 pcos,psin,ptan,psqr,psqrt,pexp,pln,pinv,
                 pminus, pcotan,parcsin,parccos,parctan,psinh,pcosh,ptanh,
                 parcsinh,parccosh,parctanh,plog10,
                 plog2,plnxpi,parctan2,pstep,ppower,phypot,
                 plogn,pnothing);

CONST
 ParserFunctionNamesUpper   : array[padd..pnothing] of string[7]=
                 ('+','-','*','/','^','!','(',')','COS','SIN',
                 'TAN','SQR','SQRT','EXP','LN','INV','-',
                 'COTAN','ARCSIN','ARCCOS','ARCTAN',
                 'SINH','COSH','TANH','ARCSINH',
                 'ARCCOSH','ARCTANH','LOG10',
                 'LOG2','LNXP1','ARCTAN2','STEP',
                 'POWER','HYPOT','LOGN','NOTHING');

 {Operator or function-}
 Priority          : array[padd..pnothing] of ArbInt=
                 (1,1,2,2,3,0,0,0,
                  4,4,4,4,4,4,4,4,
                  4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5);

 OppsXlat='+-*/^!()'; {Must match the first entries of ParseOperation.
                         Pos(OppsXlat,c)-1+ord(Padd) is typecast!}

Const
  RPNMax = 20;              { I think you only need 4-8, but just to be safe }
  OpMax  = 25;
  AllowedInToken = ['0'..'9','.','E','e'];

Type
  String15 = String[15];

Procedure ParserInternalError(const Msg:String;A,B:ArbInt);

VAR S,S2 : String;

begin
 Str(A,S);      {Usually a identification number for the occurance}
 Str(B,S2);     {Usually the value that tripped the IE}
 Raise EParserIE.Create(SParsIE+Msg+S+' '+S2);
end;

function TBaseExprParser.InFixToParseTree(Expr : String;VAR RPNexpr: String):pnode;

Var
  RPNStack   : Array[1..RPNMax] of PNode;        { Stack For RPN calculator }
  RPNTop,
  OpTop      : ArbInt;
  OpStack    : Array[1..OpMax] of ParseOperation;    { Operator stack For conversion }

Procedure RPNPush(Num : PNode); { Add an operand to the top of the RPN stack }
begin
  if RPNTop < RPNMax then
   begin
    Inc(RPNTop);
    RPNStack[RPNTop] := Num;
   end
  else
  RAISE EParserStack.Create(SParseRPNOverflow);
end;

Function RPNPop : pnode;       { Get the operand at the top of the RPN stack }
begin
  if RPNTop > 0 then
   begin
    RPNPop := RPNStack[RPNTop];
    Dec(RPNTop);
   end
  else
    RAISE EParserStack.Create(SParseRPNUnderflow);
end;

Procedure RPNCalc(Token : String15);                       { RPN Calculator }
Var
  treal : ArbFloat;
  tint  : ArbInt;
  Error : ArbInt;
begin
   RPNExpr:=RPNExpr+token+' ';
   Val(Token, treal, Error);
   IF (error=0) then
    begin
      if (Pos('.',token)=0) and (Pos('E',token)=0) Then
       begin
        Val(Token,tint,Error);
        RpnPush(Newiconst(tint));
       end
      else
       RPNPush(NewConst(Treal));
    end
   else  { Handle error }
     RPNPush(NewVar(Token));
end;

Procedure RPNOperation(Operation:ParseOperation);
{The workhorse. Creates the tree, and associates a parseoperation with
 the TExpression enumerations. Avoids some ugly (and shaky) typecasts
 between operations like in earlier versions.}

var Temp: pnode;

begin
  RPNExpr:=RPNExpr+ParserFunctionNamesUpper[Operation]+' ';
  Case Operation of                                   { Handle operators }
    padd : RPNPush(newcalc(addo,RPNPop,RPNPop));
    psub : begin
            Temp:=RPNPOP;
            RPNPush(NewCalc(subo,RPNPOP,Temp));
           end;
    pmul : RPNPush(newcalc(mulo,RPNPOP,RPNPop));
    pdvd : begin
            Temp := RPNPop;
            if Temp <> NIL then
             RPNPush(newcalc(dvdo,RPNPop,Temp))
            else
             Raise EDiv0.Create(SParsDiv0);  { Handle divide by 0 error }
           end;
    ppow,ppower : {are only different in parsing x^y and power(x,y)}
           begin
            Temp:=RpnPop;
            RpnPush(NewCalc(powo,RpnPop,Temp));
           end;
  pfacul : RPNPush(NewFunc(faculx,RPNPOP));
    psin : RPNPush(NewFunc(sinx,RPNPop));
    pcos : RPNPush(NewFunc(cosx,RPNPop));
    ptan : RPNPush(NewFunc(tanx,RPNPop));
    psqr : RPNPush(NewFunc(sqrx,RPNPop));
    pexp : RPNPush(NewFunc(expx,RPNPop));
    pln  : RPNPush(NewFunc(lnx,RPNPop));
    pinv : RPNPush(NewFunc(invx,RPNPop));
  Pminus : RPNPush(newFunc(minus,RPNPop));
  pcotan : RPNPush(NewFunc(cotanx,rpnpop));
 parcsin : RPNPush(NewFunc(arcsinx,rpnpop));
 parccos : RPNPush(NewFunc(arccosx,rpnpop));
 parctan : RPNPush(NewFunc(arctanx,rpnpop));
   psinh : RPNPush(NewFunc(sinhx,rpnpop));
   pcosh : RPNPush(NewFunc(coshx,rpnpop));
   ptanh : RPNPush(NewFunc(tanhx,rpnpop));
parcsinh : RPNPush(NewFunc(arcsinhx,rpnpop));
parccosh : RPNPush(NewFunc(arccoshx,rpnpop));
parctanh : RPNPush(NewFunc(arctanhx,rpnpop));
  plog10 : RPNPush(NewFunc(log10x,rpnpop));
   plog2 : RPNPush(NewFunc(log2x,rpnpop));
  plnxpi : RPNPush(NewFunc(lnxpix,rpnpop));
parctan2 : begin
            Temp:=RpnPop;
            RpnPush(Newfunc(arctan2x,RpnPop,temp));
           end;
   pstep : begin
            Temp:=RpnPop;
            RpnPush(Newfunc(stepx,RpnPop,temp));
           end;
   phypot: begin
            Temp:=RpnPop;
            RpnPush(Newfunc(hypotx,RpnPop,temp));
           end;
   plogn : begin
            Temp:=RpnPop;
            RpnPush(Newfunc(lognx,RpnPop,Temp));
          end;
   else
    ParserInternalError('Unknown function',1,ORD(Operation));
   end;
end;

Function IsFunction(S:String):ParseOperation;

var Count:ParseOperation;

begin
 IsFunction:=pnothing;
 for Count:=pCos to pInv do {Minus is a pseudo function, and in this category
                            because it has only 1 argument}
  begin
   If Copy(S,1,3)=ParserFunctionNamesUpper[Count] then
    IsFunction:=Count;
  end;
end;

Procedure OpPush(operation : ParseOperation);  { Add an operation onto top of the stack }
begin
  if OpTop < OpMax then
  begin
    Inc(OpTop);
    OpStack[OpTop] := operation;
  end
  else
   RAISE EParserStack.Create(SParsOpOverflow);
end;

Function OpPop : ParseOperation;        { Get operation at the top of the stack }
begin
  if OpTop > 0 then
  begin
    OpPop := OpStack[OpTop];
    Dec(OpTop);
  end
  else
   RAISE EParserStack.Create(SParsOpUnderflow);
end;

Var
  I,len       : ArbInt;
  Token       : String15;
  OperationNr : ParseOperation;
  FunctionNr  : ArbInt;
  isminus     : boolean;

begin
  RPNExpr:='';
  OpTop  := 0;                                              { Reset stacks }
  RPNTop := 0;
  Token  := '';
  {$ifdef fpc}
   Expr:=Upcase(Expr);
  {$endif}
  i:=1; len:=Length(Expr);
  while I<=Len do
   begin
    {Flush token, if we feel an infix operator coming}
     FunctionNr:=Pos(expr[I],OppsXlat);
     If (FunctionNr<>0) and (Token<>'') THEN
       begin                         { Send last built number to calc. }
        RPNCalc(Token);
        Token := '';
       end;
    If (FunctionNr>0) and (FunctionNr<7) then
    begin
      OperationNr:=ParseOperation(FunctionNr-1+ORD(padd));
      If (OperationNr=psub) then  {Minus(x) or x-y?}
       begin
        IsMinus:=False;
        if I=1 then
         IsMinus:=true
        else
         If Expr[I-1] IN ['+','(','*','/','-','^'] then
          IsMinus:=true;
        If IsMinus then
         OperationNr:=PMinus;
       end;
      While (OpTop > 0) AND
                (Priority[OperationNr] <= Priority[OpStack[OpTop]]) DO
                 RPNOperation(OpPop);
      OpPush(OperationNr);
     end
    else
    case Expr[I] of
      '0'..'9' : begin
                  While (Expr[I] in AllowedInToken) and (I<=len) do
                   begin
                    Token:=Token+Expr[I];
                    inc(i);
                   end;
                  dec(i);
                 end;
      ','      :  if Token <> '' then              {Two parameter functions}
                   begin        { Send last built number to calc. }
                    RPNCalc(Token);
                    Token := '';
                   end;
      '('      : OpPush(pleft);
      ')'      : begin
                  While OpStack[OpTop] <> pleft DO
                   RPNOperation(OpPop);
                  OpPop;                    { Pop off and ignore the '(' }
                 end;
      'A'..'Z' : begin
                  if Token <> '' then
                   begin        { Send last built number to calc. }
                    RPNCalc(Token);
                    Token := '';
                   end;
                 While (Expr[I] IN ['0'..'9','A'..'Z']) AND (I<=Len) DO
                  begin
                   Token:=Token+Expr[I];
                   Inc(I);
                  end;
                 Dec(i);
                 OperationNr:=IsFunction(Token);
                 if OperationNr<>pnothing then
                  begin
                   Token:='';
                   While (OpTop > 0) AND
                   (Priority[OperationNr] <= Priority[OpStack[OpTop]]) DO
                   RPNOperation(OpPop);
                  OpPush(OperationNr);
                  end
                 else
                  begin
                   RpnCalc(Token);
                   Token:='';
                  end;
                end;
     end; { Case }
     inc(i);
   end;
  If Token<>'' Then
   RpnCalc(Token);

  While OpTop > 0 do                     { Pop off the remaining operations }
    RPNOperation(OpPop);
 InFixToParseTree:=RpnPop;
end;

function TBaseExprParser.ParseTreeToInfix(expr:pnode):string;

var S,right,left : string;
    IsSimpleExpr : boolean;

begin
 IF expr=nil then
  ParserInternalError(SNILDeref,5,0);
 case expr^.nodetype of
   VarNode  : S:=expr^.variable;
  iconstnode: str(expr^.ivalue,S);
   ConstNode: str(expr^.value,s);
   CalcNode : begin
                right:=ParseTreeToInfix(expr^.right);
                left:=ParseTreeToInfix(expr^.left);
                S:=left+InfixOperatorName[Expr^.op]+right;
                if (expr^.op=addo) or (expr^.op=subo) then
                 S:='('+S+')';
              end;
   FuncNode : begin
               left:=functionnames[expr^.fun];
               right:=ParseTreeToInfix(expr^.son);
               issimpleExpr:=false;
               If ((Expr^.fun=minus) or (Expr^.fun=faculx)) and
                  (expr^.son^.nodetype in [varnode,iconstnode,constnode]) then
                issimpleExpr:=true;
               if expr^.fun<>faculx then
                begin
                 If IsSimpleExpr then
                  S:=Left+Right
                 else
                  S:=Left+'('+Right+')';
                 end
                else
                 If IsSimpleExpr then
                  S:=Right+Left
                 else
                  S:='('+Right+')'+Left;
              end;
  Func2Node : begin
               S:=functionnames[expr^.fun];
               Left:=ParseTreeToInfix(Expr^.son2right);
               right:=ParseTreeToInfix(expr^.son2left);
               S:=S+'('+Left+','+Right+')';
              end;
  end;
  ParseTreeToInfix:=S;
end;

function TBaseExprParser.ParseTreeToRPN(expr:pnode):string;
{not fast because of the prepending. Creating an array of pnode would maybe
be faster}

procedure SearchTree(Tree:pnode;var s:string);

var temp:string;

begin
 if tree<>nil then
 case Tree^.nodetype of
  VarNode  : s:=Tree^.Variable +' '+s;
  ConstNode: begin
              str(Tree^.value:5:9,temp);              {should be configurable}
              s:=temp+' '+s;
             end;
 iconstnode: begin
              str(Tree^.ivalue,temp);
              s:=temp+' '+s;
             end;
 CalcNode  : begin
               s:=InfixOperatorName[Tree^.op]+' '+s;
               SearchTree(tree^.right,s);
               SearchTree(tree^.left,s);
             end;
 FuncNode:    begin
               s:=functionnames[tree^.fun]+' '+s;
               SearchTree(tree^.son,s);
             end;
  Func2Node:   begin
               s:=functionnames[tree^.fun]+' '+s;
               SearchTree(tree^.son2right,s);
               SearchTree(Tree^.son2left,s);
              end;
    end;
end;

var s : String;

begin
 s:='';
 searchTree(expr,s);
 ParseTreeToRPN:=S;
end;

{
  $Log$
  Revision 1.1  2002/12/15 21:01:24  marco
  Initial revision

}