The RPN calculator implementation in CLU consists of a main procedure and an iterator. The iterator,
gettoken, reads the input and generates a sequence of tokens. The rest is done by the main program. Substantial use is made of exception handling, both for catching errors and for control structuring.
The language definition says that the starting point of the program can be any procedure, but the compiler I used (Portable CLU) requires that the name of the main procedure is
start_up, and so it is here.
A dozen of library procedures are used in the program. As all of them belong to specific types, such as
stream, etc., according to the language's rules each procedure name is prefixed by the corresponding type name and a
Type synonyms are introduced for several types by means of the so called ‘equates’. These are useful for brevity of expression. One of the equates is for a sequence, i.e. an immutable array, of procedures. The constant value
op of that type holds the procedures for the operators
/, and is used to easily refer to the needed procedure by first mapping a token to the corresponding index within
The body of the iterator is an infinite loop, in which the input is being read character by character. Sequences of successive nonblank characters are stored in the array
cs. As soon as such a sequence is completed,
cs is copied in the string
w, which is then
yielded by the iterator. An empty string is yielded wherever an end of line is encountered.
The main procedure utilizes the iterator in its
for loop. It gets a token
s yielded by
gettoken, and if
s is non-empty, it proceeds to see whether
s is a number or an operator, acting accordingly in each case. An array,
rs, is used as a stack to accumulate intermediate results as the tokens are processed. Once a complete expression is evaluated, its value is converted to a string by calling
real$unparse, because numbers cannot be printed directly.
Upon end of input, an exception is raised from
gettoken, which ends the iterator's loop and thus the iterator itself. Ultimately, this leads to ending the main procedure as well.
Statements of the form
S except when E: A end, where
S is a statement,
E is a list of exception names, and
A is one or more statements, handle exceptions: if an exception is raised within
S whose name is in
E, it gets handled by executing
A. For example, trying to parse a token as a number (
bad_format if the token is not really a number. Similarly, popping a value off
rs is empty.
bounds is also raised if we try to index
op with an invalid number.
exit statement raises an exception explicitly. A user chosen name must be provided for the exception. Here, the name
error is used at several places.
When an error is detected in the course of evaluating an expression, the main (and only) loop of the main procedure has to skip some tokens without interpreting them, until an empty one is encountered, signalling end of the current line. Then it prints an error message and continues its normal work. Whether tokens are interpreted or just skipped is controlled by the value of the variable
ac = array[char] ai = array[int] ar = array[real] ap = sequence[proctype(real,real) returns(real) signals(overflow,underflow,zero_divide)] start_up = proc() op:ap := ap$[real$add,real$sub,real$mul,real$div] ous:stream := stream$primary_output() rs:ar := ar$new() skip:bool := false for s:string in gettoken() do x,y:real if skip then exit error elseif string$empty(s) then if 1<ar$size(rs) then exit error elseif ~ar$empty(rs) then y := ar$remh(rs) stream$putl(ous,real$unparse(y)) end else x := real$parse(s) except when bad_format: if 1=string$size(s) then y,x := ar$remh(rs),ar$remh(rs) x := op[string$indexc(s,"+-*/")](x,y) else exit error end end ar$addh(rs,x) end except when error,bounds,overflow,underflow,zero_divide: skip := ~string$empty(s) if ~skip then stream$putl(ous,"error") ar$trim(rs,1,0) end end end end start_up gettoken = iter() yields (string) ins:stream := stream$primary_input() cs:ac := ac$new() tk:string c:char := ' ' while true do while c=' ' cor c='\t' do c := stream$getc(ins) end if c='\n' then c := ' ' end while 0=string$indexc(c," \t\n") do ac$addh(cs,c) c := stream$getc(ins) end tk := string$ac2s(cs) yield (tk) ac$trim(cs,1,0) end except when end_of_file: end end gettoken