RPN Calculator in Euphoria

The program below consists of several functions and a following main part. The latter is a (nominally) infinite loop where we read lines s from the standard input defice (0), call evalrpn to evaluate an RPN expression from s and print the result or an error message. As we shall see, evalrpn returns one of several possible kinds of values. If an atom, that must be the result of a successful evaluation – a number, which we print out by the ? command. A non-empty, non-numeric value would signify an error, and in such a case we act by printing "error". If evalrpn returns an empty string, the input line was empty (or only contained blanks), so we do nothing there.

We have to use the puts function for printing a string on the standard output (1) and not ?, as the latter prints the string contents as integers instead of the string proper.

The program ends by exiting the loop upon receiving -1 – an atom – instead of a string from gets, the -1 signaling end-of-input. Note that puts is called after each line reading to print an empty line. This is confusing but, by definition in Euphoria, it is the only way to ensure proper interleaving of input and output lines. (No actual empty lines are added to the output because of this; the program behaves just as required.)

Evaluating the expression within evalrpn takes place by reading tokens from the string s and maintaining a stack st where the intermediate results go. After reading a token, it gets assigned to tk, while the corresponding initial sub-string is removed from s. If tk is non-empty, we check whether it is an operator. If so, and if the stack holds at least two arguments for that operation, we perform it and store the result back on the stack top. If, instead, tk appears to parse as a number, the value of that number is obtained and pushed onto st. (The function value, which does the actual extracting from tk, returns a two-item sequence where the value we are interested in is the second entry. The library file get.e is included in the program to give access to the value function.)

When there are no more tokens in s, gettoken returns an empty string. Then, if there is only one number on the stack, that number is returned. We actually return x, making use of the fact that x is always equal to the stack top. On having more than one items on the stack, the stack itself is returned, i.e. a value of type sequence. Other errors, namely a token being neither an operator nor a number, as well as an operation missing one or two of its arguments, are dealt with by returning the string "e".

The function number is used for testing whether a token represents a number. It removes from tk a possible arithmetic sign, and then similarly removes a decimal dot if present. The remaining string must be non-empty, and consisting of digits. If this is not so, the token was not a number.

Note that number does not have to really compute the number from the string, only to check if it parses correctly. For the rest we use value. The latter, on the other hand, is not good at parsing, as it would have been satisfied by any initial substring of the token that reads as a number, and there is no way to know if there would have been left a remainder of the token that is not parsed by value. That is why we need both number and value.

The function number exemplifies a specific feature of the language. As seen, it is designated by the word type instead of function. In Euphoria, a type is a one-argument, Boolean-valued function, i.e. one with regard to the result of which we only distinguish between non-zero (true) and zero (false), and it is assumed that if a non-zero is returned for a certain value as an argument, that value has the type named by the function. Therefore, we have a type number defined here.

This is Euphoria's mechanism for user-defined types. A type's name can be used in variable declarations or as a predicate to typecheck a value, and thus user-defined datatypes have equal power to that of the predefined types. We only use number as a predicate in this program.

include get.e

object s,r

function gettoken()
  integer i,j
  i = 1
  while s[i]=' ' or s[i]='\t' do i += 1 end while
  j = i
  while 0=find(s[j]," \t\n") do j += 1 end while
  r = s[i..j-1]
  s = s[j..$]
  return r
end function

type number(sequence tk)
  integer i
  if tk[1]='+' or tk[1]='-' then tk = tk[2..$] end if
  i = find('.',tk)
  if i then tk = tk[1..i-1]&tk[i+1..$] end if
  i = length(tk)
  if i=0 then return 0 end if
  while i and find(tk[i],"0123456789") do i -= 1 end while
  return i=0
end type

function evalrpn()
  sequence tk,st
  object x,y,o
  st = {}
  while 1 do
    tk = gettoken()
    if equal(tk,"") then
      if length(st)=1 then return x else return st end if
    end if
    o = tk[1]
    if length(tk)=1 and find(o,"+-*/") then
      if length(st)<2 then return "e" end if
      x = st[$-1]  y = st[$]
      if    o='+' then x += y
      elsif o='-' then x -= y
      elsif o='*' then x *= y
      else             x /= y
      end if
      st = st[1..$-1]
      st[$] = x
    elsif number(tk) then
      x = value(tk)  x = x[2]
      st = st&x
      return "e"
    end if
  end while
end function

while 1 do
  s = gets(0)  puts(1,"\n")
  if atom(s) then exit end if
  r = evalrpn()
  if atom(r) then ?r
  elsif not equal(r,"") then puts(1,"error\n") end if
end while