Skip to content

Instantly share code, notes, and snippets.

@rdbuf
Last active November 8, 2018 17:43
Show Gist options
  • Select an option

  • Save rdbuf/ba40bef7ae5a191b4ef4fd4872d8117c to your computer and use it in GitHub Desktop.

Select an option

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.
#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