| 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