(**********************************************************
*
*		CALCULATOR PROGRAM
*
*	I gave this a quick check and it works.  It looks 
*  like it has some extra things that I didn't check out so
*  if someone does and would be so kind to send me a DOC, I
*  will republish it.  Not everyone who Modified this pro-
*  gram left their name but for those who did I left in.
*
*  Donated July, 1980
*
************************************************************)

PROGRAM CALCULATOR;(*WRITTEN BY DALE ANDER JULY 8, 1977
                      MODIFIED JULY 17, 1977*)

LABEL 999; (*PROGRAM EXIT POINT*)
CONST IDLENGTH = 8;{---19/6/80---}
      TABLESIZE = 35;  (*TABLESIZE IS MEMORYSIZE*)
      IDBLANKS = '        '; {---8 blanks---}
      LASTX = 'LASTX   ';

TYPE  TOKENKINDS = (CONSTV, EOFV, FUCIDENV, LINEV, LPARENV, MINUSV, PLUSV,
                     RPARENV, SLASHV, STARV, UNRECIDV, UNRECSYMV, UPARROWV,
                     VARIDENV, EQUALV,  LASTXV);
	idkind = packed array[1..idlength] of char;
(*---IDKIND = PACKED ARRAY[0..IDLENGTH] OF CHAR;---*)

	$STRING0 = STRING 0;
	$STRING255 = STRING 255;
	STRING80   = STRING 80; (*---80 IS THE DEFAULT LENGTH---*)

VAR
  CH		: CHAR;
  J,
  TOTALIDS,
  INDEX		: INTEGER;
  OPERATORS,
  ALPHA,
  NUMERIC	: SET OF CHAR;
  NUM,
  ANSWER	: REAL;
  SOURCE	: STRING80; (*---PASCAL/Z---*)
  TOKENTYPE	: TOKENKINDS;
  NAMETABLE	: ARRAY[0..TABLESIZE] OF
				RECORD
				  NAME: IDKIND;
				  CASE ISVAR: BOOLEAN OF
				    TRUE: (VALUE: REAL)
				END;
  TEMP		: REAL;
  ITSOK,
  GAVEERR	: BOOLEAN;

FUNCTION LENGTH(X: $STRING255): INTEGER; EXTERNAL;
PROCEDURE SETLENGTH(VAR X: $STRING0; Y: INTEGER); EXTERNAL;

PROCEDURE GETCHAR;
BEGIN
  J:=J+1;  (*J IS INDEX INTO SOURCE*)
  IF J<=LENGTH(SOURCE) THEN
    CH:=SOURCE[J]
  ELSE
    CH:='#';  (*EOF SOURCE CHAR*)
  IF (CH>='a') AND (CH<='z') THEN
    CH := CHR(ORD(CH)-32) (*CHANGE TO UPPER CASE*)
END (*OF GETCHAR*);

PROCEDURE SCANNER;
VAR DONTEAT: BOOLEAN;

PROCEDURE GETCONSTANT;
  (*Real number scanner	RJH 9 July 77*)
VAR WHOLEPART:  REAL;
    DODECIMAL:  BOOLEAN;

  FUNCTION NUMBER (FRACTION: BOOLEAN): REAL;
  (*Returns number as whole or fraction*)
  VAR SUM, COUNT: REAL;
  BEGIN
    COUNT:=1;
    SUM:=0;
    REPEAT
      IF SUM < 0.9E37 (*MAXREAL*) THEN
	BEGIN
	  SUM := 10*SUM + ORD(CH) - ORD('0');
	  COUNT:=10*COUNT
	END;
      GETCHAR
    UNTIL NOT (CH IN NUMERIC);
    IF FRACTION THEN
       NUMBER:=SUM/COUNT
    ELSE
       NUMBER:=SUM
  END (*NUMBER*);

BEGIN (*GETCONSTANT*)
  TOKENTYPE:=CONSTV;
  IF CH <> '.' THEN
    BEGIN
      WHOLEPART:=NUMBER(FALSE);
      IF CH='.' THEN GETCHAR;
      DODECIMAL:=(CH IN NUMERIC);
    END
  ELSE
    BEGIN
      WHOLEPART:=0;
      GETCHAR;
      DODECIMAL:=(CH IN NUMERIC);
      IF NOT DODECIMAL THEN TOKENTYPE:=UNRECSYMV
    END;
  IF DODECIMAL THEN
     NUM:=WHOLEPART + NUMBER(TRUE)
  ELSE
     NUM:=WHOLEPART;
  DONTEAT:=CH<>' '; (*DONT EAT NEXT IF CH IS NONBLANK  DA 7/11/77*)
END (*OF GETCONSTANT*);

PROCEDURE GETID;
VAR ID: IDKIND;
    I: INTEGER;

  FUNCTION LOOKUP(IDTEXT: IDKIND):INTEGER;
  VAR I: INTEGER;

  BEGIN
    I:=TOTALIDS;
    NAMETABLE[0].NAME:=IDTEXT;(*DON'T CHANGE--THIS IS USED
                                 INSIDE OF PRIMARY!!*)
    WHILE NAMETABLE[I].NAME<>IDTEXT DO  I:=I-1;
    LOOKUP:=I
  END (*OF LOOKUP*);

BEGIN (*GETID*)
  ID:=IDBLANKS;
  I:=1;{---start at position #1 NOT position #0---}
  REPEAT
    IF I<=IDLENGTH THEN ID[I]:=CH;
    I:=I+1;
    GETCHAR
  UNTIL NOT(CH IN ['A'..'Z','0'..'9']);
  DONTEAT:=CH<>' '; (*DONT GET NEXT IF CH IS NONBLANK*)
  IF ID=LASTX THEN
    TOKENTYPE:=LASTXV
  ELSE
    BEGIN
      INDEX:=LOOKUP(ID);
      IF INDEX>0 THEN
        IF NAMETABLE[INDEX].ISVAR THEN
	   TOKENTYPE:=VARIDENV
        ELSE
	   TOKENTYPE:=FUCIDENV
      ELSE
	TOKENTYPE:=UNRECIDV
    END
END (*OF GETID*);

BEGIN (*SCANNER*)
  DONTEAT:=FALSE;
  IF CH IN ALPHA THEN GETID
  ELSE
    IF CH IN NUMERIC+['.'] THEN GETCONSTANT
    ELSE
      IF CH IN OPERATORS THEN
        CASE CH OF
	  '+': TOKENTYPE:=PLUSV;
	  '-': TOKENTYPE:=MINUSV;
	  '*': TOKENTYPE:=STARV;
	  '/': TOKENTYPE:=SLASHV;
	  '\': TOKENTYPE:=LINEV;
	  '^': TOKENTYPE:=UPARROWV;
	  '(': TOKENTYPE:=LPARENV;
	  ')': TOKENTYPE:=RPARENV;
	  '=': TOKENTYPE:=EQUALV;
	  '#': BEGIN TOKENTYPE:=EOFV; DONTEAT:=TRUE END
        END
      ELSE TOKENTYPE:=UNRECSYMV;
  IF NOT DONTEAT THEN REPEAT GETCHAR UNTIL CH<>' ' (*GETNONBLANK*)
END (*OF SCANNER*);

FUNCTION EXPRESS(VAR ANS: REAL): BOOLEAN ;
VAR OK, CHANGESIGN: BOOLEAN;
    RSLT1, RSLT2: REAL;
    SAVEOP: TOKENKIND;

FUNCTION TERM(VAR ANS: REAL): BOOLEAN ;
VAR OK: BOOLEAN;
    SAVEOP: TOKENKIND;
    RSLT1, RSLT2: REAL;

FUNCTION FACTOR(VAR ANS: REAL): BOOLEAN ;
VAR OK: BOOLEAN;
    RSLT1, RSLT2: REAL;

FUNCTION PRIMARY(VAR ANS: REAL): BOOLEAN ;
(*REWRITTEN BY RJH 12 JULY 77
   REREWRITTEN BY DA 7/14/77*)
VAR  FUCNUM, SAVEINDEX: INTEGER;
     SAVEID: IDKIND;
     SAVETOK: TOKENKINDS;

FUNCTION PARENEXPRESSION(VAR ANS: REAL): BOOLEAN ;
BEGIN
  PARENEXPRESSION:=FALSE;
  IF TOKENTYPE=LPARENV THEN
    BEGIN
      SCANNER;
      IF EXPRESS(ANS) THEN
        IF TOKENTYPE=RPARENV THEN
	  BEGIN SCANNER; PARENEXPRESSION:=TRUE END
        ELSE
          IF TOKENTYPE<>EOFV THEN
            BEGIN GAVEERR:=TRUE; WRITE ('")"  missing') END
    END
  ELSE
    IF TOKENTYPE IN [UNRECIDV, UNRECSYMV] THEN
      BEGIN GAVEERR:=TRUE; WRITE ('Illegal symbol') END
    ELSE
      IF TOKENTYPE<>EOFV THEN
        BEGIN GAVEERR:=TRUE; WRITE ('"(" missing') END
END (*OF PARENEXPRESSION*);

FUNCTION EVALU8 (VAR ANS: REAL): BOOLEAN;
VAR ARG, TEMP: REAL;
    I: INTEGER;

	Function LOG(x:real):real;
	{ Returns the LOG to base 10 }
	begin
	  LOG := LN(10) / LN(x)
	end;

BEGIN
  EVALU8:=TRUE;
  IF PARENEXPRESSION (ARG) THEN
    CASE FUCNUM OF
      1: ANS:=SIN(ARG);
      2: ANS:=COS(ARG);
      3: IF COS(ARG)=0 THEN
	    BEGIN WRITE('Undefined TAN'); GAVEERR:=TRUE END
	 ELSE
	    ANS:=SIN(ARG)/COS(ARG);
      4: IF ARG<=0 THEN
	    BEGIN WRITE('Undefined LOG'); GAVEERR:=TRUE END
	 ELSE
	    ANS:=LOG(ARG);
      5: IF ARG<=0 THEN
	    BEGIN WRITE('Undefined LN'); GAVEERR:=TRUE END
	 ELSE
	    ANS:=LN(ARG);
      6: ANS:=ABS(ARG);
      7: IF ARG<0 THEN
	    BEGIN WRITE('Undefined SQRT'); GAVEERR:=TRUE END
	 ELSE
	    ANS:=SQRT(ARG);
      10: IF (ROUND(ARG)>33) OR (ROUND(ARG)<0) THEN
            BEGIN
	      WRITE('Cannot calculate factorial GTR 33');
	      GAVEERR:=TRUE
	    END
          ELSE
            BEGIN
              TEMP:=1;
              FOR I:=2 TO ROUND(ARG) DO TEMP:=TEMP*I;
              ANS:=TEMP
            END
    END (*OF CASE*)
  ELSE EVALU8:=FALSE;
  IF GAVEERR THEN EVALU8:=FALSE
END (*OF EVALU8*);

BEGIN (*PRIMARY*)
  PRIMARY:=FALSE;
  IF TOKENTYPE=CONSTV THEN (*CONSTANT*)
    BEGIN
      ANS:=NUM; (*GLOBAL SET BY GETCONSTANT*)
      PRIMARY:=TRUE;
      SCANNER
    END
  ELSE
    IF TOKENTYPE IN [VARIDENV, UNRECIDV] THEN
      BEGIN
        SAVETOK:=TOKENTYPE;
        SAVEID:=NAMETABLE[0].NAME; (*PUT THERE BY LOOKUP IN GETID*)
        SAVEINDEX:=INDEX; (*GLOBAL SET IN GETID*)
        SCANNER;
        IF TOKENTYPE=EQUALV THEN  (*MEMORY ASSIGNMENT*)
          BEGIN
            SCANNER;
	    IF EXPRESS(ANS) THEN
	      BEGIN
		IF SAVETOK=UNRECIDV THEN
		  IF TOTALIDS+1<=TABLESIZE THEN
		    BEGIN
		      TOTALIDS:=TOTALIDS+1;
		      SAVEINDEX:=TOTALIDS;
		      WITH NAMETABLE[SAVEINDEX] DO
			BEGIN ISVAR:=TRUE; NAME:=SAVEID END
  		    END
                  ELSE
                    BEGIN WRITE('Table full. Not done'); GAVEERR:=TRUE END;
		IF SAVEINDEX<>0 THEN
		  BEGIN NAMETABLE[SAVEINDEX].VALUE:=ANS; PRIMARY:=TRUE END
	      END
	  END
	ELSE
          IF SAVETOK=UNRECIDV THEN
            BEGIN WRITE('Unrecognized ID'); GAVEERR:=TRUE END
	  ELSE
            BEGIN PRIMARY:=TRUE; ANS:=NAMETABLE[SAVEINDEX].VALUE END
      END
    ELSE
      IF TOKENTYPE=FUCIDENV THEN (*FUNCTION*)
	BEGIN
	  FUCNUM:=INDEX; (*INDEX SET BY GETIDENT*)
	  SCANNER;
	  PRIMARY:=EVALU8 (ANS)
	END
      ELSE
        IF TOKENTYPE=LASTXV THEN
 	  BEGIN SCANNER; ANS:=ANSWER; PRIMARY:=TRUE END
        ELSE PRIMARY:=PARENEXPRESSION (ANS)
END (*OF PRIMARY*);

BEGIN (*FACTOR*)
  OK:=TRUE;
  IF PRIMARY(RSLT1) THEN
    WHILE OK AND (TOKENTYPE=UPARROWV) DO
      BEGIN
	SCANNER;
	IF PRIMARY(RSLT2) THEN
          IF RSLT1<=0 THEN
            BEGIN
	      WRITE('Cannot calculate power');
	      OK:=FALSE;
	      GAVEERR:=TRUE
	    END
          ELSE
	    RSLT1:=EXP(RSLT2*LN(RSLT1))
	ELSE
	  OK:=FALSE
      END
  ELSE
    OK:=FALSE;
  IF OK THEN ANS:=RSLT1;
  FACTOR:=OK
END (*OF FACTOR*);

BEGIN (*TERM*)
  OK:=TRUE;
  IF FACTOR(RSLT1) THEN
    WHILE OK AND (TOKENTYPE IN [STARV, SLASHV, LINEV]) DO
      BEGIN
        SAVEOP:=TOKENTYPE;
	SCANNER;
	IF FACTOR(RSLT2) THEN
	  CASE SAVEOP OF
	    STARV:	 RSLT1:=RSLT1*RSLT2;
	    SLASHV:	 IF RSLT2=0 THEN
			   BEGIN
			     OK:=FALSE;
			    GAVEERR:=TRUE;
			    WRITE('Division by zero')
			  END
			ELSE RSLT1:=RSLT1/RSLT2;
	    LINEV:	IF ROUND(RSLT2)=0 THEN
			  BEGIN
			    OK:=FALSE;
			    GAVEERR:=TRUE;
			    WRITE('MOD by zero')
			  END
			ELSE
		          RSLT1:=ROUND(RSLT1) MOD ROUND(RSLT2)
	  END (*CASE*)
	ELSE OK:=FALSE
      END
  ELSE OK:=FALSE;
  IF OK THEN ANS:=RSLT1;
  TERM:=OK
END (*TERM*);


BEGIN (*EXPRESS*)
  OK:=TRUE;
  IF TOKENTYPE IN [PLUSV,MINUSV] THEN
    BEGIN CHANGESIGN:=(TOKENTYPE=MINUSV); SCANNER END
  ELSE CHANGESIGN:=FALSE;
  IF TERM(RSLT1) THEN
    BEGIN
      IF CHANGESIGN THEN RSLT1:=-RSLT1;
      WHILE OK AND (TOKENTYPE IN [PLUSV,MINUSV]) DO
        BEGIN
          SAVEOP:=TOKENTYPE;
          SCANNER;
  	  IF TERM(RSLT2) THEN
  	    CASE SAVEOP OF
  	      PLUSV:	RSLT1:=RSLT1+RSLT2;
  	      MINUSV:	RSLT1:=RSLT1-RSLT2
  	    END
  	  ELSE OK:=FALSE
        END
    END
  ELSE OK:=FALSE;
  EXPRESS:=OK;
  IF OK THEN ANS:=RSLT1
END (*OF EXPRESS*);

PROCEDURE INITABLES;
BEGIN
  ALPHA:=['A'..'Z'];
  NUMERIC:=['0'..'9'];
  OPERATORS:=['+','=','*','-','/','\','^','(',')','#'];
  WITH NAMETABLE[1] DO
    BEGIN NAME:='SIN     '; ISVAR:=FALSE END;
  WITH NAMETABLE[2] DO
    BEGIN NAME:='COS     '; ISVAR:=FALSE END;
  WITH NAMETABLE[3] DO
    BEGIN NAME:='TAN     '; ISVAR:=FALSE END;
  WITH NAMETABLE[4] DO
    BEGIN NAME:='LOG     '; ISVAR:=FALSE END;
  WITH NAMETABLE[5] DO
    BEGIN NAME:='LN      '; ISVAR:=FALSE END;
  WITH NAMETABLE[6] DO
    BEGIN NAME:='ABS     '; ISVAR:=FALSE END;
  WITH NAMETABLE[7] DO
    BEGIN NAME:='SQRT    '; ISVAR:=FALSE END;
  WITH NAMETABLE[8] DO
    BEGIN NAME:='E       '; ISVAR:=TRUE; VALUE:=2.718282 END;
  WITH NAMETABLE[9] DO
    BEGIN NAME:='PI      '; ISVAR:=TRUE; VALUE:=3.141593 END;
  WITH NAMETABLE[10] DO
    BEGIN NAME:='FAC     '; ISVAR:=FALSE END;
  TOTALIDS:=10 (*BUILD IN NUMBER OF FUNCS & VARS*)
END (*INITABLES*);

BEGIN (*CALCULATOR*)
  ANSWER:=0;
  INITABLES;
  REPEAT
    SETLENGTH(SOURCE,0);{---PASCAL/Z---}
    GAVEERR:=FALSE;
    J:=0;
    WRITE('->');
    READLN(SOURCE);
    IF LENGTH(SOURCE)=0 THEN{EXIT(PROGRAM)}goto 999;
    REPEAT GETCHAR UNTIL CH<>' '; (*GETNONBLANK*)
    SCANNER;
    ITSOK:=EXPRESS(TEMP) AND (TOKENTYPE=EOFV);
    IF NOT ITSOK THEN
      BEGIN
	IF (TOKENTYPE=EOFV) AND NOT GAVEERR THEN
	  WRITE ('Unexpected end of expression')
        ELSE IF NOT GAVEERR THEN WRITE('Illegal Symbol');
        WRITELN(': Try Again')
      END
    ELSE
      BEGIN WRITELN('    ',TEMP); ANSWER:=TEMP END
  UNTIL FALSE;
999:{EXIT PROGRAM HERE}
END (*EXPRESSION*).

