program Parser;

{adapted to Turbo Pascal by Glenn Brooke 5/6/86 from a program
 by Herbert Shcildt.

 this program reads an expression and returns the result.  It can
 handle up to 26 one letter (A-Z) variables and real numbers.
 Supports +,-,*,/,and powers.  Not bad!  Speed isn't too bad, either.

 This kind of a program is really best as a function in your own program,
 so that the user can enter an expression, and the program can compute
 the result.  For example, a function plotting program can simply ask
 for a function like 2*X + (3.14/X^4)/1.23, and plot the curve from
 -5 to +5.  Quite powerful!

}
type
    str80 = string[80];
    Ttype = (Delimiter, Variable, Number);
var
   token, prog : str80;
   TokType : Ttype;
   code, t : integer;
   result : real;
   vars : array[0..25] of real;      {26 variables}

function IsAlpha(ch : char) : boolean;
{true if ch is letter in alphabe}
begin
     IsAlpha := (Upcase(ch) in ['A'..'Z'])
end;

function IsWhite(ch : char) : boolean;
{true if newline, space or tab}
begin
     IsWhite := (ch= ' ') or (ch=chr(9)) or (ch=chr(13));
end;

function IsDelim(ch : char) : boolean;
begin
     if pos(ch,' +-/*%^=()$')<>0 then IsDelim := true
     else IsDelim := false
end;

function Isdigit(ch : char) : boolean;
begin
     Isdigit := ch in ['0'..'9']
end;

procedure GetToken;
var temp : str80;
begin
     token := '';
     while (IsWhite(prog[t])) do t := succ(t);
     if prog[t]='$' then token := '$';
     if pos(prog[t],'+-*/%^=()')<>0 then
        begin
        TokType := Delimiter;
        token := prog[t];    {is an operator}
        t := succ(t);
        end
     else if IsAlpha(prog[t]) then
          begin
          while (not IsDelim(prog[t])) do
                begin
                token := token + prog[t];    {build token}
                t := succ(t)
                end;
          TokType := Variable;
          end
     else if IsDigit(prog[t]) then
          begin
          while (not IsDelim(prog[t])) do
                begin
                token := token + prog[t];   {build number}
                t := succ(t);
                Toktype := number;
                end;
          end;
end; {GetToken}

procedure PutBack;  {put back unused token}
begin
     t := t - length(token)
end;

procedure Serror(i : integer);  {print error msg}
begin
     case i of
          1 : writeln('Syntax error');
          2 : writeln('Unbalanced parentheses');
          3 : writeln('No expression Present')
     end;
end;

function Pwr(a,b : real) : real;
{raise a to the b power}
var t : integer;
    temp : real;
begin
     if a= 0 then pwr := 1
     else
         begin
         temp := a;
         for t := trunc(b) downto 2 do a := a * temp;
         Pwr := a
         end
end;

function FindVar(s : str80) : real;
var t : integer;
begin
     FindVar := vars[ord(upcase(s[1]))-ord('A')]
end;

procedure Arith(op : char; var result, operand : real);
begin
     case op of
          '+' : result := result + operand;
          '-' : result := result - operand;
          '*' : result := result * operand;
          '/' : result := result / operand;
          '^' : result := Pwr(result,operand);
    end
end;

{***********  Expression Parser w/ variables and assignment  ********}
procedure Level2(var result : real); forward;
procedure Level1(var result : real); forward;
procedure Level3(var result : real); forward;
procedure Level4(var result : real); forward;
procedure Level5(var result : real); forward;
procedure Level6(var result : real); forward;
procedure Primitive(var result : real); forward;


procedure GetExp(var result: real);
begin
     GetToken;
     if length(token) <> 0 then Level1(result) else Serror(3)
end;

procedure Level1;
var hold : real;
    temp : Ttype;
    slot : integer;
    TempToken : str80;
begin
     if Toktype = Variable then
        begin
        {save old token}
        temptoken := token;
        temp := toktype;
        slot := ord(upcase(token[1]))-ord('A');
        GetToken;  {see if there is an = for assignment}
        if token[1] <> '=' then  {restore}
           begin
           Putback;
           token := temptoken;
           toktype := temp;
           level2(result)
           end
        else {is assignment}
             begin
             Gettoken;
             Level2(result);
             vars[slot] := result;
             end;
        end
    else Level2(result)
end; {Level1}


procedure Level2;
var op : char;
    hold : real;
begin
     Level3(result);
     op := token[1];
     while ((op='+') or (op='-')) do
           begin
           Gettoken;
           Level3(hold);
           arith(op, result, hold);
           op := token[1]
           end;
end; {Level2}

procedure Level3;
var op : char;
    hold : real;

begin
     Level4(result);
     op := token[1];
     while ((op='*') or (op='/')) do
           begin
           Gettoken;
           level4(hold);
           arith(op, result, hold);
           op := token[1]
           end;
end; {Level3}

procedure Level4;
var hold : real;
begin
     Level5(result);
     if token[1] = '^' then
        begin
        GetToken;
        Level4(hold);
        arith('^',result, hold);    {exponent}
        end
end;

procedure Level5;
var op : char;
begin
     op := ' ';
     if ((tokType=Delimiter) and ((token[1]='+') or (token[1]= '-'))) then
        begin  {unary plus or minus}
               op := token[1];
               Gettoken
        end;
     Level6(result);
     if op='-' then result := -result
end; {level5}

procedure Level6;
begin
     if (token[1]='(') and (Toktype=Delimiter) then
        begin {parenthesized expression}
        GetToken;
        Level2(result);
        if token[1]<>')' then Serror(2);  {unbalanced}
        GetToken;
        end
     else Primitive(result);
end; {Level6}


procedure Primitive;
begin
     if TokType=Number then val(token, result, code)
     else if TokType=Variable then result := FindVar(token)
     else serror(1);
     GetToken
end; {Primitive}




{**************************  Main Test body  ******************}
begin
     for t := 0 to 25 do vars[t] := 0;  {initialize variables}
     repeat
           t := 1;
           write('  Enter an expression  (quit to stop) : ');
           readln(prog);
           prog := prog + '$';
           GetExp(result);
           writeln(result);
     until prog = 'quit$';
end.
