
{ 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, <cr> 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.
