diff --git a/README.md b/README.md index bfa2960..5d2a216 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A flexible and extensible C++ library for parsing and evaluating mathematical ex - Parentheses and operator precedence - Floating-point number support - Easy to add new operators, functions and constans -- Convertation to RPN and it's evaluation +- Easy to add and use variables ## Quick Start @@ -28,7 +28,11 @@ int main() { parser.registerOperator("max", parser::Operator{ parser::Operator::Type::FUNCTION, 4, false, 2, - [](auto args) { return std::max(args[0], args[1]); }, + [&](auto args) { + double left = parser.tokenToDouble(args[0]); // this function convert token to double, according of its type + double right = parser.tokenToDouble(args[1]); + return Token{MathParser::Token::Type::NUMBER, std::max(left, right)}; + }, "max" }); @@ -49,7 +53,10 @@ parser.registerOperator("log", parser::Operator{ 4, // Precedence false, // Right associativity 1, // Single argument - [](auto args) { return std::log(args[0]); }, + [](auto args) { + double arg = parser.tokenToDouble(args[0]); + return MathParser::Token{MathParser::Token::Type::NUMBER, std::log(arg)}; + }, // Evaluating function "log" // Function name }); ``` diff --git a/include/mathparser.hpp b/include/mathparser.hpp index d6abea8..a8ab83d 100644 --- a/include/mathparser.hpp +++ b/include/mathparser.hpp @@ -5,10 +5,34 @@ #include #include #include +#include +#include class MathParser { public: - using EvaluateFunc = std::function&)>; + struct Token { + enum class Type { + OPERATOR, + NUMBER, + VARIABLE, + CONSTANT + }; + Type type; + std::variant value; + + bool operator<(const Token& other) const { + if (type != other.type) + return static_cast(type) < static_cast(other.type); + + switch(type) { + case Type::NUMBER: + return std::get(value) < std::get(other.value); + default: + return std::get(value) < std::get(other.value); + } + } + }; + using EvaluateFunc = std::function&)>; struct Operator { enum class Type { UNARY, @@ -23,10 +47,12 @@ class MathParser { int operandCount; EvaluateFunc evaluate; std::string symbol; - }; + }; + private: std::unordered_map operators; std::map constants; + std::map variables; void init(); bool isOperator(const std::string& symbol); @@ -34,6 +60,10 @@ class MathParser { bool isConstant(const std::string& symbol); + + double evaluateRPN(const std::vector& RPN); //evaluate Reverse Polish notation + std::vector toRPN(const std::string& expression); //make Reverse Polish notation + public: MathParser(); ~MathParser() = default; @@ -41,6 +71,6 @@ class MathParser { void registerOperator(const std::string& symbol, Operator op); void registerConstant(const std::string& symbol, double value); double evaluate(const std::string& expression); - double evaluateRPN(const std::vector& RPN); //evaluate Reverse Polish notation - std::vector toRPN(const std::string& expression); //make Reverse Polish notation + double tokenToDouble(const Token& token); + double getVariableValue(const std::string& varName); }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d840e1f..2190cfe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,12 @@ -int main() { +#include "mathparser.hpp" +#include +int main() { + MathParser math; + std::cout<(args[0].value); + + double value = tokenToDouble(args[1]); + + variables[varName] = value; + return Token{Token::Type::NUMBER, value}; + }, + "=" + }); registerOperator("(", { Operator::Type::BRACKET, 0, false, 0, nullptr, "(" @@ -35,30 +52,44 @@ void MathParser::init() { registerOperator("+", { Operator::Type::BINARY, 1, false, 2, - [](auto args) { return args[0] + args[1]; }, + [this](auto args) { + double left = tokenToDouble(args[0]); + double right = tokenToDouble(args[1]); + return Token{Token::Type::NUMBER, left + right}; + }, "+" }); registerOperator("-", { Operator::Type::BINARY, 1, false, 2, - [](auto args) { return args[0] - args[1]; }, + [this](auto args) { + double left = tokenToDouble(args[0]); + double right = tokenToDouble(args[1]); + return Token{Token::Type::NUMBER, left - right}; + }, "-" }); registerOperator("*", { Operator::Type::BINARY, 2, false, 2, - [](auto args) { return args[0] * args[1]; }, + [this](auto args) { + double left = tokenToDouble(args[0]); + double right = tokenToDouble(args[1]); + return Token{Token::Type::NUMBER, left * right}; + }, "*" }); registerOperator("/", { Operator::Type::BINARY, 2, false, 2, - [](auto args) { - if (args[1] == 0) throw std::runtime_error("Division by zero"); - return args[0] / args[1]; + [this](auto args) { + double left = tokenToDouble(args[0]); + double right = tokenToDouble(args[1]); + if (right==0) throw std::runtime_error("Division by zero"); + return Token{Token::Type::NUMBER, left / right}; }, "/" }); @@ -66,47 +97,57 @@ void MathParser::init() { registerOperator("^", { Operator::Type::BINARY, 4, true, 2, - [](auto args) { return pow(args[0], args[1]); }, + [this](auto args) { + double left = tokenToDouble(args[0]); + double right = tokenToDouble(args[1]); + return Token{Token::Type::NUMBER, std::pow(left , right)}; + }, "^" }); registerOperator("~", { Operator::Type::UNARY, 3, true, 1, - [](auto args) { return -args[0]; }, + [this](auto args) { + double arg = tokenToDouble(args[0]); + return Token{Token::Type::NUMBER, -arg}; + }, "~" }); registerOperator("sqrt", { Operator::Type::FUNCTION, 5, false, 1, - [](auto args) { - if (args[0] < 0) throw std::runtime_error("Square root of negative"); - return sqrt(args[0]); + [this](auto args) { + double arg = tokenToDouble(args[0]); + return Token{Token::Type::NUMBER, sqrt(arg)}; }, "sqrt" }); registerOperator("sin", { Operator::Type::FUNCTION, 5, false, 1, - [](auto args) { - return sin(args[0]); + [this](auto args) { + double arg = tokenToDouble(args[0]); + return Token{Token::Type::NUMBER, sin(arg)}; }, "sin" }); registerOperator("cos", { Operator::Type::FUNCTION, 5, false, 1, - [](auto args) { - return cos(args[0]); + [this](auto args) { + double arg = tokenToDouble(args[0]); + return Token{Token::Type::NUMBER, cos(arg)}; }, "cos" }); registerOperator("tan", { Operator::Type::FUNCTION, 5, false, 1, - [](auto args) { - return tan(args[0]); + [this](auto args) { + double arg = tokenToDouble(args[0]); + return Token{Token::Type::NUMBER, tan(arg)}; }, "tan" }); @@ -129,9 +170,26 @@ bool MathParser::isConstant(const std::string& symbol) { return constants.find(symbol)!=constants.end(); } -std::vector MathParser::toRPN(const std::string& expression) { +double MathParser::tokenToDouble(const MathParser::Token& token) { + switch (token.type) { + case Token::Type::NUMBER: + return std::get(token.value); + case Token::Type::CONSTANT: + return constants[std::get(token.value)]; + case Token::Type::VARIABLE: { + const std::string& name = std::get(token.value); + if (variables.find(name) == variables.end()) + throw std::runtime_error("Undefined variable: " + name); + return variables[name]; + } + default: + throw std::runtime_error("Cannot convert token to number"); + } + } + +std::vector MathParser::toRPN(const std::string& expression) { std::stack opStack; - std::vector RPN; + std::vector RPN; bool expectOperand = true; int i=0; @@ -149,7 +207,7 @@ std::vector MathParser::toRPN(const std::string& expression) { j++; } std::string number = expression.substr(i,j-i); - RPN.push_back(number); + RPN.push_back(Token{Token::Type::NUMBER, stod(number)}); i=j; } else if (expression[i]=='(') { @@ -160,14 +218,14 @@ std::vector MathParser::toRPN(const std::string& expression) { else if (expression[i]==')') { while (!opStack.empty() && opStack.top().type!=Operator::Type::BRACKET) { - RPN.push_back(opStack.top().symbol); + RPN.push_back(Token{Token::Type::OPERATOR,opStack.top().symbol}); opStack.pop(); } if (opStack.empty()) throw std::runtime_error("Mismatched parentheses"); opStack.pop(); if (!opStack.empty() && opStack.top().type == Operator::Type::FUNCTION) { - RPN.push_back(opStack.top().symbol); + RPN.push_back(Token{Token::Type::OPERATOR,opStack.top().symbol}); opStack.pop(); } i++; @@ -177,18 +235,21 @@ std::vector MathParser::toRPN(const std::string& expression) { while (j MathParser::toRPN(const std::string& expression) { if (topOp.precedence < curOp.precedence) break; if (topOp.precedence == curOp.precedence && curOp.isRightAssociative) break; opStack.pop(); - RPN.push_back(topOp.symbol); + RPN.push_back(Token{Token::Type::OPERATOR,topOp.symbol}); } opStack.push(curOp); expectOperand = true; @@ -212,20 +273,20 @@ std::vector MathParser::toRPN(const std::string& expression) { } while (!opStack.empty()) { Operator op = opStack.top(); - RPN.push_back(op.symbol); + RPN.push_back(Token{Token::Type::OPERATOR,op.symbol}); opStack.pop(); } return RPN; } -double MathParser::evaluateRPN(const std::vector& RPN) { - std::stack numStack; - for (const std::string& symbol: RPN) { - if (isOperator(symbol)) { - Operator op = operators[symbol]; +double MathParser::evaluateRPN(const std::vector& RPN) { + std::stack numStack; + for (const Token& token: RPN) { + if (token.type == Token::Type::OPERATOR) { + Operator op = operators[std::get(token.value)]; if (op.type==Operator::Type::COMMA) continue; - std::vector operands; + std::vector operands; for (int i=0; i& RPN) { numStack.push(op.evaluate(operands)); } else { - if (isConstant(symbol)) - numStack.push(constants[symbol]); + if (token.type == Token::Type::CONSTANT) + numStack.push(Token{Token::Type::NUMBER, tokenToDouble(token)}); else - numStack.push(stod(symbol)); + numStack.push(token); } } - return numStack.top(); + return tokenToDouble(numStack.top()); } double MathParser::evaluate(const std::string& expression) { return evaluateRPN(toRPN(expression)); +} + +double MathParser::getVariableValue(const std::string& varName) { + return tokenToDouble(Token{Token::Type::VARIABLE, varName}); } \ No newline at end of file diff --git a/tests/test_mathparser.cpp b/tests/test_mathparser.cpp index e174e55..f00907b 100644 --- a/tests/test_mathparser.cpp +++ b/tests/test_mathparser.cpp @@ -19,7 +19,11 @@ int main() { mathParser.registerOperator("max", MathParser::Operator{ MathParser::Operator::Type::FUNCTION, 4, false, 2, - [](auto args) { return std::max(args[0], args[1]); }, + [&](auto args) { + double left = mathParser.tokenToDouble(args[0]); + double right = mathParser.tokenToDouble(args[1]); + return MathParser::Token{MathParser::Token::Type::NUMBER, std::max(left, right)}; + }, "max" }); @@ -39,6 +43,12 @@ int main() { test(fabs(mathParser.evaluate("sin(cos(-3*pi/2))")) 0) { std::cerr << "\n" << failures << " TESTS FAILED!\n";