(*------------------------------------------------------------------------

  Pute evaluates integer expressions.

    Usage: Pute <expr>

  with

    expr     = "(" expr ")" | number | expr op expr.
    op	     = "+" | "-" | "*" | "-" | "DIV" | "MOD".
    number   = digit {digit} | hexdigit {hexdigit} "H".
    digit    = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9".
    hexdigit = digit | "A" | "B" | "C" | "D" | "E" | "F".

  © Copyright 1990 by Fridtjof Siebert. Freely redistributable

------------------------------------------------------------------------*)

MODULE Pute;

(* $OvflChk- $RangeChk- $StackChk- $NilChk- $ReturnChk- $CaseChk- *)

IMPORT io, ol: OberonLib;

CONST
  lparen = 0; rparen = 1; times  = 2; plus   = 3; minus  = 4;
  div	 = 5; mod    = 6; number = 7; eof    = -1;

TYPE String = ARRAY 80 OF CHAR;

VAR
  Sym: SHORTINT;
  Number: LONGINT;
  Char: CHAR;
  buffer: POINTER TO String;
  index: INTEGER;
  Identifier: String;
  result: LONGINT;

(*-------------------------------------------------------------------------*)

PROCEDURE ReadChar;

BEGIN
  IF index=ol.dosCmdLen THEN Char := 0X;
			ELSE Char := CAP(buffer^[index]); INC(index) END;
END ReadChar;

(*-------------------------------------------------------------------------*)

PROCEDURE Error;

BEGIN
  io.WriteString("Usage: PUTE <Expression>"); io.WriteLn; HALT(0)
END Error;

(*-------------------------------------------------------------------------*)

PROCEDURE GetSym();

VAR
  digit: String;    (* used to read constant numbers *)
  cnt,i: INTEGER;
  n: SHORTINT;

BEGIN
  WHILE (Char<=" ") AND (Char>0X) DO ReadChar END;
  CASE Char OF
  "A".."Z":
    cnt := 0;
    WHILE (Char>="A") AND (Char<="Z") DO
      Identifier[cnt] := Char;
      ReadChar;
      INC(cnt); IF cnt=80 THEN Error END;
    END;
    Identifier[cnt] := 0X;
    IF	  Identifier="DIV" THEN Sym := div
    ELSIF Identifier="MOD" THEN Sym := mod
    ELSE Error END |
  "0".."9":
    cnt := -1;
    WHILE ((Char>="0") AND (Char<="9")) OR ((Char>="A") AND (Char<="Z")) DO
      INC(cnt);
      IF cnt=80 THEN Error END;
      digit[cnt] := Char;
      ReadChar;
    END;
    Number := 0; i := 0;
    IF digit[cnt]#"H" THEN
      WHILE i<=cnt DO
	n := SHORT(ORD(digit[i])-ORD("0"));
	CASE n OF 0..9: Number := 10 * Number + n ELSE Error END;
	INC(i);
      END;
    ELSE
      WHILE i<cnt DO
	n := SHORT(ORD(digit[i])-ORD("0"));
	IF n>9 THEN DEC(n,7) END;
	CASE n OF 0..15: Number := 16 * Number + n ELSE Error END;
	INC(i);
      END;
    END;
    Sym := number;
    RETURN |
  "(": Sym := lparen |
  ")": Sym := rparen |
  "*": Sym := times  |
  "+": Sym := plus   |
  "-": Sym := minus  |
  "/": Sym := div    |
  0X : Sym := eof    |
  ELSE Error END;
  ReadChar;
END GetSym;

(*-------------------------------------------------------------------------*)

PROCEDURE Expression(): LONGINT;

VAR
  c: LONGINT;
  addOperator: SHORTINT;

  PROCEDURE Term(): LONGINT;

  VAR
    d,c: LONGINT;
    s: SHORTINT;

    PROCEDURE Factor(): LONGINT;
    VAR c: LONGINT;
    BEGIN
      CASE Sym OF number: c := Number; GetSym |
		  lparen: GetSym; c:=Expression();
			  IF Sym#rparen THEN Error END;
			  GetSym |
      ELSE Error END;
      RETURN c
    END Factor;

  BEGIN
    c := Factor();
    LOOP
      CASE Sym OF
      times,div,mod:
	s := Sym;
	GetSym; d := Factor();
	IF s=times  THEN c := c * d;
	ELSIF d=0   THEN HALT(0)
	ELSIF s=div THEN c := c DIV d;
		    ELSE c := c MOD d END |
      ELSE EXIT END;
    END;
    RETURN c;
  END Term;

BEGIN
  addOperator := Sym;
  IF (addOperator=plus) OR (addOperator=minus) THEN GetSym END;
  c := Term();
  IF addOperator=minus THEN c := -c END;
  LOOP
    CASE Sym OF
    plus : GetSym; INC(c,Term()) |
    minus: GetSym; DEC(c,Term()) |
    ELSE EXIT END;
  END;
  RETURN c;
END Expression;

BEGIN
  IF ol.wbStarted THEN Error END;
  buffer := ol.dosCmdBuf;
  Char := " "; GetSym;
  result := Expression(); IF Sym#eof THEN Error END;
  io.WriteInt(result,11); io.WriteString(" = ");
  io.WriteHex(result,8); io.Write("H"); io.WriteLn;
END Pute.

