Last active
June 5, 2017 21:55
-
-
Save Raincode/e5115669bb647ceba690252ed39445f8 to your computer and use it in GitHub Desktop.
Desk Calculator in C++
This file contains 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 "Calculator.hpp" | |
#include <fstream> | |
#include <iostream> | |
#include <sstream> | |
#include "mps/str_util.hpp" | |
#include "mps/clipboard.hpp" | |
#include "mps/console_util.hpp" | |
#include "math_util.hpp" | |
#include "types.hpp" | |
#include "Warning.hpp" | |
using namespace std; | |
Calculator::Calculator() | |
: parser{ symbolTable } | |
{ | |
register_commands(); | |
ifstream ifs{ "prompt.txt" }; // ommitted error handling, since there is a default prompt in place | |
getline(ifs, prompt); | |
if (ifstream ifs{ "intro.txt" }) { | |
std::ostringstream s; | |
s << ifs.rdbuf(); | |
intro = s.str(); | |
} | |
parser.on_result([this](auto&& n) { | |
parser.symbol_table().set_var("_", n); | |
parser.symbol_table().set_var("ans", n); | |
print_complex(cout, n); | |
cout << '\n'; | |
}); | |
} | |
void Calculator::run(int argc, char* argv[]) | |
{ | |
switch (argc) { | |
case 1: | |
run_cli(); | |
break; | |
case 2: | |
if (string(argv[1]) == "-") | |
run_cli(); | |
else if (!run_file(argv[1])) | |
parser.parse(argv[1]); | |
break; | |
default: | |
throw runtime_error{ "Invalid number of arguments" }; | |
} | |
} | |
bool Calculator::run_file(const string& path) | |
{ | |
ifstream ifs{ path }; | |
if (!ifs) | |
return false; | |
parser.parse(ifs); | |
return true; | |
} | |
void Calculator::run_cli() | |
{ | |
show_intro(); | |
cout << prompt; | |
for (string s; getline(cin, s); ) { | |
s = mps::str::trim(s); | |
try { | |
if (s.size() && !handle_cmd(s)) | |
parser.parse(s); | |
} | |
catch (runtime_error& e) { | |
cerr << e.what() << '\n'; | |
} | |
cout << prompt; | |
} | |
} | |
void Calculator::show_intro() const | |
{ | |
cout << intro; | |
static Warning w{ "Copyright (C) 2017 Matthias Stauber\n" | |
"This program comes with ABSOLUTELY NO WARRANTY\n" }; | |
} | |
bool Calculator::handle_cmd(const std::string& cmd) | |
{ | |
auto found = commands.find(mps::str::tolower(cmd)); | |
if (found != end(commands)) { | |
found->second(); | |
return true; | |
} | |
return false; | |
} | |
void Calculator::register_commands() | |
{ | |
static const string helpText{ | |
"For a list of operators, commands and functions please view the readme file\n" | |
}; | |
commands["help"] = [] { std::cout << helpText; }; | |
commands["clear"] = commands["cls"] = [this] { | |
mps::cls(); | |
show_intro(); | |
}; | |
commands["clear all"] = [this] { | |
parser.symbol_table().clear(); | |
mps::cls(); | |
show_intro(); | |
}; | |
commands["clear vars"] = [this] { parser.symbol_table().clear_vars(); }; | |
commands["clear funcs"] = [this] { parser.symbol_table().clear_funcs(); }; | |
commands["clear lists"] = [this] { parser.symbol_table().clear_lists(); }; | |
commands["hide vars"] = [this] { parser.set_vardef_is_res(false); }; | |
commands["show vars"] = [this] { parser.set_vardef_is_res(true); }; | |
commands["ls"] = [this] { | |
auto&& vars = parser.symbol_table().vars(); | |
if (vars.size()) | |
cout << "Variables:\n~~~~~~~~~~\n"; | |
for (const auto& v : vars) { | |
cout << " " << v.first << " = "; | |
print_complex(cout, v.second); | |
cout << '\n'; | |
} | |
auto&& funcs = parser.symbol_table().funcs(); | |
if (funcs.size()) | |
cout << "\nFunctions:\n~~~~~~~~~~\n"; | |
for (const auto& f : funcs) | |
cout << " " << f.second << '\n'; | |
auto&& lists = parser.symbol_table().lists(); | |
if (lists.size()) | |
cout << "\nLists:\n~~~~~~\n"; | |
for (const auto& l : lists) { | |
cout << " " << l.first << " = "; | |
print_list(cout, l.second); | |
cout << '\n'; | |
} | |
}; | |
commands["run"] = [this] { | |
string fname; | |
if (cout << "file: " && getline(cin, fname)) | |
run_file(fname); | |
}; | |
commands["copy"] = [this] { | |
auto str = mps::str::to_string(parser.symbol_table().value_of("ans")); | |
mps::set_clipboard_text(str); | |
}; | |
commands["copy,"] = [this] { | |
auto str = mps::str::to_string(parser.symbol_table().value_of("ans")); | |
mps::set_clipboard_text(mps::str::format_number_EU(str)); | |
}; | |
commands["table"] = [this] { | |
string func = mps::get_str("Function: "); | |
double low = mps::get_num<double>("Low: "); | |
double high = mps::get_num<double>("High: "); | |
cout << "Sorry, table feature not implemented yet.\n"; | |
}; | |
commands["dec"] = [] { | |
cout << "hex/bin (W/ leading 0): "; | |
int val{}; | |
if (mps::read_hex(cin, val)) | |
cin.ignore(); | |
else if (!mps::read_bin(cin, val)) { | |
mps::recover_line(cin); | |
return; | |
} | |
std::cout << val << '\n'; | |
}; | |
commands["bin"] = [] { | |
cout << "dec/hex: "; | |
int val{}; | |
if (!mps::read_hex(cin, val) && !(cin >> val)) { | |
mps::recover_line(cin); | |
return; | |
} | |
cin.ignore(); | |
mps::print_binary(cout, val); | |
cout << '\n'; | |
}; | |
commands["hex"] = [] { | |
cout << "dec/bin (w/ leading 0): "; | |
int val{}; | |
if (!mps::read_bin(cin, val)) { | |
if (!(cin >> val)) { | |
mps::recover_line(cin); | |
return; | |
} | |
cin.ignore(); | |
} | |
cout << "0x" << hex << val << dec << '\n'; | |
}; | |
commands["exp"] = [this] { | |
if (!parser.has_result()) | |
return; | |
auto && n = parser.result(); | |
cout << abs(n) << "*e^(" << deg(arg(n)) << "deg)i\n"; | |
}; | |
} |
This file contains 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
#pragma once | |
#include <functional> | |
#include <map> | |
#include <string> | |
#include "Parser.hpp" | |
#include "SymbolTable.hpp" | |
class Calculator { | |
public: | |
Calculator(); | |
void run(int argc, char* argv[]); | |
private: | |
void show_intro() const; | |
bool handle_cmd(const std::string& cmd); | |
void register_commands(); | |
bool run_file(const std::string& path); | |
void run_cli(); | |
SymbolTable symbolTable; | |
Parser parser; | |
std::string prompt{ "> " }; | |
std::string intro; | |
std::map<std::string, std::function<void()>> commands; | |
}; |
This file contains 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 "Function.hpp" | |
#include <ostream> | |
#include "mps/str_util.hpp" | |
#include "Parser.hpp" | |
#include "SymbolGuard.hpp" | |
#include "SymbolTable.hpp" | |
#include "types.hpp" | |
Function::Function(const std::string& name, SymbolTable& table) | |
: funcName{ name }, table{ table } | |
{ | |
} | |
Complex Function::operator()(const List& args) const | |
{ | |
if (args.size() != vars.size()) | |
throw std::runtime_error{ funcName + " expects " + std::to_string(vars.size()) + | |
" arguments (received " + std::to_string(args.size()) + ")" }; | |
Parser parser{ table }; | |
SymbolGuard guard{ table }; | |
for (std::size_t i = 0; i < vars.size(); ++i) | |
guard.shadow_var(vars[i], args[i]); | |
parser.parse(term); | |
return parser.result(); | |
} | |
std::ostream& operator<<(std::ostream& os, const Function& func) | |
{ | |
os << func.funcName << '('; | |
std::string sep; | |
for (const auto& v : func.vars) { | |
os << sep << v; | |
sep = ","; | |
} | |
return os << ") = " << func.term; | |
} |
This file contains 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
#pragma once | |
#include <string> | |
#include <vector> | |
#include "types.hpp" | |
class SymbolTable; | |
class Function { | |
public: | |
Function(const std::string& name, SymbolTable& table); | |
Complex operator()(const List& args) const; | |
void set_term(const std::string& t) { term = t; } | |
void add_var(const std::string& v) { vars.push_back(v); } | |
std::size_t numArgs() const { return vars.size(); } | |
const std::string& name() const { return funcName; } | |
friend std::ostream& operator<<(std::ostream& os, const Function& func); | |
private: | |
SymbolTable& table; | |
std::string funcName; | |
std::string term; | |
std::vector<std::string> vars; | |
}; |
This file contains 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
Start = {Statement} End | |
Statement = [Expression | FunctionDefinition | ListDefinition | Deletion] (';' | '\n') | |
FunctionDefinition = 'fn' Identifier ParamList '=' Expression | |
Deletion = 'del' Identifier {Identifier} | |
Expression = Term { ('+' | '-') Term } | |
Term = Sign { ('*' | '/' | '%' | 'mod' | 'div' | '||') Sign } | |
Sign = ['+' | '-'] Postfix | |
Postfix = Primary ( ('^' | '**') Sign | {'!'} | Variable | FunctionCall) | |
Primary = Number | Variable | VariableDefinition | FunctionCall | '(' Expression ')' | |
ListDefinition = Identifier '=' BracketList | |
FunctionCall = Identifier ArgList | |
BracketList = '[' ListElements ']' | |
ArgList = '(' (ListElements | List) ')' | |
ListElements = [Expression] {',' Expression} | |
ParamList = '(' Identifier {',' Identifier} ')' | |
VariableDefinition = Identifier '=' Expression | |
Variable = ? Identifier stored in symbol table as Number value ? | |
List = ? Identifier stored in symbol table as List of Numbers ? | |
Identifier = Letter {'_' | Letter | Digit} | |
Number = ? C++ double ? | |
Letter = a..z | A..Z | |
Digit = 0..9 |
This file contains 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 "Parser.hpp" | |
#include <cmath> | |
#include <iostream> | |
#include "mps/stream_util.hpp" | |
#include "math_util.hpp" | |
#include "SymbolTable.hpp" | |
Parser::Parser(SymbolTable& table) | |
: table{ table } { } | |
void Parser::set_symbol_table(SymbolTable& t) | |
{ | |
table = t; | |
} | |
void Parser::parse(std::istream& is) | |
{ | |
ts.set_input(is); | |
parse(); | |
} | |
void Parser::parse(const std::string& str) | |
{ | |
ts.set_input(str); | |
parse(); | |
} | |
void Parser::parse() | |
{ | |
ts.get(); | |
while (!consume(Kind::End)) | |
stmt(); | |
} | |
void Parser::stmt() | |
{ | |
hasResult = true; | |
if (peek(Kind::FuncDef)) | |
func_def(); | |
else if (peek(Kind::Delete)) | |
deletion(); | |
else if (!peek(Kind::Print)) | |
res = expr(); | |
if (hasResult && onRes) | |
onRes(res); | |
if (!peek(Kind::End)) | |
expect(Kind::Print); | |
} | |
void Parser::func_def() | |
{ | |
expect(Kind::FuncDef); | |
Function func{ ident(), table }; | |
parse_param_list(func); | |
expect(Kind::Assign); | |
parse_func_term(func); | |
List test_args(func.numArgs(), 1); | |
func(test_args); | |
table.set_func(func.name(), func); | |
hasResult = false; | |
} | |
void Parser::parse_param_list(Function& func) | |
{ | |
expect(Kind::LParen); | |
do { | |
func.add_var(ident()); | |
} while (consume(Kind::Comma)); | |
expect(Kind::RParen); | |
} | |
void Parser::parse_func_term(Function& func) | |
{ | |
std::ostringstream term; | |
while (!peek(Kind::Print) && !peek(Kind::End) && !peek(Kind::RBracket)) { | |
term << ts.current(); | |
ts.get(); | |
} | |
func.set_term(term.str()); | |
} | |
void Parser::deletion() | |
{ | |
expect(Kind::Delete); | |
if (!peek(Kind::String)) | |
error("No arguments provided to delete"); | |
do { | |
del_symbol(); | |
} while (peek(Kind::String)); | |
hasResult = false; | |
} | |
static bool is_const(const std::string& var) | |
{ | |
return var == "pi" || var == "e" || var == "i" || var == "deg"; | |
} | |
void Parser::del_symbol() | |
{ | |
auto& name = ident(); | |
if (is_const(name)) | |
error(name, " is a constant"); | |
if (table.is_reserved_func(name)) | |
error(name, " is a built-in function"); | |
table.remove_symbol(name); | |
} | |
void Parser::list_def(const std::string& name) | |
{ | |
table.set_list(name, list()); | |
} | |
Complex Parser::expr() | |
{ | |
auto left = term(); | |
for (;;) { | |
if (consume(Kind::Plus)) | |
left += term(); | |
else if (consume(Kind::Minus)) | |
left -= term(); | |
else | |
return left; | |
} | |
} | |
Complex Parser::term() | |
{ | |
auto left = sign(); | |
for (;;) { | |
if (consume(Kind::Mul)) | |
left *= sign(); | |
else if (consume(Kind::Div)) | |
left = safe_div(left, sign()); | |
else if (consume(Kind::FloorDiv)) | |
left = safe_floordiv(left, sign()); | |
else if (consume(Kind::Mod)) | |
left = safe_mod(left, sign()); | |
else if (consume(Kind::Parallel)) | |
left = impedance_parallel(left, sign()); | |
else | |
return left; | |
} | |
} | |
Complex Parser::sign() | |
{ | |
if (consume(Kind::Minus)) | |
return -postfix(); | |
consume(Kind::Plus); | |
return postfix(); | |
} | |
Complex Parser::postfix() | |
{ | |
auto left = prim(); | |
for (;;) { | |
if (consume(Kind::Pow)) | |
return pretty_pow(left, sign()); | |
else if (peek(Kind::String)) | |
return left * postfix(); | |
else if (peek(Kind::LParen)) | |
return left * prim(); | |
else if (consume(Kind::Fac)) | |
left = factorial(left); | |
else | |
return left; | |
} | |
} | |
Complex Parser::prim() | |
{ | |
if (consume(Kind::Number)) | |
return prevTok.num; | |
if (peek(Kind::String)) | |
return resolve_str_tok(); | |
if (consume(Kind::LParen)) { | |
auto val = expr(); | |
expect(Kind::RParen); | |
return val; | |
} | |
error("Unexpected Token ", ts.current()); | |
throw std::logic_error{ "Fall through prim()" }; // silence C4715 | |
} | |
Complex Parser::resolve_str_tok() | |
{ | |
auto name = ident(); | |
if (peek(Kind::LParen)) | |
return table.call_func(name, arg_list()); | |
else if (consume(Kind::Assign)) { | |
if (peek(Kind::LBracket)) { | |
list_def(name); | |
return no_result(); | |
} | |
return var_def(name); | |
} | |
else if (table.has_list(name)) { | |
if (!peek(Kind::Print) && !peek(Kind::End)) | |
error("Unexpected Token ", ts.current()); | |
print_list(std::cout, table.list(name)); | |
std::cout << '\n'; | |
return no_result(); | |
} | |
return table.value_of(name); | |
} | |
Complex Parser::var_def(const std::string& name) | |
{ | |
if (is_const(name)) | |
error("Cannot override constant ", name); | |
if (peek(Kind::LBracket)) { | |
table.set_list(name, list()); | |
return no_result(); | |
} | |
if (table.isset(name) && !table.has_var(name)) | |
error(name, " is already defined"); | |
auto val = expr(); | |
table.set_var(name, val); | |
return varDefIsRes ? val : no_result(); | |
} | |
Complex Parser::no_result() | |
{ | |
hasResult = false; | |
return 0; // dummy value | |
} | |
List Parser::list() | |
{ | |
expect(Kind::LBracket); | |
List l; | |
if (consume(Kind::For)) { // [for var=start, end:step loopExpression] | |
auto var = ident(); | |
expect(Kind::Assign); | |
auto start = prim().real(); | |
expect(Kind::Comma); | |
auto end = prim().real(); | |
double step{ 1 }; | |
if (consume(Kind::Colon)) | |
step = prim().real(); | |
Function f{ "__internal__", table }; | |
f.add_var(var); | |
parse_func_term(f); | |
if (start < end && step <= 0 || start > end && step >= 0) | |
error("Infinite loop"); | |
for (auto i = start; i <= end; i += step) | |
l.emplace_back(f({ Complex(i) })); | |
} | |
else | |
l = list_elem(); | |
expect(Kind::RBracket); | |
return l; | |
} | |
List Parser::arg_list() | |
{ | |
expect(Kind::LParen); | |
List args; | |
if (peek(Kind::String) && table.has_list(ts.current().str)) | |
args = table.list(ident()); | |
else if (peek(Kind::LBracket)) | |
args = list(); | |
else | |
args = list_elem(); | |
expect(Kind::RParen); | |
if (args.empty()) | |
error("Invalid empty argument list"); | |
return args; | |
} | |
List Parser::list_elem() | |
{ | |
List list; | |
if (!peek(Kind::RParen) && !peek(Kind::RBracket)) { | |
do { | |
list.emplace_back(expr()); | |
} while (consume(Kind::Comma)); | |
} | |
return list; | |
} | |
const std::string& Parser::ident() | |
{ | |
expect(Kind::String); | |
return prevTok.str; | |
} | |
bool Parser::consume(Kind kind) | |
{ | |
if (ts.current().kind == kind) { | |
prevTok = ts.current(); | |
ts.get(); | |
return true; | |
} | |
return false; | |
} | |
bool Parser::peek(Kind kind) const | |
{ | |
return ts.current().kind == kind; | |
} | |
void Parser::expect(Kind kind) | |
{ | |
if (!consume(kind)) | |
error("Expected Token ", kind); | |
} |
This file contains 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
#pragma once | |
#include <istream> | |
#include <map> | |
#include <string> | |
#include "ErrorReporter.hpp" | |
#include "Function.hpp" | |
#include "TokenStream.hpp" | |
#include "types.hpp" | |
class SymbolTable; | |
class Parser { | |
public: | |
Parser(SymbolTable& table); | |
void parse(std::istream& is); | |
void parse(const std::string& input); | |
Complex result() const { return res; } | |
bool has_result() const { return hasResult; } | |
SymbolTable& symbol_table() { return table; } | |
void set_symbol_table(SymbolTable& t); | |
void set_vardef_is_res(bool isRes) { varDefIsRes = isRes; } | |
void on_result(std::function<void(Complex)> handler) { onRes = std::move(handler); } | |
private: | |
void parse(); | |
void stmt(); | |
void func_def(); | |
void parse_param_list(Function& func); | |
void parse_func_term(Function& func); | |
void deletion(); | |
void del_symbol(); | |
void list_def(const std::string& name); | |
Complex expr(); | |
Complex term(); | |
Complex sign(); | |
Complex postfix(); | |
Complex prim(); | |
Complex resolve_str_tok(); | |
Complex var_def(const std::string& name); | |
Complex no_result(); | |
List list(); | |
List arg_list(); | |
List list_elem(); | |
const std::string& ident(); | |
bool peek(Kind kind) const; | |
bool consume(Kind kind); | |
void expect(Kind kind); | |
Token prevTok; | |
SymbolTable& table; | |
TokenStream ts; | |
ErrorReporter error; | |
Complex res; | |
bool hasResult{}; | |
bool varDefIsRes{ true }; | |
std::function<void(Complex)> onRes; | |
}; | |
This file contains 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 "SymbolGuard.hpp" | |
SymbolGuard::SymbolGuard(SymbolTable& table) | |
: table{ table } | |
{ | |
} | |
SymbolGuard::~SymbolGuard() | |
{ | |
for (const auto& var : shadowedVars) | |
table.remove_var(var); | |
for (const auto& pair : varCache) | |
table.set_var(pair.first, pair.second); | |
} | |
void SymbolGuard::shadow_var(const std::string& name, Complex tempVal) | |
{ | |
if (table.has_var(name)) { | |
auto val = table.value_of(name); | |
varCache[name] = val; | |
table.remove_var(name); | |
} | |
table.set_var(name, tempVal); | |
shadowedVars.push_back(name); | |
} |
This file contains 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
#pragma once | |
#include <string> | |
#include <unordered_map> | |
#include <vector> | |
#include "SymbolTable.hpp" | |
#include "types.hpp" | |
class SymbolGuard { | |
public: | |
SymbolGuard(SymbolTable& table); | |
~SymbolGuard(); | |
void shadow_var(const std::string& name, Complex value); | |
private: | |
SymbolTable& table; | |
std::unordered_map<std::string, Complex> varCache; | |
std::vector<std::string> shadowedVars; | |
}; |
This file contains 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 "SymbolTable.hpp" | |
#include <cassert> | |
#include <iostream> | |
#include <numeric> | |
#include <stdexcept> | |
#include <mps/stl_util.hpp> | |
#include "math_util.hpp" | |
using namespace std; | |
using namespace temp; | |
using namespace mps::stl; | |
#define CHECK_SINGLE_ARG(f) \ | |
if (list.size() != 1) throw std::runtime_error{ #f " expects exactly 1 argument" }; | |
#define MAKE_COMPLEX_FUNC(f) [] (const List& list) { \ | |
CHECK_SINGLE_ARG(f) \ | |
return (f)(list.front()); } | |
#define MAKE_PROXY_FUNC(f) [] (const Complex& c) { \ | |
if (c.imag()) \ | |
throw std::runtime_error{ #f " not defined for complex numbers" }; \ | |
return Complex{ (f)(c.real()) }; } | |
#define MAKE_REAL_FUNC(f) [] (const List& list) { \ | |
CHECK_SINGLE_ARG(f) \ | |
if (list.front().imag()) \ | |
throw std::runtime_error{ #f " not defined for complex numbers" }; \ | |
return Complex{ (f)(list.front().real()) }; } | |
SymbolTable::SymbolTable() | |
{ | |
add_constants(); | |
} | |
bool SymbolTable::is_reserved_func(const std::string& name) const | |
{ | |
return contains(defaultFuncTable, name); | |
} | |
void SymbolTable::set_var(ConstStrRef name, Complex val) | |
{ | |
varTable[name] = val; | |
} | |
void SymbolTable::set_list(ConstStrRef name, List&& list) | |
{ | |
listTable[name] = list; | |
} | |
void SymbolTable::set_func(ConstStrRef name, Function func) | |
{ | |
funcTable.erase(name); | |
funcTable.emplace(name, std::move(func)); | |
} | |
Complex SymbolTable::value_of(ConstStrRef var) const | |
{ | |
return find_or_throw(varTable, var, "Variable " + var + " is undefined")->second; | |
} | |
const List& SymbolTable::list(ConstStrRef name) const | |
{ | |
return find_or_throw(listTable, name, "List " + name + " is undefined")->second; | |
} | |
Complex SymbolTable::call_func(ConstStrRef func, const List& arg) const | |
{ | |
auto f = funcTable.find(func); | |
if (f != end(funcTable)) | |
return f->second(arg); | |
return find_or_throw(defaultFuncTable, func, "Function " + func + " is undefined")->second(arg); | |
} | |
bool SymbolTable::has_var(ConstStrRef name) const | |
{ | |
return contains(varTable, name); | |
} | |
bool SymbolTable::has_list(ConstStrRef name) const | |
{ | |
return contains(listTable, name); | |
} | |
bool SymbolTable::has_func(ConstStrRef name) const | |
{ | |
return contains(funcTable, name); | |
} | |
bool SymbolTable::isset(ConstStrRef name) const | |
{ | |
return has_var(name) || has_list(name) || has_func(name); | |
} | |
void SymbolTable::remove_var(ConstStrRef name) | |
{ | |
varTable.erase(name); | |
} | |
void SymbolTable::remove_list(ConstStrRef name) | |
{ | |
listTable.erase(name); | |
} | |
void SymbolTable::remove_func(ConstStrRef name) | |
{ | |
funcTable.erase(name); | |
} | |
void SymbolTable::remove_symbol(ConstStrRef name) | |
{ | |
if (!isset(name)) | |
throw runtime_error{ name + " is undefined" }; | |
if (has_func(name)) | |
remove_func(name); | |
if (has_list(name)) | |
remove_list(name); | |
if (has_var(name)) | |
remove_var(name); | |
} | |
void SymbolTable::clear() | |
{ | |
clear_vars(); | |
clear_funcs(); | |
clear_lists(); | |
} | |
void SymbolTable::clear_vars() | |
{ | |
varTable.clear(); | |
add_constants(); | |
} | |
void SymbolTable::clear_funcs() | |
{ | |
funcTable.clear(); | |
} | |
void SymbolTable::clear_lists() | |
{ | |
listTable.clear(); | |
} | |
void SymbolTable::add_constants() | |
{ | |
varTable["i"] = { 0, 1 }; | |
varTable["pi"] = pi; | |
varTable["e"] = 2.7182818284590452354; | |
varTable["deg"] = pi / 180; | |
} | |
const SymbolTable::FuncMap SymbolTable::defaultFuncTable{ | |
{ "sin", MAKE_COMPLEX_FUNC(sin) }, | |
{ "cos", MAKE_COMPLEX_FUNC(cos) }, | |
{ "tan", MAKE_COMPLEX_FUNC(tan) }, | |
{ "asin", MAKE_COMPLEX_FUNC(asin) }, | |
{ "acos", MAKE_COMPLEX_FUNC(acos) }, | |
{ "atan", MAKE_COMPLEX_FUNC(atan) }, | |
{ "sinh", MAKE_COMPLEX_FUNC(sinh) }, | |
{ "cosh", MAKE_COMPLEX_FUNC(cosh) }, | |
{ "tanh", MAKE_COMPLEX_FUNC(tanh) }, | |
{ "asinh", MAKE_COMPLEX_FUNC(asinh) }, | |
{ "acosh", MAKE_COMPLEX_FUNC(acosh) }, | |
{ "atanh", MAKE_COMPLEX_FUNC(atanh) }, | |
{ "deg", MAKE_REAL_FUNC(deg) }, | |
{ "rad", MAKE_REAL_FUNC(rad) }, | |
{ "CtoK", MAKE_REAL_FUNC(CtoK) }, | |
{ "KtoC", MAKE_REAL_FUNC(KtoC) }, | |
{ "FtoC", MAKE_REAL_FUNC(FtoC) }, | |
{ "CtoF", MAKE_REAL_FUNC(CtoF) }, | |
{ "FtoK", MAKE_REAL_FUNC(FtoK) }, | |
{ "KtoF", MAKE_REAL_FUNC(KtoF) }, | |
{ "abs", MAKE_COMPLEX_FUNC(abs) }, | |
{ "norm", MAKE_COMPLEX_FUNC(norm) }, | |
{ "arg", MAKE_COMPLEX_FUNC(arg) }, | |
{ "exp", MAKE_COMPLEX_FUNC(exp) }, | |
{ "sqr", MAKE_COMPLEX_FUNC(sqr) }, | |
{ "sqrt", MAKE_COMPLEX_FUNC(sqrt) }, | |
{ "ln", MAKE_COMPLEX_FUNC(log) }, | |
{ "log", MAKE_COMPLEX_FUNC(log10) }, | |
{ "Re", MAKE_COMPLEX_FUNC(real) }, | |
{ "Im", MAKE_COMPLEX_FUNC(imag) }, | |
{ "floor", MAKE_REAL_FUNC(floor) }, | |
{ "ceil", MAKE_REAL_FUNC(ceil) }, | |
{ "round", MAKE_REAL_FUNC(round) }, | |
{ "trunc", MAKE_REAL_FUNC(trunc) }, | |
{ "cbrt", MAKE_REAL_FUNC(cbrt) }, | |
{ "sum", sum }, | |
{ "sum2", sqr_sum }, | |
{ "avg", avg }, | |
{ "len", len }, | |
{ "sx", standard_deviation }, | |
{ "ux", standard_uncertainty } | |
}; |
This file contains 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
#pragma once | |
#include <map> | |
#include <string> | |
#include "mps/stl_util.hpp" | |
#include "Function.hpp" | |
#include "types.hpp" | |
class SymbolTable { | |
public: | |
using ConstStrRef = const std::string&; | |
using FuncMap = std::map<std::string, Func>; | |
SymbolTable(); | |
bool is_reserved_func(const std::string& name) const; | |
void set_var(ConstStrRef name, Complex value); | |
void set_list(ConstStrRef name, List&& list); | |
void set_func(ConstStrRef name, Function func); | |
Complex value_of(ConstStrRef var) const; | |
const List& list(ConstStrRef var) const; | |
Complex call_func(ConstStrRef func, const List& args) const; | |
bool has_var(ConstStrRef name) const; | |
bool has_list(ConstStrRef name) const; | |
bool has_func(ConstStrRef name) const; | |
bool isset(ConstStrRef name) const; | |
void remove_var(ConstStrRef name); | |
void remove_list(ConstStrRef name); | |
void remove_func(ConstStrRef name); | |
void remove_symbol(ConstStrRef name); | |
void clear(); | |
void clear_vars(); | |
void clear_funcs(); | |
void clear_lists(); | |
const std::map<std::string, Complex>& vars() const { return varTable; } | |
const std::map<std::string, List>& lists() const { return listTable; } | |
const std::map<std::string, Function>& funcs() const { return funcTable; } | |
private: | |
void add_constants(); | |
static const FuncMap defaultFuncTable; | |
std::map<std::string, Complex> varTable; | |
std::map<std::string, List> listTable; | |
std::map<std::string, Function> funcTable; | |
}; |
This file contains 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
#pragma once | |
#include <string> | |
enum class Kind : char { | |
End, | |
Invalid, | |
Number, | |
String, | |
Parallel, | |
Print, | |
FloorDiv, | |
Delete, | |
FuncDef, | |
For, | |
Plus = '+', | |
Minus = '-', | |
Mul = '*', | |
Div = '/', | |
Mod = '%', | |
Pow = '^', | |
Fac = '!', | |
Assign = '=', | |
Comma = ',', | |
Colon = ':', | |
LParen = '(', | |
RParen = ')', | |
LBrace = '{', | |
RBrace = '}', | |
LBracket = '[', | |
RBracket = ']', | |
}; | |
struct Token { | |
Kind kind{}; | |
std::string str; | |
double num{}; | |
}; | |
inline std::ostream& operator<<(std::ostream& os, Kind kind) | |
{ | |
switch (kind) { | |
case Kind::End: return os << "END"; | |
case Kind::Invalid: return os << "INVALID"; | |
case Kind::Number: return os << "NUMBER"; | |
case Kind::String: return os << "STRING"; | |
case Kind::Parallel: return os << "PARALLEL"; | |
case Kind::Print: return os << "PRINT"; | |
case Kind::FloorDiv: return os << "div"; | |
default: return os << static_cast<char>(kind); | |
} | |
} | |
inline std::ostream& operator<<(std::ostream& os, const Token& token) | |
{ | |
switch (token.kind) { | |
case Kind::Number: return os << token.num; | |
case Kind::String: return os << token.str; | |
default: return os << token.kind; | |
} | |
} |
This file contains 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 "TokenStream.hpp" | |
#include <cassert> | |
#include <cctype> | |
#include <iostream> | |
#include <map> | |
#include <sstream> | |
TokenStream::TokenStream(std::istream& is) | |
: input{ &is } { } | |
TokenStream::TokenStream(std::istream* is) | |
: input{ is }, ownsInput{ true } { } | |
TokenStream::TokenStream(const std::string& str) | |
: input{ new std::istringstream{str} }, ownsInput{ true } { } | |
void TokenStream::set_input(std::istream& is) | |
{ | |
cleanup(); | |
input = &is; | |
} | |
void TokenStream::set_input(std::istream* is) | |
{ | |
cleanup(); | |
input = is; | |
ownsInput = true; | |
} | |
void TokenStream::set_input(const std::string& str) | |
{ | |
set_input(new std::istringstream{ str }); | |
} | |
static constexpr unsigned char uchar(char ch) | |
{ // for isalpha etc. which expect ch >= -1 and ch <= 255 | |
return static_cast<unsigned char>(ch); | |
} | |
Token TokenStream::get() | |
{ | |
char ch; | |
do { | |
if (!input || !input->get(ch)) | |
return ct = { Kind::End }; | |
} while (std::isspace(uchar(ch)) && ch != '\n'); | |
switch (ch) { | |
case ';': | |
case '\n': | |
return ct = { Kind::Print }; | |
case '+': | |
case '-': | |
case '%': | |
case '!': | |
case '^': | |
case ',': | |
case '=': | |
case ':': | |
case '(': case ')': | |
case '[': case ']': | |
case '{': case '}': | |
return ct = { static_cast<Kind>(ch) }; | |
case '*': | |
return ct = parse_double_op('*', Kind::Pow, Kind::Mul); | |
case '/': | |
return ct = parse_double_op('/', Kind::FloorDiv, Kind::Div); | |
case '|': | |
return ct = parse_double_op('|', Kind::Parallel, Kind::Invalid); | |
case '0': case '1': case '2': case '3': case '4': | |
case '5': case '6': case '7': case '8': case '9': | |
case '.': | |
input->unget(); | |
*input >> ct.num; | |
ct.kind = Kind::Number; | |
return ct; | |
default: | |
return ct = parse_identifier(ch); | |
} | |
} | |
Token TokenStream::parse_double_op(char next, Kind onMatch, Kind onFailure) | |
{ | |
char c; | |
if (input->get(c) && c == next) | |
return { onMatch }; | |
else { | |
input->unget(); | |
if (onFailure != Kind::Invalid) | |
return { onFailure }; | |
error("Expected Token: ", next); | |
} | |
return { Kind::Invalid }; | |
} | |
Token TokenStream::parse_identifier(char ch) | |
{ | |
if (std::isalpha(uchar(ch)) || ch == '_') { | |
std::string s{ ch }; | |
while (input->get(ch) && (std::isalnum(uchar(ch)) || ch == '_')) | |
s += ch; | |
input->unget(); | |
return identifier_to_token(s); | |
} | |
error("Bad Token ", ch); | |
return { Kind::Invalid }; | |
} | |
static const std::map<std::string, Kind> strTokens{ | |
{ "div", Kind::FloorDiv }, | |
{ "mod", Kind::Mod }, | |
{ "del", Kind::Delete }, | |
{ "fn", Kind::FuncDef }, | |
{ "for", Kind::For } | |
}; | |
Token TokenStream::identifier_to_token(const std::string& str) const | |
{ | |
auto found = strTokens.find(str); | |
if (found != cend(strTokens)) | |
return { found->second }; | |
return { Kind::String, str }; | |
} | |
void TokenStream::cleanup() | |
{ | |
if (ownsInput) { | |
delete input; | |
ownsInput = false; | |
} | |
} |
This file contains 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
#pragma once | |
#include <istream> | |
#include "ErrorReporter.hpp" | |
#include "Token.hpp" | |
class TokenStream { | |
public: | |
TokenStream() = default; | |
explicit TokenStream(std::istream& input); | |
explicit TokenStream(std::istream* input); | |
explicit TokenStream(const std::string& input); | |
TokenStream(const TokenStream&) = delete; | |
TokenStream& operator=(const TokenStream&) = delete; | |
void set_input(std::istream& input); | |
void set_input(std::istream* input); | |
void set_input(const std::string& input); | |
Token get(); | |
const Token& current() const { return ct; } | |
private: | |
Token parse_identifier(char firstChar); | |
Token parse_double_op(char expected, Kind onSuccess, Kind onFailure); | |
Token identifier_to_token(const std::string& str) const; | |
void cleanup(); | |
Token ct{ Kind::End }; | |
std::istream* input{}; | |
bool ownsInput{}; | |
ErrorReporter error; | |
}; |
This file contains 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
#pragma once | |
#include <complex> | |
#include <functional> | |
#include <iostream> | |
#include <vector> | |
using Complex = std::complex<double>; | |
using List = std::vector<Complex>; | |
using Func = std::function<Complex(const List&)>; | |
template<class T> | |
void print_complex(std::ostream& os, const std::complex<T>& n) | |
{ | |
using std::cout; | |
if (n.imag()) { | |
if (n.real()) { | |
cout << n.real(); | |
if (n.imag() > 0) | |
cout << '+'; | |
} | |
if (std::abs(n.imag()) != 1) | |
cout << n.imag(); | |
cout << (n.imag() == -1 ? "-" : "") << 'i'; | |
} | |
else | |
cout << n.real(); | |
} | |
template<class T> | |
void print_list(std::ostream& os, const std::vector<T>& v) | |
{ | |
std::cout << '['; | |
std::string sep; | |
for (const auto& item : v) { | |
std::cout << sep; | |
print_complex(std::cout, item); | |
sep = ", "; | |
} | |
std::cout << ']'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment