PLACE |
spec |
solutions |
OmniMark |
The main procedure of the program is the process
rule. The inner repeat scan
… again
loop in it calls the function rpn
which reads a line of text trying to match its contents against the syntax of an RPN expression and simultaneously evaluate the expression. Reading and matching take place in pieces. In contrast to many other implementations of the RPN calculator, there is no separate phase of tokenizing the input.
Individual tokens are extracted by means of pattern matching against regular expressions. In particular, numbers are matched by the nump
pattern, defined as a macro. We make sure that each token is preceded by at least one blank character, with the possible exception of a number starting right at the beginning of a line.
rpn
is a ‘switch
’ function. This word designates the Boolean type in OmniMark, just as ‘stream
’ means ‘a string’ and ‘shelf
’ translates as ‘list’ or ‘array’. rpn
repetitively scans a line, trying to find a number or an arithmetic operation, in the succession specified by the match
clauses. A number token is captured in the string sn
and then the number itself is obtained (calling bcd
) and pushed on the stack contained within the nums
shelf, which by declaration is of variable
size. Tokens that match one of +
, -
, *
, and /
lead to evaluating the corresponding operation on the top two elements of the stack. Matching anything else, or finding the stack empty when trying to pop
a value from it throws the programmer-defined syntax
exception.
If no cause for throwing an exception is found, when rpn
reaches the end of a line it returns true
. Otherwise the exception is handled by the catch
statement, where reading the current line is completed and a false
is returned, meaning that rpn
‘did not match’.
If the process
rule gets a false
from rpn
, the application of the next match
clause within repeat scan
is forced. It re-reads the line rejected (and thus considered unread) by rpn
and throws the syntax
exception. That exception is also thrown when rpn
did match but nums
contains more than one number left on it. In the catch
clause, the error situation is handled by printing a message and clearing nums
in preparation for a new evaluation. The outer repeat
loop in the process
rule ensures that reading and evaluation continues. Without it, the program would have been terminated once an exception occurs and gets handled by the catch
.
Note that end-of-input is not recognized by an explicit check. Instead, when the input is all through, the repeat scan
in the rpn
function gets exited without doing anything, and thus the halt
action is executed to terminate the program.
Non-integer numbers in OmniMark are library-provided and of two types: floating-point and BCD (binary coded decimal). The program uses those of the latter kind. BCD supports unlimited magnitude and limited but large precision of fractional numbers.
include "ombcd.xin" declare #main-input has unbuffered declare #main-output has unbuffered macro dgs is (digit+) macro-end macro nump is (["+-"]? ((dgs ("." dgs?)?) | ("." dgs))) macro-end declare catch syntax global bcd nums variable initial-size 0 define function push(value bcd x) as set new nums to x define bcd function pop() as local bcd x throw syntax when number of nums = 0 set x to nums remove nums return x define switch function rpn as local stream sn repeat scan #current-input match (line-start | blank) blank* nump => sn push(bcd(sn)) match blank+ "+" push(pop()+pop()) match blank+ "-" push(0-pop()+pop()) match blank+ "*" push(pop()*pop()) match blank+ "/" push(1/pop()*pop()) match blank* "%n" return true match any-text throw syntax again halt catch syntax do scan #current-input match any-text* "%n" done return false process repeat repeat scan #main-input match rpn local integer n set n to number of nums do when n = 1 output "d" % pop() || "%n" else when n > 1 throw syntax done match any-text* throw syntax again catch syntax output "error%n" clear nums again
boykobbatgmaildotcom