RPN Calculator in Go

The first thing the main function of this implementation does is creating the stdin data object (a reader) capable of performing reading operations from the standard input. That stdin then is used for repeatedly calling the library function ReadString to obtain the input for the calculator. ReadString returns a pair where the second member is an error value; eventually the error becomes non-nil, signalling end of input and leading to terminating the program.

ReadString is a general procedure which we instruct to read complete lines by telling it to stop at \n. The \n itself is also stored in the read string s. A sequence of non-blank tokens is obtained from s through a call to Fields. (The latter drops all whitespace, i.e. spaces, tab characters, and the newline character.) If the token sequence is non-empty, it is passed to evalrpn for evaluating the RPN expression.

The evaluation is carried out by running a for loop over the slice of tokens tks and maintaining a stack of intermediate results within the slice nums. The pop variable holds a function for pulling a value off the stack, and the built-in function append pushes a number onto it.

If a token tk is the name of an arithmetic operator, two numbers are popped off nums and the respective operation is performed on them. Otherwise the program attempts reading tk as a (64-bit) floating-point number. ParseFloat returns a two-value result with the second item representing an error if not nil.

Syntax errors in the RPN expression are handled in evalrpn through utilizing the defer-panic-recover mechanism. The program ‘panics’ explicitly, by calling the eponymous function upon encountering an erroneous token. It also raises a ‘panic’ situation implicitly, when pop is called with an empty stack: nums[-1] is then an invalid array/slice reference. In such an exceptional situation, control leaves (pop and) evalrpn immediately.

Regardless of whether a ‘panic’ has been raised or not, the deferred anonymous function call is executed on leaving evalrpn, before the control gets passed back to main. Within that call, if there was no ‘panic’ (recover returns nil) and there is only a single number in nums, the RPN expression is finally proven correct so the obtained result x is printed. Otherwise the expression is syntactically incorrect and an error message is duly printed.

Several standard library packages are imported for use in the program. Throughout the text, the names of the packages prefix the names of the functions that they provide to the program.

package main
import ("os"; "bufio"; "fmt"; "strings"; "strconv")

func evalrpn(tks []string) {
  var (nums []float64; x float64)
  pop := func() float64 {
    n := len(nums)-1
    t := nums[n]
    nums = nums[:n]
    return t
  defer func() {if recover()==nil && len(nums)==1 {
                  fmt.Println(x)} else {fmt.Println("error")}}()
  for _,tk := range tks {
    switch tk {
      case "+": x = pop()+pop()
      case "*": x = pop()*pop()
      case "-": x = pop(); x = pop()-x
      case "/": x = pop(); x = pop()/x
      default:  var e error
                x,e = strconv.ParseFloat(tk,64)
                if e!=nil {panic(0)}
    nums = append(nums,x)

func main() {
  stdin := bufio.NewReader(os.Stdin)
  for {
    s,e := stdin.ReadString('\n')
    if e!=nil {break}
    if tks := strings.Fields(s); len(tks)>0 {evalrpn(tks)}