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
/ 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
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’.
process rule gets a
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
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