RPN Calculator in REBOL

The script is an indefinite (forever) loop which reads and processes text lines from the REBOL console. Properly signaling end of input in the console and programmatically detecting it in REBOL is impossible; however, the program can be terminated by pressing Esc on the keyboard rather than entering another line.

The function input reads raw text which parse breaks down into a series of tokens, to which the variable expr gets bound. The second argument to parse is a string specifying the set of delimiters to be used for splitting: a space and a tab-character.

If the series expr appears to be non-empty, it is processed as follows. First, all tokens that represent arithmetic operators are replaced by the names of the corresponding built-in REBOL functions. The correspondence is established through the use of select with the block series ops, where the latter acts as an associative table. Note that the replacement values are of type word! – in other languages known as symbol. Then, expr is traversed again, so that all its items which by this time have remained strings are now converted to numbers (which they should be, since an RPN expression only consists of numbers and operations).

It should be noted that, within the sequence of executions of the block under forall's control, the name expr is automatically advanced to refer to the progressively shorter tails of the underlying series. That is why, the current item of the sequence is always the immediate one pointed at by expr, and so is referred to by expr/1. Once forall is done, however, the original value of expr is restored.

After this pre-processing comes the actual RPN expression evaluation. It is based on the reduction approach: finding an arithmetic operator and replacing it, along with its arguments in expr, with the result of applying the operator. The search for an operator is done by calling parse. Here, the parse function works differently than above. It tries to match values in a series by their types, rather than splitting a string into tokens. If a word!-typed item (i.e., an arithmetic function) is found, p is set to refer to where that function is within expr. The corresponding sub-expression is computed then by building a triple of the function, p/1, and the two items that precede it in expr. The computation takes place due to do which forces calling the function whose name is the word p/1: add etc. Finally, the triple gets replaced in expr by the result of the computation. While ensures that the same is repeated until parse can no more find an operator in expr. At this point, there only remains to check whether the series expr consists of a single item, and if so, print that item.

The entire processing of an input line takes place within a block which is an argument to a try. If an error value is generated within the block, the computation of the rest of the block is abandoned and the error value is returned as a result of the block. The actual value of the error is immaterial for this program – it is only the value’s type that is being checked by error?, so that if indeed an error is found in the RPN expression, that is reacted to by printing a message.

Observe that an error value can be generated both explicitly and implicitly. The former takes place by calling make (to which an empty string ({}) is passed, since some value is needed, no matter what). Make is being called when expr cannot be reduced to a single number. An implicit error is generated by to-decimal when its argument does not read as a number (as it should). Computing the expression p1 p/-2 p/-1 also generates an error when p turns to be too close to the beginning of expr, meaning that the function at p lacks one or both of its arguments: p/-2 and/or p/-1 then, instead of being numbers, have the special REBOL value of none (an ‘absent’ value), which invalidates the expression.

At several places, we have parenthesized some expressions. This is done not because of necessity, but to make clearer the structure of the respective larger expressions. REBOL would parse the expressions correctly regardless of the presence of parentheses.

The header, of the form REBOL […], that precedes the script, is an obligatory part of the program. The content of the header’s block – the part within the brackets – is arbitrary, and is treated as a comment by the REBOL interpreter.

REBOL [Title: {RPN calculator}]
ops: [{+} add {-} subtract {*} multiply {/} divide]
forever [
  if error? try [
    expr: parse input { ^-}
    if not empty? expr [
      foreach o [{+} {-} {*} {/}] [replace/all expr o (select ops o)]
      forall expr [if string? expr/1 [change expr to-decimal expr/1]]
      while [parse expr [to word! p: to end]]
        [change/part (at p -2) (do p/1 p/-2 p/-1) 3]
      if (length? expr) > 1 [make error! {}]
      print expr/1
  [print {error}]