PLACE
spec
solutions
Pascal

RPN Calculator in Pascal

As with all the other implementations on this site, the one here is done in a strictly standard language. This makes quite a difference in the case of Pascal. For one thing, Standard Pascal has no string manipulation facilities whatsoever, so (scanning and) parsing of the input has to be done from the ground up.

Most other implementations read a line of input entirely before processing it further. This has the advantage of having to read at only one point in the program, which results in clean separation from the rest of the work. However, there is no provision for reading arbitrarily long lines in Pascal, and implementing it is awkward. In fact, the same holds with regard even to single tokens – the language only allows statically sized arrays, and the problem specification forbids explicit size limitations. Therefore, in the program below, we only read a single character at a time, skipping whitespace and recognizing and processing individual tokens as we go. If a token is an arithmetic operator, it is immediately executed, and if it is a number, its value is accumulated from its representation character by character. Incorrect tokens and other parsing errors are detected at appropriate places, which sometimes leads to reading to the end of line just to discard the input. Thus, no buffer is needed to store an input line or even a token, but reading is scattered at various points along the program.

A stack of numbers is maintained for keeping the partial results in the course of computing an expression.

The main program, at the end of the text below, runs a loop controlled by calling the built-in function eof (end-of-file) to detect end of input. In the loop, when not immediately at end-of-line (eoln), i.e. for a non-empty line, processtoken is called repeatedly to read a number or an arithmetic operator and perform the corresponding action with it on the stack. Either an entire RPN expression is computed this way, or the error flag is set due to processtoken finding a syntax error. In the latter case the current line is read up to its end and discarded.

If a line is successfully read and processed, a number, z, is popped off and printed as the result of computing the expression, provided there are no other numbers stored in the stack (nums = nil) at that time. When there are, however, the expression is incorrect, so the error flag is set. If an error is signaled one way or another, a corresponding message is printed.

Finally, in preparation for processing the next line of input, readln and init are called. readln moves the point of reading to the beginning of the next line, while init purges any possible items from the stack, clears the error flag, and moves to the beginning of the first token. Moving, i.e. skipping whitespace, takes place by calling skipws. This is also done within processtoken for advancing between tokens.

The processtoken procedure starts parsing a token by reading a character to see if it is an operator. An additional character may be read in order to discriminate between a + or a - being indeed an operator or the arithmetic sign of a number: a nonblank character indicates a no-operator case. If a token fails to read as an operator, the nested function getnumber is called, which either succeeds in reading a number or sets the error flag. If processtoken eventually finds the current token to be an operator or a number, it proceeds by using that token to advance the computation of the RPN expression accordingly: if an operation, it is performed on the top two stack items, and if a number, it is simply pushed onto the stack.

The stack is implemented as a linked list of a user-defined type num. Throughout the program the head of the list, which is the top of the stack, is held in the variable nums: a pointer to num (of type pnum). The two procedures push and pop add to and pull items off the stack. When the stack is empty, pop returns 0 – an arbitrary value – but sets error, so that it is known that the returned value is insignificant.

Pascal's input/output is peculiar by making use of buffer variables associated with each file, not excluding the standard input and output streams. For an input file, the buffer variable, in our case input^, takes its value by forward (hidden, implicit) reading. That value is set even before any actual call of read is made. read returns the current value of the buffer variable and obtains a new one, which will be returned by a subsequent call of the same procedure. A call of get only obtains the next value of the buffer variable.

End-of-line is not a character value – another peculiarity of Pascal – so instead of it reading operations see a space. It should be noted that both forward reading and equating the end-of-line to a space are used in the program.

program rpncalc(input,output);
type pnum = ^num;
     num = record x: real; next: pnum end;
var nums: pnum;
    digits: packed array [1..10] of char;
    tktype: (nm,ad,sb,mp,dv);
    error: boolean;
    z: real;

procedure push(x: real);
var p: pnum;
begin  new(p);  p^.x := x;  p^.next := nums;  nums := p  end;

function pop: real;
var p: pnum;
begin
  if nums <> nil then begin
    p := nums;
    nums := nums^.next;
    pop := p^.x;
    dispose(p)
  end  else begin
    error := true;
    pop := 0
  end
end;

procedure skipws;
begin  while (input^ = ' ') and (not eoln) do get(input)  end;

procedure processtoken;
var c,cn: char;
    f,e,x,y: real;
    sgn,d,i: integer;

procedure getnumber;
begin
  while (c <> ' ') and (not error) do begin
    d := -1;  i := 1;
    while i < 11 do
      if digits[i] = c then
        begin d := i-1; i := 11 end
      else i := i+1;
    if c = '.' then
      if f = -1 then f := 0 else error := true
    else if d > -1 then begin
      if z = -1 then z := 0;
      if f = -1 then z := 10*z+d
      else begin e := e/10; f := f+d*e end
    end else error := true;
    if eoln then c := ' ' else read(c)
  end;
  if z = -1 then error := true;
  if not error then begin
    if f = -1 then f := 0;
    z := sgn*(z+f)
  end
end;

begin  { processtoken }
  tktype := nm;
  sgn := 1;
  z := -1;  f := -1;  e := 1;
  read(c);
  if c in ['+', '-', '*', '/'] then begin
    if eoln then cn := ' ' else read(cn);
    if cn = ' ' then
      case c of
        '+': tktype := ad;  '-': tktype := sb;
        '*': tktype := mp;  '/': tktype := dv
      end
    else  begin
      if c = '-' then sgn := -1 else error := c <> '+';
      c := cn
    end
  end;
  if (not error) and (tktype = nm) then getnumber;
  if not error then begin
    if tktype <> nm then begin
      y := pop;  x := pop;
      if not error then
        case tktype of
          ad: z := x+y;  sb: z := x-y;
          mp: z := x*y;  dv: z := x/y
        end
    end;
    push(z);
    skipws
  end
end;

procedure init;
var t: real;
begin
  while nums <> nil do t := pop;
  error := false;
  if not eof then skipws
end;

begin  { main }
  digits := '0123456789';
  nums := nil;
  init;
  while not eof do begin
    if not eoln then begin
      repeat
        processtoken;
        if error then while not eoln do get(input)
      until eoln;
      if not error then begin
        z := pop;
        if nums = nil then writeln(z:1:4) else error := true
      end;
      if error then writeln('error')
    end;
    readln; init
  end
end.

boykobbatgmaildotcom