RPN Calculator in Io

The evalrpn procedure in the following program is implemented as a method of List, the prototypical object of Io's lists. An already tokenized RPN expression, in the form of a list of tokens, is evaluated by evalrpn recursively. Each call of evalrpn modifies the list so that the corresponding (sub)expression is replaced by its result. For a correctly constructed RPN expression this will eventually result in a single number left in the list.

evalrpn works as follows. An item tk is popped off the list's end, and checked if it denotes an operator. If it does, the method calls itself twice, popping the results off the list. The first result is stored as y so that, together with tk, it is sent in a perform message to the second obtained result – this is how the corresponding arithmetic operation is computed. A list item which is not an operator is analyzed whether it reads as a number: a possible arithmetic sign is stored as a ±1 factor and removed, then the remaining string is scanned to ensure that it contains at least one digit, at most one decimal dot, and nothing else. If a string fails that test, an exception is raised. On success, tk is converted to a number using asNumber, and the arithmetic sign is taken account of. (asNumber itself cannot be relied upon for parsing a numeric token as it happens to not signal trailing characters not belonging to the number.)

Note that the items that are added to the list in evalrpn are numbers, not strings, but they are pulled back off immediately and never get parsed like the rest of the items do. These numbers only join the list as a way to represent a result of a partial evaluation and the respective remaining part of the list as a single value.

The main part of the program makes use of the while and readLine methods to read lines until the latter method returns nil. Each line s is split into tokens and the possible empty ones of them (appearing whenever the input line contains successive blanks) are filtered out. The resulting list of tokens, if not empty, is sent the evalrpn message, and if a one-item list is returned, that item is the result of the evaluation which gets printed by letting it perform the println method on itself.

Both the evaluation and the final checking if there are not unused tokens left is done in the context of the try method. Any syntax incorrectness results in signalling an exception, in which case try returns that exception. For a correctly parsed and computed RPN expression, nil is returned. The outcome of try is sent the catch method which reacts to an exception by printing an error message while not paying attention to a nil.

As Io has no character literals, at two places we use single-element strings and refer to their content by means of a subscript (at(0)). We could have used ASCII codes instead, e.g. 45 asCharacter for the minus sign, but this would have obscured the text.

List evalrpn := method (
  tk := pop
  push (
    c := tk at(0)
    if (tk size == 1 and "+-*/" contains(c)
    , y := evalrpn pop
      evalrpn pop perform(tk asString,y)
    , sgn := 1
      if ("+-" contains(c)
      , if ("-" at(0) == c, sgn := -1)
        tk := tk slice(1))
      nd := np := 0
      tk foreach(c, if (c isDigit, nd := nd+1
                    , if (c == "." at(0), np := np+1, np:=2)))
      if (nd==0 or np>1, Error raise(""))
      tk asNumber * sgn

while (s := File standardInput readLine,
  s := s split select (isEmpty not)
  (s isEmpty) ifFalse (
    try (
      r := s evalrpn
      if (r size > 1, Error raise(""))
      r first println
    ) catch(Exception,"error" println)