RPN Calculator in Falcon

The main part of this implementation is an infinite loop reading text lines, s, from the standard input. Reading takes place within a try block, so that the exception that is raised upon end of input gets caught and the loop is terminated through executing a break.

A string s is split into tokens, and the result is stored back into s. First, by using strReplace, tab characters are replaced by spaces. Then strAllTrim removes from the string any leading and trailing spaces. Splitting itself is done by strSplitTrimmed, which we instruct to use ' ' (space) as a token delimiter. In result, an array of tokens is produced.

The result is an array of tokens, or – if there was nothing in the string except possibly whitespace – an array with an empty string as the single item. (The latter is strange; a more consistent behaviour of strSplitTrimmed would be to return an empty array in that case.) When there are indeed tokens to process, evalrpn is called for evaluating the RPN expression.

The call to evalrpn is nested within a try block. Evaluating an incorrect RPN expression would result in raising an exception, either from evalrpn or when, upon its completion, there are tokens remaining in the array s. (In the latter case, the exception is ‘thrown’ explicitly, through the raise statement. As that statement requires an argument, we pass an empty string, but any other value would do as well since it is nowhere used.) The respective instance of the > (print-a-line) operator outputs either the result of the RPN expression or an error message.

evalrpn steps along the token sequence backwards, each time extracting a token tk from it and checking if tk is an arithmetic operator. If so, the function calls itself recursively to obtain the argument values for the operator and correspondingly reduce the sequence. Then, call applies the needed arithmetic function which it finds by indexing a dictionary mapping string representations to functions. If tk is not an operator token, it is expected to be a number and so is passed to number.

The numeric library function extracts a number from a string representation. However, number syntax in Falcon has certain limitations: no leading + is accepted, as well as no leading or trailing dot. The number function in our implementation takes care for overcoming this limitation by doing its own parsing and calling numeric on a possibly modified string rather than the original argument. Parsing is done through matching against a regular expression and extracting portions of the token under consideration.

If matching is successful, ci and cf are index ranges corresponding to the integer and fractional parts of the number. Each part can be empty, resulting in a range of zero length. The locally defined function rng checks whether a range is non-empty. An absent integer part implies that there is a leading dot and is therefore replaced by '0'. An absent fractional part implies a trailing dot or no dot at all, so, if present, the dot is removed. The resulting two strings h and t are reassembled into a single string constituting a number representation which numeric can process successfully. If arithmetic sign is present in the original string, it is dropped but catered for through multiplying the result of numeric by sgn.

number raises an exception if its argument does not read as a number, i.e., if match fails, or if both the integer and the fractional parts are empty. Similarly, an exception is generated if calling arrayTail on an empty array. In both cases, evalrpn is terminated abnormally, propagating the exception up to the main part of the program, as mentioned above.

Some of the standard library features of Falcon are immediately available through the so called ‘core module’. Others reside in ‘extension library modules’ which have to be imported in a program explicitly. Here, we have loaded: regex, in order to use the class Regex, including its methods match and captured; and funcext, in order to access the functions add, sub, mul, and div.

load regex; load funcext

function number(tk)
  rng = lambda r => r[0]<r[1]
  re = Regex('^[+-]?(\d*)\.?(\d*)$')
  if not re.match(tk): raise ''
  ci,cf = re.captured(1),re.captured(2)
  if not (rng(ci) or rng(cf)): raise ''
  sgn = 1; if tk[0]=='-': sgn = -1
  h = rng(ci) ? tk[ci] : '0'
  t = rng(cf) ? '.'+tk[cf] : ''
  return sgn*numeric(h+t)

function evalrpn(tks)
  tk = arrayTail(tks)
  if tk in ['+','-','*','/']
    y,x = evalrpn(tks),evalrpn(tks)
    return call(['+'=>add,'-'=>sub,'*'=>mul,'/'=>div][tk],[x,y])
    return number(tk)

  try; s = input(); catch; break; end
  s = strSplitTrimmed(strAllTrim(strReplace(s,"\t",' ')),' ')
  if len(s[0])==0: continue
    x = evalrpn(s)
    if len(s)>0: raise ''