RPN Calculator in Nial

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 transformereachleft 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 vacate, gettk, and 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 across traverses 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 tk to tonumber.

Within 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 stk.

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;
      (if isfault r or (tally r > 1) then 'error' else string r@0 endif)

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 drop, apply, and take, respectively.


or (`  (char 9) eachleft match s) cut s

can be reformulated as a function applying to a single reference of s:

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.