Small refactoring & more testing

This commit is contained in:
Robert Bendun 2022-06-05 23:54:42 +02:00
parent c1e1acd6e2
commit 7c5f3e2170
3 changed files with 116 additions and 65 deletions

View File

@ -70,7 +70,7 @@ unit-test-coverage:
bin/unit-tests bin/unit-tests
rm -rf coverage rm -rf coverage
mkdir 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 rm -rf bin/debug
xdg-open coverage/index.html xdg-open coverage/index.html

View File

@ -43,6 +43,28 @@ static bool may_be_vectorized(std::vector<Value> const& args)
return args.size() == 2 && (is_indexable(args[0].type) != is_indexable(args[1].type)); return args.size() == 2 && (is_indexable(args[0].type) != is_indexable(args[1].type));
} }
static Result<Array> into_flat_array(Interpreter &i, std::vector<Value> 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 /// Intrinsic implementation primitive to ease operation vectorization
/// @invariant args.size() == 2 /// @invariant args.size() == 2
Result<Value> vectorize(auto &&operation, Interpreter &interpreter, std::vector<Value> args) Result<Value> vectorize(auto &&operation, Interpreter &interpreter, std::vector<Value> args)
@ -316,71 +338,18 @@ Interpreter::Interpreter()
}); });
global.force_define("flat", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { global.force_define("flat", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
Array array; return Value::from(Try(into_flat_array(i, std::move(args))));
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));
}); });
global.force_define("shuffle", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { global.force_define("shuffle", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
static std::mt19937 rnd{std::random_device{}()}; static std::mt19937 rnd{std::random_device{}()};
auto array = Try(into_flat_array(i, std::move(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));
}
}
std::shuffle(array.elements.begin(), array.elements.end(), rnd); std::shuffle(array.elements.begin(), array.elements.end(), rnd);
return Value::from(std::move(array)); return Value::from(std::move(array));
}); });
global.force_define("permute", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { global.force_define("permute", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
Array array; auto array = Try(into_flat_array(i, std::move(args)));
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));
}
}
std::next_permutation(array.elements.begin(), array.elements.end()); std::next_permutation(array.elements.begin(), array.elements.end());
return Value::from(std::move(array)); return Value::from(std::move(array));
}); });

View File

@ -1,6 +1,9 @@
#include <boost/ut.hpp> #include <boost/ut.hpp>
#include <musique.hh> #include <musique.hh>
#include <algorithm>
#include <numeric>
using namespace boost::ut; using namespace boost::ut;
using namespace std::string_view_literals; using namespace std::string_view_literals;
@ -10,13 +13,15 @@ static auto capture_errors(auto lambda, reflection::source_location sl = reflect
auto result = lambda(); auto result = lambda();
if (not result.has_value()) { if (not result.has_value()) {
expect(false, sl) << "failed test with error: " << result.error(); 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<void> { return capture_errors([=]() -> Result<void> {
Interpreter interpreter; Interpreter interpreter;
auto result = Try(interpreter.eval(Try(Parser::parse(source_code, "test")))); auto result = Try(interpreter.eval(Try(Parser::parse(source_code, "test"))));
expect(eq(result, value), sl); expect(eq(result, value), sl);
@ -119,6 +124,20 @@ suite intepreter_test = [] {
Value::from(Note { .base = 3 }), Value::from(Note { .base = 3 }),
Value::from(Note { .base = 4 }) Value::from(Note { .base = 4 })
}}}), "c + [1;2;3;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") = [] { should("support number - music operations") = [] {
@ -143,12 +162,6 @@ suite intepreter_test = [] {
"chord [c;e] g"); "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") = [] { should("support eager array creation") = [] {
evaluates_to(Value::from(Array { .elements = {{ Value::from(Number(10)), Value::from(Number(20)), Value::from(Number(30)) }}}), evaluates_to(Value::from(Array { .elements = {{ Value::from(Number(10)), Value::from(Number(20)), Value::from(Number(30)) }}}),
"flat 10 20 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"); 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<int> 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<int>(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;
};
};
};
}; };