#pragma once #include #include #include #include #include #include #include #include #include #include #include namespace lispy { class cons; class function; struct empty {}; struct cons_start{}; struct cons_end{}; struct atom { int value; }; struct sp_coords { bool is_relative = false; size_t x, y; }; struct sp_range { bool is_relative = false; size_t x, y; size_t width, height; }; using lvalue = std::variant, sp_coords, sp_range, std::shared_ptr>; using token = std::variant; struct context { std::unordered_map atoms; int last_atom = 0; bool error_crash = false; bool print_parse_results = false; std::unordered_map> function_table; std::unordered_map variable_table; int get_atom(const std::string& key) { if(atoms.count(key)) { return atoms[key]; } else { return atoms[key] = ++last_atom; } } }; class cons { public: lvalue self = empty{}; std::shared_ptr other{}; cons() { self = empty{}; other = std::shared_ptr{}; } cons(lvalue first) : self(first) , other() { } cons(std::vector data) { if(data.size() == 0) { self = empty{}; other = std::shared_ptr{}; } else if(data.size() >= 1) { self = data[0]; for(auto it = data.begin()+1; it != data.end(); ++it) this->append(*it); } } cons(const cons& oth) : self(oth.self) , other(oth.other ? std::make_shared(*(oth.other)) : nullptr) {} void operator=(const cons& oth) { self = oth.self; if(oth.other) { other = std::make_shared(*oth.other); } else { other = std::shared_ptr{}; } } cons(cons&& oth) : self(oth.self) , other(oth.other ? std::move(std::make_shared(*(oth.other))) : std::shared_ptr{}) {} void append(lvalue value) { if(!other) { other = std::make_shared(value); } else { other->append(value); } } }; class function { public: virtual lvalue operator() (const std::shared_ptr arguments, lispy::context& ctx) = 0; virtual ~function(){}; }; inline char hexdigit(char v) { if(v >= '0' && v<='9') { return v - '0'; } else if(v >= 'a' && v <= 'f') { return 10 + v - 'a'; } else if(v >= 'A' && v <= 'F') { return 10 + v - 'A'; } return -1; } inline std::string escape(std::string_view v) { auto it = v.begin(); std::stringstream stream; stream<<'"'; while(it != v.end()) { switch(*it) { case '"': { stream << "\\\""; } break; case '\n': { stream << "\\n"; } break; case '\t': { stream << "\\t"; } break; case '\v': { stream << "\\v"; } break; case '\a': { stream << "\\a"; } break; default: stream << *it; } ++it; } stream<<'"'; return stream.str(); } inline void print_visitor(std::ostream& stream, const lvalue& value, const context& ctx) { std::visit([&](auto arg) { using T = std::decay_t; if constexpr (std::is_same_v) { stream << arg; } else if constexpr (std::is_same_v) { stream << arg; } else if constexpr (std::is_same_v) { stream << "()"; } else if constexpr (std::is_same_v>) { stream << "#func"<<&*arg; } else if constexpr (std::is_same_v) { stream << escape(arg); } else if constexpr (std::is_same_v>) { stream << "("; bool is_first = true; cons p = *arg; do{ if(!is_first) { p = *p.other; } print_visitor(stream, p.self, ctx); if(p.other) { stream << " "; } is_first = false; } while(p.other); stream << ")"; } else if constexpr (std::is_same_v){ stream << (arg.is_relative ? "$[" : "@[") << arg.x << "," << arg.y << "]"; } else if constexpr (std::is_same_v){ stream << (arg.is_relative ? "$[" : "@[") << arg.x << "," << arg.y << "," << arg.width << "," << arg.height << "]"; } else if constexpr (std::is_same_v) { for(auto& v : ctx.atoms) { if(v.second == arg.value) { stream << v.first; return; } } assert(false); } else { std::cerr << typeid(T).name() << " detected in print_visitor ?" << std::endl; if(ctx.error_crash) { std::exit(-1); } } }, value); } inline void print_types_visitor(std::ostream& stream, const lvalue& value, const context& ctx) { std::visit([&](auto arg) { using T = std::decay_t; if constexpr (std::is_same_v) { stream << "integer"; } else if constexpr (std::is_same_v) { stream << "double"; } else if constexpr (std::is_same_v) { stream << "nil"; } else if constexpr (std::is_same_v>) { stream << "function"; } else if constexpr (std::is_same_v) { stream << "string"; } else if constexpr (std::is_same_v>){ stream << "("; bool is_first = true; cons p = *arg; do{ if(!is_first) { p = *p.other; } print_types_visitor(stream, p.self, ctx); if(p.other) { stream << " "; } is_first = false; } while(p.other); stream << ")"; } else if constexpr (std::is_same_v){ stream << "coords"; } else if constexpr (std::is_same_v){ stream << "range"; } else if constexpr (std::is_same_v) { stream << "atom"; } else { std::cerr << typeid(T).name() << " detected in print_types_visitor ?" << std::endl; if(ctx.error_crash) { std::exit(-1); } } }, value); } inline std::pair parse_string(std::string_view data) { auto it = data.begin(); assert(*it == '\"'); ++it; std::stringstream value(std::string(data.begin(), data.end())); std::string ret; value >> std::quoted(ret); return std::make_pair(ret, std::string_view{data.begin(), (size_t)value.rdbuf()->in_avail()}); } inline std::pair parse_atom(std::string_view data, context& ctx) { assert(!iswspace(data[0])); size_t idx = 1; while(!iswspace(data[idx])) { idx++; } atom v; v.value = ctx.get_atom(std::string(data.begin(), data.begin()+idx)); return std::make_pair(lvalue{v}, std::string_view{data.begin(), idx}); } inline std::pair parse_number(std::string_view data) { char* end_f; char* end_d; double try_f = strtod (data.data(), &end_f); int err_f = errno; int64_t try_d = strtoll(data.data(), &end_d, 10); int err_d = errno; if(err_d == ERANGE) { return std::make_pair(lvalue{(double)try_f}, std::string_view{data.begin(), (size_t)(end_f-data.data())}); } if(try_f != std::trunc(try_f)) { return std::make_pair(lvalue{(double)try_f}, std::string_view{data.begin(), (size_t)(end_f-data.data())}); } return std::make_pair(lvalue{int64_t(try_d)}, std::string_view{data.begin(), (size_t)(end_f-data.data())}); } inline std::pair get_coords_from_id(const std::string_view id) { size_t x = 0; size_t y = 0; auto c = id.begin(); while(*c >= 'A' && *c <= 'Z') { x += *c-'A'+1; x *= 26; c++; } x /= 26; while(*c >= '0' && *c <= '9') { y += *c-'0'; y *= 10; c++; } y /= 10; sp_coords ret; ret.x = x; ret.y = y; return std::make_pair((size_t)(c-id.begin()) ,ret); } inline std::pair parse_selector(std::string_view data) { auto it = data.begin(); auto is_rel = *it == '$'; ++it; auto a = get_coords_from_id(std::string_view{it, data.size()-1}); a.second.is_relative = is_rel; it += a.first; if(*it == ':') { auto b = get_coords_from_id(std::string_view{++it, data.size()-1}); it += b.first; sp_range ret; a.second.is_relative = is_rel; ret.x = a.second.x; ret.y = a.second.y; ret.width = b.second.x; ret.height = b.second.y; return std::make_pair(lvalue{ret}, std::string_view{data.begin(), (size_t)(it - data.begin())}); } else { return std::make_pair(lvalue{a.second}, std::string_view{data.begin(), (size_t)(it - data.begin())}); } } inline size_t find_matching(const std::basic_string_view& data, const size_t idx) { size_t try_idx = idx; int mass = 1; do{ try_idx++; std::visit([&](auto arg) { using T = std::decay_t; if constexpr (std::is_same_v) { ++mass; } else if constexpr (std::is_same_v) { --mass; } else {} }, data[try_idx]); } while(mass != 0 && try_idx < data.size()); if(try_idx parse(const std::basic_string_view& data, context& ctx) { auto ret = std::make_shared(); size_t sz = 0; if(data.size() == 0) { return std::make_pair(0,(lvalue)empty{}); } size_t skip = 0; size_t idx = 0; while(idx < data.size()) { if(skip) { skip--; ++idx; continue; } std::visit([&](const auto& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { auto matching = find_matching(data, idx); auto res = parse(std::basic_string_view{data.begin()+idx+1, matching-idx-1}, ctx); ret->append(res.second); skip = matching - idx; ++sz; } else if constexpr (std::is_same_v) { std::cerr << typeid(T).name() << " mismatched parenthesis" << std::endl; } else if constexpr (std::is_same_v) { ret->append(arg); ++sz; } else { std::cerr << typeid(T).name() << " cat in the parser ?" << std::endl; if(ctx.error_crash) { std::exit(-1); } } }, data[idx]); ++idx; } return std::make_pair(sz, (lvalue)(std::make_shared(*ret->other))); } inline std::vector lex(const std::string_view data, context& ctx) { std::vector ret; auto it = data.begin(); bool is_done = false; do{ if (it == data.end()) { is_done = true; } else if(isdigit(*it) || (*it == '-' && isdigit(*(it+1)))) { auto value = parse_number(std::string_view{it, (size_t)(data.end() - it)}); ret.push_back(value.first); it += value.second.size(); } else if(*it == '\"') { auto value = parse_string(std::string_view{it, (size_t)(data.end() - it)}); ret.push_back(value.first); size_t forward_jump = std::string_view{it, (size_t)(data.end() - it)}.size()-value.second.size(); it += forward_jump; } else if (*it == '(') { ret.push_back(cons_start{}); ++it; } else if (*it == ')') { ret.push_back(cons_end{}); ++it; } else if (iswspace(*it)) { ++it; } else if (*it == '$' || *it == '@') { auto value = parse_selector(std::string_view{it, (size_t)(data.end() - it)}); ret.push_back(value.first); it += value.second.size()+1; } else { auto value = parse_atom(std::string_view{it, (size_t)(data.end() - it)}, ctx); ret.push_back(value.first); it += value.second.size(); } }while(!is_done); return ret; } inline lvalue eval(lvalue& data, context& ctx) { lvalue ret = empty{}; std::visit([&](auto& arg) { using T = std::decay_t; if constexpr (std::is_same_v>) { auto it = arg; auto evaluated = std::make_shared(); auto iterated = evaluated; do{ iterated->self = eval(it->self, ctx); if(it->other) { iterated->other = std::make_shared(); iterated = iterated->other; } it = it->other; } while(it); std::visit([&](auto arg) { using T = std::decay_t; if constexpr (std::is_same_v>) { ret = (*arg)(evaluated->other, ctx); } else { ret = evaluated; } }, evaluated->self); } else if constexpr (std::is_same_v) { if(ctx.function_table.count(arg.value)) { ret = ctx.function_table[arg.value]; } else if (ctx.variable_table.count(arg.value)) { ret = ctx.variable_table[arg.value]; } else { ret = arg; } } else { ret = arg; } }, data); return ret; } inline lvalue eval(const std::string_view& data, context& ctx) { auto n = lex(data, ctx); auto p = parse(std::basic_string_view(n.data(), n.size()), ctx); if(ctx.print_parse_results) { print_types_visitor(std::cerr, p.second, ctx); std::cerr << std::endl; print_visitor(std::cerr, p.second, ctx); std::cerr << std::endl; } return eval(p.second, ctx); } } namespace lispy_math { int64_t next_as_int(std::shared_ptr& arguments, lispy::context& ctx) { int64_t ret = 0; if(!arguments) { std::cerr << "no argument provided, number expected" << std::endl; if(ctx.error_crash) { std::exit(-1); } return ret; } std::visit([&](auto arg) { using T = std::decay_t; if constexpr (std::is_same_v) { ret = arg; } else if constexpr (std::is_same_v) { ret = arg; } else { std::cerr << "bad argument provided, number expected" << std::endl; if(ctx.error_crash) { std::exit(-1); } } }, arguments->self); arguments = arguments->other; return ret; } double next_as_floating(std::shared_ptr& arguments, lispy::context& ctx) { double ret = 0; if(!arguments) { std::cerr << "no argument provided, number expected" << std::endl; if(ctx.error_crash) { std::exit(-1); } return ret; } std::visit([&](auto arg) { using T = std::decay_t; if constexpr (std::is_same_v) { ret = arg; } else if constexpr (std::is_same_v) { ret = arg; } else { std::cerr << "bad argument provided, number expected" << std::endl; if(ctx.error_crash) { std::exit(-1); } } }, arguments->self); arguments = arguments->other; return ret; } namespace integer_functions { class plus : lispy::function {virtual lispy::lvalue operator() (const std::shared_ptr arguments, lispy::context& ctx) { auto p = arguments; int64_t a = next_as_int(p, ctx); int64_t b = next_as_int(p, ctx); if(p) { std::cerr << "expected arity of 2 but more arguments provided" << std::endl; if(ctx.error_crash) { std::exit(-1); } } return a+b; }}; class minus : lispy::function {virtual lispy::lvalue operator() (const std::shared_ptr arguments, lispy::context& ctx) { auto p = arguments; int64_t a = next_as_int(p, ctx); if(!p) { return -a; } int64_t b = next_as_int(p, ctx); if(p) { std::cerr << "expected arity of 1 or 2 but more arguments provided" << std::endl; if(ctx.error_crash) { std::exit(-1); } } return a-b; }}; class product : lispy::function {virtual lispy::lvalue operator() (const std::shared_ptr arguments, lispy::context& ctx) { auto p = arguments; int64_t a = next_as_int(p, ctx); int64_t b = next_as_int(p, ctx); if(p) { std::cerr << "expected arity of 2 but more arguments provided" << std::endl; if(ctx.error_crash) { std::exit(-1); } } return a*b; }}; class divide : lispy::function {virtual lispy::lvalue operator() (const std::shared_ptr arguments, lispy::context& ctx) { auto p = arguments; int64_t a = next_as_int(p, ctx); int64_t b = next_as_int(p, ctx); if(p) { std::cerr << "expected arity of 2 but more arguments provided" << std::endl; if(ctx.error_crash) { std::exit(-1); } } return a/b; }}; class remainder : lispy::function {virtual lispy::lvalue operator() (const std::shared_ptr arguments, lispy::context& ctx) { auto p = arguments; int64_t a = next_as_int(p, ctx); int64_t b = next_as_int(p, ctx); if(p) { std::cerr << "expected arity of 2 but more arguments provided" << std::endl; if(ctx.error_crash) { std::exit(-1); } } return a%b; }}; }; namespace float_functions { class plus : lispy::function {virtual lispy::lvalue operator() (const std::shared_ptr arguments, lispy::context& ctx) { auto p = arguments; double a = next_as_floating(p, ctx); double b = next_as_floating(p, ctx); if(p) { std::cerr << "expected arity of 2 but more arguments provided" << std::endl; if(ctx.error_crash) { std::exit(-1); } } return a+b; }}; class minus : lispy::function {virtual lispy::lvalue operator() (const std::shared_ptr arguments, lispy::context& ctx) { auto p = arguments; double a = next_as_floating(p, ctx); if(!p) { return -a; } double b = next_as_floating(p, ctx); if(p) { std::cerr << "expected arity of 1 or 2 but more arguments provided" << std::endl; if(ctx.error_crash) { std::exit(-1); } } return a-b; }}; class product : lispy::function {virtual lispy::lvalue operator() (const std::shared_ptr arguments, lispy::context& ctx) { auto p = arguments; double a = next_as_floating(p, ctx); double b = next_as_floating(p, ctx); if(p) { std::cerr << "expected arity of 2 but more arguments provided" << std::endl; if(ctx.error_crash) { std::exit(-1); } } return a*b; }}; class divide : lispy::function {virtual lispy::lvalue operator() (const std::shared_ptr arguments, lispy::context& ctx) { auto p = arguments; double a = next_as_floating(p, ctx); double b = next_as_floating(p, ctx); if(p) { std::cerr << "expected arity of 2 but more arguments provided" << std::endl; if(ctx.error_crash) { std::exit(-1); } } return a/b; }}; }; inline void add_integer_functions(lispy::context& ctx) { using func_ptr = std::shared_ptr; ctx.function_table[ctx.get_atom("+")] = func_ptr{(lispy::function*)new integer_functions::plus()}; ctx.function_table[ctx.get_atom("-")] = func_ptr{(lispy::function*)new integer_functions::minus()}; ctx.function_table[ctx.get_atom("*")] = func_ptr{(lispy::function*)new integer_functions::product()}; ctx.function_table[ctx.get_atom("/")] = func_ptr{(lispy::function*)new integer_functions::divide()}; ctx.function_table[ctx.get_atom("%")] = func_ptr{(lispy::function*)new integer_functions::remainder()}; } inline void add_floatingp_functions(lispy::context& ctx) { using func_ptr = std::shared_ptr; ctx.function_table[ctx.get_atom("+.")] = func_ptr{(lispy::function*)new float_functions::plus()}; ctx.function_table[ctx.get_atom("-.")] = func_ptr{(lispy::function*)new float_functions::minus()}; ctx.function_table[ctx.get_atom("*.")] = func_ptr{(lispy::function*)new float_functions::product()}; ctx.function_table[ctx.get_atom("/.")] = func_ptr{(lispy::function*)new float_functions::divide()}; } }