From c6446f92473f19b785630a209b8b479f437f4e32 Mon Sep 17 00:00:00 2001 From: Rain Date: Sun, 20 Feb 2022 17:16:57 +0800 Subject: [PATCH] impl lispoo Signed-off-by: Rain --- .gitignore | 4 + README.md | 20 +++ build.sh | 3 + core.h | 73 +++++++++++ example/lambda.lisp | 6 + example/prog.lisp | 6 + example/quote.lisp | 8 ++ example/sum.lisp | 6 + lispoo.cpp | 25 ++++ lispoo.h | 308 ++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 459 insertions(+) create mode 100755 build.sh create mode 100644 core.h create mode 100644 example/lambda.lisp create mode 100644 example/prog.lisp create mode 100644 example/quote.lisp create mode 100644 example/sum.lisp create mode 100644 lispoo.cpp create mode 100644 lispoo.h diff --git a/.gitignore b/.gitignore index 259148f..0f96239 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ # Prerequisites *.d +# lispoo + +lispoo + # Compiled Object files *.slo *.lo diff --git a/README.md b/README.md index 1a09cab..2e25748 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,22 @@ # lispoo + Code Oops Lisp Interpreter + +# build + +```sh +$ ./build.sh +``` + +# example + +```sh +$ ./lispoo example/lambda.lisp +(6)(18) + +$ ./lispoo example/prog.lisp +(3)(2)(1)(2)(1) + +$ ./lispoo example/quote.lisp +("false")((+ 1 2)) +``` \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..2798482 --- /dev/null +++ b/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +g++ -std=c++17 -I. lispoo.cpp -o lispoo diff --git a/core.h b/core.h new file mode 100644 index 0000000..2c52091 --- /dev/null +++ b/core.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +namespace lispoo { + +inline std::shared_ptr sum(const std::shared_ptr& args) { + len_eq(args, 2); + auto a = args->value()[0], b = args->value()[1]; + if (a->type() == Type::Float + && b->type() == Type::Float) { + return std::make_shared(std::static_pointer_cast(a)->value() + + std::static_pointer_cast(b)->value()); + } + if (a->type() == Type::Integer + && b->type() == Type::Integer) { + return std::make_shared(std::static_pointer_cast(a)->value() + + std::static_pointer_cast(b)->value()); + } + if (a->type() == Type::Float) { + return std::make_shared(std::static_pointer_cast(a)->value() + + std::static_pointer_cast(b)->value()); + } + if (b->type() == Type::Float) { + return std::make_shared(std::static_pointer_cast(a)->value() + + std::static_pointer_cast(b)->value()); + } +} + +inline std::shared_ptr message(const std::shared_ptr& expr) { + if (!expr) { + return null_expr; + } + auto type = expr->type(); + switch (type) { + case Type::Null: { + std::cout << "nil"; + break; + } + case Type::Integer: { + std::cout << std::static_pointer_cast(expr)->value(); + break; + } + case Type::Float: { + std::cout << std::static_pointer_cast(expr)->value(); + break; + } + case Type::Symbol: { + std::cout << std::static_pointer_cast(expr)->value(); + break; + } + case Type::Callable: { + std::cout << ": " << expr.get(); + break; + } + case Type::List: { + auto& value = std::static_pointer_cast(expr)->value(); + std::cout << "("; + for (auto i = 0; i < value.size(); ++i) { + if (i > 0) { + std::cout << " "; + } + message(value[i]); + } + std::cout << ")"; + } + default: + break; + } + return null_expr; +} + +} // namespace lispoo diff --git a/example/lambda.lisp b/example/lambda.lisp new file mode 100644 index 0000000..30835d9 --- /dev/null +++ b/example/lambda.lisp @@ -0,0 +1,6 @@ +(prog + (def abc (lambda (a b c) (+ a (+ b c)))) + (set! x (abc 1 2 3)) + (message x) + (prog (+ 1.2 3.9) (def plus (lambda (a b) (+ a b))) (message (plus 9 9))) + ) diff --git a/example/prog.lisp b/example/prog.lisp new file mode 100644 index 0000000..848fd58 --- /dev/null +++ b/example/prog.lisp @@ -0,0 +1,6 @@ +(prog + (set! i 3) + (while i (prog (message i) (set! i (+ i -1)))) + (prog (set! x 0) (message (if x 1 2))) + (prog (set! x 1) (message x)) + ) diff --git a/example/quote.lisp b/example/quote.lisp new file mode 100644 index 0000000..4037143 --- /dev/null +++ b/example/quote.lisp @@ -0,0 +1,8 @@ +(prog + (def test (lambda (x) x)) + (if (test (quote (+ 1 -1))) + (message (quote "true")) + (message (quote "false")) + ) + (message (quote (+ 1 2))) + ) diff --git a/example/sum.lisp b/example/sum.lisp new file mode 100644 index 0000000..99168c4 --- /dev/null +++ b/example/sum.lisp @@ -0,0 +1,6 @@ +(prog + (message (+ + (+ 2 1.1) + 1)) + (message (+ 1 2) (+ 1.1 1.1) (+ 2 1.1) (+ 1.9 -1)) + ) diff --git a/lispoo.cpp b/lispoo.cpp new file mode 100644 index 0000000..7149b31 --- /dev/null +++ b/lispoo.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + if (argc != 2) { + lispoo::oops("lispoo ./xxx.lisp"); + } + std::ifstream ifs(argv[1]); + std::stringstream ss; + if (!ifs.is_open()) { + lispoo::oops("can't open: " + std::string(argv[1])); + } + ss << ifs.rdbuf(); + std::vector tokens; + lispoo::tokenize(ss.str(), tokens); + unsigned long cursor = 0; + auto expr = lispoo::parse(tokens, cursor); + + lispoo::register_symbol("+", [](auto args) { return lispoo::sum(args); }); + lispoo::register_symbol("message", [](auto args) { return lispoo::message(args); }); + lispoo::eval(expr, lispoo::global); + return 0; +} diff --git a/lispoo.h b/lispoo.h new file mode 100644 index 0000000..28078ad --- /dev/null +++ b/lispoo.h @@ -0,0 +1,308 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +namespace lispoo { + +inline void oops(const std::string& err) { + std::cerr << err << std::endl; + exit(-1); +} + +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 null_expr = std::make_shared(); + +class List: public Expr { +public: + Type type() override { return Type::List; } + const std::vector>& value() const { return value_; } + void append(const std::shared_ptr& expr) { value_.emplace_back(expr); } + +private: + std::vector> value_; +}; + +class Callable: public Expr { +public: + using Fn = std::function(const std::shared_ptr&)>; + + explicit Callable(Fn&& lambda) : lambda_(lambda) {} + Type type() override { return Type::Callable; } + const Fn& value() const { + return lambda_; + } + +private: + Fn lambda_; +}; + +class Env { +public: + Env(const std::shared_ptr& env) : env_(env) {} + + std::shared_ptr get(const std::shared_ptr& symbol) const { + // std::cout << "get: " << symbol->value() << std::endl; + auto it = expr_map_.find(symbol->value()); + if (it != expr_map_.end()) { + return it->second; + } + if (env_) { + return env_->get(symbol); + } + return null_expr; + } + void put(const std::shared_ptr& symbol, const std::shared_ptr& expr) { + // std::cout << "put: " << symbol->value() << std::endl; + expr_map_[symbol->value()] = expr; + } + +private: + std::unordered_map> expr_map_; + std::shared_ptr env_; +}; + +static std::shared_ptr global = std::make_shared(std::shared_ptr()); + +inline void register_symbol(const std::string& symbol, Callable::Fn&& lambda) { + global->put(std::make_shared(symbol), std::make_shared(std::forward(lambda))); +} + +inline bool is_par(char ch) { + return ch == '(' || ch == ')'; +} +inline bool is_number(const Type& type) { + return type == Type::Integer || type == Type::Float; +} +inline bool is_atom(const Type& type) { + return type < Type::Atom; +} +inline bool is_true(const std::shared_ptr& expr) { + if (!is_number(expr->type())) { + return false; + } + if (expr->type() == Type::Integer) { + return std::static_pointer_cast(expr)->value(); + } + return std::static_pointer_cast(expr)->value() != 0.0; +} +inline void len_eq(const std::shared_ptr& expr, unsigned long expect) { + if (expr->type() != Type::List) { + oops("len_eq() can't check non List type"); + } + auto list = std::static_pointer_cast(expr); + if (list->value().size() != expect) { + auto symbol = std::static_pointer_cast(list->value()[0]); + oops("symbol: " + symbol->value() + " length not eq: " + std::to_string(expect)); + } +} + +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)); + } + return std::shared_ptr(); +} + +inline std::shared_ptr parse(const std::vector& tokens, unsigned long& cursor) { + if (cursor >= tokens.size()) { + oops("parse error"); + } + // std::cout << "parse: " << tokens[cursor] << std::endl; + 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 null_expr; + } + auto type = expr->type(); + if (is_number(type)) { + return expr; + } + if (type == Type::Symbol) { + return env->get(std::static_pointer_cast(expr)); + } + if (type != Type::List) { + oops("syntax error"); + } + auto& value = std::static_pointer_cast(expr)->value(); + std::shared_ptr _ = std::static_pointer_cast(value[0]); + auto& name = _->value(); + if (name == "quote") { + len_eq(expr, 2); + return value[1]; + } + if (name == "def") { + len_eq(expr, 3); + auto symbol = std::static_pointer_cast(value[1]); + if (env->get(symbol)->type() != Type::Null) { + oops("symbol defined: " + symbol->value()); + } + env->put(symbol, eval(value[2], env)); + return null_expr; + } + if (name == "set!") { + len_eq(expr, 3); + auto symbol = std::static_pointer_cast(value[1]); + env->put(symbol, eval(value[2], env)); + return null_expr; + } + if (name == "prog") { + for (auto i = 1; i < value.size(); ++i) { + eval(value[i], env); + } + return null_expr; + } + if (name == "if") { + // (if (cond) (then body) (else body)) + len_eq(expr, 4); + if (is_true(eval(value[1], env))) { + return eval(value[2], env); + } else { + return eval(value[3], env); + } + } + if (name == "while") { + // (while (cond) (loop body)) + len_eq(expr, 3); + while (is_true(eval(value[1], env))) { + eval(value[2], env); + } + return null_expr; + } + if (name == "lambda") { + // (lambda (args) (body)) + len_eq(expr, 3); + auto lambda = [expr, parent = env](const std::shared_ptr& args) { + auto& value = std::static_pointer_cast(expr)->value(); + auto symbols = std::static_pointer_cast(value[1]); + // arguments bind + len_eq(symbols, args->value().size()); + auto env = std::make_shared(parent); + for (auto i = 0; i < symbols->value().size(); ++i) { + auto symbol = std::static_pointer_cast(symbols->value()[i]); + env->put(symbol, args->value()[i]); + } + // eval body + return eval(value[2], env); + }; + return std::make_shared(std::move(lambda)); + } + // function/lambda call + // (symbol arg1 arg2 arg3 ... ) + auto callable = env->get(_); + if (!callable) { + oops("unknow symbol: " + name); + } + if (callable->type() != Type::Callable) { + oops("can't call symbol: " + name); + } + auto args = std::make_shared(); + for (auto i = 1; i < value.size(); ++i) { + args->append(eval(value[i], env)); + } + return std::static_pointer_cast(callable)->value()(args); +} + +} // namespace lispoo