mirror of https://github.com/RainMark/lispoo.git
10 changed files with 459 additions and 0 deletions
@ -1,2 +1,22 @@ |
|||||
# lispoo |
# lispoo |
||||
|
|
||||
Code Oops Lisp Interpreter |
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)) |
||||
|
``` |
||||
@ -0,0 +1,3 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
g++ -std=c++17 -I. lispoo.cpp -o lispoo |
||||
@ -0,0 +1,73 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include <lispoo.h> |
||||
|
|
||||
|
namespace lispoo { |
||||
|
|
||||
|
inline std::shared_ptr<Expr> sum(const std::shared_ptr<List>& 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<Float>(std::static_pointer_cast<Float>(a)->value() + |
||||
|
std::static_pointer_cast<Float>(b)->value()); |
||||
|
} |
||||
|
if (a->type() == Type::Integer |
||||
|
&& b->type() == Type::Integer) { |
||||
|
return std::make_shared<Integer>(std::static_pointer_cast<Integer>(a)->value() + |
||||
|
std::static_pointer_cast<Integer>(b)->value()); |
||||
|
} |
||||
|
if (a->type() == Type::Float) { |
||||
|
return std::make_shared<Float>(std::static_pointer_cast<Float>(a)->value() + |
||||
|
std::static_pointer_cast<Integer>(b)->value()); |
||||
|
} |
||||
|
if (b->type() == Type::Float) { |
||||
|
return std::make_shared<Float>(std::static_pointer_cast<Integer>(a)->value() + |
||||
|
std::static_pointer_cast<Float>(b)->value()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inline std::shared_ptr<Expr> message(const std::shared_ptr<Expr>& 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<Integer>(expr)->value(); |
||||
|
break; |
||||
|
} |
||||
|
case Type::Float: { |
||||
|
std::cout << std::static_pointer_cast<Float>(expr)->value(); |
||||
|
break; |
||||
|
} |
||||
|
case Type::Symbol: { |
||||
|
std::cout << std::static_pointer_cast<Symbol>(expr)->value(); |
||||
|
break; |
||||
|
} |
||||
|
case Type::Callable: { |
||||
|
std::cout << "<function>: " << expr.get(); |
||||
|
break; |
||||
|
} |
||||
|
case Type::List: { |
||||
|
auto& value = std::static_pointer_cast<List>(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
|
||||
@ -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))) |
||||
|
) |
||||
@ -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)) |
||||
|
) |
||||
@ -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))) |
||||
|
) |
||||
@ -0,0 +1,6 @@ |
|||||
|
(prog |
||||
|
(message (+ |
||||
|
(+ 2 1.1) |
||||
|
1)) |
||||
|
(message (+ 1 2) (+ 1.1 1.1) (+ 2 1.1) (+ 1.9 -1)) |
||||
|
) |
||||
@ -0,0 +1,25 @@ |
|||||
|
#include <lispoo.h> |
||||
|
#include <core.h> |
||||
|
#include <sstream> |
||||
|
#include <fstream> |
||||
|
|
||||
|
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<std::string> 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; |
||||
|
} |
||||
@ -0,0 +1,308 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include <iostream> |
||||
|
#include <exception> |
||||
|
|
||||
|
#include <string> |
||||
|
#include <vector> |
||||
|
#include <memory> |
||||
|
#include <functional> |
||||
|
#include <unordered_map> |
||||
|
|
||||
|
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 <typename T> |
||||
|
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<std::string> { |
||||
|
public: |
||||
|
explicit Symbol(const std::string& value) : Atom(value) {} |
||||
|
Type type() override { return Type::Symbol; } |
||||
|
}; |
||||
|
|
||||
|
class Integer: public Atom<long> { |
||||
|
public: |
||||
|
explicit Integer(const long& value) : Atom(value) {} |
||||
|
Type type() override { return Type::Integer; } |
||||
|
}; |
||||
|
|
||||
|
class Float: public Atom<double> { |
||||
|
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> null_expr = std::make_shared<Null>(); |
||||
|
|
||||
|
class List: public Expr { |
||||
|
public: |
||||
|
Type type() override { return Type::List; } |
||||
|
const std::vector<std::shared_ptr<Expr>>& value() const { return value_; } |
||||
|
void append(const std::shared_ptr<Expr>& expr) { value_.emplace_back(expr); } |
||||
|
|
||||
|
private: |
||||
|
std::vector<std::shared_ptr<Expr>> value_; |
||||
|
}; |
||||
|
|
||||
|
class Callable: public Expr { |
||||
|
public: |
||||
|
using Fn = std::function<std::shared_ptr<Expr>(const std::shared_ptr<List>&)>; |
||||
|
|
||||
|
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_(env) {} |
||||
|
|
||||
|
std::shared_ptr<Expr> get(const std::shared_ptr<Symbol>& 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>& symbol, const std::shared_ptr<Expr>& expr) { |
||||
|
// std::cout << "put: " << symbol->value() << std::endl;
|
||||
|
expr_map_[symbol->value()] = expr; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
std::unordered_map<std::string, std::shared_ptr<Expr>> expr_map_; |
||||
|
std::shared_ptr<Env> env_; |
||||
|
}; |
||||
|
|
||||
|
static std::shared_ptr<Env> global = std::make_shared<Env>(std::shared_ptr<Env>()); |
||||
|
|
||||
|
inline void register_symbol(const std::string& symbol, Callable::Fn&& lambda) { |
||||
|
global->put(std::make_shared<Symbol>(symbol), std::make_shared<Callable>(std::forward<Callable::Fn>(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>& expr) { |
||||
|
if (!is_number(expr->type())) { |
||||
|
return false; |
||||
|
} |
||||
|
if (expr->type() == Type::Integer) { |
||||
|
return std::static_pointer_cast<Integer>(expr)->value(); |
||||
|
} |
||||
|
return std::static_pointer_cast<Float>(expr)->value() != 0.0; |
||||
|
} |
||||
|
inline void len_eq(const std::shared_ptr<Expr>& expr, unsigned long expect) { |
||||
|
if (expr->type() != Type::List) { |
||||
|
oops("len_eq() can't check non List type"); |
||||
|
} |
||||
|
auto list = std::static_pointer_cast<List>(expr); |
||||
|
if (list->value().size() != expect) { |
||||
|
auto symbol = std::static_pointer_cast<Symbol>(list->value()[0]); |
||||
|
oops("symbol: " + symbol->value() + " length not eq: " + std::to_string(expect)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inline void tokenize(const std::string& str, std::vector<std::string>& 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<Expr> parse_atom(const std::vector<std::string>& 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<Symbol>(token); |
||||
|
case Type::Integer: |
||||
|
return std::make_shared<Integer>(std::stol(token)); |
||||
|
case Type::Float: |
||||
|
return std::make_shared<Float>(std::stod(token)); |
||||
|
} |
||||
|
return std::shared_ptr<Expr>(); |
||||
|
} |
||||
|
|
||||
|
inline std::shared_ptr<Expr> parse(const std::vector<std::string>& 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<List>(); |
||||
|
while (tokens[++cursor] != ")") { |
||||
|
list->append(parse(tokens, cursor)); |
||||
|
} |
||||
|
return list; |
||||
|
} |
||||
|
return parse_atom(tokens, cursor); |
||||
|
} |
||||
|
|
||||
|
inline std::shared_ptr<Expr> eval(const std::shared_ptr<Expr>& expr, const std::shared_ptr<Env>& 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<Symbol>(expr)); |
||||
|
} |
||||
|
if (type != Type::List) { |
||||
|
oops("syntax error"); |
||||
|
} |
||||
|
auto& value = std::static_pointer_cast<List>(expr)->value(); |
||||
|
std::shared_ptr<Symbol> _ = std::static_pointer_cast<Symbol>(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<Symbol>(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<Symbol>(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<List>& args) { |
||||
|
auto& value = std::static_pointer_cast<List>(expr)->value(); |
||||
|
auto symbols = std::static_pointer_cast<List>(value[1]); |
||||
|
// arguments bind
|
||||
|
len_eq(symbols, args->value().size()); |
||||
|
auto env = std::make_shared<Env>(parent); |
||||
|
for (auto i = 0; i < symbols->value().size(); ++i) { |
||||
|
auto symbol = std::static_pointer_cast<Symbol>(symbols->value()[i]); |
||||
|
env->put(symbol, args->value()[i]); |
||||
|
} |
||||
|
// eval body
|
||||
|
return eval(value[2], env); |
||||
|
}; |
||||
|
return std::make_shared<Callable>(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<List>(); |
||||
|
for (auto i = 1; i < value.size(); ++i) { |
||||
|
args->append(eval(value[i], env)); |
||||
|
} |
||||
|
return std::static_pointer_cast<Callable>(callable)->value()(args); |
||||
|
} |
||||
|
|
||||
|
} // namespace lispoo
|
||||
Loading…
Reference in new issue