From 64417bf187016f97233a78a8362e58d76d7bfeaa Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Tue, 24 May 2022 17:39:07 +0200 Subject: [PATCH] changed shape of Value constructors; added Array type --- src/interpreter.cc | 44 ++++++++++++++--------------- src/musique.hh | 45 +++++++++++++++++++++++++----- src/tests/environment.cc | 22 +++++++-------- src/tests/interpreter.cc | 60 ++++++++++++++++++++-------------------- src/tests/value.cc | 30 ++++++++++---------- src/value.cc | 44 +++++++++++++++++++++-------- 6 files changed, 149 insertions(+), 96 deletions(-) diff --git a/src/interpreter.cc b/src/interpreter.cc index e50d635..85cadad 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -28,7 +28,7 @@ constexpr auto equality_operator() { return [](Interpreter&, std::vector args) -> Result { 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 args) -> Result { 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 args) -> Result { @@ -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 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: diff --git a/src/musique.hh b/src/musique.hh index 4648fc4..5261f6d 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -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(*)(Interpreter &i, std::vector); +/// 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 -constexpr auto is_one_of = (std::is_same_v || ...); +/// Eager Array +struct Array +{ + /// Elements that are stored in array + std::vector elements; + + Result index(Interpreter &i, unsigned position); +}; // TODO Add location struct Value { static Result 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 }; diff --git a/src/tests/environment.cc b/src/tests/environment.cc index 11dc1e3..66f9bd6 100644 --- a/src/tests/environment.cc +++ b/src/tests/environment.cc @@ -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(); diff --git a/src/tests/interpreter.cc b/src/tests/interpreter.cc index 9e3900a..f570827 100644 --- a/src/tests/interpreter.cc +++ b/src/tests/interpreter.cc @@ -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"); }; }; }; diff --git a/src/tests/value.cc b/src/tests/value.cc index bd0df69..f5d00df 100644 --- a/src/tests/value.cc +++ b/src/tests/value.cc @@ -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(""sv, str(Value(Intrinsic(nullptr))))); }; diff --git a/src/value.cc b/src/value.cc index dae4ebe..110b418 100644 --- a/src/value.cc +++ b/src/value.cc @@ -65,22 +65,22 @@ Result 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::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 << ""; + 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";