diff --git a/Makefile b/Makefile index ab5b3c4..79afca1 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,7 @@ unit-test-coverage: bin/unit-tests rm -rf coverage mkdir coverage - gcovr -e '.*\.hpp' --html --html-details -o coverage/index.html + gcovr -e '.*\.hpp' -e 'src/tests/.*' -e 'src/pretty.cc' --html --html-details -o coverage/index.html rm -rf bin/debug xdg-open coverage/index.html diff --git a/src/interpreter.cc b/src/interpreter.cc index e7008f4..e219958 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -43,6 +43,28 @@ static bool may_be_vectorized(std::vector const& args) return args.size() == 2 && (is_indexable(args[0].type) != is_indexable(args[1].type)); } +static Result into_flat_array(Interpreter &i, std::vector args) +{ + Array array; + for (auto &arg : args) { + switch (arg.type) { + case Value::Type::Array: + std::move(arg.array.elements.begin(), arg.array.elements.end(), std::back_inserter(array.elements)); + break; + + case Value::Type::Block: + for (auto j = 0u; j < arg.blk.size(); ++j) { + array.elements.push_back(Try(arg.blk.index(i, j))); + } + break; + + default: + array.elements.push_back(std::move(arg)); + } + } + return array; +} + /// Intrinsic implementation primitive to ease operation vectorization /// @invariant args.size() == 2 Result vectorize(auto &&operation, Interpreter &interpreter, std::vector args) @@ -316,71 +338,18 @@ Interpreter::Interpreter() }); global.force_define("flat", +[](Interpreter &i, std::vector args) -> Result { - Array array; - for (auto &arg : args) { - switch (arg.type) { - case Value::Type::Array: - std::move(arg.array.elements.begin(), arg.array.elements.end(), std::back_inserter(array.elements)); - break; - - case Value::Type::Block: - for (auto j = 0u; j < arg.blk.size(); ++j) { - array.elements.push_back(Try(arg.blk.index(i, j))); - } - break; - - default: - array.elements.push_back(std::move(arg)); - } - } - - return Value::from(std::move(array)); + return Value::from(Try(into_flat_array(i, std::move(args)))); }); global.force_define("shuffle", +[](Interpreter &i, std::vector args) -> Result { static std::mt19937 rnd{std::random_device{}()}; - - Array array; - for (auto &arg : args) { - switch (arg.type) { - case Value::Type::Array: - std::move(arg.array.elements.begin(), arg.array.elements.end(), std::back_inserter(array.elements)); - break; - - case Value::Type::Block: - for (auto j = 0u; j < arg.blk.size(); ++j) { - array.elements.push_back(Try(arg.blk.index(i, j))); - } - break; - - default: - array.elements.push_back(std::move(arg)); - } - } - + auto array = Try(into_flat_array(i, std::move(args))); std::shuffle(array.elements.begin(), array.elements.end(), rnd); return Value::from(std::move(array)); }); global.force_define("permute", +[](Interpreter &i, std::vector args) -> Result { - Array array; - for (auto &arg : args) { - switch (arg.type) { - case Value::Type::Array: - std::move(arg.array.elements.begin(), arg.array.elements.end(), std::back_inserter(array.elements)); - break; - - case Value::Type::Block: - for (auto j = 0u; j < arg.blk.size(); ++j) { - array.elements.push_back(Try(arg.blk.index(i, j))); - } - break; - - default: - array.elements.push_back(std::move(arg)); - } - } - + auto array = Try(into_flat_array(i, std::move(args))); std::next_permutation(array.elements.begin(), array.elements.end()); return Value::from(std::move(array)); }); diff --git a/src/tests/interpreter.cc b/src/tests/interpreter.cc index 66feff7..03badda 100644 --- a/src/tests/interpreter.cc +++ b/src/tests/interpreter.cc @@ -1,6 +1,9 @@ #include #include +#include +#include + using namespace boost::ut; using namespace std::string_view_literals; @@ -10,13 +13,15 @@ static auto capture_errors(auto lambda, reflection::source_location sl = reflect auto result = lambda(); if (not result.has_value()) { expect(false, sl) << "failed test with error: " << result.error(); + return false; } + return true; }; } -void evaluates_to(Value value, std::string_view source_code, reflection::source_location sl = reflection::source_location::current()) +auto evaluates_to(Value value, std::string_view source_code, reflection::source_location sl = reflection::source_location::current()) { - capture_errors([=]() -> Result { + return capture_errors([=]() -> Result { Interpreter interpreter; auto result = Try(interpreter.eval(Try(Parser::parse(source_code, "test")))); expect(eq(result, value), sl); @@ -119,6 +124,20 @@ suite intepreter_test = [] { Value::from(Note { .base = 3 }), Value::from(Note { .base = 4 }) }}}), "c + [1;2;3;4]"); + + evaluates_to(Value::from(Array { .elements = {{ + Value::from(Number(2)), + Value::from(Number(4)), + Value::from(Number(6)), + Value::from(Number(8)) + }}}), "[1;2;3;4] * 2"); + + evaluates_to(Value::from(Array { .elements = {{ + Value::from(Note { .base = 1 }), + Value::from(Note { .base = 2 }), + Value::from(Note { .base = 3 }), + Value::from(Note { .base = 4 }) + }}}), "[1;2;3;4] + c"); }; should("support number - music operations") = [] { @@ -143,12 +162,6 @@ suite intepreter_test = [] { "chord [c;e] g"); }; - should("support if builtin") = [] { - evaluates_to(Value::from(Number(10)), "if true [10] [20]"); - evaluates_to(Value::from(Number(20)), "if false [10] [20]"); - evaluates_to(Value{}, "if false [10]"); - }; - should("support eager array creation") = [] { evaluates_to(Value::from(Array { .elements = {{ Value::from(Number(10)), Value::from(Number(20)), Value::from(Number(30)) }}}), "flat 10 20 30"); @@ -176,4 +189,73 @@ suite intepreter_test = [] { evaluates_to(Value::from(Chord { .notes = {{ Note { .base = 0, .octave = 5 }}}}), "c 5"); }; }; + + "Interpreter boolean operators"_test = [] { + should("Properly support 'and' truth table") = [] { + evaluates_to(Value::from(false), "false and false"); + evaluates_to(Value::from(false), "true and false"); + evaluates_to(Value::from(false), "false and true"); + evaluates_to(Value::from(true), "true and true"); + + evaluates_to(Value::from(Number(0)), "0 and 0"); + evaluates_to(Value::from(Number(0)), "0 and 1"); + evaluates_to(Value::from(Number(0)), "1 and 0"); + evaluates_to(Value::from(Number(1)), "1 and 1"); + evaluates_to(Value::from(Number(42)), "1 and 42"); + }; + + should("Properly support 'or' truth table") = [] { + evaluates_to(Value::from(false), "false or false"); + evaluates_to(Value::from(true), "true or false"); + evaluates_to(Value::from(true), "false or true"); + evaluates_to(Value::from(true), "true or true"); + + evaluates_to(Value::from(Number(0)), "0 or 0"); + evaluates_to(Value::from(Number(1)), "0 or 1"); + evaluates_to(Value::from(Number(1)), "1 or 0"); + evaluates_to(Value::from(Number(1)), "1 or 1"); + evaluates_to(Value::from(Number(1)), "1 or 42"); + evaluates_to(Value::from(Number(42)), "42 or 1"); + }; + + // TODO Implement this test + // Currently not implemented due to interpeter inability to accept + // stateful intrinsics. Probably needs to be solved with using in-language + // variable like "__called_times_" or using names that cannot be variables + // like " ". But I need some time to think about that + skip / should("Short circuit on 'and' and 'or' operators") = [] { + }; + }; + + "Interpreter's default builtins"_test = [] { + should("Conditional execution with 'if'") = [] { + evaluates_to(Value::from(Number(10)), "if true [10] [20]"); + evaluates_to(Value::from(Number(20)), "if false [10] [20]"); + evaluates_to(Value{}, "if false [10]"); + }; + + should("Permutations generation with 'permute'") = [] { + std::vector src(5); + std::iota(src.begin(), src.end(), 0); + + auto exp = Value::from(Array{}); + exp.array.elements.resize(src.size()); + + for (bool quit = false; !quit;) { + std::stringstream ss; + ss << "permute "; + std::copy(src.begin(), src.end(), std::ostream_iterator(ss, " ")); + + quit = not std::next_permutation(src.begin(), src.end()); + + std::transform(src.begin(), src.end(), exp.array.elements.begin(), [](int x) { + return Value::from(Number(x)); + }); + + auto str = ss.str(); + if (not evaluates_to(exp, str)) + quit = true; + }; + }; + }; };