PLACE |
spec |
solutions |
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}] ]
boykobbatgmaildotcom