Improvement of builtin functions; added try builtin

Including moving functions from lambdas into global scope, adding
documentation, improvement of error reporting
This commit is contained in:
Robert Bendun 2022-09-04 17:46:04 +02:00
parent 421f8111f0
commit 497a4bcc6a
3 changed files with 397 additions and 263 deletions

View File

@ -140,7 +140,7 @@ namespace errors
struct Unsupported_Types_For struct Unsupported_Types_For
{ {
/// Type of operation /// Type of operation
enum { Operator, Function } type; enum Type { Operator, Function } type;
/// Name of operation /// Name of operation
std::string_view name; std::string_view name;

View File

@ -3,6 +3,38 @@
#include <musique.hh> #include <musique.hh>
/// Allows creation of guards that ensure proper type
template<usize N>
struct Guard
{
std::string_view name;
std::array<std::string_view, N> possibilities;
errors::Unsupported_Types_For::Type type = errors::Unsupported_Types_For::Function;
inline Error yield_error() const
{
auto error = errors::Unsupported_Types_For {
.type = type,
.name = std::string(name),
.possibilities = {}
};
std::transform(possibilities.begin(), possibilities.end(), std::back_inserter(error.possibilities), [](auto s) {
return std::string(s);
});
return Error { std::move(error) };
}
inline Result<void> yield_result() const
{
return yield_error();
}
inline Result<void> operator()(bool(*predicate)(Value::Type), Value const& v) const
{
return predicate(v.type) ? Result<void>{} : yield_result();
}
};
/// Binary operation may be vectorized when there are two argument which one is indexable and other is not /// Binary operation may be vectorized when there are two argument which one is indexable and other is not
static inline bool may_be_vectorized(std::vector<Value> const& args) static inline bool may_be_vectorized(std::vector<Value> const& args)
{ {

View File

@ -10,28 +10,27 @@ void Interpreter::register_callbacks()
assert(callbacks == nullptr, "This field should be uninitialized"); assert(callbacks == nullptr, "This field should be uninitialized");
callbacks = std::make_unique<Interpreter::Incoming_Midi_Callbacks>(); callbacks = std::make_unique<Interpreter::Incoming_Midi_Callbacks>();
callbacks->add_callbacks(*midi_connection, *this); callbacks->add_callbacks(*midi_connection, *this);
callbacks->note_on = Value(+[](Interpreter &, std::vector<Value> args) -> Result<Value> {
std::cout << "Received: " << args[1] << "\r\n" << std::flush;
return Value{};
});
} }
/// Check if type has index method
template<typename T> template<typename T>
concept With_Index_Method = requires (T t, Interpreter interpreter, usize position) { concept With_Index_Method = requires (T t, Interpreter interpreter, usize position) {
{ t.index(interpreter, position) } -> std::convertible_to<Result<Value>>; { t.index(interpreter, position) } -> std::convertible_to<Result<Value>>;
}; };
/// Check if type has index operator
template<typename T> template<typename T>
concept With_Index_Operator = requires (T t, unsigned i) { concept With_Index_Operator = requires (T t, unsigned i) {
{ t[i] } -> std::convertible_to<Value>; { t[i] } -> std::convertible_to<Value>;
}; };
/// Check if type has either (index operator or method) and size() method
template<typename T> template<typename T>
concept Iterable = (With_Index_Method<T> || With_Index_Operator<T>) && requires (T const t) { concept Iterable = (With_Index_Method<T> || With_Index_Operator<T>) && requires (T const t) {
{ t.size() } -> std::convertible_to<usize>; { t.size() } -> std::convertible_to<usize>;
}; };
/// Create chord out of given notes
template<Iterable T> template<Iterable T>
static inline Result<void> create_chord(std::vector<Note> &chord, Interpreter &interpreter, T args) static inline Result<void> create_chord(std::vector<Note> &chord, Interpreter &interpreter, T args)
{ {
@ -54,16 +53,16 @@ static inline Result<void> create_chord(std::vector<Note> &chord, Interpreter &i
break; break;
default: default:
assert(false, "this type is not supported inside chord"); assert(false, "this type is not supported inside chord"); // TODO(assert)
} }
} }
return {}; return {};
} }
/// Define handler for members of context that allow reading and writing to them
template<auto Mem_Ptr> template<auto Mem_Ptr>
Result<Value> ctx_read_write_property(Interpreter &interpreter, std::vector<Value> args) static Result<Value> ctx_read_write_property(Interpreter &interpreter, std::vector<Value> args)
{ {
assert(args.size() <= 1, "Ctx get or set is only supported (wrong number of arguments)"); // TODO(assert) assert(args.size() <= 1, "Ctx get or set is only supported (wrong number of arguments)"); // TODO(assert)
@ -84,6 +83,7 @@ Result<Value> ctx_read_write_property(Interpreter &interpreter, std::vector<Valu
return Value{}; return Value{};
} }
/// Iterate over array and it's subarrays to create one flat array
static Result<Array> into_flat_array(Interpreter &i, std::span<Value> args) static Result<Array> into_flat_array(Interpreter &i, std::span<Value> args)
{ {
Array array; Array array;
@ -111,11 +111,13 @@ static Result<Array> into_flat_array(Interpreter &i, std::vector<Value> args)
return into_flat_array(i, std::span(args)); return into_flat_array(i, std::span(args));
} }
/// Helper to convert method to it's name
template<auto> struct Number_Method_Name; template<auto> struct Number_Method_Name;
template<> struct Number_Method_Name<&Number::floor> { static constexpr auto value = "floor"; }; template<> struct Number_Method_Name<&Number::floor> { static constexpr auto value = "floor"; };
template<> struct Number_Method_Name<&Number::ceil> { static constexpr auto value = "ceil"; }; template<> struct Number_Method_Name<&Number::ceil> { static constexpr auto value = "ceil"; };
template<> struct Number_Method_Name<&Number::round> { static constexpr auto value = "round"; }; template<> struct Number_Method_Name<&Number::round> { static constexpr auto value = "round"; };
/// Apply method like Number::floor to arguments
template<auto Method> template<auto Method>
static Result<Value> apply_numeric_transform(Interpreter &i, std::vector<Value> args) static Result<Value> apply_numeric_transform(Interpreter &i, std::vector<Value> args)
{ {
@ -145,7 +147,10 @@ invalid_argument_type:
}; };
} }
/// Direction used in range definition (up -> 1, 2, 3; down -> 3, 2, 1)
enum class Range_Direction { Up, Down }; enum class Range_Direction { Up, Down };
/// Create range according to direction and specification, similar to python
template<Range_Direction dir> template<Range_Direction dir>
Result<Value> builtin_range(Interpreter&, std::vector<Value> args) Result<Value> builtin_range(Interpreter&, std::vector<Value> args)
{ {
@ -181,6 +186,7 @@ Result<Value> builtin_range(Interpreter&, std::vector<Value> args)
return Value::from(std::move(array)); return Value::from(std::move(array));
} }
/// Send MIDI Program Change message
static auto builtin_program_change(Interpreter &i, std::vector<Value> args) -> Result<Value> { static auto builtin_program_change(Interpreter &i, std::vector<Value> args) -> Result<Value> {
using Program = Shape<Value::Type::Number>; using Program = Shape<Value::Type::Number>;
using Channel_Program = Shape<Value::Type::Number, Value::Type::Number>; using Channel_Program = Shape<Value::Type::Number, Value::Type::Number>;
@ -233,12 +239,14 @@ static inline Result<void> sequential_play(Interpreter &i, Value v)
return {}; return {};
} }
/// Play what's given
static Result<void> action_play(Interpreter &i, Value v) static Result<void> action_play(Interpreter &i, Value v)
{ {
Try(sequential_play(i, std::move(v))); Try(sequential_play(i, std::move(v)));
return {}; return {};
} }
/// Play notes
template<With_Index_Operator Container = std::vector<Value>> template<With_Index_Operator Container = std::vector<Value>>
static inline Result<Value> builtin_play(Interpreter &i, Container args) static inline Result<Value> builtin_play(Interpreter &i, Container args)
{ {
@ -262,6 +270,7 @@ static inline Result<Value> builtin_play(Interpreter &i, Container args)
return {}; return {};
} }
/// Play first argument while playing all others
static Result<Value> builtin_par(Interpreter &i, std::vector<Value> args) { static Result<Value> builtin_par(Interpreter &i, std::vector<Value> args) {
Try(ensure_midi_connection_available(i, Midi_Connection_Type::Output, "par")); Try(ensure_midi_connection_available(i, Midi_Connection_Type::Output, "par"));
@ -288,7 +297,9 @@ static Result<Value> builtin_par(Interpreter &i, std::vector<Value> args) {
return result; return result;
} }
// based on https://math.stackexchange.com/a/3678200 /// Calculate upper bound for sieve that has to yield n primes
///
/// Based on https://math.stackexchange.com/a/3678200
static inline size_t upper_sieve_bound_to_yield_n_primes(size_t n_primes) static inline size_t upper_sieve_bound_to_yield_n_primes(size_t n_primes)
{ {
if (n_primes < 4) { return 10; } if (n_primes < 4) { return 10; }
@ -357,31 +368,86 @@ static Result<Value> builtin_primes(Interpreter&, std::vector<Value> args)
}; };
} }
/// Iterate over container
static Result<Value> builtin_for(Interpreter &i, std::vector<Value> args) static Result<Value> builtin_for(Interpreter &i, std::vector<Value> args)
{ {
if (is_indexable(args[0].type)) { constexpr auto guard = Guard<1> {
for (size_t n = 0; n < args[0].size(); ++n) { .name = "for",
Try(args[1](i, { Try(args[0].index(i, n)) })); .possibilities = { "(array, callback) -> any" }
} };
return Value{};
if (args.size() != 2) {
return guard.yield_error();
} }
return Error { Try(guard(is_indexable, args[0]));
.details = errors::Unsupported_Types_For { Try(guard(is_callable, args[1]));
.type = errors::Unsupported_Types_For::Function,
.name = "for", Value result{};
.possibilities = { for (size_t n = 0; n < args[0].size(); ++n) {
"(array, callback) -> nil", result = Try(args[1](i, { Try(args[0].index(i, n)) }));
}, }
}, return result;
};
} }
void Interpreter::register_builtin_functions() /// Execute blocks depending on condition
{ static Result<Value> builtin_if(Interpreter &i, std::vector<Value> args) {
auto &global = *Env::global; constexpr auto guard = Guard<2> {
.name = "if",
.possibilities = {
"(any, function) -> any",
"(any, function, function) -> any"
}
};
global.force_define("update", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { if (args.size() != 2 && args.size() != 3) {
return guard.yield_error();
}
if (args.front().truthy()) {
Try(guard(is_callable, args[1]));
return args[1](i, {});
} else if (args.size() == 3) {
Try(guard(is_callable, args[2]));
return args[2](i, {});
}
return Value{};
}
/// Try executing all but last block and if it fails execute last one
static Result<Value> builtin_try(Interpreter &interpreter, std::vector<Value> args)
{
constexpr auto guard = Guard<1> {
.name = "try",
.possibilities = {
"(...function) -> any"
}
};
if (args.size() == 1) {
Try(guard(is_callable, args[0]));
return std::move(args[0])(interpreter, {}).value_or(Value{});
}
Value success;
for (usize i = 0; i+1 < args.size(); ++i) {
Try(guard(is_callable, args[i]));
if (auto result = std::move(args[i])(interpreter, {})) {
success = *std::move(result);
} else {
Try(guard(is_callable, args.back()));
return std::move(args.back())(interpreter, {});
}
}
return success;
}
/// Update value inside of array
static Result<Value> builtin_update(Interpreter &i, std::vector<Value> args)
{
assert(args.size() == 3, "Update requires 3 arguments"); // TODO(assert) assert(args.size() == 3, "Update requires 3 arguments"); // TODO(assert)
using Eager_And_Number = Shape<Value::Type::Array, Value::Type::Number>; using Eager_And_Number = Shape<Value::Type::Array, Value::Type::Number>;
using Lazy_And_Number = Shape<Value::Type::Block, Value::Type::Number>; using Lazy_And_Number = Shape<Value::Type::Block, Value::Type::Number>;
@ -400,97 +466,97 @@ void Interpreter::register_builtin_functions()
} }
unimplemented("Wrong shape of update function"); unimplemented("Wrong shape of update function");
}); }
global.force_define("typeof", +[](Interpreter&, std::vector<Value> args) -> Result<Value> { /// Return typeof variable
static Result<Value> builtin_typeof(Interpreter&, std::vector<Value> args)
{
assert(args.size() == 1, "typeof expects only one argument"); assert(args.size() == 1, "typeof expects only one argument");
return Value::from(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> { /// Return length of container or set/get default length to play
if (args.size() != 2 && args.size() != 3) { static Result<Value> builtin_len(Interpreter &i, std::vector<Value> args)
error: {
return Error {
.details = errors::Unsupported_Types_For {
.type = errors::Unsupported_Types_For::Function,
.name = "if",
.possibilities = {
"(any, function) -> any",
"(any, function, function) -> any"
}
}
};
}
if (args.front().truthy()) {
if (not is_callable(args[1].type)) goto error;
return args[1](i, {});
} else if (args.size() == 3) {
if (not is_callable(args[2].type)) goto error;
return args[2](i, {});
} else {
return Value{};
}
});
global.force_define("len", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
if (args.size() != 1 || !is_indexable(args.front().type)) { if (args.size() != 1 || !is_indexable(args.front().type)) {
return ctx_read_write_property<&Context::length>(i, std::move(args)); return ctx_read_write_property<&Context::length>(i, std::move(args));
} }
return Value::from(Number(args.front().size())); return Value::from(Number(args.front().size()));
}); }
global.force_define("play", builtin_play); /// Join arguments into flat array
static Result<Value> builtin_flat(Interpreter &i, std::vector<Value> args)
global.force_define("flat", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { {
return Value::from(Try(into_flat_array(i, std::move(args)))); return Value::from(Try(into_flat_array(i, std::move(args))));
}); }
global.force_define("shuffle", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { /// Shuffle arguments
static Result<Value> builtin_shuffle(Interpreter &i, std::vector<Value> args)
{
static std::mt19937 rnd{std::random_device{}()}; static std::mt19937 rnd{std::random_device{}()};
auto array = Try(into_flat_array(i, std::move(args))); auto array = Try(into_flat_array(i, std::move(args)));
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> { /// Permute arguments
static Result<Value> builtin_permute(Interpreter &i, std::vector<Value> args)
{
auto array = Try(into_flat_array(i, std::move(args))); auto array = Try(into_flat_array(i, std::move(args)));
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));
}); }
global.force_define("sort", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { /// Sort arguments
static Result<Value> builtin_sort(Interpreter &i, std::vector<Value> args)
{
auto array = Try(into_flat_array(i, std::move(args))); auto array = Try(into_flat_array(i, std::move(args)));
std::sort(array.elements.begin(), array.elements.end()); std::sort(array.elements.begin(), array.elements.end());
return Value::from(std::move(array)); return Value::from(std::move(array));
}); }
global.force_define("reverse", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { /// Reverse arguments
static Result<Value> builtin_reverse(Interpreter &i, std::vector<Value> args)
{
auto array = Try(into_flat_array(i, std::move(args))); auto array = Try(into_flat_array(i, std::move(args)));
std::reverse(array.elements.begin(), array.elements.end()); std::reverse(array.elements.begin(), array.elements.end());
return Value::from(std::move(array)); return Value::from(std::move(array));
}); }
global.force_define("min", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { /// Get minimum of arguments
static Result<Value> builtin_min(Interpreter &i, std::vector<Value> args)
{
auto array = Try(into_flat_array(i, std::move(args))); auto array = Try(into_flat_array(i, std::move(args)));
auto min = std::min_element(array.elements.begin(), array.elements.end()); auto min = std::min_element(array.elements.begin(), array.elements.end());
if (min == array.elements.end()) if (min == array.elements.end())
return Value{}; return Value{};
return *min; return *min;
}); }
global.force_define("max", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { /// Get maximum of arguments
static Result<Value> builtin_max(Interpreter &i, std::vector<Value> args)
{
auto array = Try(into_flat_array(i, std::move(args))); auto array = Try(into_flat_array(i, std::move(args)));
auto max = std::max_element(array.elements.begin(), array.elements.end()); auto max = std::max_element(array.elements.begin(), array.elements.end());
if (max == array.elements.end()) if (max == array.elements.end())
return Value{}; return Value{};
return *max; return *max;
}); }
/// Parition arguments into 2 arrays based on predicate
static Result<Value> builtin_partition(Interpreter &i, std::vector<Value> args)
{
constexpr auto guard = Guard<1> {
.name = "partition",
.possibilities = { "(function, ...array) -> array" }
};
if (args.empty()) {
return guard.yield_error();
}
global.force_define("partition", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
assert(!args.empty(), "partition requires function to partition with"); // TODO(assert)
auto predicate = std::move(args.front()); auto predicate = std::move(args.front());
assert(is_callable(predicate.type), "partition requires function to partition with"); // TODO(assert) Try(guard(is_callable, predicate));
auto array = Try(into_flat_array(i, std::span(args).subspan(1))); auto array = Try(into_flat_array(i, std::span(args).subspan(1)));
Array tuple[2] = {}; Array tuple[2] = {};
@ -502,10 +568,20 @@ error:
Value::from(std::move(tuple[true])), Value::from(std::move(tuple[true])),
Value::from(std::move(tuple[false])) Value::from(std::move(tuple[false]))
}}); }});
}); }
/// Rotate arguments by n steps
static Result<Value> builtin_rotate(Interpreter &i, std::vector<Value> args)
{
constexpr auto guard = Guard<1> {
.name = "rotate",
.possibilities = { "(number, ...array) -> array" }
};
if (args.empty() || args.front().type != Value::Type::Number) {
return guard.yield_error();
}
global.force_define("rotate", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
assert(!args.empty(), "rotate requires offset"); // TODO(assert)
auto offset = std::move(args.front()).n.as_int(); auto offset = std::move(args.front()).n.as_int();
auto array = Try(into_flat_array(i, std::span(args).subspan(1))); auto array = Try(into_flat_array(i, std::span(args).subspan(1)));
if (offset > 0) { if (offset > 0) {
@ -516,18 +592,19 @@ error:
std::rotate(array.elements.rbegin(), array.elements.rbegin() + offset, array.elements.rend()); std::rotate(array.elements.rbegin(), array.elements.rbegin() + offset, array.elements.rend());
} }
return Value::from(std::move(array)); return Value::from(std::move(array));
}); }
global.force_define("chord", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { /// Build chord from arguments
static Result<Value> builtin_chord(Interpreter &i, std::vector<Value> args)
{
Chord chord; Chord chord;
Try(create_chord(chord.notes, i, std::move(args))); Try(create_chord(chord.notes, i, std::move(args)));
return Value::from(std::move(chord)); return Value::from(std::move(chord));
}); }
global.force_define("bpm", &ctx_read_write_property<&Context::bpm>); /// Send MIDI message Note On
global.force_define("oct", &ctx_read_write_property<&Context::octave>); static Result<Value> builtin_note_on(Interpreter &i, std::vector<Value> args)
{
global.force_define("note_on", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
using Channel_Note_Velocity = Shape<Value::Type::Number, Value::Type::Number, Value::Type::Number>; using Channel_Note_Velocity = Shape<Value::Type::Number, Value::Type::Number, Value::Type::Number>;
using Channel_Music_Velocity = Shape<Value::Type::Number, Value::Type::Music, Value::Type::Number>; using Channel_Music_Velocity = Shape<Value::Type::Number, Value::Type::Music, Value::Type::Number>;
@ -557,9 +634,11 @@ error:
}, },
.location = {} .location = {}
}; };
}); }
global.force_define("note_off", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { /// Send MIDI message Note Off
static Result<Value> builtin_note_off(Interpreter &i, std::vector<Value> args)
{
using Channel_Note = Shape<Value::Type::Number, Value::Type::Number>; using Channel_Note = Shape<Value::Type::Number, Value::Type::Number>;
using Channel_Music = Shape<Value::Type::Number, Value::Type::Music>; using Channel_Music = Shape<Value::Type::Number, Value::Type::Music>;
@ -589,9 +668,11 @@ error:
}, },
.location = {} .location = {}
}; };
}); }
global.force_define("incoming", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { /// Add handler for incoming midi messages
static Result<Value> builtin_incoming(Interpreter &i, std::vector<Value> args)
{
if (args.size() != 2 || args[0].type != Value::Type::Symbol || !is_callable(args[1].type)) { if (args.size() != 2 || args[0].type != Value::Type::Symbol || !is_callable(args[1].type)) {
return Error { return Error {
.details = errors::Unsupported_Types_For { .details = errors::Unsupported_Types_For {
@ -613,22 +694,43 @@ error:
} }
return Value{}; return Value{};
}); }
global.force_define("par", builtin_par); void Interpreter::register_builtin_functions()
{
global.force_define("instrument", builtin_program_change); auto &global = *Env::global;
global.force_define("pgmchange", builtin_program_change);
global.force_define("program_change", builtin_program_change); global.force_define("bpm", ctx_read_write_property<&Context::bpm>);
global.force_define("ceil", apply_numeric_transform<&Number::ceil>);
global.force_define("ceil", apply_numeric_transform<&Number::ceil>); global.force_define("chord", builtin_chord);
global.force_define("floor", apply_numeric_transform<&Number::floor>); global.force_define("down", builtin_range<Range_Direction::Down>);
global.force_define("round", apply_numeric_transform<&Number::round>); global.force_define("flat", builtin_flat);
global.force_define("floor", apply_numeric_transform<&Number::floor>);
global.force_define("range", builtin_range<Range_Direction::Up>); global.force_define("for", builtin_for);
global.force_define("up", builtin_range<Range_Direction::Up>); global.force_define("if", builtin_if);
global.force_define("down", builtin_range<Range_Direction::Down>); global.force_define("incoming", builtin_incoming);
global.force_define("instrument", builtin_program_change);
global.force_define("nprimes", builtin_primes); global.force_define("len", builtin_len);
global.force_define("for", builtin_for); global.force_define("max", builtin_max);
global.force_define("min", builtin_min);
global.force_define("note_off", builtin_note_off);
global.force_define("note_on", builtin_note_on);
global.force_define("nprimes", builtin_primes);
global.force_define("oct", ctx_read_write_property<&Context::octave>);
global.force_define("par", builtin_par);
global.force_define("partition", builtin_partition);
global.force_define("permute", builtin_permute);
global.force_define("pgmchange", builtin_program_change);
global.force_define("play", builtin_play);
global.force_define("program_change", builtin_program_change);
global.force_define("range", builtin_range<Range_Direction::Up>);
global.force_define("reverse", builtin_reverse);
global.force_define("rotate", builtin_rotate);
global.force_define("round", apply_numeric_transform<&Number::round>);
global.force_define("shuffle", builtin_shuffle);
global.force_define("sort", builtin_sort);
global.force_define("try", builtin_try);
global.force_define("typeof", builtin_typeof);
global.force_define("up", builtin_range<Range_Direction::Up>);
global.force_define("update", builtin_update);
} }