No, that was a mistake in my example. You need to get a copy of the value at the top of the stack, and then the actual value itself is popped off of the stack on the pop() call (so a reference to top() after calling pop() would be invalid).
Switching the type to an enum is a good first step, here is a full expanded example of what I meant r.e. parsing a token at a time, but it starts to get into parser territory.
Code:
#include <iostream>
#include <sstream>
#include <string>
#include <cctype>
enum token_type {
TOK_INVALID = 0,
TOK_NUMBER,
TOK_OPERATOR,
TOK_COMMAND
};
class token {
public:
token(enum token_type type, std::string val) : m_type(type), m_val(val) {}
const enum token_type type()
{
return m_type;
}
const std::string &value()
{
return m_val;
}
private:
enum token_type m_type;
std::string m_val;
};
token next_token(std::istream &in)
{
std::stringstream buf;
char ch;
if (!in.get(ch)) {
// signal error;
}
enum token_type type = TOK_INVALID;
if (std::isspace(ch)) {
return next_token(in); // skip leading spaces
} else if (std::isdigit(ch)) {
type = TOK_NUMBER;
} else if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '^') {
type = TOK_OPERATOR;
} else if (ch == ':') {
type = TOK_COMMAND;
}
do {
// additionally validate characters of tokens as you parse them
if (std::isspace(ch)) {
break;
}
buf << ch;
} while (in.get(ch));
return token(type, buf.str());
}
int main() {
std::stringstream input("123456 789 +");
while (!input.eof()) {
token tok = next_token(input);
switch (tok.type()) {
case TOK_INVALID:
std::cout << "Invalid token" << std::endl;
break;
case TOK_NUMBER:
std::cout << "Number token" << std::endl;
break;
case TOK_OPERATOR:
std::cout << "Operator token" << std::endl;
break;
case TOK_COMMAND:
std::cout << "Command token" << std::endl;
break;
}
std::cout << "Value: " << tok.value() << std::endl;
}
return 0;
}
After doing that you can switch up your main loop like this:
Code:
int main()
{
std::istream &in = std::cin;
while (running && !in.eof()) {
token tok = next_token(in);
enum token_type type = tok.type();
if (type == TOK_COMMAND) {
// handle your commands
} else if (type == TOK_NUMBER) {
// push number to stack
} else if (type == TOK_OPERATOR) {
// push return val to stack
} else {
// handle invalid tokens
}
}
std::cout << "\t" << "RESULT" << "\t\t" << num_stack << std::endl << std::endl;
return 0;
}
Though this slightly changes how your program deals with input data, since you're evaluating groups of tokens at a time at the moment. The semantics of the program remain the same though. Now you can terminate the program and print the RESULT by handling your ":q" command or sending EOF (^D in a shell). If you still want to print the result after a group of tokens, you can parse '\n' separately as a delimiter token and avoid the need for dealing with whole lines at a time.
edit: also RPN calculators are particularly fun to play with. You can expand on this by implementing the Shunting-Yard algorithm and compiling your RPN to some target code (LLVM IR, libgccjit, bytecode), effectively implementing a small JIT compiler for math expressions.
edit 2: on top of compiling your RPN to some target code, you could also allow calling functions defined on the target platform that you're compiling for (libc functions or java.lang.Math functions)