Separated Musique functions and C++ intrinsics; blocks as delayed computations
This commit is contained in:
parent
de1ed62f6b
commit
bde30f5d99
@ -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]);
|
||||
|
@ -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)];
|
||||
|
@ -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 ¶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:
|
||||
|
@ -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();
|
||||
|
@ -394,4 +394,7 @@ void dump(Ast const& tree, unsigned indent)
|
||||
std::cout << i << '}';
|
||||
}
|
||||
std::cout << '\n';
|
||||
if (indent == 0) {
|
||||
std::cout << std::flush;
|
||||
}
|
||||
}
|
||||
|
@ -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)))));
|
||||
};
|
||||
};
|
||||
|
50
src/value.cc
50
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> 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();
|
||||
|
Loading…
Reference in New Issue
Block a user