RPN Calculator in BASIC

As BASIC has numerous diverse realizations, the question arises, which of its many incarnations shall we use as ‘standard’ to implement a calculator? I believe that to be QuickBASIC. It was very popular on MS DOS in the pre-Windows times, and sufficiently advanced to have influenced strongly most if not nearly all BASIC dialects in use today – therefore QuickBASIC is rather representative of the BASICs in general. The nominal standard, ANSI/ISO BASIC, is much less popular, and eventually ineffective as a standard.

The following program runs under QuickBASIC and compatible compilers/interpreters. With small changes it can be adapted to a number of other dialects.

The main, initial, part of the program runs an indefinite loop reading a string s$ (as a way of implicit data typing, the names of string-valued variables and functions must end with a $-sign) and passing it to the sub-routine evalrpn for evaluation. Before calling evalrpn, all possible occurrences of tab charactes get replaced by spaces, and then all leading and trailing spaces are removed from s$, so that we can tell if the string is empty and avoid processing it.

Evalrpn returns a flag value y – true when the RPN expression evaluation is successful. In that case it also returns the value r of the expression. The main program checks y and prints either r or an error message.

Evalrpn starts by adding a space to the string it receives, for the sake of uniformity of extracting tokens from it. Then it creates a numeric array stk of large enough size – with respect to the string s$ – to serve as a stack of intermediate values in the course of evaluating the RPN expression. (One can notice that, at the very beginning of the program, we have set a base 1 for array subscripts – the default 0 turns to be a bit less convenient.) Nst counts the actual number of stored values in stk.

The expression is evaluated by repeatedly extracting a token from s$ and acting on it accordingly. Extraction is delegated to the gettoken sub-routine, which returns tk$ and the shortened s$. If tk$ appears to be one of the four arithmetic operators and stk contains the two arguments needed to apply the operator, the corresponding computation is carried out. (Here and elsewhere : is used to join two or more program sentences in a single line.) Otherwise, we call getnumber to check if tk$ is a number. Succeeding in either of the two cases results in storing the obtained value x on the stack. The process is repeated until either s$ becomes empty or an error occurs due to an invalid token or an insufficient number of operator arguments in the stack. Once the loop ends, an additional check is done to ensure that there is only one value left on the stack. Since x, the parameter of evalrpn, is always a copy of the topmost value on the stack, evalrpn's caller does receive the value that it needs.

In getnumber, we first ‘detach’ a possible arithmetic sign from the front of the string, keeping a mark of what the sign was in sg. Then the string is scanned character by character to ensure that it contains a valid representation of a number: the flag yes is set when at least one digit, at most one decimal dot and no other characters are found. If yes holds true, the built-in function val is employed for actually extracting the numeric value from the string.

The built-in function mid$, used throughout the program, is a means to refer to a substring or a single character in a given string. As may be observed, this also allows changing the respective part. Another function, instr, locates where, if at all, a string is included as a substring within another.

As written, the program keeps reading input lines forever (unless, of course, forcefully interrupted by the user). This is because there is no way of detecting the end of the standard input stream. Some implementations of BASIC solve this issue in their own ways.

option base 1
  line input s$
  for i=1 to len(s$)
    if mid$(s$,i,1)=chr$(9) then mid$(s$,i,1) = " "
  next i
  s$ = ltrim$(rtrim$(s$))
  if len(s$)>0 then
    call evalrpn(s$,r,y)
    if y then print r else print "error"
  end if

sub evalrpn(so$,x,yes)
  s$ = so$+" "
  dim stk(len(s$)\2) : nst = 0
    call gettoken(s$,tk$)
    yes = len(tk$)=1 and instr("+-*/",tk$)>0 and nst>=2
    if yes then
      x = stk(nst-1) : y = stk(nst) : nst = nst-2
      select case tk$
      case "+" : x = x+y
      case "-" : x = x-y
      case "*" : x = x*y
      case "/" : x = x/y
      end select
      call getnumber(tk$,x,yes)
    end if
    if yes then
      nst = nst+1
      stk(nst) = x
    end if
  loop until not yes or s$=""
  yes = yes and nst=1
end sub

sub gettoken(s$,tk$)
  i = instr(s$," ")
  tk$ = mid$(s$,1,i-1)
  for i=i+1 to len(s$)
    if mid$(s$,i,1)<>" " then exit for
  next i
  s$ = mid$(s$,i)
end sub

sub getnumber(so$,x,yes)
  c$ = mid$(so$,1,1)
  sg = 1 : s$ = so$
  if c$="+" or c$="-" then
    if c$="-" then sg = -1
    s$ = mid$(so$,2)
  end if
  d = 0 : t = 0
  for i=1 to len(s$)
    k = instr(".0123456789",mid$(s$,i,1))
    if k=0 then exit for
    if k>1 then d = d+1 else t = t+1
  next i
  yes = k>0 and d>0 and t<=1
  if yes then x = sg*val(s$)
end sub