Repository URL to install this package:
Version:
3.0.0 ▾
|
{
$ 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
}