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 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]);

View File

@ -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)];

View File

@ -2,44 +2,47 @@
#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());
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<Number, std::invoke_result_t<decltype(binop), Number, Number>>) {
result.n = binop(std::move(result.n), std::move(v).n);
if constexpr (std::is_same_v<Number, std::invoke_result_t<Binary_Operation, Number, Number>>) {
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<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)
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.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<Value> args) -> Value {
global.force_define("typeof", +[](Interpreter&, std::vector<Value> args) -> Result<Value> {
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<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>]");
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<Value> args) -> Value {
global.force_define("say", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
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<std::plus<>>();
operators["-"] = binary_operator<std::minus<>>();
operators["*"] = binary_operator<std::multiplies<>>();
operators["/"] = binary_operator<std::divides<>>();
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<std::less<>>();
operators[">"] = comparison_operator<std::greater<>>();
operators["<="] = comparison_operator<std::less_equal<>>();
operators[">="] = comparison_operator<std::greater_equal<>>();
operators["=="] = equality_operator(std::equal_to<>{});
operators["!="] = equality_operator(std::not_equal_to<>{});
operators["=="] = equality_operator<std::equal_to<>>();
operators["!="] = equality_operator<std::not_equal_to<>>();
}
Result<Value> Interpreter::eval(Ast &&ast)
@ -171,19 +174,22 @@ Result<Value> 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 &param : 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 &param : 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:

View File

@ -434,9 +434,9 @@ struct Env;
struct Interpreter;
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;
std::vector<std::string> 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<typename Callable>
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}
inline Value(Intrinsic intr) : type{Type::Intrinsic}, intr(intr)
{
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;
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<std::string, Function> operators;
std::unordered_map<std::string, Intrinsic> operators;
std::shared_ptr<Env> env;
Interpreter();

View File

@ -394,4 +394,7 @@ void dump(Ast const& tree, unsigned indent)
std::cout << i << '}';
}
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::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("<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;
}
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> Value::operator()(Interpreter &i, std::vector<Value> 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 << "<lambda>";
case Value::Type::Intrinsic:
return os << "<intrinsic>";
case Value::Type::Block:
return os << "<block>";
}
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<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);
i.enter_scope();