PLACE
spec
solutions
C++

RPN Calculator in C++

As C++ is an extension to C, when we consider an implementation in this language, it is natural to be interested in how we can do better than in plain C. As it turns out, the main advantage of C++ over C in solving our problem is reading and tokenizing the input. C++'s standard library is particularly convenient in this respect.

In the main function, the outer loop calls getline on cin to read lines into the variable s. The return value of getline is used to determine when the loop and the program are to be terminated due to exhausting the input. cin is a library-provided stream object associated with the standard input, and s is of class string. In contrast to the C implementation, lines of arbitrary length are read by a single function call, and the buffer needed to accomodate the data is automatically and transparently allocated.

For tokenizing the input, we create a ‘string stream’ sstr on s, so as, in the inner loop, individual tokens are extracted using the standard input operator >>. All tokens go into the stack tks, from where, unless the input line contained no tokens at all, they are passed to evalrpn, which evaluates the RPN expression by recursively working downwards on tks.

Within evalrpn, a token tk is popped off tks and checked for being an operator or a number. If an operator, we are also interested in whether tks contains the respective operands, pull them off the stack, and perform the corresponding arithmetical operation. If tk is not an operator, stod is used to read it as a number. In any case, the result goes into x, and is eventually returned as the value of evalrpn.

Syntax errors are signalled by throwing exceptions. For convenience, the test for an erroneous situation and the throwing itself are packed in a macro definition. As we do not want to differentiate between the various reasons that may cause a syntax error, what exactly value is being thrown is irrelevant. The call of evalrpn in main is nested in a try block. A throw occurs either directly in that block or within evalrpn, so all throws are handled by the single catch statement following the try block. An implicit throw takes place within stod whenever it fails to read tk as a number. The ellipsis in the handler stands for a ‘don't care’ parameter, meaning that it can accept any possible value thrown but is not actually using it.

For comparison, the C implementation handles errors by the setjmp/longjmp mechanism, which is less convenient to write or read, but also significantly less time-consuming than the throw/catch exception handling mechanism of C++ (although the latter does not matter here).

#include <iostream>
#include <sstream>
#include <string>
#include <stack>

using namespace std;

#define ASSERT(b) if (!(b)) throw ""

double evalrpn(stack<string> & tks) {
  ASSERT(!tks.empty());
  double x,y;
  auto tk = tks.top(); tks.pop();
  auto n = tk.size();
  if (n==1 && string("+-*/").find(tk)!=string::npos) {
    y = evalrpn(tks);
    x = evalrpn(tks);
    if      (tk[0]=='+')  x += y;
    else if (tk[0]=='-')  x -= y;
    else if (tk[0]=='*')  x *= y;
    else                  x /= y;
  }  else {
    unsigned i;  x = stod(tk,&i);  ASSERT(i==n);
  }
  return x;
}

int main() {
  string s;
  while (getline(cin,s)) {
    stack<string> tks;
    istringstream sstr(s);
    string tk;
    while (sstr >> tk) tks.push(tk);
    if (!tks.empty())
      try {
        auto z = evalrpn(tks);
        ASSERT(tks.empty());
        cout << z << endl;
      } catch (...) {cout << "error\n";}
  }
  return 0;
}

boykobbatgmaildotcom