I am fairly new to C++ and I was wondering if there is anything I can improve in this code(perf, readability)? Thanks. This is the full code: Github Link
I will post the parser that creates a rpn representation of an input string and a function that evaluates the rpn here(the rest is on the github):
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <string_view>
#include <string>
#include <vector>
#include <utility>
#include <stack>
#include <numbers>
#include <regex>
#include <variant>
#include <chrono>
#include <cmath>
constexpr int LEFT_ASSOC = 0;
constexpr int RIGHT_ASSOC = 1;
//pair is <prec, assoc_id>
static const std::unordered_map<std::string_view, std::pair<int, int>> assoc_prec{ {"^", std::make_pair(4, RIGHT_ASSOC)},
{"*", std::make_pair(3, LEFT_ASSOC)},
{"/", std::make_pair(3, LEFT_ASSOC)},
{"+", std::make_pair(2, LEFT_ASSOC)},
{"-", std::make_pair(2, LEFT_ASSOC)} };
static const std::unordered_map<std::string_view, double(*)(double)> unary_func_tbl{ {"sin", &sin},
{"cos", &cos},
{"sqrt", &sqrt},
{"abs", &abs},
{"tan", &tan},
{"acos", &acos},
{"asin", &asin},
{"atan", &atan},
{"log", &log},
{"log10", &log10},
{"cosh", &cosh},
{"sinh", &sinh},
{"tanh", &tanh},
{"exp", &exp},
{"cbrt", &cbrt},
{"tgamma", &tgamma},
{"lgamma", &lgamma},
{"ceil", &ceil},
{"floor", &floor},
{"acosh", &acosh},
{"asinh", &asinh},
{"trunc", &trunc},
{"atanh", &atanh} };
static const std::unordered_set<std::string_view> funcs{ "sin", "tan", "acos", "asin", "asin", "abs",
"atan", "cosh", "sinh", "cos", "tanh",
"acosh", "asinh", "atanh", "exp", "ldexp",
"log", "log10", "sqrt", "cbrt", "tgamma",
"lgamma", "ceil", "floor", "trunc" };
bool is_left_assoc(std::string_view str)
{
int id = assoc_prec.at(str).second;
if (id == 0) return true;
else return false;
}
bool is_binary_op(std::string_view str)
{
if (str == "/" || str == "*" || str == "+" || str == "-" || str == "^")
{
return true;
}
else return false;
}
bool is_func(std::string_view str)
{
if (funcs.count(str) > 0) return true;
else return false;
}
//handls decimal numbers
bool is_num(std::string_view str)
{
int num_found_periods = 0;
for (const auto c : str)
{
if (c == '.')
{
num_found_periods++;
if (num_found_periods > 1)
{
return false;
}
}
if (!isdigit(c) && c != '.')
{
return false;
}
}
return true;
}
int get_prec(std::string_view str)
{
if (is_func(str))
{
return 1; //TODO: check it this is the correct value
}
else if (is_binary_op(str))
{
return assoc_prec.at(str).first;
}
else
{
return 0;
}
}
//from https://stackoverflow.com/a/56204256
//modified regex expr
std::vector<std::string> tokenize(const std::string& str)
{
std::vector<std::string> res;
const std::regex words_regex("(sin|tan|acos|asin|abs|atan|cosh|sinh|cos|"
"tanh|acosh|asinh|atanh|exp|ldexp|log|log10|"
"sqrt|cbrt|tgamma|lgamma|ceil|floor|x|e)|^-|[0-9]?"
"([0-9]*[.])?[0-9]+|[\\-\\+\\\\\(\\)\\/\\*\\^\\]",
std::regex_constants::egrep);
auto words_begin = std::sregex_iterator(str.begin(), str.end(), words_regex);
auto words_end = std::sregex_iterator();
for (std::sregex_iterator i = words_begin; i != words_end; ++i)
{
res.push_back((*i).str());
}
return res;
}
//params:
//str - string to be converted
//var_name - any occurances of this as a seperate token will be treated as a varable
//convert string in infix notation to a string in Reverse Polish Notation
//using dijkstra's shunting yard algorithm
std::vector<std::variant<double, std::string>> s_yard(const std::string& str, std::string var_name)
{
std::vector<std::variant<double, std::string>> output_queue;
std::stack<std::string> op_stack;
for (const auto& tok : tokenize(str))
{
if (tok == "pi")
{
output_queue.push_back(std::numbers::pi_v<double>);
}
else if (tok == "e")
{
output_queue.push_back(std::numbers::e_v<double>);
}
else if (tok == var_name)
{
output_queue.push_back(tok);
}
else if (is_num(tok))
{
output_queue.push_back(strtod(tok.c_str(), NULL));
}
else if (is_func(tok))
{
op_stack.push(tok);
}
else if (is_binary_op(tok))
{
while (!op_stack.empty() && \
(is_binary_op(op_stack.top()) && get_prec(op_stack.top()) > (get_prec(tok)) || \
(get_prec(op_stack.top()) == get_prec(tok) && is_left_assoc(tok))) && \
(op_stack.top() != "("))
{
//pop operators from stack to queue
while (!op_stack.empty())
{
output_queue.push_back(op_stack.top());
op_stack.pop();
}
}
op_stack.push(tok);
}
else if (tok == "(")
{
op_stack.push(tok);
}
else if (tok == ")")
{
while (op_stack.top() != "(")
{
output_queue.push_back(op_stack.top());
op_stack.pop();
}
if (op_stack.top() == "(")
{
op_stack.pop();
}
if (is_func(op_stack.top()))
{
output_queue.push_back(op_stack.top());
op_stack.pop();
}
}
}
//all tokens read
while (!op_stack.empty())
{
//there are mismatched parentheses
if (op_stack.top() == "(" || op_stack.top() == ")")
{
std::cout << "mismatched parentheses\n";
}
output_queue.push_back(op_stack.top());
op_stack.pop();
}
return output_queue;
}
double compute_binary_ops(double d1, double d2, std::string_view op)
{
if (op == "*") return d1 * d2;
else if (op == "+") return d1 + d2;
else if (op == "-") return d1 - d2;
else if (op == "/") return d1 / d2;
else if (op == "^") return pow(d1, d2);
else
{
std::cout << R"(invalid operator: ")" << op << R"(" passed to func "compute_binary_ops")" << '\n';
exit(-1);
}
}
double eval_rpn(const std::vector<std::variant<double, std::string>>& tokens, std::string var_name, double var_value)
{
double d2 = 0.0;
double res = 0.0;
std::stack<std::variant<double, std::string>> stack;
for (const auto& tok : tokens)
{
if (const double *number = std::get_if<double>(&tok))
{
stack.push(*number);
}
else if (std::get<std::string>(tok) == var_name)
{
stack.push(var_value);
}
//handle binary operaters
else if(is_binary_op(std::get<std::string>(tok)))
{
d2 = std::get<double>(stack.top());
stack.pop();
if (!stack.empty())
{
const double d1 = std::get<double>(stack.top());
stack.pop();
res = compute_binary_ops(d1, d2, std::get<std::string>(tok));
stack.push(res);
}
else
{
if (std::get<std::string>(tok) == "-") res = -(d2);
else res = d2;
stack.push(res);
}
}
//handle funcs(unary ops)
else if (is_func(std::get<std::string>(tok)))
{
if (!stack.empty())
{
const double d1 = std::get<double>(stack.top());
stack.pop();
res = (*unary_func_tbl.at(std::get<std::string>(tok)))(d1);
stack.push(res);
}
else
{
if (std::get<std::string>(tok) == "-") res = -(d2);
else res = d2;
stack.push(res);
}
}
}
return std::get<double>(stack.top());
}
tokenizereturns and some examples of the kinds of equations you're intending to parse would be helpful. \$\endgroup\$