From bde30f5d99b56758097587b97056cc68f8a01806 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Sun, 22 May 2022 05:29:41 +0200 Subject: [PATCH] Separated Musique functions and C++ intrinsics; blocks as delayed computations --- examples/church.mq | 10 +++--- examples/factorial.mq | 9 ++---- src/interpreter.cc | 74 +++++++++++++++++++++++-------------------- src/musique.hh | 28 ++++++---------- src/parser.cc | 3 ++ src/tests/value.cc | 8 ++--- src/value.cc | 50 +++++++++++++++++------------ 7 files changed, 91 insertions(+), 91 deletions(-) diff --git a/examples/church.mq b/examples/church.mq index a45b088..4aa9a1c 100644 --- a/examples/church.mq +++ b/examples/church.mq @@ -15,20 +15,20 @@ var tail = [list | cdr (cdr list)]; var cons = [head tail | pair false (pair head tail)]; var for_each = [list iterator | - if (is_empty list) [| nil ] [| + if (is_empty list) [ nil ] [ iterator (head list); for_each (tail list) iterator ]]; var map = [list iterator | - if (is_empty list) [| null ] [| + if (is_empty list) [ null ] [| cons (iterator (head list)) (map (tail list) iterator) ]]; var foldr = [list init folder | if (is_empty list) - [| init ] - [| foldr (tail list) (folder (head list) init) folder ]]; + [ init ] + [ foldr (tail list) (folder (head list) init) folder ]]; -var range = [start stop | if (start >= stop) [|cons start null] [|cons start (range (start+1) stop)]]; +var range = [start stop | if (start >= stop) [cons start null] [cons start (range (start+1) stop)]]; var xs = range 1 5; say (foldr xs 1 [x y|x*y]); diff --git a/examples/factorial.mq b/examples/factorial.mq index 8f8c6c0..ea84260 100644 --- a/examples/factorial.mq +++ b/examples/factorial.mq @@ -1,12 +1,9 @@ var for = [ start stop iteration | if (start > stop) - [| nil ] - [| iteration start; for (start + 1) stop iteration ] + [nil] + [iteration start; for (start + 1) stop iteration] ]; -var factorial = [n | if (n <= 1) - [| 1] - [| n * (factorial (n-1)) ] -]; +var factorial = [n | if (n <= 1) [1] [n * (factorial (n-1))]]; for 1 10 [i | say (factorial i)]; diff --git a/src/interpreter.cc b/src/interpreter.cc index 932d86b..438c6d8 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -2,44 +2,47 @@ #include -constexpr auto binary_operator(auto binop) +template +constexpr auto binary_operator() { - return [binop = std::move(binop)](Interpreter&, std::vector args) -> Result { + return [](Interpreter&, std::vector args) -> Result { auto result = std::move(args.front()); for (auto &v : std::span(args).subspan(1)) { assert(result.type == Value::Type::Number, "LHS should be a number"); assert(v.type == Value::Type::Number, "RHS should be a number"); - if constexpr (std::is_same_v>) { - result.n = binop(std::move(result.n), std::move(v).n); + if constexpr (std::is_same_v>) { + result.n = Binary_Operation{}(std::move(result.n), std::move(v).n); } else { result.type = Value::Type::Bool; - result.b = binop(std::move(result.n), std::move(v).n); + result.b = Binary_Operation{}(std::move(result.n), std::move(v).n); } } return result; }; } -constexpr auto equality_operator(auto binop) +template +constexpr auto equality_operator() { - return [binop = std::move(binop)](Interpreter&, std::vector args) -> Result { + return [](Interpreter&, std::vector args) -> Result { assert(args.size() == 2, "(in)Equality only allows for 2 operands"); // TODO(assert) - return Value::boolean(binop(std::move(args.front()), std::move(args.back()))); + return Value::boolean(Binary_Predicate{}(std::move(args.front()), std::move(args.back()))); }; } -constexpr auto comparison_operator(auto binop) +template +constexpr auto comparison_operator() { - return [binop = std::move(binop)](Interpreter&, std::vector args) -> Result { + return [](Interpreter&, std::vector args) -> Result { assert(args.size() == 2, "(in)Equality only allows for 2 operands"); // TODO(assert) assert(args.front().type == args.back().type, "Only values of the same type can be ordered"); // TODO(assert) switch (args.front().type) { case Value::Type::Number: - return Value::boolean(binop(std::move(args.front()).n, std::move(args.back()).n)); + return Value::boolean(Binary_Predicate{}(std::move(args.front()).n, std::move(args.back()).n)); case Value::Type::Bool: - return Value::boolean(binop(std::move(args.front()).b, std::move(args.back()).b)); + return Value::boolean(Binary_Predicate{}(std::move(args.front()).b, std::move(args.back()).b)); default: assert(false, "Cannot compare value of given types"); // TODO(assert) @@ -66,12 +69,12 @@ Interpreter::Interpreter(std::ostream& out) env = Env::global = Env::make(); auto &global = *Env::global; - global.force_define("typeof", [](Interpreter&, std::vector args) -> Value { + global.force_define("typeof", +[](Interpreter&, std::vector args) -> Result { assert(args.size() == 1, "typeof expects only one argument"); return Value::symbol(std::string(type_name(args.front().type))); }); - global.force_define("if", [](Interpreter &i, std::vector args) -> Result { + global.force_define("if", +[](Interpreter &i, std::vector args) -> Result { assert(args.size() == 2 || args.size() == 3, "argument count does not add up - expected: if []"); if (args.front().truthy()) { return args[1](i, {}); @@ -82,7 +85,7 @@ Interpreter::Interpreter(std::ostream& out) } }); - global.force_define("say", [](Interpreter &i, std::vector args) -> Value { + global.force_define("say", +[](Interpreter &i, std::vector args) -> Result { for (auto it = args.begin(); it != args.end(); ++it) { i.out << *it; if (std::next(it) != args.end()) @@ -92,18 +95,18 @@ Interpreter::Interpreter(std::ostream& out) return {}; }); - operators["+"] = binary_operator(std::plus<>{}); - operators["-"] = binary_operator(std::minus<>{}); - operators["*"] = binary_operator(std::multiplies<>{}); - operators["/"] = binary_operator(std::divides<>{}); + operators["+"] = binary_operator>(); + operators["-"] = binary_operator>(); + operators["*"] = binary_operator>(); + operators["/"] = binary_operator>(); - operators["<"] = comparison_operator(std::less<>{}); - operators[">"] = comparison_operator(std::greater<>{}); - operators["<="] = comparison_operator(std::less_equal<>{}); - operators[">="] = comparison_operator(std::greater_equal<>{}); + operators["<"] = comparison_operator>(); + operators[">"] = comparison_operator>(); + operators["<="] = comparison_operator>(); + operators[">="] = comparison_operator>(); - operators["=="] = equality_operator(std::equal_to<>{}); - operators["!="] = equality_operator(std::not_equal_to<>{}); + operators["=="] = equality_operator>(); + operators["!="] = equality_operator>(); } Result Interpreter::eval(Ast &&ast) @@ -171,19 +174,22 @@ Result Interpreter::eval(Ast &&ast) return Value{}; } + case Ast::Type::Block: case Ast::Type::Lambda: { - Lambda lambda; - auto parameters = std::span(ast.arguments.begin(), std::prev(ast.arguments.end())); - lambda.parameters.reserve(parameters.size()); - for (auto ¶m : parameters) { - assert(param.type == Ast::Type::Literal && param.token.type == Token::Type::Symbol, "Not a name in parameter section of Ast::lambda"); - lambda.parameters.push_back(std::string(std::move(param).token.source)); + Block block; + if (ast.type == Ast::Type::Lambda) { + auto parameters = std::span(ast.arguments.begin(), std::prev(ast.arguments.end())); + block.parameters.reserve(parameters.size()); + for (auto ¶m : parameters) { + assert(param.type == Ast::Type::Literal && param.token.type == Token::Type::Symbol, "Not a name in parameter section of Ast::lambda"); + block.parameters.push_back(std::string(std::move(param).token.source)); + } } - lambda.context = env; - lambda.body = std::move(ast.arguments.back()); - return Value::lambda(std::move(lambda)); + block.context = env; + block.body = std::move(ast.arguments.back()); + return Value::block(std::move(block)); } default: diff --git a/src/musique.hh b/src/musique.hh index f95d7e6..7a0c7e8 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -434,9 +434,9 @@ struct Env; struct Interpreter; struct Value; -using Function = std::function(Interpreter &i, std::vector)>; +using Intrinsic = Result(*)(Interpreter &i, std::vector); -struct Lambda +struct Block { Location location; std::vector parameters; @@ -456,7 +456,7 @@ struct Value static Value boolean(bool b); static Value number(Number n); static Value symbol(std::string s); - static Value lambda(Function f); + static Value block(Block &&l); enum class Type { @@ -464,7 +464,8 @@ struct Value Bool, Number, Symbol, - Lambda, + Intrinsic, + Block }; Value() = default; @@ -473,26 +474,15 @@ struct Value Value& operator=(Value const&) = default; Value& operator=(Value &&) = default; - template - requires (!std::is_same_v, Value>) - && std::invocable> - && is_one_of>, Value, Result> - inline Value(Callable &&callable) - : type{Type::Lambda} + inline Value(Intrinsic intr) : type{Type::Intrinsic}, intr(intr) { - if constexpr (std::is_same_v, std::invoke_result_t>>) { - f = std::move(callable); - } else { - f = [fun = std::move(callable)](Interpreter &i, std::vector args) -> Result { - return fun(i, std::move(args)); - }; - } } Type type = Type::Nil; bool b{}; Number n{}; - Function f{}; + Intrinsic intr{}; + Block blk; // TODO Most strings should not be allocated by Value, but reference to string allocated previously // Wrapper for std::string is needed that will allocate only when needed, middle ground between: @@ -541,7 +531,7 @@ private: struct Interpreter { std::ostream &out; - std::unordered_map operators; + std::unordered_map operators; std::shared_ptr env; Interpreter(); diff --git a/src/parser.cc b/src/parser.cc index e3fb8f7..d084558 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -394,4 +394,7 @@ void dump(Ast const& tree, unsigned indent) std::cout << i << '}'; } std::cout << '\n'; + if (indent == 0) { + std::cout << std::flush; + } } diff --git a/src/tests/value.cc b/src/tests/value.cc index ad99066..348fe6b 100644 --- a/src/tests/value.cc +++ b/src/tests/value.cc @@ -48,7 +48,7 @@ suite value_test = [] { either_truthy_or_falsy(&Value::truthy, Value::boolean(true)); either_truthy_or_falsy(&Value::truthy, Value::number(Number(1))); either_truthy_or_falsy(&Value::truthy, Value::symbol("foo")); - either_truthy_or_falsy(&Value::truthy, Value::lambda(nullptr)); + either_truthy_or_falsy(&Value::truthy, Value(Intrinsic(nullptr))); either_truthy_or_falsy(&Value::falsy, Value{}); either_truthy_or_falsy(&Value::falsy, Value::boolean(false)); @@ -57,10 +57,6 @@ suite value_test = [] { }; "Value comparisons"_test = [] { - should("be always not equal for lambdas") = [] { - expect(neq(Value::lambda(nullptr), Value::lambda(nullptr))); - }; - should("are always not equal when types differ") = [] { expect(neq(Value::symbol("0"), Value::number(Number(0)))); }; @@ -76,6 +72,6 @@ suite value_test = [] { expect(eq("foo"sv, str(Value::symbol("foo")))); - expect(eq(""sv, str(Value::lambda(nullptr)))); + expect(eq(""sv, str(Value(Intrinsic(nullptr))))); }; }; diff --git a/src/value.cc b/src/value.cc index dfd4e98..344212b 100644 --- a/src/value.cc +++ b/src/value.cc @@ -44,21 +44,23 @@ Value Value::symbol(std::string s) return v; } -Value Value::lambda(Function f) +Value Value::block(Block &&block) { Value v; - v.type = Type::Lambda; - v.f = std::move(f); + v.type = Type::Block; + v.blk = std::move(block); return v; } Result Value::operator()(Interpreter &i, std::vector args) { - if (type == Type::Lambda) { - return f(i, std::move(args)); + switch (type) { + case Type::Intrinsic: return intr(i, std::move(args)); + case Type::Block: return blk(i, std::move(args)); + default: + // TODO Fill location + return errors::not_callable(std::nullopt, type); } - // TODO Fill location - return errors::not_callable(std::nullopt, type); } bool Value::truthy() const @@ -67,7 +69,8 @@ bool Value::truthy() const case Type::Bool: return b; case Type::Nil: return false; case Type::Number: return n != Number(0); - case Type::Lambda: + case Type::Block: + case Type::Intrinsic: case Type::Symbol: return true; } unreachable(); @@ -84,11 +87,12 @@ bool Value::operator==(Value const& other) const return false; switch (type) { - case Type::Nil: return true; - case Type::Number: return n == other.n; - case Type::Symbol: return s == other.s; - case Type::Lambda: return false; // TODO Reconsider if functions are comparable - case Type::Bool: return b == other.b; + case Type::Nil: return true; + case Type::Number: return n == other.n; + case Type::Symbol: return s == other.s; + case Type::Intrinsic: return intr == other.intr; + case Type::Block: return false; // TODO Reconsider if functions are comparable + case Type::Bool: return b == other.b; } unreachable(); @@ -109,8 +113,11 @@ std::ostream& operator<<(std::ostream& os, Value const& v) case Value::Type::Bool: return os << (v.b ? "true" : "false"); - case Value::Type::Lambda: - return os << ""; + case Value::Type::Intrinsic: + return os << ""; + + case Value::Type::Block: + return os << ""; } unreachable(); } @@ -118,16 +125,17 @@ std::ostream& operator<<(std::ostream& os, Value const& v) std::string_view type_name(Value::Type t) { switch (t) { - case Value::Type::Bool: return "bool"; - case Value::Type::Lambda: return "lambda"; - case Value::Type::Nil: return "nil"; - case Value::Type::Number: return "number"; - case Value::Type::Symbol: return "symbol"; + case Value::Type::Bool: return "bool"; + case Value::Type::Intrinsic: return "intrinsic"; + case Value::Type::Block: return "block"; + case Value::Type::Nil: return "nil"; + case Value::Type::Number: return "number"; + case Value::Type::Symbol: return "symbol"; } unreachable(); } -Result Lambda::operator()(Interpreter &i, std::vector arguments) +Result Block::operator()(Interpreter &i, std::vector arguments) { auto old_scope = std::exchange(i.env, context); i.enter_scope();