changed shape of Value constructors; added Array type

This commit is contained in:
Robert Bendun 2022-05-24 17:39:07 +02:00
parent d549d23f0a
commit 64417bf187
6 changed files with 149 additions and 96 deletions

View File

@ -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:

View File

@ -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
};

View File

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

View File

@ -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");
};
};
};

View File

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

View File

@ -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";