I was in the mood for some C++ and decided to write a command line calculator, which understands addition, subtraction, multiplication and parentheses. See what I have:
main.cpp
#include <algorithm>
#include <cctype>
#include <iostream>
#include <iterator>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <utility>
#include <vector>
using std::cin;
using std::copy;
using std::cout;
using std::endl;
using std::isspace;
using std::ostream_iterator;
using std::pair;
using std::runtime_error;
using std::stack;
using std::string;
using std::stringstream;
using std::vector;
enum class TokenType {
OPERAND,
OPERATOR
};
enum class Operator {
PLUS,
MINUS,
MULTIPLY,
LEFT_PARENTHESIS,
RIGHT_PARENTHESIS
};
static int get_operator_precedence(Operator _operator)
{
switch (_operator) {
case Operator::PLUS:
case Operator::MINUS:
return 1;
case Operator::MULTIPLY:
return 0;
default:
if (_operator == Operator::LEFT_PARENTHESIS)
{
throw runtime_error(
"Left parenthesis has no precedence assigned to it.");
}
if (_operator == Operator::RIGHT_PARENTHESIS)
{
throw runtime_error(
"Right parenthesis has no precedence assigned to it.");
}
throw std::runtime_error(
"Unknown operator. This should not happen.");
}
}
class Token
{
TokenType m_token_type;
Operator m_operator;
int m_operand;
public:
Token(int operand) : m_operand{operand},
m_token_type{TokenType::OPERAND} {}
Token(Operator _operator) : m_operand{0},
m_token_type(TokenType::OPERATOR),
m_operator{_operator} {}
TokenType get_token_type() const
{
return m_token_type;
}
Operator get_operator() const
{
return m_operator;
}
int get_operand() const
{
return m_operand;
}
friend std::ostream& operator<<(std::ostream&, const Token&);
};
std::ostream& operator<<(std::ostream& out, const Token& token)
{
if (token.m_token_type == TokenType::OPERAND)
{
out << token.m_operand;
}
else
{
switch (token.m_operator)
{
case Operator::PLUS:
out << "+";
break;
case Operator::MINUS:
out << "-";
break;
case Operator::MULTIPLY:
out << "*";
break;
case Operator::LEFT_PARENTHESIS:
out << "(";
break;
case Operator::RIGHT_PARENTHESIS:
out << ")";
break;
}
}
return out;
}
vector<Token> tokenize(const string& expression)
{
vector<Token> token_vector;
stringstream number_string_stream;
bool number_string_stream_empty = true;
size_t char_index = 0;
size_t expression_length = expression.length();
while (char_index < expression_length)
{
const char current_char = expression[char_index];
switch (current_char)
{
case '(':
if (!number_string_stream_empty)
{
int number;
number_string_stream >> number;
number_string_stream.clear();
token_vector.push_back(Token(number));
number_string_stream_empty = true;
}
token_vector.push_back(Token(Operator::LEFT_PARENTHESIS));
break;
case ')':
if (!number_string_stream_empty)
{
int number;
number_string_stream >> number;
number_string_stream.clear();
token_vector.push_back(Token(number));
number_string_stream_empty = true;
}
token_vector.push_back(Token(Operator::RIGHT_PARENTHESIS));
break;
case '+':
if (!number_string_stream_empty)
{
int number;
number_string_stream >> number;
number_string_stream.clear();
token_vector.push_back(Token(number));
number_string_stream_empty = true;
}
token_vector.push_back(Token(Operator::PLUS));
break;
case '-':
if (!number_string_stream_empty)
{
int number;
number_string_stream >> number;
number_string_stream.clear();
token_vector.push_back(Token(number));
number_string_stream_empty = true;
}
token_vector.push_back(Token(Operator::MINUS));
break;
case '*':
if (!number_string_stream_empty)
{
int number;
number_string_stream >> number;
number_string_stream.clear();
token_vector.push_back(Token(number));
number_string_stream_empty = true;
}
token_vector.push_back(Token(Operator::MULTIPLY));
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
number_string_stream_empty = false;
number_string_stream << current_char;
break;
default:
if (!isspace(current_char))
{
stringstream err_msg;
err_msg << "Bad character: '"
<< current_char
<< "' at index "
<< char_index
<< "!";
throw runtime_error(err_msg.str());
}
}
++char_index;
}
if (!number_string_stream_empty)
{
int operand;
number_string_stream >> operand;
token_vector.push_back(Token(operand));
}
return token_vector;
}
vector<Token> shunting_yard_algorithm(const vector<Token>& token_vector)
{
vector<Token> postfix_token_vector;
stack<Token> operator_stack;
for (const Token& token : token_vector)
{
if (token.get_token_type() == TokenType::OPERAND)
{
postfix_token_vector.push_back(token);
}
else if (token.get_operator() == Operator::LEFT_PARENTHESIS)
{
operator_stack.push(token);
}
else if (token.get_operator() == Operator::RIGHT_PARENTHESIS)
{
while (!operator_stack.empty()
&& operator_stack.top().get_operator()
!= Operator::LEFT_PARENTHESIS)
{
postfix_token_vector.push_back(operator_stack.top());
operator_stack.pop();
}
if (operator_stack.empty()
|| operator_stack.top().get_operator()
!= Operator::LEFT_PARENTHESIS)
{
throw runtime_error("The parentheses are invalid.");
}
else
{
operator_stack.pop();
}
}
else
{
while (!operator_stack.empty()
&& operator_stack.top().get_operator()
!= Operator::LEFT_PARENTHESIS
&& get_operator_precedence(
operator_stack.top().get_operator()) <
get_operator_precedence(token.get_operator()))
{
postfix_token_vector.push_back(operator_stack.top());
operator_stack.pop();
}
operator_stack.push(token);
}
}
while (!operator_stack.empty())
{
postfix_token_vector.push_back(operator_stack.top());
operator_stack.pop();
}
return postfix_token_vector;
}
pair<int, vector<Token>> coderodde_evaluate(const string& expression)
{
vector<Token> infix_token_vector = tokenize(expression);
vector<Token> postfix_token_vector =
shunting_yard_algorithm(infix_token_vector);
stack<Token> _stack;
for (const Token& token : postfix_token_vector)
{
if (token.get_token_type() == TokenType::OPERATOR
&& token.get_operator() == Operator::LEFT_PARENTHESIS)
{
throw runtime_error(
"The parentheses are invalid.");
}
if (token.get_token_type() == TokenType::OPERAND)
{
_stack.push(token.get_operand());
}
else
{
if (_stack.size() < 2)
{
throw runtime_error("The parentheses are invalid.");
}
int operand2 = _stack.top().get_operand(); _stack.pop();
int operand1 = _stack.top().get_operand(); _stack.pop();
switch (token.get_operator())
{
case Operator::PLUS:
_stack.push(Token(operand1 + operand2));
break;
case Operator::MINUS:
_stack.push(Token(operand1 - operand2));
break;
case Operator::MULTIPLY:
_stack.push(Token(operand1 * operand2));
break;
}
}
}
if (_stack.size() != 1)
{
throw runtime_error("The parentheses are invalid.");
}
return make_pair(_stack.top().get_operand(), postfix_token_vector);
}
int main()
{
string expr;
while (true)
{
cout << "> ";
getline(cin, expr);
if (expr == "quit")
{
break;
}
try {
pair<int, vector<Token>> data = coderodde_evaluate(expr);
cout << "Value: " << data.first << ", postfix notation: ";
copy(data.second.cbegin(),
data.second.cend(),
ostream_iterator<Token>(cout, " "));
cout << endl;
}
catch (runtime_error& err)
{
cout << "ERROR: " << err.what() << endl;
}
}
}
Critique request
Am I doing good C++ here? Please tell me anything that comes to mind.