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 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]);
|
||||||
|
@ -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)];
|
||||||
|
@ -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 ¶m : 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 ¶m : 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:
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)))));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
50
src/value.cc
50
src/value.cc
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user