#pragma once #include #include #include #include #include #include #include namespace lispoo { inline void oops(const std::string& err) { std::cerr << err << std::endl; std::exit(EXIT_FAILURE); } enum class Type { Null = 0, Float, Integer, Symbol, Atom, List, Callable, }; class Expr { public: virtual Type type() = 0; }; template class Atom: public Expr { public: explicit Atom(const T& value) : value_(value) {} Type type() override { return Type::Atom; } const T& value() const { return value_; } private: T value_; }; class Symbol: public Atom { public: explicit Symbol(const std::string& value) : Atom(value) {} Type type() override { return Type::Symbol; } }; class Integer: public Atom { public: explicit Integer(const long& value) : Atom(value) {} Type type() override { return Type::Integer; } }; class Float: public Atom { public: explicit Float(const double& value) : Atom(value) {} Type type() override { return Type::Float; } }; class Null: public Expr { public: Type type() override { return Type::Null; } }; static const std::shared_ptr nil = std::make_shared(); class List: public Expr { public: Type type() override { return Type::List; } std::vector>& value() { return value_; } void append(const std::shared_ptr& expr) { value_.emplace_back(expr); } private: std::vector> value_; }; class Env; class Callable: public Expr { public: using Fn = std::function(const std::shared_ptr&, const std::shared_ptr& env)>; explicit Callable(Fn&& lambda) : lambda_(lambda) {} Type type() override { return Type::Callable; } const Fn& value() const { return lambda_; } private: Fn lambda_; }; // environment class Env { public: Env(const std::shared_ptr& env) : env_(env) {} std::shared_ptr get(const std::string& symbol) const { auto it = expr_map_.find(symbol); if (it != expr_map_.end()) { return it->second; } if (env_) { return env_->get(symbol); } return nil; } void put(const std::string& symbol, const std::shared_ptr& expr) { expr_map_[symbol] = expr; } private: std::unordered_map> expr_map_; std::shared_ptr env_; }; static std::shared_ptr global = std::make_shared(std::shared_ptr()); inline void putenv(const std::string& symbol, Callable::Fn&& lambda) { global->put(symbol, std::make_shared(std::forward(lambda))); } // type utils inline bool is_par(char ch) { return ch == '(' || ch == ')'; } template inline decltype(auto) get_value(const std::shared_ptr& expr) { return std::static_pointer_cast(expr)->value(); } template inline bool is_type(const std::shared_ptr& expr) { return expr && expr->type() == t; } inline bool is_nil(const std::shared_ptr& expr) { return is_type(expr); } inline bool is_number(const std::shared_ptr& expr) { return is_type(expr) || is_type(expr); } inline bool is_symbol(const std::shared_ptr& expr) { return is_type(expr); } inline bool is_true(const std::shared_ptr& expr) { if (!is_number(expr)) { return false; } if (is_type(expr)) { return get_value(expr); } return get_value(expr); } // asserts template inline void assert_type(const std::shared_ptr& expr) { if (!is_type(expr)) { oops("syntax error"); } } inline void assert_len(const std::shared_ptr& expr, unsigned long expect) { if (!is_type(expr)) { oops("assert_len() failed, not List type"); } auto value = get_value(expr); if (value.size() != expect) { oops("assert_len() failed, expect: " + std::to_string(expect)); } } // parse & evaluate inline void tokenize(const std::string& str, std::vector& tokens) { for (auto i = 0; i < str.size(); ++i) { if (std::isspace(str[i])) { continue; } if (is_par(str[i])) { tokens.emplace_back(str.c_str() + i, 1); continue; } auto s = i; for (; !(std::isspace(str[i]) || is_par(str[i])); ++i); tokens.emplace_back(str.c_str() + s, i - s); i--; } } inline std::shared_ptr parse_atom(const std::vector& tokens, unsigned long& cursor) { auto token = tokens[cursor]; auto type = Type::Symbol; if (std::isdigit(token[0]) || token[0] == '-') { type = Type::Integer; for (auto i = 1; i < token.size(); ++i) { if (token[i] == '.') { if (type == Type::Float) { oops("parse failed, token: " + token); } type = Type::Float; continue; } if (!std::isdigit(token[i])) { oops("parse failed, token: " + token); } } } switch (type) { case Type::Symbol: return std::make_shared(token); case Type::Integer: return std::make_shared(std::stol(token)); case Type::Float: return std::make_shared(std::stod(token)); default: break; } return nil; } inline std::shared_ptr parse(const std::vector& tokens, unsigned long& cursor) { if (cursor >= tokens.size()) { oops("parse error"); } if (tokens[cursor] == "(") { auto list = std::make_shared(); while (tokens[++cursor] != ")") { list->append(parse(tokens, cursor)); } return list; } return parse_atom(tokens, cursor); } inline std::shared_ptr eval(const std::shared_ptr& expr, const std::shared_ptr& env) { if (!expr) { oops("syntax error"); return nil; } if (is_number(expr)) { return expr; } if (is_symbol(expr)) { return env->get(get_value(expr)); } assert_type(expr); auto value = get_value(expr); assert_type(value[0]); auto name = get_value(value[0]); // function/lambda call // (symbol arg1 arg2 arg3 ... ) auto callable = env->get(name); if (!callable) { oops("unknow symbol: " + name); } if (!is_type(callable)) { oops("can't call symbol: " + name); } return get_value(callable)(expr, env); } } // namespace lispoo