Separated Musique functions and C++ intrinsics; blocks as delayed computations

This commit is contained in:
Robert Bendun 2022-05-22 05:29:41 +02:00
parent de1ed62f6b
commit bde30f5d99
7 changed files with 91 additions and 91 deletions

View File

@ -15,20 +15,20 @@ var tail = [list | cdr (cdr list)];
var cons = [head tail | pair false (pair head tail)]; var cons = [head tail | pair false (pair head tail)];
var for_each = [list iterator | var for_each = [list iterator |
if (is_empty list) [| nil ] [| if (is_empty list) [ nil ] [
iterator (head list); iterator (head list);
for_each (tail list) iterator ]]; for_each (tail list) iterator ]];
var map = [list iterator | var map = [list iterator |
if (is_empty list) [| null ] [| if (is_empty list) [ null ] [|
cons (iterator (head list)) (map (tail list) iterator) ]]; cons (iterator (head list)) (map (tail list) iterator) ]];
var foldr = [list init folder | var foldr = [list init folder |
if (is_empty list) if (is_empty list)
[| init ] [ init ]
[| foldr (tail list) (folder (head list) init) folder ]]; [ 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; var xs = range 1 5;
say (foldr xs 1 [x y|x*y]); say (foldr xs 1 [x y|x*y]);

View File

@ -1,12 +1,9 @@
var for = [ start stop iteration | var for = [ start stop iteration |
if (start > stop) if (start > stop)
[| nil ] [nil]
[| iteration start; for (start + 1) stop iteration ] [iteration start; for (start + 1) stop iteration]
]; ];
var factorial = [n | if (n <= 1) var factorial = [n | if (n <= 1) [1] [n * (factorial (n-1))]];
[| 1]
[| n * (factorial (n-1)) ]
];
for 1 10 [i | say (factorial i)]; for 1 10 [i | say (factorial i)];

View File

@ -2,44 +2,47 @@
#include <iostream> #include <iostream>
constexpr auto binary_operator(auto binop) template<typename Binary_Operation>
constexpr auto binary_operator()
{ {
return [binop = std::move(binop)](Interpreter&, std::vector<Value> args) -> Result<Value> { return [](Interpreter&, std::vector<Value> args) -> Result<Value> {
auto result = std::move(args.front()); auto result = std::move(args.front());
for (auto &v : std::span(args).subspan(1)) { for (auto &v : std::span(args).subspan(1)) {
assert(result.type == Value::Type::Number, "LHS should be a number"); assert(result.type == Value::Type::Number, "LHS should be a number");
assert(v.type == Value::Type::Number, "RHS should be a number"); assert(v.type == Value::Type::Number, "RHS should be a number");
if constexpr (std::is_same_v<Number, std::invoke_result_t<decltype(binop), Number, Number>>) { if constexpr (std::is_same_v<Number, std::invoke_result_t<Binary_Operation, Number, Number>>) {
result.n = binop(std::move(result.n), std::move(v).n); result.n = Binary_Operation{}(std::move(result.n), std::move(v).n);
} else { } else {
result.type = Value::Type::Bool; 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; return result;
}; };
} }
constexpr auto equality_operator(auto binop) template<typename Binary_Predicate>
constexpr auto equality_operator()
{ {
return [binop = std::move(binop)](Interpreter&, std::vector<Value> args) -> Result<Value> { return [](Interpreter&, std::vector<Value> args) -> Result<Value> {
assert(args.size() == 2, "(in)Equality only allows for 2 operands"); // TODO(assert) 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<typename Binary_Predicate>
constexpr auto comparison_operator()
{ {
return [binop = std::move(binop)](Interpreter&, std::vector<Value> args) -> Result<Value> { return [](Interpreter&, std::vector<Value> args) -> Result<Value> {
assert(args.size() == 2, "(in)Equality only allows for 2 operands"); // TODO(assert) 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) assert(args.front().type == args.back().type, "Only values of the same type can be ordered"); // TODO(assert)
switch (args.front().type) { switch (args.front().type) {
case Value::Type::Number: 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: 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: default:
assert(false, "Cannot compare value of given types"); // TODO(assert) assert(false, "Cannot compare value of given types"); // TODO(assert)
@ -66,12 +69,12 @@ Interpreter::Interpreter(std::ostream& out)
env = Env::global = Env::make(); env = Env::global = Env::make();
auto &global = *Env::global; auto &global = *Env::global;
global.force_define("typeof", [](Interpreter&, std::vector<Value> args) -> Value { global.force_define("typeof", +[](Interpreter&, std::vector<Value> args) -> Result<Value> {
assert(args.size() == 1, "typeof expects only one argument"); assert(args.size() == 1, "typeof expects only one argument");
return Value::symbol(std::string(type_name(args.front().type))); return Value::symbol(std::string(type_name(args.front().type)));
}); });
global.force_define("if", [](Interpreter &i, std::vector<Value> args) -> Result<Value> { global.force_define("if", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
assert(args.size() == 2 || args.size() == 3, "argument count does not add up - expected: if <condition> <then> [<else>]"); assert(args.size() == 2 || args.size() == 3, "argument count does not add up - expected: if <condition> <then> [<else>]");
if (args.front().truthy()) { if (args.front().truthy()) {
return args[1](i, {}); return args[1](i, {});
@ -82,7 +85,7 @@ Interpreter::Interpreter(std::ostream& out)
} }
}); });
global.force_define("say", [](Interpreter &i, std::vector<Value> args) -> Value { global.force_define("say", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
for (auto it = args.begin(); it != args.end(); ++it) { for (auto it = args.begin(); it != args.end(); ++it) {
i.out << *it; i.out << *it;
if (std::next(it) != args.end()) if (std::next(it) != args.end())
@ -92,18 +95,18 @@ Interpreter::Interpreter(std::ostream& out)
return {}; return {};
}); });
operators["+"] = binary_operator(std::plus<>{}); operators["+"] = binary_operator<std::plus<>>();
operators["-"] = binary_operator(std::minus<>{}); operators["-"] = binary_operator<std::minus<>>();
operators["*"] = binary_operator(std::multiplies<>{}); operators["*"] = binary_operator<std::multiplies<>>();
operators["/"] = binary_operator(std::divides<>{}); operators["/"] = binary_operator<std::divides<>>();
operators["<"] = comparison_operator(std::less<>{}); operators["<"] = comparison_operator<std::less<>>();
operators[">"] = comparison_operator(std::greater<>{}); operators[">"] = comparison_operator<std::greater<>>();
operators["<="] = comparison_operator(std::less_equal<>{}); operators["<="] = comparison_operator<std::less_equal<>>();
operators[">="] = comparison_operator(std::greater_equal<>{}); operators[">="] = comparison_operator<std::greater_equal<>>();
operators["=="] = equality_operator(std::equal_to<>{}); operators["=="] = equality_operator<std::equal_to<>>();
operators["!="] = equality_operator(std::not_equal_to<>{}); operators["!="] = equality_operator<std::not_equal_to<>>();
} }
Result<Value> Interpreter::eval(Ast &&ast) Result<Value> Interpreter::eval(Ast &&ast)
@ -171,19 +174,22 @@ Result<Value> Interpreter::eval(Ast &&ast)
return Value{}; return Value{};
} }
case Ast::Type::Block:
case Ast::Type::Lambda: case Ast::Type::Lambda:
{ {
Lambda lambda; Block block;
auto parameters = std::span(ast.arguments.begin(), std::prev(ast.arguments.end())); if (ast.type == Ast::Type::Lambda) {
lambda.parameters.reserve(parameters.size()); auto parameters = std::span(ast.arguments.begin(), std::prev(ast.arguments.end()));
for (auto &param : parameters) { block.parameters.reserve(parameters.size());
assert(param.type == Ast::Type::Literal && param.token.type == Token::Type::Symbol, "Not a name in parameter section of Ast::lambda"); for (auto &param : parameters) {
lambda.parameters.push_back(std::string(std::move(param).token.source)); 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; block.context = env;
lambda.body = std::move(ast.arguments.back()); block.body = std::move(ast.arguments.back());
return Value::lambda(std::move(lambda)); return Value::block(std::move(block));
} }
default: default:

View File

@ -434,9 +434,9 @@ struct Env;
struct Interpreter; struct Interpreter;
struct Value; struct Value;
using Function = std::function<Result<Value>(Interpreter &i, std::vector<Value>)>; using Intrinsic = Result<Value>(*)(Interpreter &i, std::vector<Value>);
struct Lambda struct Block
{ {
Location location; Location location;
std::vector<std::string> parameters; std::vector<std::string> parameters;
@ -456,7 +456,7 @@ struct Value
static Value boolean(bool b); static Value boolean(bool b);
static Value number(Number n); static Value number(Number n);
static Value symbol(std::string s); static Value symbol(std::string s);
static Value lambda(Function f); static Value block(Block &&l);
enum class Type enum class Type
{ {
@ -464,7 +464,8 @@ struct Value
Bool, Bool,
Number, Number,
Symbol, Symbol,
Lambda, Intrinsic,
Block
}; };
Value() = default; Value() = default;
@ -473,26 +474,15 @@ struct Value
Value& operator=(Value const&) = default; Value& operator=(Value const&) = default;
Value& operator=(Value &&) = default; Value& operator=(Value &&) = default;
template<typename Callable> inline Value(Intrinsic intr) : type{Type::Intrinsic}, intr(intr)
requires (!std::is_same_v<std::decay_t<Callable>, Value>)
&& std::invocable<Callable, Interpreter&, std::vector<Value>>
&& is_one_of<std::invoke_result_t<Callable, Interpreter&, std::vector<Value>>, Value, Result<Value>>
inline Value(Callable &&callable)
: type{Type::Lambda}
{ {
if constexpr (std::is_same_v<Result<Value>, std::invoke_result_t<Callable, Interpreter&, std::vector<Value>>>) {
f = std::move(callable);
} else {
f = [fun = std::move(callable)](Interpreter &i, std::vector<Value> args) -> Result<Value> {
return fun(i, std::move(args));
};
}
} }
Type type = Type::Nil; Type type = Type::Nil;
bool b{}; bool b{};
Number n{}; Number n{};
Function f{}; Intrinsic intr{};
Block blk;
// TODO Most strings should not be allocated by Value, but reference to string allocated previously // 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: // Wrapper for std::string is needed that will allocate only when needed, middle ground between:
@ -541,7 +531,7 @@ private:
struct Interpreter struct Interpreter
{ {
std::ostream &out; std::ostream &out;
std::unordered_map<std::string, Function> operators; std::unordered_map<std::string, Intrinsic> operators;
std::shared_ptr<Env> env; std::shared_ptr<Env> env;
Interpreter(); Interpreter();

View File

@ -394,4 +394,7 @@ void dump(Ast const& tree, unsigned indent)
std::cout << i << '}'; std::cout << i << '}';
} }
std::cout << '\n'; std::cout << '\n';
if (indent == 0) {
std::cout << std::flush;
}
} }

View File

@ -48,7 +48,7 @@ suite value_test = [] {
either_truthy_or_falsy(&Value::truthy, Value::boolean(true)); 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::number(Number(1)));
either_truthy_or_falsy(&Value::truthy, Value::symbol("foo")); 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{});
either_truthy_or_falsy(&Value::falsy, Value::boolean(false)); either_truthy_or_falsy(&Value::falsy, Value::boolean(false));
@ -57,10 +57,6 @@ suite value_test = [] {
}; };
"Value comparisons"_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") = [] { should("are always not equal when types differ") = [] {
expect(neq(Value::symbol("0"), Value::number(Number(0)))); 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("foo"sv, str(Value::symbol("foo"))));
expect(eq("<lambda>"sv, str(Value::lambda(nullptr)))); expect(eq("<intrinsic>"sv, str(Value(Intrinsic(nullptr)))));
}; };
}; };

View File

@ -44,21 +44,23 @@ Value Value::symbol(std::string s)
return v; return v;
} }
Value Value::lambda(Function f) Value Value::block(Block &&block)
{ {
Value v; Value v;
v.type = Type::Lambda; v.type = Type::Block;
v.f = std::move(f); v.blk = std::move(block);
return v; return v;
} }
Result<Value> Value::operator()(Interpreter &i, std::vector<Value> args) Result<Value> Value::operator()(Interpreter &i, std::vector<Value> args)
{ {
if (type == Type::Lambda) { switch (type) {
return f(i, std::move(args)); 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 bool Value::truthy() const
@ -67,7 +69,8 @@ bool Value::truthy() const
case Type::Bool: return b; case Type::Bool: return b;
case Type::Nil: return false; case Type::Nil: return false;
case Type::Number: return n != Number(0); case Type::Number: return n != Number(0);
case Type::Lambda: case Type::Block:
case Type::Intrinsic:
case Type::Symbol: return true; case Type::Symbol: return true;
} }
unreachable(); unreachable();
@ -84,11 +87,12 @@ bool Value::operator==(Value const& other) const
return false; return false;
switch (type) { switch (type) {
case Type::Nil: return true; case Type::Nil: return true;
case Type::Number: return n == other.n; case Type::Number: return n == other.n;
case Type::Symbol: return s == other.s; case Type::Symbol: return s == other.s;
case Type::Lambda: return false; // TODO Reconsider if functions are comparable case Type::Intrinsic: return intr == other.intr;
case Type::Bool: return b == other.b; case Type::Block: return false; // TODO Reconsider if functions are comparable
case Type::Bool: return b == other.b;
} }
unreachable(); unreachable();
@ -109,8 +113,11 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
case Value::Type::Bool: case Value::Type::Bool:
return os << (v.b ? "true" : "false"); return os << (v.b ? "true" : "false");
case Value::Type::Lambda: case Value::Type::Intrinsic:
return os << "<lambda>"; return os << "<intrinsic>";
case Value::Type::Block:
return os << "<block>";
} }
unreachable(); unreachable();
} }
@ -118,16 +125,17 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
std::string_view type_name(Value::Type t) std::string_view type_name(Value::Type t)
{ {
switch (t) { switch (t) {
case Value::Type::Bool: return "bool"; case Value::Type::Bool: return "bool";
case Value::Type::Lambda: return "lambda"; case Value::Type::Intrinsic: return "intrinsic";
case Value::Type::Nil: return "nil"; case Value::Type::Block: return "block";
case Value::Type::Number: return "number"; case Value::Type::Nil: return "nil";
case Value::Type::Symbol: return "symbol"; case Value::Type::Number: return "number";
case Value::Type::Symbol: return "symbol";
} }
unreachable(); unreachable();
} }
Result<Value> Lambda::operator()(Interpreter &i, std::vector<Value> arguments) Result<Value> Block::operator()(Interpreter &i, std::vector<Value> arguments)
{ {
auto old_scope = std::exchange(i.env, context); auto old_scope = std::exchange(i.env, context);
i.enter_scope(); i.enter_scope();