Small refactoring & more testing
This commit is contained in:
parent
c1e1acd6e2
commit
7c5f3e2170
2
Makefile
2
Makefile
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user