PLACE |
spec |
solutions |
Pure |
The function definitions in this implementation of the calculator look like ones in most modern functional languages. These definitions are particular cases of Pure's rewriting rules, but the greater generality of the language in this respect is not made use of here.
The rpn
function is the main one, and so is called at the very end of the program. The first thing that it does is checking the guard condition ~(feof stdin)
which ensures that the standard input is not exhausted. Then the when
part is computed which provides a local value s
for rpn
's main expression. s
is obtained by reading (gets
) a line of text, breaking it down to substrings, of which the RPN expression tokens are to be extracted, and filtering out the empty ones among them. rpn
finally passes s
to process
and calls itself tail-recursively. ($$
is a sequencing operator throwing away the result of its left argument, which is anyway of no significance here.)
The process
function is a composition of several parts applied in a right-to-left order ($
being the function application written explicitly as a right-associative infix operator). In this ‘pipeline’, map xtok
translates all strings to numbers or operators: xtok
translates a single member of the sequence s
, and map
replicates xtok
's action over each item of s
. An empty list is attached to the resulting token sequence, and the so constructed list pair is passed to evalrpn
for computing the RPN expression in the first list. Next in the pipeline is the call of the catch
special form with an error handling function which, ignoring its argument – the actual error – just returns the error
symbol. Finally, whatever catch
returns – a resulting number or the error
symbol – is transformed (str
) into a string which puts
prints out to the standard output.
Two things should be stressed about how process
works. One is that all it does is guarded by a condition ensuring that only non-empty sequences are processed. The other is that the call of map xtok
is nested within that of evalrpn
, which in turn takes place within the call of catch
. As both xtok
and evalrpn
can throw exceptions but each of these functions is called witin the catch
, any such exception is properly caught.
For token recognition xtok
makes use of regular expressions: nu
for numbers and op
for operators. Matching is done by a call of regex
wrapped within the help function mtk
, in order to vary the pattern being applied without repeating the actual, rather bulky, regex
calling sequence.
A token representing an arithmetic operator is mapped to the respective function through looking it up in the associative table fn
. Obtaining the value of a token that reads as a number is a bit more complicated. For this, in principle, we use the built-in function val
which is designed to evaluate any common kind of expression written as a string. However, as numbers in Pure cannot begin with a +
, neither is +
a valid monadic operator, and also because Pure does not accept numbers ending with a decimal dot, these peculiarities, if present, are removed from the processed string before passing it to val
. This is achieved by wrapping val
within the num
function and calling the latter in place of the former.
A token parsing as neither a number or an operator is handled by throw
ing an exception; the value passed (the empty tuple ()
) is nowhere used and because of that immaterial.
One can note that the definition of xtok
makes use of with
and when
clauses, both for local definitions: Pure requires using the former for defining only functions, and the latter for defining other kinds of values. More generally, with
is for term transforming equations, while when
is for name binding through pattern matching. In certain cases an equation may be syntactically indistinguishable from a matcher, hence the need for superimposing a disambiguating structure.
evalrpn
traverses the token sequence which is the first in the pair of lists that is its argument, maintaining a stack in the second list. Numbers from the first list move to the stack. Operators apply on the two topmost numbers from the stack, and the result (t x y
) gets pushed on their place. When the expression sequence gets empty, the stack should contain a single number n
; if there are more, an exception is thrown. An exception is also raised implicitly if ns
fails to match the pattern y:x:rs
that pops the arguments for applying an operator.
Typically of Pure, four kinds of functions and function-like constructs are used in this program. The functions head
, tail
, init
, last
, map
, and filter
are from the standard library, as are stdin
, feof
, gets
, puts
, regex
, and regsplit
. The first ones are all members of the prelude module, whose content is made available to the program automatically. The second group is from the system
module which is loaded explicitly through a using
clause. There are also built-in primitives – val
, str
, realp
, and throw
in this program – and special forms, such as if–then–else
and catch
. To each special form there corresponds a specific rule for its computation. As a rule, special forms cannot be expressed as ordinary functions.
using system; rpn = process s $$ rpn if ~(feof stdin) when s = filter (~= "") (regsplit "[ \t]" 0 gets 0) end; process s = puts $ str $ catch (\_ -> error) $ evalrpn $ (,[]) $ map xtok s if s ~= []; xtok tk = if mtk nu then num tk else if mtk op then fn!tk else throw () with mtk p = regex p (REG_EXTENDED or REG_NOSUB) tk 0; num t = val (if last t1 == "." then init t1 else t1) when t1 = if head t == "+" then tail t else t end end when nu = "^[+-]?(\\.[0-9]+|[0-9]+(\\.[0-9]*)?)$"; op = "^[-+*/]$"; fn = {"+" => (+), "-" => (-), "*" => (*), "/" => (/)} end; evalrpn (t:ts,ns) = evalrpn (ts, (if realp t then t:ns else (t x y : rs when y:x:rs = ns end))); evalrpn ([],n:ns) = n if ns == []; = throw (); rpn;
boykobbatgmaildotcom