{ eval.pas } PROGRAM evalexpr(input,output); { Evaluate an infix expression typed on the command line. Give no arguments to get the help message. Bruce K. Hillyer. This program is written for Microsoft pascal to use the REAL8 type, which seems to avoid answers like 0.999999999999999 when the correct answer is 1. Note that some versions of Microsoft pascal incorrectly decide that your pc has an 8087 or 80287 math coprocessor when in fact it doesn't. To check this, try a simple multiplication. If eval 2*3 says 2, rather than 6, set the enviornment variable set NO87=X in your autoexec.bat file. This code is derived in part from the spreadsheet that comes with turbo pascal, which contains the following message: MICROCALC DEMONSTRATION PROGRAM Version 1.00A This program is hereby donated to the public domain for non-commercial use only. Dot commands are for the program lister: LISTT.PAS (available with our TURBO TUTOR): .PA, .CP20, etc... } TYPE exprStr = LSTRING(80); VAR cmdTail : ADS OF LSTRING(80); Cesxqq [EXTERN] : WORD; retnVl : REAL8; errLoc : INTEGER; i : INTEGER; { functions for REAL8 } FUNCTION Andrqq(CONSTS a : REAL8) : REAL8; EXTERN; { round } FUNCTION Aidrqq(CONSTS a : REAL8) : REAL8; EXTERN; { trunc } FUNCTION Srdrqq(CONSTS a : REAL8) : REAL8; EXTERN; { sqrt } FUNCTION Sndrqq(CONSTS a : REAL8) : REAL8; EXTERN; { sin } FUNCTION Cndrqq(CONSTS a : REAL8) : REAL8; EXTERN; { cos } FUNCTION Tndrqq(CONSTS a : REAL8) : REAL8; EXTERN; { tan } FUNCTION Asdrqq(CONSTS a : REAL8) : REAL8; EXTERN; { arcsin } FUNCTION Acdrqq(CONSTS a : REAL8) : REAL8; EXTERN; { arccos } FUNCTION Atdrqq(CONSTS a : REAL8) : REAL8; EXTERN; { arctan } FUNCTION Shdrqq(CONSTS a : REAL8) : REAL8; EXTERN; { sinh } FUNCTION Chdrqq(CONSTS a : REAL8) : REAL8; EXTERN; { cosh } FUNCTION Thdrqq(CONSTS a : REAL8) : REAL8; EXTERN; { tanh } FUNCTION Lndrqq(CONSTS a : REAL8) : REAL8; EXTERN; { ln } FUNCTION Lddrqq(CONSTS a : REAL8) : REAL8; EXTERN; { log } FUNCTION Exdrqq(CONSTS a : REAL8) : REAL8; EXTERN; { exp } FUNCTION Pidrqq(CONSTS a : REAL8; CONSTS b : INTEGER4) : REAL8; EXTERN;{power} FUNCTION Prdrqq(CONSTS a, b : REAL8) : REAL8; EXTERN; { power } FUNCTION Mddrqq(CONSTS a, b : REAL8) : REAL8; EXTERN; { mod } FUNCTION Mndrqq(CONSTS a, b : REAL8) : REAL8; EXTERN; { min } FUNCTION Mxdrqq(CONSTS a, b : REAL8) : REAL8; EXTERN; { max } PROCEDURE Endxqq; EXTERN; { halt } PROCEDURE strToNum(formula : exprStr; start, len : INTEGER; VAR retVal : REAL8; VAR errPos : INTEGER); VAR tempStr : LSTRING(80); i : INTEGER; BEGIN FOR i:=1 TO len DO tempStr[i] := formula[start+i-1]; tempStr.Len := Wrd(len); WHILE (tempStr.Len > 0) AND (tempStr[1] = ' ') DO Delete(tempStr,1,1); IF tempStr[1] = '.' THEN Insert('0',tempStr,1); IF tempStr[1] = '+' THEN Delete(tempStr,1,1); IF NOT Decode(tempStr,retVal) THEN errPos := start END; { strToNum } PROCEDURE printNum(num : REAL8); VAR pointLoc : INTEGER; tempStr : LSTRING(40); BEGIN IF (num = Andrqq(num)) AND (num <= 1.0e17) THEN { integer } BEGIN IF NOT Encode(tempStr,num:1:0) THEN Writeln(output,'output bug '); tempStr.Len := Wrd(Ord(tempStr.Len) - 1); { no point } Writeln(output,tempStr) END ELSE IF Abs(num) > 1.0e6 THEN Writeln(output,num:24) { big float } ELSE BEGIN IF NOT Encode(tempStr,Abs(num):1:16) THEN Write(output,'output bug '); { the position of the decimal point is one more than the number of digits in the absolute value of the integer part } pointLoc := Positn('.',tempStr,1); IF pointLoc = 0 THEN Writeln(output,num:1:0) ELSE BEGIN IF NOT Encode(tempStr,num:1:(16-pointLoc)) THEN Write(output,'output bug '); WHILE (Ord(tempStr.Len) > pointLoc) AND (tempStr[Ord(tempStr.Len)] = '0') DO tempStr.Len := Wrd(Ord(tempStr.Len) - 1); IF tempStr[Ord(tempStr.Len)] = '.' THEN tempStr.Len := Wrd(Ord(tempStr.Len) - 1); Writeln(output,tempStr) END END END; { printNum } PROCEDURE evaluate(formula : exprStr; VAR exprVl: REAL8; VAR errPos: INTEGER); { evaluate the formula } VAR pos : INTEGER; { current position in formula } ch : CHAR; { Current character being scanned } PROCEDURE nextCh; { get the next character into ch, set pos, indicates eos } BEGIN REPEAT pos := pos + 1; IF pos <= Ord(formula.Len) THEN ch := formula[pos] ELSE ch := Chr(0) UNTIL ch <> ' ' END; { nextCh } FUNCTION expression : REAL8; VAR e : REAL8; FUNCTION simpleExpression : REAL8; VAR s : REAL8; FUNCTION term : REAL8; VAR t,t2 : REAL8; FUNCTION signedFactor : REAL8; FUNCTION factor : REAL8; TYPE builtin = (fabs, fround, ftrunc, fsqrt, fsqr, fsin, fcos, ftan, farcsin, farccos, farctan, fsinh, fcosh, ftanh, fln, flog, flog2, fexp, ffact); builtinList = ARRAY[builtin] OF LSTRING(6); CONST builtinNames = builtinList ('abs', 'round', 'trunc', 'sqrt', 'sqr', 'sin', 'cos','tan', 'arcsin', 'arccos', 'arctan', 'sinh', 'cosh', 'tanh', 'ln', 'log', 'log2', 'exp', 'fact'); VAR e,l : INTEGER; { intermediate variables } found : BOOLEAN; f : REAL8; fn : builtin; start : INTEGER; FUNCTION thisFn(inp : exprStr; pos : INTEGER; fn : builtin) : BOOLEAN; { see if the input at location pos contains the fn name } VAR i : INTEGER; BEGIN thisFn := TRUE; FOR i:=1 TO Ord(builtinNames[fn].Len) DO IF inp[i+pos-1] <> builtinNames[fn,i] THEN thisFn := FALSE END; { thisFn } FUNCTION factorial(arg : REAL8): REAL8; BEGIN arg := Andrqq(arg); { round it to avoid strangeness } IF arg > 170 THEN BEGIN Writeln(output,'factorial: Too large argument'); Endxqq END; IF arg < 0 THEN BEGIN Writeln(output,'factorial: Negative argument'); Endxqq END; IF arg > 0 THEN factorial := arg * factorial(arg-1) ELSE factorial := 1 END; { factorial } FUNCTION log2(CONSTS a : REAL8) : REAL8; BEGIN log2 := Lndrqq(a) / Lndrqq(2.0) END; { log2 } BEGIN { factor } IF ((ch >= '0') AND (ch <= '9')) OR (ch = '.') THEN BEGIN start := pos; REPEAT nextCh UNTIL (ch < '0') OR (ch > '9'); IF ch = '.' THEN REPEAT nextCh UNTIL (ch < '0') OR (ch > '9'); IF (ch='E') OR (ch='e') THEN BEGIN nextCh; REPEAT nextCh UNTIL (ch < '0') OR (ch > '9') END; strToNum(formula,start,pos-start,f,errPos) END ELSE IF ch='(' THEN BEGIN nextCh; f := expression; IF ch=')' THEN nextCh ELSE errPos := pos END ELSE BEGIN { parse builtin function } found := false; FOR fn := Lower(fn) TO Upper(fn) DO IF NOT found THEN BEGIN { check this function name } l := Ord(builtinNames[fn].Len); IF thisFn(formula,pos,fn) THEN BEGIN { call builtin } pos := pos + l - 1; nextCh; f := factor; CASE fn OF fabs: f:=Abs(f); fround: f:=Andrqq(f); ftrunc: f:=Aidrqq(f); fsqrt: f:=Srdrqq(f); fsqr: f:=f*f; fsin: f:=Sndrqq(f); fcos: f:=Cndrqq(f); ftan: f:=Tndrqq(f); farcsin: f:=Asdrqq(f); farccos: f:=Acdrqq(f); farctan: f:=Atdrqq(f); fsinh : f:=Shdrqq(f); fcosh : f:=Chdrqq(f); ftanh : f:=Thdrqq(f); fln : f:=Lndrqq(f); flog: f:=Lddrqq(f); flog2: f:=log2(f); fexp: f:=Exdrqq(f); ffact: f:=factorial(f); END; { CASE } found := TRUE; END; { call builtin } END; { check this function name } IF NOT found THEN errPos := pos; END; { parse builtin function } factor := f END; { factor } BEGIN { signedFactor } WHILE ch = ' ' DO nextCh; IF ch = '-' THEN BEGIN nextCh; signedFactor := -factor END ELSE IF ch = '+' THEN BEGIN nextCh; signedFactor := factor END ELSE signedFactor := factor END; { signedFactor } BEGIN { term } t := signedFactor; WHILE (ch = '^') AND (errPos = 0) DO BEGIN nextCh; t2 := signedFactor; { check if t2 is integer by rounding } IF t2 = Andrqq(t2) THEN t := Pidrqq(t,Round4(t2)) ELSE t := Prdrqq(t,t2) END; term := t END; { term } BEGIN { simpleExpression } s := term; WHILE ((ch = '*') OR (ch = '/') OR (ch = '\') OR (ch = 'm')) AND (errPos = 0) DO IF ch = '/' THEN BEGIN nextCh; s := s / term END ELSE IF ch = '*' THEN BEGIN nextCh; s := s * term END ELSE IF ch = '\' THEN BEGIN nextCh; s := Mddrqq(s,(term)) END ELSE IF ch = 'm' THEN BEGIN nextCh; IF ch = 'i' THEN BEGIN nextCh; IF ch = 'n' THEN BEGIN nextCh; s := Mndrqq(s,(term)) END ELSE errPos := pos END ELSE IF ch = 'a' THEN BEGIN nextCh; IF ch = 'x' THEN BEGIN nextCh; s := Mxdrqq(s,(term)) END ELSE errPos := pos END ELSE errPos := pos END; simpleExpression := s END; { simpleExpression } BEGIN { expression } e := simpleExpression; WHILE ((ch = '+') OR (ch = '-')) AND (errPos = 0) DO IF ch = '-' THEN BEGIN nextCh; e := e - simpleExpression END ELSE BEGIN nextCh; e := e + simpleExpression END; expression := e END; { expression } BEGIN { evaluate } { first lower case the string } FOR pos:=1 TO Ord(formula.Len) DO IF (formula[pos] >= 'A') AND (formula[pos] <= 'Z') THEN formula[pos] := Chr(Ord(formula[pos]) + Ord('a') - Ord('A')); pos := 0; errPos := 0; nextCh; exprVl := expression; IF ch <> Chr(0) THEN errPos := pos END; { evaluate } BEGIN { main } cmdTail.S := Cesxqq; cmdTail.R := 128; IF cmdTail^.Len = 0 THEN BEGIN Writeln(output, 'Infix expressions using: + - * / \ ^ ( ) max min'); Writeln(output,' unary prefix operators: + - abs round trunc', ' sqrt sqr sin cos tan'); Writeln(output,' arcsin arccos arctan', ' sinh cosh tanh'); Writeln(output,' ln log log2 exp', ' fact'); END ELSE IF cmdTail^ = ' who' THEN Writeln(output,'adapted from Turbo Pascal spreadsheet, Bruce K. Hillyer') ELSE BEGIN evaluate(cmdTail^,retnVl,errLoc); IF errLoc > 0 THEN BEGIN Write(output,' '); { pass the 'C>eval' } FOR i:=1 TO errLoc-1 DO Write(output,' '); Writeln(output,'^----- error') END ELSE printNum(retnVl) END END. .