changed shape of Value constructors; added Array type
This commit is contained in:
parent
d549d23f0a
commit
64417bf187
@ -28,7 +28,7 @@ constexpr auto equality_operator()
|
||||
{
|
||||
return [](Interpreter&, std::vector<Value> args) -> Result<Value> {
|
||||
assert(args.size() == 2, "(in)Equality only allows for 2 operands"); // TODO(assert)
|
||||
return Value::boolean(Binary_Predicate{}(std::move(args.front()), std::move(args.back())));
|
||||
return Value::from(Binary_Predicate{}(std::move(args.front()), std::move(args.back())));
|
||||
};
|
||||
}
|
||||
|
||||
@ -41,10 +41,10 @@ constexpr auto comparison_operator()
|
||||
|
||||
switch (args.front().type) {
|
||||
case Value::Type::Number:
|
||||
return Value::boolean(Binary_Predicate{}(std::move(args.front()).n, std::move(args.back()).n));
|
||||
return Value::from(Binary_Predicate{}(std::move(args.front()).n, std::move(args.back()).n));
|
||||
|
||||
case Value::Type::Bool:
|
||||
return Value::boolean(Binary_Predicate{}(std::move(args.front()).b, std::move(args.back()).b));
|
||||
return Value::from(Binary_Predicate{}(std::move(args.front()).b, std::move(args.back()).b));
|
||||
|
||||
default:
|
||||
assert(false, "Cannot compare value of given types"); // TODO(assert)
|
||||
@ -57,21 +57,21 @@ constexpr auto comparison_operator()
|
||||
static inline void register_note_length_constants()
|
||||
{
|
||||
auto &global = *Env::global;
|
||||
global.force_define("fn", Value::number(Number(1, 1)));
|
||||
global.force_define("dfn", Value::number(Number(3, 2)));
|
||||
global.force_define("hn", Value::number(Number(1, 2)));
|
||||
global.force_define("dhn", Value::number(Number(3, 4)));
|
||||
global.force_define("ddhn", Value::number(Number(7, 8)));
|
||||
global.force_define("qn", Value::number(Number(1, 4)));
|
||||
global.force_define("dqn", Value::number(Number(3, 8)));
|
||||
global.force_define("ddqn", Value::number(Number(7, 16)));
|
||||
global.force_define("en", Value::number(Number(1, 8)));
|
||||
global.force_define("den", Value::number(Number(3, 16)));
|
||||
global.force_define("dden", Value::number(Number(7, 32)));
|
||||
global.force_define("sn", Value::number(Number(1, 16)));
|
||||
global.force_define("dsn", Value::number(Number(3, 32)));
|
||||
global.force_define("tn", Value::number(Number(1, 32)));
|
||||
global.force_define("dtn", Value::number(Number(3, 64)));
|
||||
global.force_define("fn", Value::from(Number(1, 1)));
|
||||
global.force_define("dfn", Value::from(Number(3, 2)));
|
||||
global.force_define("hn", Value::from(Number(1, 2)));
|
||||
global.force_define("dhn", Value::from(Number(3, 4)));
|
||||
global.force_define("ddhn", Value::from(Number(7, 8)));
|
||||
global.force_define("qn", Value::from(Number(1, 4)));
|
||||
global.force_define("dqn", Value::from(Number(3, 8)));
|
||||
global.force_define("ddqn", Value::from(Number(7, 16)));
|
||||
global.force_define("en", Value::from(Number(1, 8)));
|
||||
global.force_define("den", Value::from(Number(3, 16)));
|
||||
global.force_define("dden", Value::from(Number(7, 32)));
|
||||
global.force_define("sn", Value::from(Number(1, 16)));
|
||||
global.force_define("dsn", Value::from(Number(3, 32)));
|
||||
global.force_define("tn", Value::from(Number(1, 32)));
|
||||
global.force_define("dtn", Value::from(Number(3, 64)));
|
||||
}
|
||||
|
||||
Interpreter::Interpreter()
|
||||
@ -90,7 +90,7 @@ Interpreter::Interpreter()
|
||||
|
||||
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)));
|
||||
return Value::from(std::string(type_name(args.front().type)));
|
||||
});
|
||||
|
||||
global.force_define("if", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
|
||||
@ -108,9 +108,9 @@ Interpreter::Interpreter()
|
||||
assert(args.size() == 1, "len only accepts one argument");
|
||||
assert(args.front().type == Value::Type::Block, "Only blocks can be measure");
|
||||
if (args.front().blk.body.type != Ast::Type::Sequence) {
|
||||
return Value::number(Number(1));
|
||||
return Value::from(Number(1));
|
||||
} else {
|
||||
return Value::number(Number(args.front().blk.body.arguments.size()));
|
||||
return Value::from(Number(args.front().blk.body.arguments.size()));
|
||||
}
|
||||
});
|
||||
|
||||
@ -228,7 +228,7 @@ Result<Value> Interpreter::eval(Ast &&ast)
|
||||
|
||||
block.context = env;
|
||||
block.body = std::move(ast.arguments.back());
|
||||
return Value::block(std::move(block));
|
||||
return Value::from(std::move(block));
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -201,6 +201,23 @@ struct [[nodiscard("This value may contain critical error, so it should NOT be i
|
||||
std::move(try_value).value(); \
|
||||
})
|
||||
|
||||
/// Drop in replacement for bool when C++ implcit conversions stand in your way
|
||||
struct Explicit_Bool
|
||||
{
|
||||
bool value;
|
||||
|
||||
constexpr Explicit_Bool(bool b) : value(b)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr Explicit_Bool(auto &&) = delete;
|
||||
|
||||
constexpr operator bool() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
namespace unicode
|
||||
{
|
||||
inline namespace special_runes
|
||||
@ -439,6 +456,7 @@ struct Value;
|
||||
|
||||
using Intrinsic = Result<Value>(*)(Interpreter &i, std::vector<Value>);
|
||||
|
||||
/// Lazy Array / Continuation / Closure type thingy
|
||||
struct Block
|
||||
{
|
||||
Location location;
|
||||
@ -475,18 +493,30 @@ struct Note
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Note const& note);
|
||||
|
||||
template<typename T, typename ...XS>
|
||||
constexpr auto is_one_of = (std::is_same_v<T, XS> || ...);
|
||||
/// Eager Array
|
||||
struct Array
|
||||
{
|
||||
/// Elements that are stored in array
|
||||
std::vector<Value> elements;
|
||||
|
||||
Result<Value> index(Interpreter &i, unsigned position);
|
||||
};
|
||||
|
||||
// TODO Add location
|
||||
struct Value
|
||||
{
|
||||
static Result<Value> from(Token t);
|
||||
static Value boolean(bool b);
|
||||
static Value number(Number n);
|
||||
static Value symbol(std::string s);
|
||||
static Value block(Block &&l);
|
||||
static Value music(Note n);
|
||||
static Value from(Explicit_Bool b);
|
||||
static Value from(Number n);
|
||||
|
||||
// Symbol creating functions
|
||||
static Value from(std::string s);
|
||||
static Value from(std::string_view s);
|
||||
static Value from(char const* s);
|
||||
|
||||
static Value from(Block &&l);
|
||||
static Value from(Array &&array);
|
||||
static Value from(Note n);
|
||||
|
||||
enum class Type
|
||||
{
|
||||
@ -496,6 +526,7 @@ struct Value
|
||||
Symbol,
|
||||
Intrinsic,
|
||||
Block,
|
||||
Array,
|
||||
Music
|
||||
};
|
||||
|
||||
|
@ -27,22 +27,22 @@ suite environment_test = [] {
|
||||
should("nested scoping preserve outer scope") = [] {
|
||||
Interpreter i;
|
||||
|
||||
i.env->force_define("x", Value::number(Number(10)));
|
||||
i.env->force_define("y", Value::number(Number(20)));
|
||||
i.env->force_define("x", Value::from(Number(10)));
|
||||
i.env->force_define("y", Value::from(Number(20)));
|
||||
|
||||
equals(i.env->find("x"), Value::number(Number(10)));
|
||||
equals(i.env->find("y"), Value::number(Number(20)));
|
||||
equals(i.env->find("x"), Value::from(Number(10)));
|
||||
equals(i.env->find("y"), Value::from(Number(20)));
|
||||
|
||||
i.enter_scope();
|
||||
{
|
||||
i.env->force_define("x", Value::number(Number(30)));
|
||||
equals(i.env->find("x"), Value::number(Number(30)));
|
||||
equals(i.env->find("y"), Value::number(Number(20)));
|
||||
i.env->force_define("x", Value::from(Number(30)));
|
||||
equals(i.env->find("x"), Value::from(Number(30)));
|
||||
equals(i.env->find("y"), Value::from(Number(20)));
|
||||
}
|
||||
i.leave_scope();
|
||||
|
||||
equals(i.env->find("x"), Value::number(Number(10)));
|
||||
equals(i.env->find("y"), Value::number(Number(20)));
|
||||
equals(i.env->find("x"), Value::from(Number(10)));
|
||||
equals(i.env->find("y"), Value::from(Number(20)));
|
||||
};
|
||||
|
||||
should("nested variables missing from outer scope") = [] {
|
||||
@ -50,8 +50,8 @@ suite environment_test = [] {
|
||||
|
||||
i.enter_scope();
|
||||
{
|
||||
i.env->force_define("x", Value::number(Number(30)));
|
||||
equals(i.env->find("x"), Value::number(Number(30)));
|
||||
i.env->force_define("x", Value::from(Number(30)));
|
||||
equals(i.env->find("x"), Value::from(Number(30)));
|
||||
}
|
||||
i.leave_scope();
|
||||
|
||||
|
@ -27,26 +27,26 @@ void evaluates_to(Value value, std::string_view source_code, reflection::source_
|
||||
suite intepreter_test = [] {
|
||||
"Interpreter"_test = [] {
|
||||
should("evaluate literals") = [] {
|
||||
evaluates_to(Value::boolean(false), "false");
|
||||
evaluates_to(Value::boolean(true), "true");
|
||||
evaluates_to(Value::number(Number(10)), "10");
|
||||
evaluates_to(Value::from(false), "false");
|
||||
evaluates_to(Value::from(true), "true");
|
||||
evaluates_to(Value::from(Number(10)), "10");
|
||||
evaluates_to(Value{}, "nil");
|
||||
};
|
||||
|
||||
should("evaluate arithmetic") = [] {
|
||||
evaluates_to(Value::number(Number(10)), "5 + 3 + 2");
|
||||
evaluates_to(Value::number(Number(25)), "5 * (3 + 2)");
|
||||
evaluates_to(Value::number(Number(1, 2)), "1 / 2");
|
||||
evaluates_to(Value::number(Number(-10)), "10 - 20");
|
||||
evaluates_to(Value::from(Number(10)), "5 + 3 + 2");
|
||||
evaluates_to(Value::from(Number(25)), "5 * (3 + 2)");
|
||||
evaluates_to(Value::from(Number(1, 2)), "1 / 2");
|
||||
evaluates_to(Value::from(Number(-10)), "10 - 20");
|
||||
};
|
||||
|
||||
should("call builtin functions") = [] {
|
||||
evaluates_to(Value::symbol("nil"), "typeof nil");
|
||||
evaluates_to(Value::symbol("number"), "typeof 100");
|
||||
evaluates_to(Value::from("nil"), "typeof nil");
|
||||
evaluates_to(Value::from("number"), "typeof 100");
|
||||
};
|
||||
|
||||
should("allows only for calling which is callable") = [] {
|
||||
evaluates_to(Value::number(Number(0)), "[i|i] 0");
|
||||
evaluates_to(Value::from(Number(0)), "[i|i] 0");
|
||||
{
|
||||
Interpreter i;
|
||||
{
|
||||
@ -55,7 +55,7 @@ suite intepreter_test = [] {
|
||||
expect(eq(result.error().type, errors::Not_Callable));
|
||||
}
|
||||
{
|
||||
i.env->force_define("call_me", Value::number(Number(10)));
|
||||
i.env->force_define("call_me", Value::from(Number(10)));
|
||||
auto result = Parser::parse("call_me 20", "test").and_then([&](Ast &&ast) { return i.eval(std::move(ast)); });
|
||||
expect(!result.has_value()) << "Expected code to have failed";
|
||||
expect(eq(result.error().type, errors::Not_Callable));
|
||||
@ -64,36 +64,36 @@ suite intepreter_test = [] {
|
||||
};
|
||||
|
||||
should("allow for value (in)equality comparisons") = [] {
|
||||
evaluates_to(Value::boolean(true), "nil == nil");
|
||||
evaluates_to(Value::boolean(false), "nil != nil");
|
||||
evaluates_to(Value::from(true), "nil == nil");
|
||||
evaluates_to(Value::from(false), "nil != nil");
|
||||
|
||||
evaluates_to(Value::boolean(true), "true == true");
|
||||
evaluates_to(Value::boolean(false), "true != true");
|
||||
evaluates_to(Value::boolean(false), "true == false");
|
||||
evaluates_to(Value::boolean(true), "true != false");
|
||||
evaluates_to(Value::from(true), "true == true");
|
||||
evaluates_to(Value::from(false), "true != true");
|
||||
evaluates_to(Value::from(false), "true == false");
|
||||
evaluates_to(Value::from(true), "true != false");
|
||||
|
||||
evaluates_to(Value::boolean(true), "0 == 0");
|
||||
evaluates_to(Value::boolean(false), "0 != 0");
|
||||
evaluates_to(Value::boolean(true), "1 != 0");
|
||||
evaluates_to(Value::boolean(false), "1 == 0");
|
||||
evaluates_to(Value::from(true), "0 == 0");
|
||||
evaluates_to(Value::from(false), "0 != 0");
|
||||
evaluates_to(Value::from(true), "1 != 0");
|
||||
evaluates_to(Value::from(false), "1 == 0");
|
||||
};
|
||||
|
||||
should("allow for value ordering comparisons") = [] {
|
||||
evaluates_to(Value::boolean(false), "true < true");
|
||||
evaluates_to(Value::boolean(true), "true <= true");
|
||||
evaluates_to(Value::boolean(true), "false < true");
|
||||
evaluates_to(Value::boolean(false), "false > true");
|
||||
evaluates_to(Value::from(false), "true < true");
|
||||
evaluates_to(Value::from(true), "true <= true");
|
||||
evaluates_to(Value::from(true), "false < true");
|
||||
evaluates_to(Value::from(false), "false > true");
|
||||
|
||||
evaluates_to(Value::boolean(false), "0 < 0");
|
||||
evaluates_to(Value::boolean(true), "0 <= 0");
|
||||
evaluates_to(Value::boolean(true), "1 < 2");
|
||||
evaluates_to(Value::boolean(false), "1 > 2");
|
||||
evaluates_to(Value::from(false), "0 < 0");
|
||||
evaluates_to(Value::from(true), "0 <= 0");
|
||||
evaluates_to(Value::from(true), "1 < 2");
|
||||
evaluates_to(Value::from(false), "1 > 2");
|
||||
};
|
||||
|
||||
// Added to explicitly test against bug that was in old implementation of enviroments.
|
||||
// Previously this test would segfault
|
||||
should("allow assigning result of function calls to a variable") = [] {
|
||||
evaluates_to(Value::number(Number(42)), "var x = [i|i] 42; x");
|
||||
evaluates_to(Value::from(Number(42)), "var x = [i|i] 42; x");
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -54,40 +54,40 @@ static void test_note_resolution(
|
||||
suite value_test = [] {
|
||||
"Value"_test = [] {
|
||||
should("be properly created using Value::from") = [] {
|
||||
expect_value(Value::from({ Token::Type::Numeric, "10", {} }), Value::number(Number(10)));
|
||||
expect_value(Value::from({ Token::Type::Numeric, "10", {} }), Value::from(Number(10)));
|
||||
expect_value(Value::from({ Token::Type::Keyword, "nil", {} }), Value{});
|
||||
expect_value(Value::from({ Token::Type::Keyword, "true", {} }), Value::boolean(true));
|
||||
expect_value(Value::from({ Token::Type::Keyword, "false", {} }), Value::boolean(false));
|
||||
expect_value(Value::from({ Token::Type::Symbol, "foobar", {} }), Value::symbol("foobar"));
|
||||
expect_value(Value::from({ Token::Type::Keyword, "true", {} }), Value::from(true));
|
||||
expect_value(Value::from({ Token::Type::Keyword, "false", {} }), Value::from(false));
|
||||
expect_value(Value::from({ Token::Type::Symbol, "foobar", {} }), Value::from("foobar"));
|
||||
};
|
||||
|
||||
should("have be considered truthy or falsy") = [] {
|
||||
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::from(true));
|
||||
either_truthy_or_falsy(&Value::truthy, Value::from(Number(1)));
|
||||
either_truthy_or_falsy(&Value::truthy, Value::from("foo"));
|
||||
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));
|
||||
either_truthy_or_falsy(&Value::falsy, Value::number(Number(0)));
|
||||
either_truthy_or_falsy(&Value::falsy, Value::from(false));
|
||||
either_truthy_or_falsy(&Value::falsy, Value::from(Number(0)));
|
||||
};
|
||||
};
|
||||
|
||||
"Value comparisons"_test = [] {
|
||||
should("are always not equal when types differ") = [] {
|
||||
expect(neq(Value::symbol("0"), Value::number(Number(0))));
|
||||
expect(neq(Value::from("0"), Value::from(Number(0))));
|
||||
};
|
||||
};
|
||||
|
||||
"Value printing"_test = [] {
|
||||
expect(eq("nil"sv, str(Value{})));
|
||||
expect(eq("true"sv, str(Value::boolean(true))));
|
||||
expect(eq("false"sv, str(Value::boolean(false))));
|
||||
expect(eq("true"sv, str(Value::from(true))));
|
||||
expect(eq("false"sv, str(Value::from(false))));
|
||||
|
||||
expect(eq("10"sv, str(Value::number(Number(10)))));
|
||||
expect(eq("1/2"sv, str(Value::number(Number(2, 4)))));
|
||||
expect(eq("10"sv, str(Value::from(Number(10)))));
|
||||
expect(eq("1/2"sv, str(Value::from(Number(2, 4)))));
|
||||
|
||||
expect(eq("foo"sv, str(Value::symbol("foo"))));
|
||||
expect(eq("foo"sv, str(Value::from("foo"))));
|
||||
|
||||
expect(eq("<intrinsic>"sv, str(Value(Intrinsic(nullptr)))));
|
||||
};
|
||||
|
44
src/value.cc
44
src/value.cc
@ -65,22 +65,22 @@ Result<Value> Value::from(Token t)
|
||||
{
|
||||
switch (t.type) {
|
||||
case Token::Type::Numeric:
|
||||
return Value::number(Try(Number::from(std::move(t))));
|
||||
return Value::from(Try(Number::from(std::move(t))));
|
||||
|
||||
case Token::Type::Symbol:
|
||||
return Value::symbol(std::string(t.source));
|
||||
return Value::from(std::string(t.source));
|
||||
|
||||
case Token::Type::Keyword:
|
||||
if (t.source == "false") return Value::boolean(false);
|
||||
if (t.source == "false") return Value::from(false);
|
||||
if (t.source == "nil") return Value{};
|
||||
if (t.source == "true") return Value::boolean(true);
|
||||
if (t.source == "true") return Value::from(true);
|
||||
unreachable();
|
||||
|
||||
case Token::Type::Chord:
|
||||
if (t.source.size() == 1 || (t.source.size() == 2 && t.source.back() == '#')) {
|
||||
auto maybe_note = Note::from(t.source);
|
||||
assert(maybe_note.has_value(), "Somehow parser passed invalid note literal");
|
||||
return Value::music(*maybe_note);
|
||||
return Value::from(*maybe_note);
|
||||
}
|
||||
|
||||
unimplemented("only simple note values (like c or e#) are supported now");
|
||||
@ -90,7 +90,7 @@ Result<Value> Value::from(Token t)
|
||||
}
|
||||
}
|
||||
|
||||
Value Value::boolean(bool b)
|
||||
Value Value::from(Explicit_Bool b)
|
||||
{
|
||||
Value v;
|
||||
v.type = Value::Type::Bool;
|
||||
@ -98,7 +98,7 @@ Value Value::boolean(bool b)
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::number(Number n)
|
||||
Value Value::from(Number n)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Number;
|
||||
@ -106,7 +106,7 @@ Value Value::number(Number n)
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::symbol(std::string s)
|
||||
Value Value::from(std::string s)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Symbol;
|
||||
@ -114,7 +114,23 @@ Value Value::symbol(std::string s)
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::block(Block &&block)
|
||||
Value Value::from(std::string_view s)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Symbol;
|
||||
v.s = std::move(s);
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::from(char const* s)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Symbol;
|
||||
v.s = std::move(s);
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::from(Block &&block)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Block;
|
||||
@ -122,7 +138,7 @@ Value Value::block(Block &&block)
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::music(Note n)
|
||||
Value Value::from(Note n)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Music;
|
||||
@ -174,7 +190,8 @@ bool Value::truthy() const
|
||||
case Type::Bool: return b;
|
||||
case Type::Nil: return false;
|
||||
case Type::Number: return n != Number(0);
|
||||
case Type::Block:
|
||||
case Type::Array: // for array and block maybe test emptyness?
|
||||
case Type::Block: //
|
||||
case Type::Intrinsic:
|
||||
case Type::Music:
|
||||
case Type::Symbol: return true;
|
||||
@ -200,6 +217,7 @@ bool Value::operator==(Value const& other) const
|
||||
case Type::Block: return false; // TODO Reconsider if functions are comparable
|
||||
case Type::Bool: return b == other.b;
|
||||
case Type::Music: return note == other.note;
|
||||
case Type::Array: unimplemented();
|
||||
}
|
||||
|
||||
unreachable();
|
||||
@ -226,6 +244,9 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
|
||||
case Value::Type::Block:
|
||||
return os << "<block>";
|
||||
|
||||
case Value::Type::Array:
|
||||
unimplemented();
|
||||
|
||||
case Value::Type::Music:
|
||||
return os << v.note;
|
||||
}
|
||||
@ -235,6 +256,7 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
|
||||
std::string_view type_name(Value::Type t)
|
||||
{
|
||||
switch (t) {
|
||||
case Value::Type::Array: return "array";
|
||||
case Value::Type::Block: return "block";
|
||||
case Value::Type::Bool: return "bool";
|
||||
case Value::Type::Intrinsic: return "intrinsic";
|
||||
|
Loading…
Reference in New Issue
Block a user