Last active
November 8, 2018 17:43
-
-
Save rdbuf/ba40bef7ae5a191b4ef4fd4872d8117c to your computer and use it in GitHub Desktop.
A calculator that effectively implements untyped lambda calculus + some syntactic sugar, written by me back in the day. Pratt parsing algorithm is employed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <vector> | |
| #include <memory> | |
| #include <variant> | |
| #include <exception> | |
| #include <algorithm> | |
| #include <string> | |
| #include <cmath> | |
| #include <functional> | |
| #include <unordered_map> | |
| #include <iostream> | |
| bool almost_equal(double x, double y/*, int ulp*/) { | |
| // return std::abs(x-y) <= std::numeric_limits<double>::epsilon() * std::abs(x+y) * ulp || std::abs(x-y) < std::numeric_limits<double>::min(); | |
| return std::abs(x-y) <= 1e-14; | |
| } | |
| struct lexeme_number { double value; }; | |
| struct lexeme_star {}; | |
| struct lexeme_plus {}; | |
| struct lexeme_slash {}; | |
| struct lexeme_minus {}; | |
| struct lexeme_left_paren {}; | |
| struct lexeme_right_paren {}; | |
| struct lexeme_identifier { std::string name; }; | |
| struct lexeme_hat {}; | |
| using token = std::variant<lexeme_number, lexeme_star, lexeme_plus, lexeme_slash, lexeme_minus, lexeme_left_paren, lexeme_right_paren, lexeme_identifier, lexeme_hat>; | |
| std::vector<token> tokenize(std::string::iterator it, const std::string::iterator input_end) { | |
| std::vector<token> result; | |
| for (; it != input_end; ++it) { | |
| switch (*it) { | |
| case '+': result.push_back(lexeme_plus()); break; | |
| case '-': result.push_back(lexeme_minus()); break; | |
| case '*': result.push_back(lexeme_star()); break; | |
| case '/': result.push_back(lexeme_slash()); break; | |
| case '(': result.push_back(lexeme_left_paren()); break; | |
| case ')': result.push_back(lexeme_right_paren()); break; | |
| case '^': result.push_back(lexeme_hat()); break; | |
| case ' ': break; | |
| default: { | |
| if (std::isdigit(*it)) { | |
| lexeme_number val; | |
| sscanf(&*it, "%lf", &val.value); | |
| result.push_back(val); | |
| it = std::prev(std::find_if_not(it + 1, input_end, [](char x) -> bool { return std::isdigit(x) || x == '.'; })); | |
| break; | |
| } else if (std::isalpha(*it)) { | |
| auto end = std::find_if_not(it + 1, input_end, (int(*)(int))std::isalnum); | |
| result.push_back(lexeme_identifier{std::string(it, end)}); | |
| it = std::prev(end); | |
| break; | |
| } else throw std::runtime_error("unexpected token '" + std::string(it, it+1) + "'"); | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| struct expr_fund { std::function<double(double)> fun; }; | |
| struct expr_constant { double value; }; | |
| struct expr_add; | |
| struct expr_mul; | |
| struct expr_sub; | |
| struct expr_div; | |
| struct expr_negate; | |
| struct expr_apply; | |
| struct expr_power; | |
| struct expr_abstr; | |
| struct expr_variable; | |
| using expression = std::variant<expr_constant, expr_add, expr_mul, expr_sub, expr_div, expr_negate, expr_apply, expr_power, expr_abstr, expr_variable, expr_fund>; | |
| struct expr_add { std::shared_ptr<expression> lhs, rhs; }; | |
| struct expr_mul { std::shared_ptr<expression> lhs, rhs; }; | |
| struct expr_sub { std::shared_ptr<expression> lhs, rhs; }; | |
| struct expr_div { std::shared_ptr<expression> lhs, rhs; }; | |
| struct expr_negate { std::shared_ptr<expression> value; }; | |
| struct expr_apply { std::shared_ptr<expression> fun; std::shared_ptr<expression> arg; }; | |
| struct expr_power { std::shared_ptr<expression> lhs, rhs; }; | |
| struct expr_abstr { std::string arg; std::shared_ptr<expression> body; }; | |
| struct expr_variable { std::string name; }; | |
| expression parse(int rbp, std::vector<token>::iterator& current, const std::vector<token>::iterator& end); | |
| template<class T> | |
| void match(std::vector<token>::iterator& current) { | |
| if (!std::holds_alternative<T>(*current)) throw std::runtime_error("unmatched token"); | |
| } | |
| struct prefix { | |
| std::vector<token>::iterator& pre_begin; | |
| const std::vector<token>::iterator& end; | |
| expression operator()(lexeme_number x) { return expr_constant{x.value}; } | |
| expression operator()(lexeme_star) { throw std::runtime_error("'*' in the prefix form"); } | |
| expression operator()(lexeme_plus) { throw std::runtime_error("'+' in the prefix form"); } | |
| expression operator()(lexeme_slash) { throw std::runtime_error("'/' in the prefix form"); } | |
| expression operator()(lexeme_minus) { return expr_negate{std::make_shared<expression>(parse(100, ++pre_begin, end))}; } | |
| expression operator()(lexeme_left_paren) { expression result = parse(0, ++pre_begin, end); match<lexeme_right_paren>(++pre_begin); return result; } | |
| expression operator()(lexeme_right_paren) { throw std::runtime_error("')' in the prefix form"); } | |
| expression operator()(lexeme_identifier id) { return expr_variable{id.name}; } | |
| expression operator()(lexeme_hat) { throw std::runtime_error("'^' in the prefix form"); } | |
| }; | |
| struct infix { | |
| expression lhs; | |
| std::vector<token>::iterator& pre_begin; | |
| const std::vector<token>::iterator& end; | |
| expression operator()(lexeme_number x) { return expr_apply{std::make_shared<expression>(lhs), std::make_shared<expression>(expr_constant{x.value})}; } | |
| expression operator()(lexeme_star) { return expr_mul{std::make_shared<expression>(lhs), std::make_shared<expression>(parse(20, ++pre_begin, end))}; } | |
| expression operator()(lexeme_plus) { return expr_add{std::make_shared<expression>(lhs), std::make_shared<expression>(parse(10, ++pre_begin, end))}; } | |
| expression operator()(lexeme_slash) { return expr_div{std::make_shared<expression>(lhs), std::make_shared<expression>(parse(20, ++pre_begin, end))}; } | |
| expression operator()(lexeme_minus) { return expr_sub{std::make_shared<expression>(lhs), std::make_shared<expression>(parse(10, ++pre_begin, end))}; } | |
| expression operator()(lexeme_left_paren) { | |
| expression result = expr_apply{std::make_shared<expression>(lhs), std::make_shared<expression>(parse(0, ++pre_begin, end))}; | |
| match<lexeme_right_paren>(++pre_begin); | |
| return result; | |
| } | |
| expression operator()(lexeme_right_paren) { throw std::runtime_error("')' in the infix form"); } | |
| expression operator()(lexeme_identifier id) { return expr_apply{std::make_shared<expression>(lhs), std::make_shared<expression>(expr_variable{id.name})}; } | |
| expression operator()(lexeme_hat) { return expr_power{std::make_shared<expression>(lhs), std::make_shared<expression>(parse(29, ++pre_begin, end))}; } | |
| }; | |
| struct lbp { | |
| int operator()(lexeme_number) { return 100; } | |
| int operator()(lexeme_plus) { return 10; } | |
| int operator()(lexeme_star) { return 20; } | |
| int operator()(lexeme_minus) { return 10; } | |
| int operator()(lexeme_slash) { return 20; } | |
| int operator()(lexeme_left_paren) { return 100; } | |
| int operator()(lexeme_right_paren) { return 0; } | |
| int operator()(lexeme_identifier) { return 100; } | |
| int operator()(lexeme_hat) { return 30; } | |
| }; | |
| // Pratt parsing algorithm | |
| expression parse(int rbp, std::vector<token>::iterator& current, const std::vector<token>::iterator& end) { | |
| if (current == end) throw std::runtime_error("seems incomplete"); | |
| expression left = std::visit(prefix{current, end}, *current); | |
| while (current + 1 < end && rbp < std::visit(lbp(), *(current + 1))) { | |
| ++current; | |
| left = std::visit(infix{left, current, end}, *current); | |
| } | |
| return left; | |
| } | |
| expression parse_helper(std::vector<token>::iterator begin, const std::vector<token>::iterator& end) { | |
| expression result = parse(0, begin, end); | |
| if (begin + 1 != end) throw std::runtime_error("there are " + std::to_string(end - begin - 1) + " unused tokens left in the input"); | |
| return result; | |
| } | |
| struct substitute { | |
| const std::string& target; | |
| const expression& value; | |
| expression operator()(const expr_abstr& e) { | |
| if (e.arg == target) return e; | |
| return expr_abstr{e.arg, std::make_shared<expression>(std::visit(*this, *e.body))}; | |
| } | |
| expression operator()(const expr_apply& e) { | |
| return expr_apply{std::make_shared<expression>(std::visit(*this, *e.fun)), std::make_shared<expression>(std::visit(*this, *e.arg)) }; | |
| } | |
| expression operator()(const expr_constant& e) { return e; } | |
| expression operator()(const expr_add& e) { | |
| return expr_add{std::make_shared<expression>(std::visit(*this, *e.lhs)), std::make_shared<expression>(std::visit(*this, *e.rhs))}; | |
| } | |
| expression operator()(const expr_mul& e) { | |
| return expr_mul{std::make_shared<expression>(std::visit(*this, *e.lhs)), std::make_shared<expression>(std::visit(*this, *e.rhs))}; | |
| } | |
| expression operator()(const expr_sub& e) { | |
| return expr_sub{std::make_shared<expression>(std::visit(*this, *e.lhs)), std::make_shared<expression>(std::visit(*this, *e.rhs))}; | |
| } | |
| expression operator()(const expr_div& e) { | |
| return expr_div{std::make_shared<expression>(std::visit(*this, *e.lhs)), std::make_shared<expression>(std::visit(*this, *e.rhs))}; | |
| } | |
| expression operator()(const expr_negate& e) { return expr_negate{std::make_shared<expression>(std::visit(*this, *e.value))}; } | |
| expression operator()(const expr_power& e) { | |
| return expr_power{std::make_shared<expression>(std::visit(*this, *e.lhs)), std::make_shared<expression>(std::visit(*this, *e.rhs))}; | |
| } | |
| expression operator()(const expr_variable& e) { return e.name == target ? value : e; } | |
| expression operator()(const expr_fund& e) { return e; } | |
| }; | |
| struct evaluate { | |
| expression operator()(const expr_constant& e); | |
| expression operator()(const expr_add& e); | |
| expression operator()(const expr_mul& e); | |
| expression operator()(const expr_sub& e); | |
| expression operator()(const expr_div& e); | |
| expression operator()(const expr_negate& e); | |
| expression operator()(const expr_apply& e); | |
| expression operator()(const expr_power& e); | |
| expression operator()(const expr_abstr& e); | |
| expression operator()(const expr_variable& e); | |
| expression operator()(const expr_fund& e); | |
| }; | |
| std::unordered_map<std::string, expression> globals{ | |
| // { "x", expr_add{std::make_shared<expression>(expr_constant{4}), std::make_shared<expression>(expr_constant{4})} }, | |
| // { "plus1", expr_abstr{"x", std::make_shared<expression>(expr_add{std::make_shared<expression>(expr_variable{"x"}), std::make_shared<expression>(expr_constant{1})})} }, | |
| // { "id", expr_abstr{"x", std::make_shared<expression>(expr_variable{"x"})} }, | |
| { "sin", expr_fund{(double(*)(double))std::sin} }, | |
| { "cos", expr_fund{(double(*)(double))std::cos} }, | |
| { "tg", expr_fund{(double(*)(double))std::tan} }, | |
| { "ctg", expr_fund{[](double x) { return 1 / std::tan(x); }} }, | |
| { "exp", expr_fund{(double(*)(double))std::exp} }, | |
| { "ln", expr_fund{(double(*)(double))std::log} } | |
| }; | |
| expression abstr_parsing_helper(std::string::iterator it, const std::string::iterator input_end) { // ad-hoc but working | |
| while (*it == ' ') ++it; | |
| if (*it == '=') { | |
| std::vector<token> input = tokenize(++it, input_end); | |
| return parse_helper(input.begin(), input.end()); | |
| } | |
| if (!std::isalpha(*it)) throw std::runtime_error("function argument name must begin with a letter"); | |
| std::string::iterator argname_end = std::find_if_not(it+1, input_end, (int(*)(int))std::isalnum); | |
| return expr_abstr{std::string(it, argname_end), std::make_shared<expression>(abstr_parsing_helper(argname_end, input_end))}; | |
| } | |
| expression get_global(const std::string& name) { | |
| while (globals.find(name) == globals.end()) { | |
| std::cout << "define " + name + " please:\n>> "; | |
| std::string input; std::getline(std::cin, input); | |
| if (!input.length() && !std::cin) throw std::runtime_error(name + " left undefined"); | |
| if (std::find(input.begin(), input.end(), '=') == input.end()) { | |
| std::cout << "example: `sqrt x = x ^ 0.5`\n"; | |
| return get_global(name); | |
| } | |
| if (!std::isalpha(*input.begin())) { | |
| std::cout << "function name must begin with a letter\n"; | |
| return get_global(name); | |
| } | |
| std::string::iterator func_name_end = std::find_if_not(input.begin(), input.end(), (int(*)(int))std::isalnum); | |
| globals[std::string(input.begin(), func_name_end)] = abstr_parsing_helper(func_name_end, input.end()); | |
| } | |
| return globals[name]; | |
| } | |
| struct apply { | |
| const expression& arg; | |
| expression operator()(const expr_abstr& e) { return std::visit(substitute{e.arg, arg}, *e.body); } | |
| expression operator()(const expr_apply& e) { return std::visit(*this, std::visit(apply{*e.arg}, *e.fun)); } | |
| expression operator()(const expr_constant& e) { throw std::runtime_error("application failed (1)"); } | |
| expression operator()(const expr_add& e) { throw std::runtime_error("application failed (2)"); } | |
| expression operator()(const expr_mul& e) { throw std::runtime_error("application failed (3)"); } | |
| expression operator()(const expr_sub& e) { throw std::runtime_error("application failed (4)"); } | |
| expression operator()(const expr_div& e) { throw std::runtime_error("application failed (5)"); } | |
| expression operator()(const expr_negate& e) { throw std::runtime_error("application failed (6)"); } | |
| expression operator()(const expr_power& e) { throw std::runtime_error("application failed (7)"); } | |
| expression operator()(const expr_variable& e) { return std::visit(*this, std::visit(evaluate(), get_global(e.name))); } | |
| expression operator()(const expr_fund& e) { return expr_constant{e.fun(std::get<expr_constant>(std::visit(evaluate(), arg)).value)}; } | |
| }; | |
| // just folding | |
| expression evaluate::operator()(const expr_constant& e) { return e; } | |
| expression evaluate::operator()(const expr_add& e) { | |
| return expr_constant{std::get<expr_constant>(std::visit(*this, *e.lhs)).value + std::get<expr_constant>(std::visit(*this, *e.rhs)).value}; | |
| } | |
| expression evaluate::operator()(const expr_mul& e) { | |
| return expr_constant{std::get<expr_constant>(std::visit(*this, *e.lhs)).value * std::get<expr_constant>(std::visit(*this, *e.rhs)).value}; | |
| } | |
| expression evaluate::operator()(const expr_sub& e) { | |
| return expr_constant{std::get<expr_constant>(std::visit(*this, *e.lhs)).value - std::get<expr_constant>(std::visit(*this, *e.rhs)).value}; | |
| } | |
| expression evaluate::operator()(const expr_div& e) { | |
| double denominator = std::get<expr_constant>(std::visit(*this, *e.rhs)).value; | |
| if (almost_equal(denominator, 0)) throw std::runtime_error("division by zero"); | |
| return expr_constant{std::get<expr_constant>(std::visit(*this, *e.lhs)).value / denominator}; | |
| } | |
| expression evaluate::operator()(const expr_negate& e) { | |
| return expr_constant{-std::get<expr_constant>(std::visit(*this, *e.value)).value}; | |
| } | |
| expression evaluate::operator()(const expr_apply& e) { return std::visit(*this, std::visit(apply{*e.arg}, *e.fun)); } | |
| expression evaluate::operator()(const expr_power& e) { | |
| return expr_constant{std::pow(std::get<expr_constant>(std::visit(*this, *e.lhs)).value, std::get<expr_constant>(std::visit(*this, *e.rhs)).value)}; | |
| } | |
| expression evaluate::operator()(const expr_abstr& e) { return e; } | |
| expression evaluate::operator()(const expr_variable& e) { return std::visit(*this, get_global(e.name)); }; | |
| expression evaluate::operator()(const expr_fund& e) { return e; } | |
| #include <iostream> | |
| int main() { | |
| while (std::cin) { | |
| try { | |
| std::cout << "> "; | |
| std::string input; std::getline(std::cin, input); | |
| if (input.length() == 0 && !std::cin) { | |
| std::cout << "\r\r"; | |
| return 0; | |
| } | |
| std::vector<token> data = tokenize(input.begin(), input.end()); | |
| std::cout << std::get<expr_constant>(std::visit(evaluate(), parse_helper(data.begin(), data.end()))).value << "\n"; | |
| } catch (const std::bad_variant_access&) { | |
| std::cout << "the formula is malformed: could not evaluate to normal form\n"; | |
| } catch (const std::exception& e) { | |
| std::cout << "the formula is malformed: " << e.what() << "\n"; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment