As is typical of Nial programs, the following implementation combines array processing in mostly functional style with some imperative features.
The main program is a
while loop reading the standard input a line at a time and processing it. Reading is done by a general function for file reading with a parameter
0 specifying the stdin. Using
readscreen, a function specific for stdin, appears to be inadequate here, because it cannot detect end of input.
Readfile, on the other hand, returns either a string, or, when there is no more input – a value of the special
fault type. A presence of a fault value is detected by the predicate
isfault, so the loop is ended as expected.
Once this happens, the Nial interpreter is exited by issuing a
bye command. (This could have been skipped if we only wanted to execute the program and did not care of leaving the interpreter. However, a truly complete implementation of the calculator must indeed leave the hosting environment and thus behave no differently than a stand-alone executable – see also the K solution in this respect.)
If a text line
s is successfully read, it is being split into tokens, resulting in an array
tks of strings. To do that,
s is matched against a list of a space (
` ) and a tab (
char 9) characters. The ‘function transformer’
eachleft ensures that matching applies twice, once for each of the space and the tab. The two resulting Boolean vectors, each the length of
s, are ‘
ored’ itemwise to produce a single vector with true values corresponding to indices in
s where whitespace items are. That vector is then passed, together with
s itself, to
cut which does the splitting accordingly.
A non-empty array
tks receives a further processing by applying
across, another ‘transformer’, with an ‘atlas’ of the functions
evaltk to a reversed copy of it. Reversal is necessary, because
across processes its array argument backwards, and we mean forward processing, so reverse in advance.
The first function of the atlas acts on the last item of the array. In our case the function is
vacate – it actually does nothing with its argument proper, creating an empty list in its place. This empty list is the initial stack of intermediate results for the evaluation of the RPN expression. As
tks, each token is passed to the second function,
gettk, and the result, in turn, together with the stack array, is passed to the third function,
evaltk. The return value of
evaltk is the new stack, and when the traversal is finished, the final stack array is what
across eventually returns.
Gettk checks whether its argument
tk is one of the arithmetic operators, or whether it represents a number. In the latter case that number is returned, and in the former
tk goes back unchanged. The function
tonumber either succeeds and indeed produces a number, or fails, due to
tk being an invalid token. Failure is signified by returning a fault value, so
gettk, besides an operator string or a number, may also return a fault.
In Nial, numeric constants cannot have a leading plus sign, so if one is present in
tk, it is removed before passing
evaltk, if either the current token,
it, or the stack,
stk, is a fault, or if
it is a string – therefore being an arithmetic operator – but
stk has less than two items to provide arguments for the operator, another fault value (
?) is returned. Otherwise, if
it is a string, the corresponding operator applies to the top two stack items and the result replaces them in
stk. If neither of the above cases holds,
it is a number, and that number is pushed onto
Thus, once a fault is returned by
gettk, it is also returned by
evaltk. And once
evaltk itself returns a fault (for whatever of the described reasons), it keeps doing so until
across finally passes back that fault to the main program: the stack is replaced by the fault value. In that case, and also when the result is a stack of more than two items, the main program knows that there was an error in the RPN expression and prints an error message. Upon successfully evaluating the expression, the only number on the stack, at index 0 (
r@0) is being converted to a string and printed.
The Nial interpreter would normally react to generating a fault by interrupting the program execution and entering a debugging mode. This is changed by doing
settrigger o (
o is a synonym of
false), in effect forcing faults to be treated as ordinary values – as needed further on in the program.
gettk is op tk ( if tk in '+' '-' '*' '/' then tk else tonumber (if tk@0 = `+ then rest tk else tk endif) endif ); evaltk is op it stk ( if isfault stk or (isfault it) or (isstring it and (tally stk < 2)) then ? elseif isstring it then (drop -2 stk) append (apply it (take -2 stk)) else stk append it endif ); settrigger o; while not isfault (s := readfile 0) do tks := or (` (char 9) eachleft match s) cut s; if tally tks > 0 then r := across [vacate,gettk,evaltk] reverse tks; writescreen (if isfault r or (tally r > 1) then 'error' else string r@0 endif) endif; endwhile; bye
As a stylistic note, some of the expressions in the above program could be written in a more applicative form that makes use of partial application and function composition. Whichever is used is mostly a matter of personal preference. For example, the expression
(drop -2 stk) append (apply it (take -2 stk))
could be rephrased as
append [-2 drop, it apply (-2 take)] stk
In the latter form, there is only one reference of
stk to which all the preceding function expression applies. This form is also shorter. The expression is a composition of
append over an atlas of two functions. The two functions in the atlas are
-2 drop and the composition of
it apply over
-2 take, those being partial applications of
or (` (char 9) eachleft match s) cut s
can be reformulated as a function applying to a single reference of
cut [or (` (char 9) eachleft match), pass] s
but the result has to involve the identity function
pass, is longer, and perhaps harder to read.