Moved Value from beeing bodged sum type to proper one
This allowed for simpler type checking approach, disallowed using non-initialized members and hopefully will allow further code simplification and reliability
This commit is contained in:
parent
ceeb25ea82
commit
6a98178690
69
musique/accessors.hh
Normal file
69
musique/accessors.hh
Normal file
@ -0,0 +1,69 @@
|
||||
#ifndef MUSIQUE_ACCESSORS_HH
|
||||
#define MUSIQUE_ACCESSORS_HH
|
||||
|
||||
#include <musique/errors.hh>
|
||||
#include <variant>
|
||||
|
||||
template<typename Desired, typename ...V>
|
||||
constexpr Desired* get_if(std::variant<V...> &v)
|
||||
{
|
||||
return std::visit([]<typename Actual>(Actual &act) -> Desired* {
|
||||
if constexpr (std::is_same_v<Desired, Actual>) {
|
||||
return &act;
|
||||
} else if constexpr (std::is_base_of_v<Desired, Actual>) {
|
||||
return static_cast<Desired*>(&act);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}, v);
|
||||
}
|
||||
|
||||
template<typename Desired, typename ...V>
|
||||
constexpr Desired const* get_if(std::variant<V...> const& v)
|
||||
{
|
||||
return std::visit([]<typename Actual>(Actual const& act) -> Desired const* {
|
||||
if constexpr (std::is_same_v<Desired, Actual>) {
|
||||
return &act;
|
||||
} else if constexpr (std::is_base_of_v<Desired, Actual>) {
|
||||
return static_cast<Desired const*>(&act);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}, v);
|
||||
}
|
||||
|
||||
template<typename Desired, typename ...V>
|
||||
constexpr Desired& get_ref(std::variant<V...> &v)
|
||||
{
|
||||
if (auto result = get_if<Desired>(v)) { return *result; }
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#if 0
|
||||
template<typename ...T, typename Values>
|
||||
constexpr auto match(Values& values) -> std::optional<std::tuple<T...>>
|
||||
{
|
||||
return [&]<std::size_t ...I>(std::index_sequence<I...>) -> std::optional<std::tuple<T...>> {
|
||||
if (sizeof...(T) == values.size() && (std::holds_alternative<T>(values[I].data) && ...)) {
|
||||
return {{ std::get<T>(values[I].data)... }};
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
} (std::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
|
||||
template<typename ...T, typename Values>
|
||||
constexpr auto match_ref(Values& values) -> std::optional<std::tuple<T&...>>
|
||||
{
|
||||
return [&]<std::size_t ...I>(std::index_sequence<I...>) -> std::optional<std::tuple<T&...>> {
|
||||
if (sizeof...(T) == values.size() && (get_if<T>(values[I].data) && ...)) {
|
||||
return {{ get_ref<T>(values[I].data)... }};
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
} (std::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -24,6 +24,7 @@ using isize = std::ptrdiff_t;
|
||||
/// Combine several lambdas into one for visiting std::variant
|
||||
template<typename ...Lambdas>
|
||||
struct Overloaded : Lambdas... { using Lambdas::operator()...; };
|
||||
template<class... Ts> Overloaded(Ts...) -> Overloaded<Ts...>;
|
||||
|
||||
/// Returns if provided thingy is a given template
|
||||
template<template<typename ...> typename Template, typename>
|
||||
|
@ -18,56 +18,42 @@ Value_Formatter Value_Formatter::nest(Context nested) const
|
||||
|
||||
std::optional<Error> Value_Formatter::format(std::ostream& os, Interpreter &interpreter, Value const& value)
|
||||
{
|
||||
switch (value.type) {
|
||||
break; case Value::Type::Nil:
|
||||
os << "nil";
|
||||
|
||||
break; case Value::Type::Symbol:
|
||||
os << value.s;
|
||||
|
||||
break; case Value::Type::Bool:
|
||||
os << std::boolalpha << value.b;
|
||||
|
||||
break; case Value::Type::Number:
|
||||
if (auto n = value.n.simplify(); n.den == 1) {
|
||||
os << n.num << '/' << n.den;
|
||||
} else {
|
||||
os << n.num;
|
||||
}
|
||||
|
||||
break; case Value::Type::Intrinsic:
|
||||
return std::visit(Overloaded {
|
||||
[&](Intrinsic const& intrinsic) -> std::optional<Error> {
|
||||
for (auto const& [key, val] : Env::global->variables) {
|
||||
if (val.type == Value::Type::Intrinsic && val.intr == value.intr) {
|
||||
if (auto other = get_if<Intrinsic>(val); intrinsic == *other) {
|
||||
os << "<intrinsic '" << key << "'>";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
os << "<intrinsic>";
|
||||
|
||||
|
||||
break; case Value::Type::Array:
|
||||
return {};
|
||||
},
|
||||
[&](Array const& array) -> std::optional<Error> {
|
||||
os << '[';
|
||||
for (auto i = 0u; i < value.array.elements.size(); ++i) {
|
||||
for (auto i = 0u; i < array.elements.size(); ++i) {
|
||||
if (i > 0) {
|
||||
os << "; ";
|
||||
}
|
||||
Try(nest(Inside_Block).format(os, interpreter, value.array.elements[i]));
|
||||
Try(nest(Inside_Block).format(os, interpreter, array.elements[i]));
|
||||
}
|
||||
os << ']';
|
||||
|
||||
break; case Value::Type::Block:
|
||||
return {};
|
||||
},
|
||||
[&](Block const& block) -> std::optional<Error> {
|
||||
os << '[';
|
||||
for (auto i = 0u; i < value.blk.size(); ++i) {
|
||||
for (auto i = 0u; i < block.size(); ++i) {
|
||||
if (i > 0) {
|
||||
os << "; ";
|
||||
}
|
||||
Try(nest(Inside_Block).format(os, interpreter, Try(value.index(interpreter, i))));
|
||||
Try(nest(Inside_Block).format(os, interpreter, Try(block.index(interpreter, i))));
|
||||
}
|
||||
os << ']';
|
||||
|
||||
break; case Value::Type::Music:
|
||||
os << value.chord;
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
[&](auto&&) -> std::optional<Error> {
|
||||
os << value;
|
||||
return {};
|
||||
}
|
||||
}, value.data);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <musique/accessors.hh>
|
||||
#include <musique/common.hh>
|
||||
#include <musique/errors.hh>
|
||||
#include <musique/value/value.hh>
|
||||
@ -33,9 +34,19 @@ struct Guard
|
||||
return yield_error();
|
||||
}
|
||||
|
||||
inline std::optional<Error> operator()(bool(*predicate)(Value::Type), Value const& v) const
|
||||
template<typename T>
|
||||
inline Result<T*> match(Value &v) const
|
||||
{
|
||||
return predicate(v.type) ? std::optional<Error>{} : yield_result();
|
||||
if (auto p = get_if<T>(v)) {
|
||||
return p;
|
||||
} else {
|
||||
return yield_error();
|
||||
}
|
||||
}
|
||||
|
||||
inline std::optional<Error> operator()(bool(*predicate)(Value const&), Value const& v) const
|
||||
{
|
||||
return predicate(v) ? std::optional<Error>{} : yield_result();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include <musique/interpreter/incoming_midi.hh>
|
||||
#include <musique/interpreter/interpreter.hh>
|
||||
#include <musique/try.hh>
|
||||
#include <musique/value/typecheck.hh>
|
||||
|
||||
#include <random>
|
||||
#include <memory>
|
||||
@ -26,12 +25,6 @@ concept With_Index_Method = requires (T t, Interpreter interpreter, usize positi
|
||||
{ t.index(interpreter, position) } -> std::convertible_to<Result<Value>>;
|
||||
};
|
||||
|
||||
/// Check if type has index operator
|
||||
template<typename T>
|
||||
concept With_Index_Operator = requires (T t, unsigned i) {
|
||||
{ t[i] } -> std::convertible_to<Value>;
|
||||
};
|
||||
|
||||
/// Check if type has either (index operator or method) and size() method
|
||||
template<typename T>
|
||||
concept Iterable = (With_Index_Method<T> || With_Index_Operator<T>) && requires (T const t) {
|
||||
@ -40,7 +33,7 @@ concept Iterable = (With_Index_Method<T> || With_Index_Operator<T>) && requires
|
||||
|
||||
/// Create chord out of given notes
|
||||
template<Iterable T>
|
||||
static inline std::optional<Error> create_chord(std::vector<Note> &chord, Interpreter &interpreter, T args)
|
||||
static inline std::optional<Error> create_chord(std::vector<Note> &chord, Interpreter &interpreter, T&& args)
|
||||
{
|
||||
for (auto i = 0u; i < args.size(); ++i) {
|
||||
Value arg;
|
||||
@ -50,19 +43,21 @@ static inline std::optional<Error> create_chord(std::vector<Note> &chord, Interp
|
||||
arg = std::move(args[i]);
|
||||
}
|
||||
|
||||
switch (arg.type) {
|
||||
case Value::Type::Array:
|
||||
case Value::Type::Block:
|
||||
Try(create_chord(chord, interpreter, std::move(arg)));
|
||||
break;
|
||||
|
||||
case Value::Type::Music:
|
||||
std::copy_if(arg.chord.notes.begin(), arg.chord.notes.end(), std::back_inserter(chord), [](Note const& n) { return bool(n.base); });
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false, "this type is not supported inside chord"); // TODO(assert)
|
||||
if (auto arg_chord = get_if<Chord>(arg)) {
|
||||
std::ranges::copy_if(
|
||||
arg_chord->notes,
|
||||
std::back_inserter(chord),
|
||||
[](Note const& n) { return n.base.has_value(); }
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto collection = get_if<Collection>(arg)) {
|
||||
Try(create_chord(chord, interpreter, *collection));
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(false, "this type is not supported inside chord"); // TODO(assert)
|
||||
}
|
||||
|
||||
return {};
|
||||
@ -80,38 +75,42 @@ static Result<Value> ctx_read_write_property(Interpreter &interpreter, std::vect
|
||||
return Value::from(Number(interpreter.context_stack.back().*(Mem_Ptr)));
|
||||
}
|
||||
|
||||
assert(args.front().type == Value::Type::Number, "Ctx only holds numeric values");
|
||||
assert(std::holds_alternative<Number>(args.front().data), "Ctx only holds numeric values");
|
||||
|
||||
if constexpr (std::is_same_v<Member_Type, Number>) {
|
||||
interpreter.context_stack.back().*(Mem_Ptr) = args.front().n;
|
||||
interpreter.context_stack.back().*(Mem_Ptr) = std::get<Number>(args.front().data);
|
||||
} else {
|
||||
interpreter.context_stack.back().*(Mem_Ptr) = static_cast<Member_Type>(args.front().n.as_int());
|
||||
interpreter.context_stack.back().*(Mem_Ptr) = static_cast<Member_Type>(
|
||||
std::get<Number>(args.front().data).as_int()
|
||||
);
|
||||
}
|
||||
|
||||
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 &interpreter, std::span<Value> args)
|
||||
{
|
||||
Array array;
|
||||
Array target;
|
||||
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)));
|
||||
std::visit(Overloaded {
|
||||
[&target](Array &&array) -> std::optional<Error> {
|
||||
std::ranges::move(array.elements, std::back_inserter(target.elements));
|
||||
return {};
|
||||
},
|
||||
[&target, &interpreter](Block &&block) -> std::optional<Error> {
|
||||
for (auto i = 0u; i < block.size(); ++i) {
|
||||
target.elements.push_back(Try(block.index(interpreter, i)));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
array.elements.push_back(std::move(arg));
|
||||
return {};
|
||||
},
|
||||
[&target, &arg](auto&&) -> std::optional<Error> {
|
||||
target.elements.push_back(std::move(arg));
|
||||
return {};
|
||||
},
|
||||
}, std::move(arg.data));
|
||||
}
|
||||
}
|
||||
return array;
|
||||
return target;
|
||||
}
|
||||
|
||||
static Result<Array> into_flat_array(Interpreter &i, std::vector<Value> args)
|
||||
@ -119,7 +118,6 @@ static Result<Array> into_flat_array(Interpreter &i, std::vector<Value> args)
|
||||
return into_flat_array(i, std::span(args));
|
||||
}
|
||||
|
||||
|
||||
/// Helper to convert method to it's name
|
||||
template<auto> struct Number_Method_Name;
|
||||
template<> struct Number_Method_Name<&Number::floor> { static constexpr auto value = "floor"; };
|
||||
@ -130,20 +128,23 @@ template<> struct Number_Method_Name<&Number::round> { static constexpr auto val
|
||||
template<auto Method>
|
||||
static Result<Value> apply_numeric_transform(Interpreter &i, std::vector<Value> args)
|
||||
{
|
||||
using N = Shape<Value::Type::Number>;
|
||||
if (N::typecheck(args)) {
|
||||
return Value::from((std::get<0>(N::move_from(args)).*Method)());
|
||||
if (args.size()) {
|
||||
if (auto number = get_if<Number>(args.front().data)) {
|
||||
return Value::from((number->*Method)());
|
||||
}
|
||||
}
|
||||
|
||||
auto array = Try(into_flat_array(i, std::span(args)));
|
||||
|
||||
for (Value &arg : array.elements) {
|
||||
if (arg.type != Value::Type::Number)
|
||||
if (auto number = get_if<Number>(arg.data)) {
|
||||
*number = (number->*Method)();
|
||||
} else {
|
||||
goto invalid_argument_type;
|
||||
arg.n = (arg.n.*Method)();
|
||||
}
|
||||
}
|
||||
return Value::from(std::move(array));
|
||||
|
||||
|
||||
invalid_argument_type:
|
||||
return Error {
|
||||
.details = errors::Unsupported_Types_For {
|
||||
@ -163,19 +164,14 @@ enum class Range_Direction { Up, Down };
|
||||
template<Range_Direction dir>
|
||||
static Result<Value> builtin_range(Interpreter&, std::vector<Value> args)
|
||||
{
|
||||
using N = Shape<Value::Type::Number>;
|
||||
using NN = Shape<Value::Type::Number, Value::Type::Number>;
|
||||
using NNN = Shape<Value::Type::Number, Value::Type::Number, Value::Type::Number>;
|
||||
|
||||
auto start = Number(0), stop = Number(0), step = Number(1);
|
||||
|
||||
if (0) {}
|
||||
else if (auto a = N::typecheck_and_move(args)) { std::tie(stop) = *a; }
|
||||
else if (auto a = NN::typecheck_and_move(args)) { std::tie(start, stop) = *a; }
|
||||
else if (auto a = NNN::typecheck_and_move(args)) { std::tie(start, stop, step) = *a; }
|
||||
else if (auto a = match<Number>(args)) { std::tie(stop) = *a; }
|
||||
else if (auto a = match<Number, Number>(args)) { std::tie(start, stop) = *a; }
|
||||
else if (auto a = match<Number, Number, Number>(args)) { std::tie(start, stop, step) = *a; }
|
||||
else {
|
||||
return Error {
|
||||
.details = errors::Unsupported_Types_For {
|
||||
return errors::Unsupported_Types_For {
|
||||
.type = errors::Unsupported_Types_For::Function,
|
||||
.name = "range",
|
||||
.possibilities = {
|
||||
@ -183,8 +179,6 @@ static Result<Value> builtin_range(Interpreter&, std::vector<Value> args)
|
||||
"(start: number, stop: number) -> array of number",
|
||||
"(start: number, stop: number, step: number) -> array of number",
|
||||
}
|
||||
},
|
||||
.location = {}
|
||||
};
|
||||
}
|
||||
|
||||
@ -203,17 +197,14 @@ static Result<Value> builtin_range(Interpreter&, std::vector<Value> args)
|
||||
|
||||
/// Send MIDI Program Change message
|
||||
static auto builtin_program_change(Interpreter &i, std::vector<Value> args) -> Result<Value> {
|
||||
using Program = Shape<Value::Type::Number>;
|
||||
using Channel_Program = Shape<Value::Type::Number, Value::Type::Number>;
|
||||
|
||||
if (Program::typecheck(args)) {
|
||||
auto [program] = Program::move_from(args);
|
||||
if (auto a = match<Number>(args)) {
|
||||
auto [program] = *a;
|
||||
i.midi_connection->send_program_change(0, program.as_int());
|
||||
return Value{};
|
||||
}
|
||||
|
||||
if (Channel_Program::typecheck(args)) {
|
||||
auto [chan, program] = Channel_Program::move_from(args);
|
||||
if (auto a = match<Number, Number>(args)) {
|
||||
auto [chan, program] = *a;
|
||||
i.midi_connection->send_program_change(chan.as_int(), program.as_int());
|
||||
return Value{};
|
||||
}
|
||||
@ -236,19 +227,16 @@ static auto builtin_program_change(Interpreter &i, std::vector<Value> args) -> R
|
||||
/// @invariant default_action is play one
|
||||
static inline std::optional<Error> sequential_play(Interpreter &i, Value v)
|
||||
{
|
||||
switch (v.type) {
|
||||
break; case Value::Type::Array:
|
||||
for (auto &el : v.array.elements)
|
||||
if (auto array = get_if<Array>(v)) {
|
||||
for (auto &el : array->elements) {
|
||||
Try(sequential_play(i, std::move(el)));
|
||||
|
||||
break; case Value::Type::Block:
|
||||
Try(sequential_play(i, Try(i.eval(std::move(v).blk.body))));
|
||||
|
||||
break; case Value::Type::Music:
|
||||
return i.play(v.chord);
|
||||
|
||||
break; default:
|
||||
;
|
||||
}
|
||||
}
|
||||
else if (auto block = get_if<Block>(v)) {
|
||||
Try(sequential_play(i, Try(i.eval(std::move(block->body)))));
|
||||
}
|
||||
else if (auto chord = get_if<Chord>(v)) {
|
||||
return i.play(*chord);
|
||||
}
|
||||
|
||||
return {};
|
||||
@ -291,16 +279,20 @@ static Result<Value> builtin_par(Interpreter &i, std::vector<Value> args) {
|
||||
|
||||
assert(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert)
|
||||
if (args.size() == 1) {
|
||||
Try(i.play(std::move(args.front()).chord));
|
||||
auto chord = get_if<Chord>(args.front());
|
||||
assert(chord, "Par expects music value as first argument"); // TODO(assert)
|
||||
Try(i.play(std::move(*chord)));
|
||||
return Value{};
|
||||
}
|
||||
|
||||
// Create chord that should sustain during playing of all other notes
|
||||
auto &ctx = i.context_stack.back();
|
||||
auto chord = std::move(args.front()).chord;
|
||||
std::for_each(chord.notes.begin(), chord.notes.end(), [&](Note ¬e) { note = ctx.fill(note); });
|
||||
auto chord = get_if<Chord>(args.front());
|
||||
assert(chord, "par expects music value as first argument"); // TODO(assert)
|
||||
|
||||
for (auto const& note : chord.notes) {
|
||||
std::for_each(chord->notes.begin(), chord->notes.end(), [&](Note ¬e) { note = ctx.fill(note); });
|
||||
|
||||
for (auto const& note : chord->notes) {
|
||||
if (note.base) {
|
||||
i.midi_connection->send_note_on(0, *note.into_midi_note(), 127);
|
||||
}
|
||||
@ -308,7 +300,7 @@ static Result<Value> builtin_par(Interpreter &i, std::vector<Value> args) {
|
||||
|
||||
auto result = builtin_play(i, std::span(args).subspan(1));
|
||||
|
||||
for (auto const& note : chord.notes) {
|
||||
for (auto const& note : chord->notes) {
|
||||
if (note.base) {
|
||||
i.midi_connection->send_note_off(0, *note.into_midi_note(), 127);
|
||||
}
|
||||
@ -342,14 +334,14 @@ static Result<Value> builtin_sim(Interpreter &interpreter, std::vector<Value> ar
|
||||
|
||||
std::optional<Error> operator()(std::vector<Chord> &track, Value &arg)
|
||||
{
|
||||
if (arg.type == Value::Type::Music) {
|
||||
track.push_back(std::move(arg).chord);
|
||||
if (auto chord = get_if<Chord>(arg)) {
|
||||
track.push_back(*chord);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (is_indexable(arg.type)) {
|
||||
for (auto i = 0u; i < arg.size(); ++i) {
|
||||
auto value = Try(arg.index(interpreter, i));
|
||||
if (auto collection = get_if<Collection>(arg)) {
|
||||
for (auto i = 0u; i < collection->size(); ++i) {
|
||||
auto value = Try(collection->index(interpreter, i));
|
||||
Try((*this)(track, value));
|
||||
}
|
||||
return {};
|
||||
@ -447,12 +439,10 @@ static inline size_t upper_sieve_bound_to_yield_n_primes(size_t n_primes)
|
||||
/// Generate n primes
|
||||
static Result<Value> builtin_primes(Interpreter&, std::vector<Value> args)
|
||||
{
|
||||
using N = Shape<Value::Type::Number>;
|
||||
|
||||
if (N::typecheck(args)) {
|
||||
if (auto a = match<Number>(args)) {
|
||||
auto [n_frac] = *a;
|
||||
// Better sieve could be Sieve of Atkin, but it's more complicated
|
||||
// so for now we would use Eratosthenes one.
|
||||
auto [n_frac] = N::move_from(args);
|
||||
if (n_frac.simplify_inplace(); n_frac.num <= 1) {
|
||||
return Value::from(Array{});
|
||||
}
|
||||
@ -481,7 +471,7 @@ static Result<Value> builtin_primes(Interpreter&, std::vector<Value> args)
|
||||
results.push_back(Value::from(Number(i)));
|
||||
}
|
||||
}
|
||||
return Value::from(Array { .elements = results });
|
||||
return Value::from(Array(std::move(results)));
|
||||
}
|
||||
|
||||
return Error {
|
||||
@ -503,18 +493,16 @@ static Result<Value> builtin_for(Interpreter &i, std::vector<Value> args)
|
||||
.possibilities = { "(array, callback) -> any" }
|
||||
};
|
||||
|
||||
if (args.size() != 2) {
|
||||
return guard.yield_error();
|
||||
}
|
||||
|
||||
Try(guard(is_indexable, args[0]));
|
||||
Try(guard(is_callable, args[1]));
|
||||
|
||||
if (auto a = match<Collection, Function>(args)) {
|
||||
auto& [collection, func] = *a;
|
||||
Value result{};
|
||||
for (size_t n = 0; n < args[0].size(); ++n) {
|
||||
result = Try(args[1](i, { Try(args[0].index(i, n)) }));
|
||||
for (size_t n = 0; n < collection.size(); ++n) {
|
||||
result = Try(func(i, { Try(collection.index(i, n)) }));
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return guard.yield_error();
|
||||
}
|
||||
}
|
||||
|
||||
/// Fold container
|
||||
@ -543,14 +531,13 @@ static Result<Value> builtin_fold(Interpreter &interpreter, std::vector<Value> a
|
||||
return guard.yield_error();
|
||||
}
|
||||
|
||||
Try(guard(is_indexable, array));
|
||||
Try(guard(is_callable, callback));
|
||||
auto collection = Try(guard.match<Collection>(array));
|
||||
auto function = Try(guard.match<Function>(callback));
|
||||
|
||||
for (auto i = 0u; i < array.size(); ++i) {
|
||||
auto element = Try(array.index(interpreter, i));
|
||||
init = Try(callback(interpreter, { std::move(init), std::move(element) }));
|
||||
for (auto i = 0u; i < collection->size(); ++i) {
|
||||
auto element = Try(collection->index(interpreter, i));
|
||||
init = Try((*function)(interpreter, { std::move(init), std::move(element) }));
|
||||
}
|
||||
|
||||
return init;
|
||||
}
|
||||
|
||||
@ -569,11 +556,11 @@ static Result<Value> builtin_if(Interpreter &i, std::vector<Value> args) {
|
||||
}
|
||||
|
||||
if (args.front().truthy()) {
|
||||
Try(guard(is_callable, args[1]));
|
||||
return args[1](i, {});
|
||||
auto fun = Try(guard.match<Function>(args[1]));
|
||||
return (*fun)(i, {});
|
||||
} else if (args.size() == 3) {
|
||||
Try(guard(is_callable, args[2]));
|
||||
return args[2](i, {});
|
||||
auto fun = Try(guard.match<Function>(args[2]));
|
||||
return (*fun)(i, {});
|
||||
}
|
||||
|
||||
return Value{};
|
||||
@ -590,19 +577,19 @@ static Result<Value> builtin_try(Interpreter &interpreter, std::vector<Value> ar
|
||||
};
|
||||
|
||||
if (args.size() == 1) {
|
||||
Try(guard(is_callable, args[0]));
|
||||
return std::move(args[0])(interpreter, {}).value_or(Value{});
|
||||
auto callable = Try(guard.match<Function>(args[0]));
|
||||
return std::move(*callable)(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, {})) {
|
||||
auto callable = Try(guard.match<Function>(args[i]));
|
||||
if (auto result = std::move(*callable)(interpreter, {})) {
|
||||
success = *std::move(result);
|
||||
} else {
|
||||
Try(guard(is_callable, args.back()));
|
||||
return std::move(args.back())(interpreter, {});
|
||||
auto callable = Try(guard.match<Function>(args.back()));
|
||||
return std::move(*callable)(interpreter, {});
|
||||
}
|
||||
}
|
||||
|
||||
@ -623,17 +610,14 @@ static Result<Value> builtin_update(Interpreter &i, std::vector<Value> args)
|
||||
return guard.yield_error();
|
||||
}
|
||||
|
||||
using Eager_And_Number = Shape<Value::Type::Array, Value::Type::Number>;
|
||||
using Lazy_And_Number = Shape<Value::Type::Block, Value::Type::Number>;
|
||||
|
||||
if (Eager_And_Number::typecheck_front(args)) {
|
||||
auto [v, index] = Eager_And_Number::move_from(args);
|
||||
v.elements[index.as_int()] = std::move(args.back());
|
||||
if (auto a = match<Array, Number, Value>(args)) {
|
||||
auto& [v, index, value] = *a;
|
||||
v.elements[index.as_int()] = std::move(std::move(value));
|
||||
return Value::from(std::move(v));
|
||||
}
|
||||
|
||||
if (Lazy_And_Number::typecheck_front(args)) {
|
||||
auto [v, index] = Lazy_And_Number::move_from(args);
|
||||
if (auto a = match<Block, Number, Value>(args)) {
|
||||
auto& [v, index, value] = *a;
|
||||
auto array = Try(flatten(i, { Value::from(std::move(v)) }));
|
||||
array[index.as_int()] = std::move(args.back());
|
||||
return Value::from(std::move(array));
|
||||
@ -645,17 +629,20 @@ static Result<Value> builtin_update(Interpreter &i, std::vector<Value> args)
|
||||
/// Return typeof variable
|
||||
static Result<Value> builtin_typeof(Interpreter&, std::vector<Value> args)
|
||||
{
|
||||
assert(args.size() == 1, "typeof expects only one argument");
|
||||
return Value::from(std::string(type_name(args.front().type)));
|
||||
assert(args.size() == 1, "typeof expects only one argument"); // TODO(assert)
|
||||
return Value::from(Symbol(type_name(args.front())));
|
||||
}
|
||||
|
||||
/// Return length of container or set/get default length to play
|
||||
static Result<Value> builtin_len(Interpreter &i, std::vector<Value> args)
|
||||
{
|
||||
if (args.size() != 1 || !is_indexable(args.front().type)) {
|
||||
return ctx_read_write_property<&Context::length>(i, std::move(args));
|
||||
if (args.size() == 1) {
|
||||
if (auto coll = get_if<Collection>(args.front())) {
|
||||
return Value::from(Number(coll->size()));
|
||||
}
|
||||
return Value::from(Number(args.front().size()));
|
||||
}
|
||||
// TODO Add overload that tells length of array to error reporting
|
||||
return ctx_read_write_property<&Context::length>(i, std::move(args));
|
||||
}
|
||||
|
||||
/// Join arguments into flat array
|
||||
@ -729,8 +716,7 @@ static Result<Value> builtin_partition(Interpreter &i, std::vector<Value> args)
|
||||
return guard.yield_error();
|
||||
}
|
||||
|
||||
auto predicate = std::move(args.front());
|
||||
Try(guard(is_callable, predicate));
|
||||
auto& predicate = *Try(guard.match<Function>(args.front()));
|
||||
auto array = Try(flatten(i, std::span(args).subspan(1)));
|
||||
|
||||
Array tuple[2] = {};
|
||||
@ -738,7 +724,7 @@ static Result<Value> builtin_partition(Interpreter &i, std::vector<Value> args)
|
||||
tuple[Try(predicate(i, { std::move(value) })).truthy()].elements.push_back(std::move(value));
|
||||
}
|
||||
|
||||
return Value::from(Array { .elements = {
|
||||
return Value::from(Array {{
|
||||
Value::from(std::move(tuple[true])),
|
||||
Value::from(std::move(tuple[false]))
|
||||
}});
|
||||
@ -752,11 +738,10 @@ static Result<Value> builtin_rotate(Interpreter &i, std::vector<Value> args)
|
||||
.possibilities = { "(number, ...array) -> array" }
|
||||
};
|
||||
|
||||
if (args.empty() || args.front().type != Value::Type::Number) {
|
||||
return guard.yield_error();
|
||||
}
|
||||
|
||||
auto offset = std::move(args.front()).n.as_int();
|
||||
if (args.size()) {
|
||||
if (auto const offset_source = get_if<Number>(args.front())) {
|
||||
auto offset = offset_source->as_int();
|
||||
auto array = Try(flatten(i, std::span(args).subspan(1)));
|
||||
if (offset > 0) {
|
||||
offset = offset % array.size();
|
||||
@ -767,6 +752,10 @@ static Result<Value> builtin_rotate(Interpreter &i, std::vector<Value> args)
|
||||
}
|
||||
return Value::from(std::move(array));
|
||||
}
|
||||
}
|
||||
|
||||
return guard.yield_error();
|
||||
}
|
||||
|
||||
/// Returns unique collection of arguments
|
||||
static Result<Value> builtin_unique(Interpreter &i, std::vector<Value> args)
|
||||
@ -821,18 +810,14 @@ static Result<Value> builtin_chord(Interpreter &i, std::vector<Value> args)
|
||||
/// Send MIDI message Note On
|
||||
static Result<Value> builtin_note_on(Interpreter &i, std::vector<Value> args)
|
||||
{
|
||||
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>;
|
||||
|
||||
if (Channel_Note_Velocity::typecheck(args)) {
|
||||
auto [chan, note, vel] = Channel_Note_Velocity::move_from(args);
|
||||
if (auto a = match<Number, Number, Number>(args)) {
|
||||
auto [chan, note, vel] = *a;
|
||||
i.midi_connection->send_note_on(chan.as_int(), note.as_int(), vel.as_int());
|
||||
return Value {};
|
||||
}
|
||||
|
||||
if (Channel_Music_Velocity::typecheck(args)) {
|
||||
auto [chan, chord, vel] = Channel_Music_Velocity::move_from(args);
|
||||
|
||||
if (auto a = match<Number, Chord, Number>(args)) {
|
||||
auto [chan, chord, vel] = *a;
|
||||
for (auto note : chord.notes) {
|
||||
note = i.context_stack.back().fill(note);
|
||||
i.midi_connection->send_note_on(chan.as_int(), *note.into_midi_note(), vel.as_int());
|
||||
@ -855,17 +840,14 @@ static Result<Value> builtin_note_on(Interpreter &i, std::vector<Value> args)
|
||||
/// 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_Music = Shape<Value::Type::Number, Value::Type::Music>;
|
||||
|
||||
if (Channel_Note::typecheck(args)) {
|
||||
auto [chan, note] = Channel_Note::move_from(args);
|
||||
if (auto a = match<Number, Number>(args)) {
|
||||
auto [chan, note] = *a;
|
||||
i.midi_connection->send_note_off(chan.as_int(), note.as_int(), 127);
|
||||
return Value {};
|
||||
}
|
||||
|
||||
if (Channel_Music::typecheck(args)) {
|
||||
auto [chan, chord] = Channel_Music::move_from(args);
|
||||
if (auto a = match<Number, Chord>(args)) {
|
||||
auto& [chan, chord] = *a;
|
||||
|
||||
for (auto note : chord.notes) {
|
||||
note = i.context_stack.back().fill(note);
|
||||
@ -889,19 +871,8 @@ static Result<Value> builtin_note_off(Interpreter &i, std::vector<Value> args)
|
||||
/// 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)) {
|
||||
return Error {
|
||||
.details = errors::Unsupported_Types_For {
|
||||
.type = errors::Unsupported_Types_For::Function,
|
||||
.name = "incoming",
|
||||
.possibilities = { "(symbol, function) -> nil" }
|
||||
},
|
||||
.location = {}
|
||||
};
|
||||
}
|
||||
|
||||
std::string const& symbol = args[0].s;
|
||||
|
||||
if (auto a = match<Symbol, Function>(args)) {
|
||||
auto& [symbol, fun] = *a;
|
||||
if (symbol == "note_on" || symbol == "noteon") {
|
||||
i.callbacks->note_on = std::move(args[1]);
|
||||
} else if (symbol == "note_off" || symbol == "noteoff") {
|
||||
@ -912,6 +883,13 @@ static Result<Value> builtin_incoming(Interpreter &i, std::vector<Value> args)
|
||||
return Value{};
|
||||
}
|
||||
|
||||
return errors::Unsupported_Types_For {
|
||||
.type = errors::Unsupported_Types_For::Function,
|
||||
.name = "incoming",
|
||||
.possibilities = { "(symbol, function) -> nil" }
|
||||
};
|
||||
}
|
||||
|
||||
/// Interleaves arguments
|
||||
static Result<Value> builtin_mix(Interpreter &i, std::vector<Value> args)
|
||||
{
|
||||
@ -919,7 +897,7 @@ static Result<Value> builtin_mix(Interpreter &i, std::vector<Value> args)
|
||||
|
||||
std::unordered_map<std::size_t, std::size_t> indicies;
|
||||
|
||||
size_t awaiting_containers = std::count_if(args.begin(), args.end(), [](Value const& v) { return is_indexable(v.type); });
|
||||
size_t awaiting_containers = std::count_if(args.begin(), args.end(), holds_alternative<Collection>);
|
||||
|
||||
// Algorithm description:
|
||||
// Repeat until all arguments were exhausted:
|
||||
@ -929,12 +907,13 @@ static Result<Value> builtin_mix(Interpreter &i, std::vector<Value> args)
|
||||
// Otherwise append element to the list
|
||||
do {
|
||||
for (size_t idx = 0; idx < args.size(); ++idx) {
|
||||
if (auto &arg = args[idx]; is_indexable(arg.type)) {
|
||||
result.push_back(Try(arg.index(i, indicies[idx]++ % arg.size())));
|
||||
if (indicies[idx] == arg.size())
|
||||
if (auto coll = get_if<Collection>(args[idx])) {
|
||||
result.push_back(Try(coll->index(i, indicies[idx]++ % coll->size())));
|
||||
if (indicies[idx] == coll->size()) {
|
||||
awaiting_containers--;
|
||||
}
|
||||
} else {
|
||||
result.push_back(arg);
|
||||
result.push_back(args[idx]);
|
||||
}
|
||||
}
|
||||
} while (awaiting_containers);
|
||||
@ -952,14 +931,12 @@ static Result<Value> builtin_call(Interpreter &i, std::vector<Value> args)
|
||||
}
|
||||
};
|
||||
|
||||
if (args.size() == 0) {
|
||||
if (args.empty()) {
|
||||
return guard.yield_error();
|
||||
}
|
||||
|
||||
auto callable = args.front();
|
||||
Try(guard(is_callable, callable));
|
||||
auto &callable = *Try(guard.match<Function>(args.front()));
|
||||
args.erase(args.begin());
|
||||
|
||||
return callable(i, std::move(args));
|
||||
}
|
||||
|
||||
|
@ -3,25 +3,29 @@
|
||||
#include <musique/guard.hh>
|
||||
#include <musique/interpreter/interpreter.hh>
|
||||
#include <musique/try.hh>
|
||||
#include <musique/value/typecheck.hh>
|
||||
#include <musique/value/intrinsic.hh>
|
||||
|
||||
/// Intrinsic implementation primitive to ease operation vectorization
|
||||
static Result<Value> vectorize(auto &&operation, Interpreter &interpreter, Value lhs, Value rhs)
|
||||
{
|
||||
auto lhs_coll = get_if<Collection>(lhs);
|
||||
auto rhs_coll = get_if<Collection>(rhs);
|
||||
|
||||
if (is_indexable(lhs.type) && !is_indexable(rhs.type)) {
|
||||
if (lhs_coll != nullptr && rhs_coll == nullptr) {
|
||||
Array array;
|
||||
for (auto i = 0u; i < lhs.size(); ++i) {
|
||||
for (auto i = 0u; i < lhs_coll->size(); ++i) {
|
||||
array.elements.push_back(
|
||||
Try(operation(interpreter, { Try(lhs.index(interpreter, i)), rhs })));
|
||||
Try(operation(interpreter, { Try(lhs_coll->index(interpreter, i)), rhs })));
|
||||
}
|
||||
return Value::from(std::move(array));
|
||||
}
|
||||
|
||||
assert(rhs_coll != nullptr, "Trying to vectorize two non-collections");
|
||||
|
||||
Array array;
|
||||
for (auto i = 0u; i < rhs.size(); ++i) {
|
||||
for (auto i = 0u; i < rhs_coll->size(); ++i) {
|
||||
array.elements.push_back(
|
||||
Try(operation(interpreter, { lhs, Try(rhs.index(interpreter, i)) })));
|
||||
Try(operation(interpreter, { lhs, Try(rhs_coll->index(interpreter, i)) })));
|
||||
}
|
||||
return Value::from(std::move(array));
|
||||
}
|
||||
@ -37,30 +41,13 @@ static Result<Value> vectorize(auto &&operation, Interpreter &interpreter, std::
|
||||
/// Helper simlifiing implementation of symetric binary operations.
|
||||
///
|
||||
/// Calls binary if values matches types any permutation of {t1, t2}, always in shape (t1, t2)
|
||||
inline std::optional<Value> symetric(Value::Type t1, Value::Type t2, Value &lhs, Value &rhs, auto binary)
|
||||
template<typename T1, typename T2>
|
||||
inline std::optional<Value> symetric(Value &lhs, Value &rhs, auto binary)
|
||||
{
|
||||
if (lhs.type == t1 && rhs.type == t2) {
|
||||
return binary(std::move(lhs), std::move(rhs));
|
||||
} else if (lhs.type == t2 && rhs.type == t1) {
|
||||
return binary(std::move(rhs), std::move(lhs));
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper simlifiing implementation of symetric binary operations.
|
||||
///
|
||||
/// Calls binary if values matches predicates in any permutation; always with shape (p1, p2)
|
||||
inline auto symetric(
|
||||
std::predicate<Value::Type> auto&& p1,
|
||||
std::predicate<Value::Type> auto&& p2,
|
||||
Value &lhs, Value &rhs,
|
||||
auto binary) -> std::optional<decltype(binary(std::move(lhs), std::move(rhs)))>
|
||||
{
|
||||
if (p1(lhs.type) && p2(rhs.type)) {
|
||||
return binary(std::move(lhs), std::move(rhs));
|
||||
} else if (p2(lhs.type) && p1(rhs.type)) {
|
||||
return binary(std::move(rhs), std::move(lhs));
|
||||
if (auto a = match<T1, T2>(lhs, rhs)) {
|
||||
return std::apply(std::move(binary), *a);
|
||||
} else if (auto a = match<T1, T2>(rhs, lhs)) {
|
||||
return std::apply(std::move(binary), *a);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
@ -79,24 +66,24 @@ static Result<Value> plus_minus_operator(Interpreter &interpreter, std::vector<V
|
||||
|
||||
Value init = args.front();
|
||||
return algo::fold(std::span(args).subspan(1), std::move(init), [&interpreter](Value lhs, Value &rhs) -> Result<Value> {
|
||||
if (lhs.type == Value::Type::Number && rhs.type == Value::Type::Number) {
|
||||
return Value::from(Binary_Operation{}(std::move(lhs).n, std::move(rhs).n));
|
||||
if (auto a = match<Number, Number>(lhs, rhs)) {
|
||||
return Value::from(std::apply(Binary_Operation{}, *a));
|
||||
}
|
||||
|
||||
auto result = symetric(Value::Type::Music, Value::Type::Number, lhs, rhs, [](Value lhs, Value rhs) {
|
||||
for (auto ¬e : lhs.chord.notes) {
|
||||
auto result = symetric<Chord, Number>(lhs, rhs, [](Chord &lhs, Number rhs) {
|
||||
for (auto ¬e : lhs.notes) {
|
||||
if (note.base) {
|
||||
*note.base = Binary_Operation{}(*note.base, rhs.n.as_int());
|
||||
*note.base = Binary_Operation{}(*note.base, rhs.as_int());
|
||||
note.simplify_inplace();
|
||||
}
|
||||
}
|
||||
return lhs;
|
||||
return Value::from(lhs);
|
||||
});
|
||||
if (result.has_value()) {
|
||||
return *std::move(result);
|
||||
}
|
||||
|
||||
if (is_indexable(lhs.type) != is_indexable(rhs.type)) {
|
||||
if (holds_alternative<Collection>(lhs) != holds_alternative<Collection>(rhs)) {
|
||||
return vectorize(plus_minus_operator<Binary_Operation>, interpreter, std::move(lhs), std::move(rhs));
|
||||
}
|
||||
|
||||
@ -128,11 +115,11 @@ static Result<Value> binary_operator(Interpreter& interpreter, std::vector<Value
|
||||
auto init = std::move(args.front());
|
||||
return algo::fold(std::span(args).subspan(1), std::move(init),
|
||||
[&interpreter](Value lhs, Value &rhs) -> Result<Value> {
|
||||
if (lhs.type == Value::Type::Number && rhs.type == Value::Type::Number) {
|
||||
return wrap_value(Binary_Operation{}(std::move(lhs).n, std::move(rhs).n));
|
||||
if (auto a = match<Number, Number>(lhs, rhs)) {
|
||||
return wrap_value(std::apply(Binary_Operation{}, *a));
|
||||
}
|
||||
|
||||
if (is_indexable(lhs.type) != is_indexable(rhs.type)) {
|
||||
if (holds_alternative<Collection>(lhs) != holds_alternative<Collection>(rhs)) {
|
||||
return vectorize(binary_operator<Binary_Operation, Chars...>, interpreter, std::move(lhs), std::move(rhs));
|
||||
}
|
||||
|
||||
@ -156,24 +143,26 @@ static Result<Value> comparison_operator(Interpreter &interpreter, std::vector<V
|
||||
return Value::from(algo::pairwise_all(std::move(args), Binary_Predicate{}));
|
||||
}
|
||||
|
||||
auto result = symetric(is_indexable, std::not_fn(is_indexable), args.front(), args.back(), [&interpreter](Value lhs, Value rhs) -> Result<Value> {
|
||||
auto lhs_coll = get_if<Collection>(args.front());
|
||||
auto rhs_coll = get_if<Collection>(args.back());
|
||||
|
||||
if (bool(lhs_coll) != bool(rhs_coll)) {
|
||||
auto coll = lhs_coll ? lhs_coll : rhs_coll;
|
||||
auto element = lhs_coll ? &args.back() : &args.front();
|
||||
|
||||
std::vector<Value> result;
|
||||
result.reserve(lhs.size());
|
||||
for (auto i = 0u; i < lhs.size(); ++i) {
|
||||
result.reserve(coll->size());
|
||||
for (auto i = 0u; i < coll->size(); ++i) {
|
||||
result.push_back(
|
||||
Value::from(
|
||||
Binary_Predicate{}(
|
||||
Try(lhs.index(interpreter, i)),
|
||||
rhs
|
||||
Try(coll->index(interpreter, i)),
|
||||
*element
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return Value::from(Array { std::move(result) });
|
||||
});
|
||||
|
||||
if (result.has_value()) {
|
||||
return *std::move(result);
|
||||
}
|
||||
|
||||
return Value::from(Binary_Predicate{}(std::move(args.front()), std::move(args.back())));
|
||||
@ -188,10 +177,8 @@ static Result<Value> multiplication_operator(Interpreter &i, std::vector<Value>
|
||||
auto init = std::move(args.front());
|
||||
return algo::fold(std::span(args).subspan(1), std::move(init), [&i](Value lhs, Value &rhs) -> Result<Value> {
|
||||
{
|
||||
auto result = symetric(Value::Type::Number, Value::Type::Music, lhs, rhs, [](Value lhs, Value rhs) {
|
||||
return Value::from(Array {
|
||||
.elements = std::vector<Value>(lhs.n.floor().as_int(), std::move(rhs))
|
||||
});
|
||||
auto result = symetric<Number, Chord>(lhs, rhs, [](Number lhs, Chord &rhs) {
|
||||
return Value::from(Array { std::vector<Value>(lhs.floor().as_int(), Value::from(std::move(rhs))) });
|
||||
});
|
||||
|
||||
if (result.has_value()) {
|
||||
@ -214,7 +201,7 @@ static Result<Value> multiplication_operator(Interpreter &i, std::vector<Value>
|
||||
});
|
||||
}
|
||||
|
||||
using Operator_Entry = std::tuple<char const*, Intrinsic>;
|
||||
using Operator_Entry = std::tuple<char const*, Intrinsic::Function_Pointer>;
|
||||
|
||||
using power = decltype([](Number lhs, Number rhs) -> Result<Number> {
|
||||
return lhs.pow(rhs);
|
||||
@ -237,32 +224,33 @@ static constexpr auto Operators = std::array {
|
||||
Operator_Entry { ">=", comparison_operator<std::greater_equal<>> },
|
||||
|
||||
Operator_Entry { ".",
|
||||
+[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
|
||||
if (args.size() == 2 && is_indexable(args[0].type)) {
|
||||
if (args[1].type == Value::Type::Number) {
|
||||
return std::move(args.front()).index(i, std::move(args.back()).n.as_int());
|
||||
+[](Interpreter &interpreter, std::vector<Value> args) -> Result<Value> {
|
||||
if (auto a = match<Collection, Number>(args)) {
|
||||
auto& [coll, pos] = *a;
|
||||
return coll.index(interpreter, pos.as_int());
|
||||
}
|
||||
|
||||
if (args[1].type == Value::Type::Bool) {
|
||||
return std::move(args.front()).index(i, args.back().b ? 1 : 0);
|
||||
if (auto a = match<Collection, Bool>(args)) {
|
||||
auto& [coll, pos] = *a;
|
||||
return coll.index(interpreter, pos ? 1 : 0);
|
||||
}
|
||||
if (auto a = match<Collection, Collection>(args)) {
|
||||
auto& [source, positions] = *a;
|
||||
|
||||
if (is_indexable(args[1].type)) {
|
||||
std::vector<Value> result;
|
||||
for (size_t n = 0; n < args[1].size(); ++n) {
|
||||
auto const v = Try(args[1].index(i, n));
|
||||
switch (v.type) {
|
||||
break; case Value::Type::Number:
|
||||
result.push_back(Try(args[0].index(i, v.n.as_int())));
|
||||
for (size_t n = 0; n < positions.size(); ++n) {
|
||||
auto const v = Try(positions.index(interpreter, n));
|
||||
|
||||
break; case Value::Type::Bool: default:
|
||||
if (v.truthy()) {
|
||||
result.push_back(Try(args[0].index(i, n)));
|
||||
auto index = std::visit(Overloaded {
|
||||
[](Number n) -> std::optional<size_t> { return n.floor().as_int(); },
|
||||
[](Bool b) -> std::optional<size_t> { return b ? 1 : 0; },
|
||||
[](auto &&) -> std::optional<size_t> { return std::nullopt; }
|
||||
}, v.data);
|
||||
|
||||
if (index) {
|
||||
result.push_back(Try(source.index(interpreter, *index)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Value::from(Array { result });
|
||||
}
|
||||
return Value::from(Array(std::move(result)));
|
||||
}
|
||||
|
||||
return Error {
|
||||
@ -290,11 +278,13 @@ static constexpr auto Operators = std::array {
|
||||
.type = errors::Unsupported_Types_For::Operator
|
||||
};
|
||||
|
||||
using Chord_Chord = Shape<Value::Type::Music, Value::Type::Music>;
|
||||
if (Chord_Chord::typecheck(args)) {
|
||||
auto [lhs, rhs] = Chord_Chord::move_from(args);
|
||||
auto &l = lhs.notes;
|
||||
auto &r = rhs.notes;
|
||||
if (auto a = match<Chord, Chord>(args)) {
|
||||
auto [lhs, rhs] = *a;
|
||||
auto &l = lhs.notes, &r = rhs.notes;
|
||||
if (l.size() < r.size()) {
|
||||
std::swap(l, r);
|
||||
std::swap(lhs, rhs);
|
||||
}
|
||||
|
||||
// Append one set of notes to another to make bigger chord!
|
||||
l.reserve(l.size() + r.size());
|
||||
@ -304,8 +294,8 @@ static constexpr auto Operators = std::array {
|
||||
}
|
||||
|
||||
auto result = Array {};
|
||||
for (auto&& array : args) {
|
||||
Try(guard(is_indexable, array));
|
||||
for (auto&& a : args) {
|
||||
auto &array = *Try(guard.match<Collection>(a));
|
||||
for (auto n = 0u; n < array.size(); ++n) {
|
||||
result.elements.push_back(Try(array.index(i, n)));
|
||||
}
|
||||
|
@ -31,11 +31,11 @@ struct Interpreter::Incoming_Midi_Callbacks
|
||||
// in our own note abstraction, not as numbers.
|
||||
target = [interpreter = &i, callback = &callback](T ...source_args)
|
||||
{
|
||||
if (callback->type != Value::Type::Nil) {
|
||||
if (!std::holds_alternative<Nil>(callback->data)) {
|
||||
std::vector<Value> args { Value::from(Number(source_args))... };
|
||||
args[1] = Value::from(Chord { .notes { Note {
|
||||
.base = i32(args[1].n.num % 12),
|
||||
.octave = args[1].n.num / 12
|
||||
args[1] = Value::from(Chord { { Note {
|
||||
.base = i32(std::get<Number>(args[1].data).num % 12),
|
||||
.octave = std::get<Number>(args[1].data).num / 12
|
||||
}}});
|
||||
auto result = (*callback)(*interpreter, std::move(args));
|
||||
// We discard this since callback is running in another thread.
|
||||
@ -46,7 +46,7 @@ struct Interpreter::Incoming_Midi_Callbacks
|
||||
// Generic case, preserve all passed parameters as numbers
|
||||
target = [interpreter = &i, callback = &callback](T ...source_args)
|
||||
{
|
||||
if (callback->type != Value::Type::Nil) {
|
||||
if (!std::holds_alternative<Nil>(callback->data)) {
|
||||
auto result = (*callback)(*interpreter, { Value::from(Number(source_args))... });
|
||||
// We discard this since callback is running in another thread.
|
||||
(void) result;
|
||||
|
@ -135,7 +135,7 @@ struct Runner
|
||||
|
||||
Env::global->force_define("say", +[](Interpreter &interpreter, std::vector<Value> args) -> Result<Value> {
|
||||
for (auto it = args.begin(); it != args.end(); ++it) {
|
||||
Try(format(interpreter, *it));
|
||||
std::cout << Try(format(interpreter, *it));
|
||||
if (std::next(it) != args.end())
|
||||
std::cout << ' ';
|
||||
}
|
||||
@ -168,7 +168,7 @@ struct Runner
|
||||
dump(ast);
|
||||
return {};
|
||||
}
|
||||
if (auto result = Try(interpreter.eval(std::move(ast))); output && result.type != Value::Type::Nil) {
|
||||
if (auto result = Try(interpreter.eval(std::move(ast))); output && not holds_alternative<Nil>(result)) {
|
||||
std::cout << Try(format(interpreter, result)) << std::endl;
|
||||
}
|
||||
return {};
|
||||
@ -193,7 +193,7 @@ void completion(char const* buf, bestlineCompletions *lc)
|
||||
}
|
||||
|
||||
/// Fancy main that supports Result forwarding on error (Try macro)
|
||||
static Result<void> Main(std::span<char const*> args)
|
||||
static std::optional<Error> Main(std::span<char const*> args)
|
||||
{
|
||||
if (isatty(STDOUT_FILENO) && getenv("NO_COLOR") == nullptr) {
|
||||
pretty::terminal_mode();
|
||||
@ -350,8 +350,8 @@ int main(int argc, char const** argv)
|
||||
{
|
||||
auto const args = std::span(argv, argc).subspan(1);
|
||||
auto const result = Main(args);
|
||||
if (not result.has_value()) {
|
||||
std::cerr << result.error() << std::flush;
|
||||
if (result.has_value()) {
|
||||
std::cerr << result.value() << std::flush;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ struct Range_Table
|
||||
usize latin_offset;
|
||||
};
|
||||
|
||||
constexpr auto Letter = Range_Table {
|
||||
std::array {
|
||||
constexpr auto Letter = Range_Table<359, 227> {
|
||||
{
|
||||
R16 {0x0041, 0x005a, 1},
|
||||
R16 {0x0061, 0x007a, 1},
|
||||
R16 {0x00aa, 0x00b5, 11},
|
||||
|
40
musique/value/array.cc
Normal file
40
musique/value/array.cc
Normal file
@ -0,0 +1,40 @@
|
||||
#include <musique/value/array.hh>
|
||||
#include <musique/value/value.hh>
|
||||
|
||||
Array::Array() = default;
|
||||
Array::Array(Array const&) = default;
|
||||
Array::Array(Array &&) = default;
|
||||
Array::~Array() = default;
|
||||
|
||||
Array::Array(std::vector<Value>&& elements)
|
||||
: elements{std::move(elements)}
|
||||
{
|
||||
}
|
||||
|
||||
Result<Value> Array::index(Interpreter&, unsigned position) const
|
||||
{
|
||||
if (elements.size() < position) {
|
||||
return errors::Out_Of_Range {
|
||||
.required_index = position,
|
||||
.size = elements.size()
|
||||
};
|
||||
}
|
||||
return elements[position];
|
||||
}
|
||||
|
||||
usize Array::size() const
|
||||
{
|
||||
return elements.size();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Array const& v)
|
||||
{
|
||||
os << '[';
|
||||
for (auto it = v.elements.begin(); it != v.elements.end(); ++it) {
|
||||
os << *it;
|
||||
if (std::next(it) != v.elements.end()) {
|
||||
os << "; ";
|
||||
}
|
||||
}
|
||||
return os << ']';
|
||||
}
|
@ -1,27 +1,40 @@
|
||||
#ifndef MUSIQUE_ARRAY_HH
|
||||
#define MUSIQUE_ARRAY_HH
|
||||
|
||||
#include <vector>
|
||||
#include <musique/result.hh>
|
||||
#include <musique/value/collection.hh>
|
||||
#include <vector>
|
||||
|
||||
struct Interpreter;
|
||||
struct Value;
|
||||
|
||||
/// Eager Array
|
||||
struct Array
|
||||
struct Array : Collection
|
||||
{
|
||||
/// Elements that are stored in array
|
||||
std::vector<Value> elements;
|
||||
|
||||
Array();
|
||||
Array(std::vector<Value>&&);
|
||||
Array(Array const&);
|
||||
Array(Array &&);
|
||||
~Array() override;
|
||||
|
||||
Array& operator=(Array const&) = default;
|
||||
Array& operator=(Array &&) = default;
|
||||
|
||||
/// Index element of an array
|
||||
Result<Value> index(Interpreter &i, unsigned position) const;
|
||||
Result<Value> index(Interpreter &i, unsigned position) const override;
|
||||
|
||||
/// Count of elements
|
||||
usize size() const;
|
||||
usize size() const override;
|
||||
|
||||
/// Arrays are equal if all of their elements are equal
|
||||
bool operator==(Array const&) const = default;
|
||||
|
||||
/// Print array
|
||||
friend std::ostream& operator<<(std::ostream& os, Array const& v);
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Array const& v);
|
||||
|
||||
#endif
|
||||
|
59
musique/value/block.cc
Normal file
59
musique/value/block.cc
Normal file
@ -0,0 +1,59 @@
|
||||
#include <musique/interpreter/env.hh>
|
||||
#include <musique/interpreter/interpreter.hh>
|
||||
#include <musique/try.hh>
|
||||
#include <musique/value/block.hh>
|
||||
#include <musique/value/value.hh>
|
||||
|
||||
/// Helper that produces error when trying to access container with too few elements for given index
|
||||
static inline std::optional<Error> guard_index(unsigned index, unsigned size)
|
||||
{
|
||||
if (index < size) return {};
|
||||
return Error {
|
||||
.details = errors::Out_Of_Range { .required_index = index, .size = size }
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Add memoization
|
||||
Result<Value> Block::index(Interpreter &i, unsigned position) const
|
||||
{
|
||||
assert(parameters.size() == 0, "cannot index into block with parameters (for now)");
|
||||
if (body.type != Ast::Type::Sequence) {
|
||||
Try(guard_index(position, 1));
|
||||
return i.eval((Ast)body);
|
||||
}
|
||||
|
||||
Try(guard_index(position, body.arguments.size()));
|
||||
return i.eval((Ast)body.arguments[position]);
|
||||
}
|
||||
|
||||
usize Block::size() const
|
||||
{
|
||||
return body.type == Ast::Type::Sequence ? body.arguments.size() : 1;
|
||||
}
|
||||
|
||||
Result<Value> Block::operator()(Interpreter &i, std::vector<Value> arguments) const
|
||||
{
|
||||
auto old_scope = std::exchange(i.env, context);
|
||||
i.enter_scope();
|
||||
|
||||
if (parameters.size() != arguments.size()) {
|
||||
return errors::Wrong_Arity_Of {
|
||||
.type = errors::Wrong_Arity_Of::Function,
|
||||
// TODO Let user defined functions have name of their first assigment (Zig like)
|
||||
// or from place of definition like <block at file:line:column>
|
||||
.name = "<block>",
|
||||
.expected_arity = parameters.size(),
|
||||
.actual_arity = arguments.size(),
|
||||
};
|
||||
}
|
||||
|
||||
for (usize j = 0; j < parameters.size(); ++j) {
|
||||
i.env->force_define(parameters[j], std::move(arguments[j]));
|
||||
}
|
||||
|
||||
Ast body_copy = body;
|
||||
auto result = i.eval(std::move(body_copy));
|
||||
|
||||
i.env = old_scope;
|
||||
return result;
|
||||
}
|
@ -1,20 +1,21 @@
|
||||
#ifndef MUSIQUE_BLOCK_HH
|
||||
#define MUSIQUE_BLOCK_HH
|
||||
|
||||
#include <musique/result.hh>
|
||||
#include <musique/parser/ast.hh>
|
||||
|
||||
#include <memory>
|
||||
#include <musique/parser/ast.hh>
|
||||
#include <musique/result.hh>
|
||||
#include <musique/value/collection.hh>
|
||||
#include <musique/value/function.hh>
|
||||
|
||||
struct Env;
|
||||
struct Interpreter;
|
||||
struct Value;
|
||||
|
||||
using Intrinsic = Result<Value>(*)(Interpreter &i, std::vector<Value>);
|
||||
|
||||
/// Lazy Array / Continuation / Closure type thingy
|
||||
struct Block
|
||||
struct Block : Collection, Function
|
||||
{
|
||||
~Block() override = default;
|
||||
|
||||
/// Location of definition / creation
|
||||
Location location;
|
||||
|
||||
@ -28,13 +29,13 @@ struct Block
|
||||
std::shared_ptr<Env> context;
|
||||
|
||||
/// Calling block
|
||||
Result<Value> operator()(Interpreter &i, std::vector<Value> params);
|
||||
Result<Value> operator()(Interpreter &i, std::vector<Value> params) const override;
|
||||
|
||||
/// Indexing block
|
||||
Result<Value> index(Interpreter &i, unsigned position) const;
|
||||
Result<Value> index(Interpreter &i, unsigned position) const override;
|
||||
|
||||
/// Count of elements in block
|
||||
usize size() const;
|
||||
usize size() const override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
141
musique/value/chord.cc
Normal file
141
musique/value/chord.cc
Normal file
@ -0,0 +1,141 @@
|
||||
#include <musique/accessors.hh>
|
||||
#include <musique/guard.hh>
|
||||
#include <musique/try.hh>
|
||||
#include <musique/value/chord.hh>
|
||||
#include <musique/value/value.hh>
|
||||
|
||||
Chord::Chord(Note note)
|
||||
: notes{ note }
|
||||
{
|
||||
}
|
||||
|
||||
Chord::Chord(std::vector<Note> &¬es)
|
||||
: notes{std::move(notes)}
|
||||
{
|
||||
}
|
||||
|
||||
Chord Chord::from(std::string_view source)
|
||||
{
|
||||
auto note = Note::from(source);
|
||||
assert(note.has_value(), "don't know how this could happen");
|
||||
|
||||
Chord chord;
|
||||
source.remove_prefix(1 + (source[1] == '#'));
|
||||
chord.notes.push_back(*std::move(note));
|
||||
|
||||
if (note->base) {
|
||||
for (char digit : source) {
|
||||
chord.notes.push_back(Note { .base = note->base.value() + i32(digit - '0') });
|
||||
}
|
||||
}
|
||||
|
||||
return chord;
|
||||
}
|
||||
|
||||
Result<Value> Chord::operator()(Interpreter& interpreter, std::vector<Value> args) const
|
||||
{
|
||||
std::vector<Value> array;
|
||||
std::vector<Chord> current = { *this };
|
||||
|
||||
enum State {
|
||||
Waiting_For_Octave,
|
||||
Waiting_For_Length,
|
||||
Waiting_For_Note
|
||||
} state = Waiting_For_Octave;
|
||||
|
||||
static constexpr auto guard = Guard<1> {
|
||||
.name = "note creation",
|
||||
.possibilities = {
|
||||
"(note:music [octave:number [duration:number]])+"
|
||||
}
|
||||
};
|
||||
|
||||
auto const next = [&state] {
|
||||
switch (state) {
|
||||
break; case Waiting_For_Length: state = Waiting_For_Note;
|
||||
break; case Waiting_For_Note: state = Waiting_For_Octave;
|
||||
break; case Waiting_For_Octave: state = Waiting_For_Length;
|
||||
}
|
||||
};
|
||||
|
||||
auto const update = [&state](Chord &chord, Number number) -> std::optional<Error> {
|
||||
auto const resolve = [&chord](auto field, auto new_value) {
|
||||
for (auto ¬e : chord.notes) {
|
||||
(note.*field) = new_value;
|
||||
}
|
||||
};
|
||||
|
||||
switch (state) {
|
||||
break; case Waiting_For_Octave:
|
||||
resolve(&Note::octave, number.floor().as_int());
|
||||
return {};
|
||||
|
||||
break; case Waiting_For_Length:
|
||||
resolve(&Note::length, number);
|
||||
return {};
|
||||
|
||||
default:
|
||||
return guard.yield_error();
|
||||
}
|
||||
};
|
||||
|
||||
for (auto &arg : args) {
|
||||
if (auto collection = get_if<Collection>(arg)) {
|
||||
if (state != Waiting_For_Length && state != Waiting_For_Octave) {
|
||||
return guard.yield_error();
|
||||
}
|
||||
|
||||
auto const ring_size = current.size();
|
||||
for (usize i = 0; i < arg.size() && current.size() < arg.size(); ++i) {
|
||||
current.push_back(current[i % ring_size]);
|
||||
}
|
||||
|
||||
for (usize i = 0; i < current.size(); ++i) {
|
||||
Value value = Try(collection->index(interpreter, i % collection->size()));
|
||||
if (auto number = get_if<Number>(value)) {
|
||||
Try(update(current[i], *number));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
next();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto number = get_if<Number>(arg)) {
|
||||
for (auto &chord : current) {
|
||||
Try(update(chord, *number));
|
||||
}
|
||||
next();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto chord = get_if<Chord>(arg)) {
|
||||
std::transform(current.begin(), current.end(), std::back_inserter(array),
|
||||
[](Chord &c) { return Value::from(std::move(c)); });
|
||||
current.clear();
|
||||
current.push_back(std::move(*chord));
|
||||
state = Waiting_For_Octave;
|
||||
}
|
||||
}
|
||||
|
||||
std::transform(current.begin(), current.end(), std::back_inserter(array),
|
||||
[](Chord &c) { return Value::from(std::move(c)); });
|
||||
|
||||
assert(not array.empty(), "At least *this should be in this array");
|
||||
return Value::from(Array{std::move(array)});
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Chord const& chord)
|
||||
{
|
||||
if (chord.notes.size() == 1) {
|
||||
return os << chord.notes.front();
|
||||
}
|
||||
|
||||
os << "chord[";
|
||||
for (auto it = chord.notes.begin(); it != chord.notes.end(); ++it) {
|
||||
os << *it;
|
||||
if (std::next(it) != chord.notes.end())
|
||||
os << "; ";
|
||||
}
|
||||
return os << ']';
|
||||
}
|
@ -1,22 +1,32 @@
|
||||
#ifndef MUSIQUE_VALUE_CHORD_HH
|
||||
#define MUSIQUE_VALUE_CHORD_HH
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <musique/value/note.hh>
|
||||
#include <musique/value/function.hh>
|
||||
|
||||
struct Interpreter;
|
||||
struct Value;
|
||||
|
||||
/// Represantation of simultaneously played notes, aka musical chord
|
||||
struct Chord
|
||||
struct Chord : Function
|
||||
{
|
||||
std::vector<Note> notes; ///< Notes composing a chord
|
||||
|
||||
Chord() = default;
|
||||
explicit Chord(Note note);
|
||||
explicit Chord(std::vector<Note> &¬es);
|
||||
|
||||
/// Parse chord literal from provided source
|
||||
static Chord from(std::string_view source);
|
||||
|
||||
bool operator==(Chord const&) const = default;
|
||||
|
||||
/// Fill length and octave or sequence multiple chords
|
||||
Result<Value> operator()(Interpreter &i, std::vector<Value> args);
|
||||
Result<Value> operator()(Interpreter &i, std::vector<Value> args) const override;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Chord const& chord);
|
||||
|
||||
#endif // MUSIQUE_VALUE_CHORD_HH
|
||||
|
23
musique/value/collection.hh
Normal file
23
musique/value/collection.hh
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef MUSIQUE_VALUE_COLLECTION_HH
|
||||
#define MUSIQUE_VALUE_COLLECTION_HH
|
||||
|
||||
#include <musique/result.hh>
|
||||
|
||||
struct Interpreter;
|
||||
struct Value;
|
||||
|
||||
/// Abstraction of Collection
|
||||
struct Collection
|
||||
{
|
||||
virtual ~Collection() = default;
|
||||
|
||||
/// Return element at position inside collection
|
||||
virtual Result<Value> index(Interpreter &i, unsigned position) const = 0;
|
||||
|
||||
/// Return elements count
|
||||
virtual usize size() const = 0;
|
||||
|
||||
bool operator==(Collection const&) const = default;
|
||||
};
|
||||
|
||||
#endif // MUSIQUE_VALUE_COLLECTION_HH
|
17
musique/value/function.hh
Normal file
17
musique/value/function.hh
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef MUSIQUE_VALUE_FUNCTION_HH
|
||||
#define MUSIQUE_VALUE_FUNCTION_HH
|
||||
|
||||
#include <musique/result.hh>
|
||||
|
||||
struct Value;
|
||||
struct Interpreter;
|
||||
|
||||
struct Function
|
||||
{
|
||||
constexpr virtual ~Function() = default;
|
||||
virtual Result<Value> operator()(Interpreter &i, std::vector<Value> params) const = 0;
|
||||
|
||||
constexpr bool operator==(Function const&) const = default;
|
||||
};
|
||||
|
||||
#endif // MUSIQUE_VALUE_FUNCTION_HH
|
7
musique/value/intrinsic.cc
Normal file
7
musique/value/intrinsic.cc
Normal file
@ -0,0 +1,7 @@
|
||||
#include <musique/value/intrinsic.hh>
|
||||
#include <musique/value/value.hh>
|
||||
|
||||
Result<Value> Intrinsic::operator()(Interpreter& interpreter, std::vector<Value> args) const
|
||||
{
|
||||
return function_pointer(interpreter, std::move(args));
|
||||
}
|
31
musique/value/intrinsic.hh
Normal file
31
musique/value/intrinsic.hh
Normal file
@ -0,0 +1,31 @@
|
||||
#ifndef MUSIQUE_VALUE_INTRINSIC_HH
|
||||
#define MUSIQUE_VALUE_INTRINSIC_HH
|
||||
|
||||
#include <musique/result.hh>
|
||||
#include <musique/value/function.hh>
|
||||
|
||||
struct Interpreter;
|
||||
struct Value;
|
||||
|
||||
struct Intrinsic : Function
|
||||
{
|
||||
using Function_Pointer = Result<Value>(*)(Interpreter &i, std::vector<Value>);
|
||||
Function_Pointer function_pointer = nullptr;
|
||||
|
||||
constexpr Intrinsic() = default;
|
||||
|
||||
constexpr Intrinsic(Function_Pointer fp)
|
||||
: function_pointer{fp}
|
||||
{
|
||||
}
|
||||
|
||||
constexpr ~Intrinsic() = default;
|
||||
|
||||
/// Calls underlying function pointer
|
||||
Result<Value> operator()(Interpreter&, std::vector<Value>) const;
|
||||
|
||||
/// Compares if function pointers are equal
|
||||
bool operator==(Intrinsic const&) const = default;
|
||||
};
|
||||
|
||||
#endif // MUSIQUE_VALUE_INTRINSIC_HH
|
129
musique/value/note.cc
Normal file
129
musique/value/note.cc
Normal file
@ -0,0 +1,129 @@
|
||||
#include <musique/value/note.hh>
|
||||
|
||||
/// Finds numeric value of note. This form is later used as in
|
||||
/// note to midi resolution in formula octave * 12 + note_index
|
||||
constexpr u8 note_index(u8 note)
|
||||
{
|
||||
switch (note) {
|
||||
case 'c': return 0;
|
||||
case 'd': return 2;
|
||||
case 'e': return 4;
|
||||
case 'f': return 5;
|
||||
case 'g': return 7;
|
||||
case 'a': return 9;
|
||||
case 'h': return 11;
|
||||
case 'b': return 11;
|
||||
}
|
||||
// Parser should limit range of characters that is called with this function
|
||||
unreachable();
|
||||
}
|
||||
|
||||
constexpr std::string_view note_index_to_string(int note_index)
|
||||
{
|
||||
note_index %= 12;
|
||||
if (note_index < 0) {
|
||||
note_index = 12 + note_index;
|
||||
}
|
||||
|
||||
switch (note_index) {
|
||||
case 0: return "c";
|
||||
case 1: return "c#";
|
||||
case 2: return "d";
|
||||
case 3: return "d#";
|
||||
case 4: return "e";
|
||||
case 5: return "f";
|
||||
case 6: return "f#";
|
||||
case 7: return "g";
|
||||
case 8: return "g#";
|
||||
case 9: return "a";
|
||||
case 10: return "a#";
|
||||
case 11: return "b";
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
std::optional<Note> Note::from(std::string_view literal)
|
||||
{
|
||||
if (literal.starts_with('p')) {
|
||||
return Note {};
|
||||
}
|
||||
|
||||
if (auto const base = note_index(literal[0]); base != u8(-1)) {
|
||||
Note note { .base = base };
|
||||
|
||||
while (literal.remove_prefix(1), not literal.empty()) {
|
||||
switch (literal.front()) {
|
||||
case '#': case 's': ++*note.base; break;
|
||||
case 'b': case 'f': --*note.base; break;
|
||||
default: return note;
|
||||
}
|
||||
}
|
||||
return note;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<u8> Note::into_midi_note() const
|
||||
{
|
||||
return octave ? std::optional(into_midi_note(0)) : std::nullopt;
|
||||
}
|
||||
|
||||
u8 Note::into_midi_note(i8 default_octave) const
|
||||
{
|
||||
assert(bool(this->base), "Pause don't translate into MIDI");
|
||||
auto const octave = this->octave.has_value() ? *this->octave : default_octave;
|
||||
// octave is in range [-1, 9] where Note { .base = 0, .octave = -1 } is midi note 0
|
||||
return (octave + 1) * 12 + *base;
|
||||
}
|
||||
|
||||
void Note::simplify_inplace()
|
||||
{
|
||||
if (base && octave) {
|
||||
octave = std::clamp(*octave + int(*base / 12), -1, 9);
|
||||
if ((*base %= 12) < 0) {
|
||||
base = 12 + *base;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::partial_ordering Note::operator<=>(Note const& rhs) const
|
||||
{
|
||||
if (base.has_value() == rhs.base.has_value()) {
|
||||
if (!base.has_value()) {
|
||||
if (length.has_value() == rhs.length.has_value() && length.has_value()) {
|
||||
return *length <=> *rhs.length;
|
||||
}
|
||||
return std::partial_ordering::unordered;
|
||||
}
|
||||
|
||||
if (octave.has_value() == rhs.octave.has_value()) {
|
||||
if (octave.has_value())
|
||||
return (12 * *octave) + *base <=> (12 * *rhs.octave) + *rhs.base;
|
||||
return *base <=> *rhs.base;
|
||||
}
|
||||
}
|
||||
return std::partial_ordering::unordered;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Note note)
|
||||
{
|
||||
note.simplify_inplace();
|
||||
if (note.base) {
|
||||
os << note_index_to_string(*note.base);
|
||||
if (note.octave) {
|
||||
os << ":oct=" << int(*note.octave);
|
||||
}
|
||||
} else {
|
||||
os << "p";
|
||||
}
|
||||
if (note.length) {
|
||||
os << ":len=" << *note.length;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
bool Note::operator==(Note const& other) const
|
||||
{
|
||||
return octave == other.octave && base == other.base && length == other.length;
|
||||
}
|
||||
|
@ -1,47 +0,0 @@
|
||||
#ifndef MUSIQUE_TYPECHECK_HH
|
||||
#define MUSIQUE_TYPECHECK_HH
|
||||
|
||||
#include <musique/value/value.hh>
|
||||
|
||||
/// Intrinsic implementation primitive providing a short way to check if arguments match required type signature
|
||||
static inline bool typecheck(std::vector<Value> const& args, auto const& ...expected_types)
|
||||
{
|
||||
return (args.size() == sizeof...(expected_types)) &&
|
||||
[&args, expected_types...]<std::size_t ...I>(std::index_sequence<I...>) {
|
||||
return ((expected_types == args[I].type) && ...);
|
||||
} (std::make_index_sequence<sizeof...(expected_types)>{});
|
||||
}
|
||||
|
||||
/// Intrinsic implementation primitive providing a short way to move values based on matched type signature
|
||||
static inline bool typecheck_front(std::vector<Value> const& args, auto const& ...expected_types)
|
||||
{
|
||||
return (args.size() >= sizeof...(expected_types)) &&
|
||||
[&args, expected_types...]<std::size_t ...I>(std::index_sequence<I...>) {
|
||||
return ((expected_types == args[I].type) && ...);
|
||||
} (std::make_index_sequence<sizeof...(expected_types)>{});
|
||||
}
|
||||
|
||||
/// Intrinsic implementation primitive providing a short way to move values based on matched type signature
|
||||
template<auto ...Types>
|
||||
static inline auto move_from(std::vector<Value>& args)
|
||||
{
|
||||
return [&args]<std::size_t ...I>(std::index_sequence<I...>) {
|
||||
return std::tuple { (std::move(args[I]).*(Member_For_Value_Type<Types>::value)) ... };
|
||||
} (std::make_index_sequence<sizeof...(Types)>{});
|
||||
}
|
||||
|
||||
/// Shape abstraction to define what types are required once
|
||||
template<auto ...Types>
|
||||
struct Shape
|
||||
{
|
||||
static inline auto move_from(std::vector<Value>& args) { return ::move_from<Types...>(args); }
|
||||
static inline auto typecheck(std::vector<Value>& args) { return ::typecheck(args, Types...); }
|
||||
static inline auto typecheck_front(std::vector<Value>& args) { return ::typecheck_front(args, Types...); }
|
||||
|
||||
static inline auto typecheck_and_move(std::vector<Value>& args)
|
||||
{
|
||||
return typecheck(args) ? std::optional { move_from(args) } : std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
@ -1,5 +1,6 @@
|
||||
#include <musique/interpreter/env.hh>
|
||||
#include <musique/accessors.hh>
|
||||
#include <musique/guard.hh>
|
||||
#include <musique/interpreter/env.hh>
|
||||
#include <musique/interpreter/interpreter.hh>
|
||||
#include <musique/try.hh>
|
||||
#include <musique/value/value.hh>
|
||||
@ -7,47 +8,7 @@
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
|
||||
/// Finds numeric value of note. This form is later used as in
|
||||
/// note to midi resolution in formula octave * 12 + note_index
|
||||
constexpr u8 note_index(u8 note)
|
||||
{
|
||||
switch (note) {
|
||||
case 'c': return 0;
|
||||
case 'd': return 2;
|
||||
case 'e': return 4;
|
||||
case 'f': return 5;
|
||||
case 'g': return 7;
|
||||
case 'a': return 9;
|
||||
case 'h': return 11;
|
||||
case 'b': return 11;
|
||||
}
|
||||
// Parser should limit range of characters that is called with this function
|
||||
unreachable();
|
||||
}
|
||||
|
||||
constexpr std::string_view note_index_to_string(int note_index)
|
||||
{
|
||||
note_index %= 12;
|
||||
if (note_index < 0) {
|
||||
note_index = 12 + note_index;
|
||||
}
|
||||
|
||||
switch (note_index) {
|
||||
case 0: return "c";
|
||||
case 1: return "c#";
|
||||
case 2: return "d";
|
||||
case 3: return "d#";
|
||||
case 4: return "e";
|
||||
case 5: return "f";
|
||||
case 6: return "f#";
|
||||
case 7: return "g";
|
||||
case 8: return "g#";
|
||||
case 9: return "a";
|
||||
case 10: return "a#";
|
||||
case 11: return "b";
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
Value::Value() = default;
|
||||
|
||||
Result<Value> Value::from(Token t)
|
||||
{
|
||||
@ -81,126 +42,102 @@ Result<Value> Value::from(Token t)
|
||||
Value Value::from(Explicit_Bool b)
|
||||
{
|
||||
Value v;
|
||||
v.type = Value::Type::Bool;
|
||||
v.b = b;
|
||||
v.data = b.value;
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::from(Number n)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Number;
|
||||
v.n = std::move(n).simplify();
|
||||
v.data = std::move(n).simplify();
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::from(std::string s)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Symbol;
|
||||
v.s = std::move(s);
|
||||
v.data = Symbol(std::move(s));
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::from(std::string_view s)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Symbol;
|
||||
v.s = std::move(s);
|
||||
v.data = Symbol(std::move(s));
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::from(char const* s)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Symbol;
|
||||
v.s = std::move(s);
|
||||
v.data = Symbol(s);
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::from(Block &&block)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Block;
|
||||
v.blk = std::move(block);
|
||||
v.data = std::move(block);
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::from(Array &&array)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Array;
|
||||
v.array = std::move(array);
|
||||
v.data = std::move(array);
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::from(std::vector<Value> &&array)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Array;
|
||||
v.array = Array { .elements = std::move(array) };
|
||||
v.data = Array(std::move(array));
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::from(Note n)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Music;
|
||||
v.chord = { .notes = { n } };
|
||||
v.data = Chord(n);
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Value::from(Chord chord)
|
||||
{
|
||||
Value v;
|
||||
v.type = Type::Music;
|
||||
v.chord = std::move(chord);
|
||||
v.data = std::move(chord);
|
||||
return v;
|
||||
}
|
||||
|
||||
Result<Value> Value::operator()(Interpreter &i, std::vector<Value> args)
|
||||
Result<Value> Value::operator()(Interpreter &i, std::vector<Value> args) const
|
||||
{
|
||||
switch (type) {
|
||||
case Type::Intrinsic: return intr(i, std::move(args));
|
||||
case Type::Block: return blk(i, std::move(args));
|
||||
case Type::Music: return chord(i, std::move(args));
|
||||
default:
|
||||
return Error {
|
||||
.details = errors::Not_Callable { .type = type_name(type) },
|
||||
.location = std::nullopt,
|
||||
};
|
||||
if (auto func = get_if<Function>(data)) {
|
||||
return (*func)(i, std::move(args));
|
||||
}
|
||||
|
||||
return errors::Not_Callable { .type = type_name(*this) };
|
||||
}
|
||||
|
||||
|
||||
Result<Value> Value::index(Interpreter &i, unsigned position) const
|
||||
{
|
||||
switch (type) {
|
||||
case Type::Block:
|
||||
return blk.index(i, position);
|
||||
|
||||
case Type::Array:
|
||||
return array.index(i, position);
|
||||
|
||||
default:
|
||||
assert(false, "Block indexing is not supported for this type"); // TODO(assert)
|
||||
if (auto collection = get_if<Collection>(data)) {
|
||||
return collection->index(i, position);
|
||||
}
|
||||
|
||||
assert(false, "Block indexing is not supported for this type"); // TODO(assert)
|
||||
unreachable();
|
||||
}
|
||||
|
||||
bool Value::truthy() const
|
||||
{
|
||||
switch (type) {
|
||||
case Type::Bool: return b;
|
||||
case Type::Nil: return false;
|
||||
case Type::Number: return n != Number(0);
|
||||
case Type::Array: // for array and block maybe test emptyness?
|
||||
case Type::Block: //
|
||||
case Type::Intrinsic:
|
||||
case Type::Music:
|
||||
case Type::Symbol: return true;
|
||||
}
|
||||
unreachable();
|
||||
return std::visit(Overloaded {
|
||||
[](Bool b) { return b; },
|
||||
[](Nil) { return false; },
|
||||
[](Number const& n) { return n != Number(0); },
|
||||
[](Array const& a) { return a.size() != 0; },
|
||||
[](Block const& b) { return b.size() != 0; },
|
||||
[](auto&&) { return true; }
|
||||
}, data);
|
||||
}
|
||||
|
||||
bool Value::falsy() const
|
||||
@ -210,417 +147,82 @@ bool Value::falsy() const
|
||||
|
||||
bool Value::operator==(Value const& other) const
|
||||
{
|
||||
if (type != other.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case Type::Nil: return true;
|
||||
case Type::Number: return n == other.n;
|
||||
case Type::Symbol: return s == other.s;
|
||||
case Type::Intrinsic: return intr == other.intr;
|
||||
case Type::Block: return false; // TODO Reconsider if functions are comparable
|
||||
case Type::Bool: return b == other.b;
|
||||
case Type::Music: return chord == other.chord;
|
||||
case Type::Array: return array == other.array;
|
||||
}
|
||||
|
||||
unreachable();
|
||||
return std::visit(Overloaded {
|
||||
[]<typename T>(T const& lhs, T const& rhs) -> bool requires (!std::is_same_v<T, Block>) {
|
||||
return lhs == rhs;
|
||||
},
|
||||
[](auto&&...) { return false; }
|
||||
}, data, other.data);
|
||||
}
|
||||
|
||||
usize Value::size() const
|
||||
{
|
||||
switch (type) {
|
||||
case Type::Array:
|
||||
return array.size();
|
||||
|
||||
case Type::Block:
|
||||
return blk.size();
|
||||
|
||||
default:
|
||||
assert(false, "This type does not support Value::size()"); // TODO(assert)
|
||||
if (auto collection = get_if<Collection>(data)) {
|
||||
return collection->size();
|
||||
}
|
||||
|
||||
assert(false, "This type does not support Value::size()"); // TODO(assert)
|
||||
unreachable();
|
||||
}
|
||||
|
||||
std::partial_ordering Value::operator<=>(Value const& rhs) const
|
||||
{
|
||||
// TODO Block - array comparison should be allowed
|
||||
if (type != rhs.type) {
|
||||
return std::partial_ordering::unordered;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case Type::Nil:
|
||||
return std::partial_ordering::equivalent;
|
||||
|
||||
case Type::Bool:
|
||||
return b <=> rhs.b;
|
||||
|
||||
case Type::Symbol:
|
||||
return s <=> rhs.s;
|
||||
|
||||
case Type::Number:
|
||||
return n <=> rhs.n;
|
||||
|
||||
case Type::Array:
|
||||
return std::visit(Overloaded {
|
||||
[](Nil, Nil) { return std::partial_ordering::equivalent; },
|
||||
[](Array const& lhs, Array const& rhs) {
|
||||
return std::lexicographical_compare_three_way(
|
||||
array.elements.begin(), array.elements.end(),
|
||||
rhs.array.elements.begin(), rhs.array.elements.end()
|
||||
lhs.elements.begin(), lhs.elements.end(),
|
||||
rhs.elements.begin(), rhs.elements.end()
|
||||
);
|
||||
|
||||
case Type::Music:
|
||||
return chord.notes.front() <=> rhs.chord.notes.front();
|
||||
|
||||
// Block should be compared but after evaluation so for now it's Type::Block
|
||||
case Type::Block:
|
||||
case Type::Intrinsic:
|
||||
return std::partial_ordering::unordered;
|
||||
}
|
||||
|
||||
unreachable();
|
||||
},
|
||||
[](Chord const& lhs, Chord const& rhs) {
|
||||
return std::lexicographical_compare_three_way(
|
||||
lhs.notes.begin(), lhs.notes.end(),
|
||||
rhs.notes.begin(), rhs.notes.end()
|
||||
);
|
||||
},
|
||||
[]<typename T>(T const& lhs, T const& rhs) -> std::partial_ordering requires std::three_way_comparable<T> {
|
||||
return lhs <=> rhs;
|
||||
},
|
||||
[](auto&&...) { return std::partial_ordering::unordered; }
|
||||
}, data, rhs.data);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Value const& v)
|
||||
{
|
||||
switch (v.type) {
|
||||
case Value::Type::Nil:
|
||||
return os << "nil";
|
||||
|
||||
case Value::Type::Number:
|
||||
return os << v.n;
|
||||
|
||||
case Value::Type::Symbol:
|
||||
return os << v.s;
|
||||
|
||||
case Value::Type::Bool:
|
||||
return os << (v.b ? "true" : "false");
|
||||
|
||||
case Value::Type::Intrinsic:
|
||||
return os << "<intrinsic>";
|
||||
|
||||
case Value::Type::Block:
|
||||
return os << "<block>";
|
||||
|
||||
case Value::Type::Array:
|
||||
return os << v.array;
|
||||
|
||||
case Value::Type::Music:
|
||||
return os << v.chord;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
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";
|
||||
case Value::Type::Music: return "music";
|
||||
case Value::Type::Nil: return "nil";
|
||||
case Value::Type::Number: return "number";
|
||||
case Value::Type::Symbol: return "symbol";
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
Result<Value> Block::operator()(Interpreter &i, std::vector<Value> arguments)
|
||||
{
|
||||
auto old_scope = std::exchange(i.env, context);
|
||||
i.enter_scope();
|
||||
|
||||
if (parameters.size() != arguments.size()) {
|
||||
return errors::Wrong_Arity_Of {
|
||||
.type = errors::Wrong_Arity_Of::Function,
|
||||
// TODO Let user defined functions have name of their first assigment (Zig like)
|
||||
// or from place of definition like <block at file:line:column>
|
||||
.name = "<block>",
|
||||
.expected_arity = parameters.size(),
|
||||
.actual_arity = arguments.size(),
|
||||
};
|
||||
}
|
||||
|
||||
for (usize j = 0; j < parameters.size(); ++j) {
|
||||
i.env->force_define(parameters[j], std::move(arguments[j]));
|
||||
}
|
||||
|
||||
Ast body_copy = body;
|
||||
auto result = i.eval(std::move(body_copy));
|
||||
|
||||
i.env = old_scope;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Helper that produces error when trying to access container with too few elements for given index
|
||||
static inline Result<void> guard_index(unsigned index, unsigned size)
|
||||
{
|
||||
if (index < size) return {};
|
||||
return Error {
|
||||
.details = errors::Out_Of_Range { .required_index = index, .size = size }
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Add memoization
|
||||
Result<Value> Block::index(Interpreter &i, unsigned position) const
|
||||
{
|
||||
assert(parameters.size() == 0, "cannot index into block with parameters (for now)");
|
||||
if (body.type != Ast::Type::Sequence) {
|
||||
Try(guard_index(position, 1));
|
||||
return i.eval((Ast)body);
|
||||
}
|
||||
|
||||
Try(guard_index(position, body.arguments.size()));
|
||||
return i.eval((Ast)body.arguments[position]);
|
||||
}
|
||||
|
||||
usize Block::size() const
|
||||
{
|
||||
return body.type == Ast::Type::Sequence ? body.arguments.size() : 1;
|
||||
}
|
||||
|
||||
Result<Value> Array::index(Interpreter &, unsigned position) const
|
||||
{
|
||||
Try(guard_index(position, elements.size()));
|
||||
return elements[position];
|
||||
}
|
||||
|
||||
usize Array::size() const
|
||||
{
|
||||
return elements.size();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Array const& v)
|
||||
{
|
||||
os << '[';
|
||||
for (auto it = v.elements.begin(); it != v.elements.end(); ++it) {
|
||||
os << *it;
|
||||
if (std::next(it) != v.elements.end()) {
|
||||
os << "; ";
|
||||
}
|
||||
}
|
||||
return os << ']';
|
||||
}
|
||||
|
||||
std::optional<Note> Note::from(std::string_view literal)
|
||||
{
|
||||
if (literal.starts_with('p')) {
|
||||
return Note {};
|
||||
}
|
||||
|
||||
if (auto const base = note_index(literal[0]); base != u8(-1)) {
|
||||
Note note { .base = base };
|
||||
|
||||
while (literal.remove_prefix(1), not literal.empty()) {
|
||||
switch (literal.front()) {
|
||||
case '#': case 's': ++*note.base; break;
|
||||
case 'b': case 'f': --*note.base; break;
|
||||
default: return note;
|
||||
}
|
||||
}
|
||||
return note;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<u8> Note::into_midi_note() const
|
||||
{
|
||||
return octave ? std::optional(into_midi_note(0)) : std::nullopt;
|
||||
}
|
||||
|
||||
u8 Note::into_midi_note(i8 default_octave) const
|
||||
{
|
||||
assert(bool(this->base), "Pause don't translate into MIDI");
|
||||
auto const octave = this->octave.has_value() ? *this->octave : default_octave;
|
||||
// octave is in range [-1, 9] where Note { .base = 0, .octave = -1 } is midi note 0
|
||||
return (octave + 1) * 12 + *base;
|
||||
}
|
||||
|
||||
void Note::simplify_inplace()
|
||||
{
|
||||
if (base && octave) {
|
||||
octave = std::clamp(*octave + int(*base / 12), -1, 9);
|
||||
if ((*base %= 12) < 0) {
|
||||
base = 12 + *base;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::partial_ordering Note::operator<=>(Note const& rhs) const
|
||||
{
|
||||
if (base.has_value() == rhs.base.has_value()) {
|
||||
if (!base.has_value()) {
|
||||
if (length.has_value() == rhs.length.has_value() && length.has_value()) {
|
||||
return *length <=> *rhs.length;
|
||||
}
|
||||
return std::partial_ordering::unordered;
|
||||
}
|
||||
|
||||
if (octave.has_value() == rhs.octave.has_value()) {
|
||||
if (octave.has_value())
|
||||
return (12 * *octave) + *base <=> (12 * *rhs.octave) + *rhs.base;
|
||||
return *base <=> *rhs.base;
|
||||
}
|
||||
}
|
||||
return std::partial_ordering::unordered;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Note note)
|
||||
{
|
||||
note.simplify_inplace();
|
||||
if (note.base) {
|
||||
os << note_index_to_string(*note.base);
|
||||
if (note.octave) {
|
||||
os << ":oct=" << int(*note.octave);
|
||||
}
|
||||
} else {
|
||||
os << "p";
|
||||
}
|
||||
if (note.length) {
|
||||
os << ":len=" << *note.length;
|
||||
}
|
||||
std::visit(Overloaded {
|
||||
[&](Bool b) { os << std::boolalpha << b; },
|
||||
[&](Nil) { os << "nil"; },
|
||||
[&](Intrinsic) { os << "<intrinisic>"; },
|
||||
[&](Block const&) { os << "<block>"; },
|
||||
[&](auto const& s) { os << s; }
|
||||
}, v.data);
|
||||
return os;
|
||||
}
|
||||
|
||||
bool Note::operator==(Note const& other) const
|
||||
|
||||
std::string_view type_name(Value const& v)
|
||||
{
|
||||
return octave == other.octave && base == other.base && length == other.length;
|
||||
}
|
||||
|
||||
Chord Chord::from(std::string_view source)
|
||||
{
|
||||
auto note = Note::from(source);
|
||||
assert(note.has_value(), "don't know how this could happen");
|
||||
|
||||
Chord chord;
|
||||
source.remove_prefix(1 + (source[1] == '#'));
|
||||
chord.notes.push_back(*std::move(note));
|
||||
|
||||
if (note->base) {
|
||||
for (char digit : source) {
|
||||
chord.notes.push_back(Note { .base = note->base.value() + i32(digit - '0') });
|
||||
}
|
||||
}
|
||||
|
||||
return chord;
|
||||
}
|
||||
|
||||
Result<Value> Chord::operator()(Interpreter& interpreter, std::vector<Value> args)
|
||||
{
|
||||
std::vector<Value> array;
|
||||
std::vector<Chord> current = { *this };
|
||||
|
||||
enum State {
|
||||
Waiting_For_Octave,
|
||||
Waiting_For_Length,
|
||||
Waiting_For_Note
|
||||
} state = Waiting_For_Octave;
|
||||
|
||||
static constexpr auto guard = Guard<1> {
|
||||
.name = "note creation",
|
||||
.possibilities = {
|
||||
"(note:music [octave:number [duration:number]])+"
|
||||
}
|
||||
};
|
||||
|
||||
auto const next = [&state] {
|
||||
switch (state) {
|
||||
break; case Waiting_For_Length: state = Waiting_For_Note;
|
||||
break; case Waiting_For_Note: state = Waiting_For_Octave;
|
||||
break; case Waiting_For_Octave: state = Waiting_For_Length;
|
||||
}
|
||||
};
|
||||
|
||||
auto const update = [&state](Chord &chord, Value &arg) -> Result<void> {
|
||||
auto const resolve = [&chord](auto field, auto new_value) {
|
||||
for (auto ¬e : chord.notes) {
|
||||
(note.*field) = new_value;
|
||||
}
|
||||
};
|
||||
|
||||
switch (state) {
|
||||
break; case Waiting_For_Octave:
|
||||
resolve(&Note::octave, arg.n.floor().as_int());
|
||||
return {};
|
||||
|
||||
break; case Waiting_For_Length:
|
||||
resolve(&Note::length, arg.n);
|
||||
return {};
|
||||
|
||||
default:
|
||||
return guard.yield_error();
|
||||
}
|
||||
};
|
||||
|
||||
for (auto &arg : args) {
|
||||
if (is_indexable(arg.type)) {
|
||||
if (state != Waiting_For_Length && state != Waiting_For_Octave) {
|
||||
return guard.yield_error();
|
||||
}
|
||||
|
||||
auto const ring_size = current.size();
|
||||
for (usize i = 0; i < arg.size() && current.size() < arg.size(); ++i) {
|
||||
current.push_back(current[i % ring_size]);
|
||||
}
|
||||
|
||||
for (usize i = 0; i < current.size(); ++i) {
|
||||
if (Value value = Try(arg.index(interpreter, i % arg.size())); value.type == Value::Type::Number) {
|
||||
Try(update(current[i], value));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
next();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.type == Value::Type::Number) {
|
||||
for (auto &chord : current) {
|
||||
Try(update(chord, arg));
|
||||
}
|
||||
next();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.type == Value::Type::Music) {
|
||||
std::transform(current.begin(), current.end(), std::back_inserter(array),
|
||||
[](Chord &c) { return Value::from(std::move(c)); });
|
||||
current.clear();
|
||||
current.push_back(arg.chord);
|
||||
state = Waiting_For_Octave;
|
||||
}
|
||||
}
|
||||
|
||||
std::transform(current.begin(), current.end(), std::back_inserter(array),
|
||||
[](Chord &c) { return Value::from(std::move(c)); });
|
||||
|
||||
assert(not array.empty(), "At least *this should be in this array");
|
||||
return Value::from(Array{array});
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Chord const& chord)
|
||||
{
|
||||
if (chord.notes.size() == 1) {
|
||||
return os << chord.notes.front();
|
||||
}
|
||||
|
||||
os << "chord[";
|
||||
for (auto it = chord.notes.begin(); it != chord.notes.end(); ++it) {
|
||||
os << *it;
|
||||
if (std::next(it) != chord.notes.end())
|
||||
os << "; ";
|
||||
}
|
||||
return os << ']';
|
||||
return std::visit(Overloaded {
|
||||
[&](Array const&) { return "array"; },
|
||||
[&](Block const&) { return "block"; },
|
||||
[&](Bool const&) { return "bool"; },
|
||||
[&](Chord const&) { return "music"; },
|
||||
[&](Intrinsic const&) { return "intrinsic"; },
|
||||
[&](Nil const&) { return "nil"; },
|
||||
[&](Number const&) { return "number"; },
|
||||
[&](Symbol const&) { return "symbol"; },
|
||||
}, v.data);
|
||||
}
|
||||
|
||||
Result<std::vector<Value>> flatten(Interpreter &interpreter, std::span<Value> args)
|
||||
{
|
||||
std::vector<Value> result;
|
||||
for (auto &x : args) {
|
||||
if (is_indexable(x.type)) {
|
||||
for (usize i = 0; i < x.size(); ++i) {
|
||||
result.push_back(Try(x.index(interpreter, i)));
|
||||
if (auto collection = get_if<Collection>(x)) {
|
||||
for (usize i = 0; i < collection->size(); ++i) {
|
||||
result.push_back(Try(collection->index(interpreter, i)));
|
||||
}
|
||||
} else {
|
||||
result.push_back(std::move(x));
|
||||
@ -636,28 +238,26 @@ Result<std::vector<Value>> flatten(Interpreter &i, std::vector<Value> args)
|
||||
|
||||
std::size_t std::hash<Value>::operator()(Value const& value) const
|
||||
{
|
||||
size_t value_hash = 0;
|
||||
switch (value.type) {
|
||||
break; case Value::Type::Nil: value_hash = 0;
|
||||
break; case Value::Type::Number: value_hash = std::hash<Number>{}(value.n);
|
||||
break; case Value::Type::Symbol: value_hash = std::hash<std::string>{}(value.s);
|
||||
break; case Value::Type::Bool: value_hash = std::hash<bool>{}(value.b);
|
||||
break; case Value::Type::Intrinsic: value_hash = ptrdiff_t(value.intr);
|
||||
break; case Value::Type::Block: value_hash = hash_combine(std::hash<Ast>{}(value.blk.body), value.blk.parameters.size());
|
||||
|
||||
break; case Value::Type::Array:
|
||||
value_hash = std::accumulate(value.array.elements.begin(), value.array.elements.end(), value_hash, [this](size_t h, Value const& v) {
|
||||
return hash_combine(h, operator()(v));
|
||||
});
|
||||
|
||||
break; case Value::Type::Music:
|
||||
value_hash = std::accumulate(value.chord.notes.begin(), value.chord.notes.end(), value_hash, [](size_t h, Note const& n) {
|
||||
auto const value_hash = std::visit(Overloaded {
|
||||
[](Nil) { return std::size_t(0); },
|
||||
[](Intrinsic i) { return size_t(i.function_pointer); },
|
||||
[](Block const& b) { return hash_combine(std::hash<Ast>{}(b.body), b.parameters.size()); },
|
||||
[this](Array const& array) {
|
||||
return std::accumulate(
|
||||
array.elements.begin(), array.elements.end(), size_t(0),
|
||||
[this](size_t h, Value const& v) { return hash_combine(h, operator()(v)); }
|
||||
);
|
||||
},
|
||||
[](Chord const& chord) {
|
||||
return std::accumulate(chord.notes.begin(), chord.notes.end(), size_t(0), [](size_t h, Note const& n) {
|
||||
h = hash_combine(h, std::hash<std::optional<int>>{}(n.base));
|
||||
h = hash_combine(h, std::hash<std::optional<Number>>{}(n.length));
|
||||
h = hash_combine(h, std::hash<std::optional<i8>>{}(n.octave));
|
||||
return h;
|
||||
});
|
||||
}
|
||||
},
|
||||
[]<typename T>(T const& t) { return std::hash<T>{}(t); },
|
||||
}, value.data);
|
||||
|
||||
return hash_combine(value_hash, size_t(value.type));
|
||||
return hash_combine(value_hash, size_t(value.data.index()));
|
||||
}
|
||||
|
@ -1,13 +1,23 @@
|
||||
#ifndef MUSIQUE_VALUE_HH
|
||||
#define MUSIQUE_VALUE_HH
|
||||
|
||||
#include <musique/accessors.hh>
|
||||
#include <musique/common.hh>
|
||||
#include <musique/lexer/token.hh>
|
||||
#include <musique/result.hh>
|
||||
#include <musique/value/array.hh>
|
||||
#include <musique/value/block.hh>
|
||||
#include <musique/value/chord.hh>
|
||||
#include <musique/common.hh>
|
||||
#include <musique/value/intrinsic.hh>
|
||||
#include <musique/value/note.hh>
|
||||
#include <musique/result.hh>
|
||||
#include <musique/lexer/token.hh>
|
||||
|
||||
struct Nil
|
||||
{
|
||||
bool operator==(Nil const&) const = default;
|
||||
};
|
||||
|
||||
using Bool = bool;
|
||||
using Symbol = std::string;
|
||||
|
||||
/// Representation of any value in language
|
||||
struct Value
|
||||
@ -30,42 +40,36 @@ struct Value
|
||||
static Value from(std::string_view s); ///< Create value of type symbol holding provided symbol
|
||||
static Value from(std::vector<Value> &&array); ///< Create value of type array holding provided array
|
||||
|
||||
enum class Type
|
||||
{
|
||||
Nil, ///< Unit type, used for denoting emptiness and result of some side effect only functions
|
||||
Bool, ///< Boolean type, used for logic computations
|
||||
Number, ///< Number type, representing only rational numbers
|
||||
Symbol, ///< Symbol type, used to represent identifiers
|
||||
Intrinsic, ///< Intrinsic functions that are implemented in C++
|
||||
Block, ///< Block type, containing block value (lazy array/closure/lambda like)
|
||||
Array, ///< Array type, eager array
|
||||
Music, ///< Music type,
|
||||
};
|
||||
|
||||
Value() = default;
|
||||
Value(Value const&) = default;
|
||||
Value(Value &&) = default;
|
||||
Value& operator=(Value const&) = default;
|
||||
Value& operator=(Value &&) = default;
|
||||
|
||||
/// Contructs Intrinsic, used to simplify definition of intrinsics
|
||||
inline Value(Intrinsic intr) : type{Type::Intrinsic}, intr(intr)
|
||||
{
|
||||
}
|
||||
|
||||
Type type = Type::Nil;
|
||||
bool b;
|
||||
Number n;
|
||||
Intrinsic intr;
|
||||
Block blk;
|
||||
Chord chord;
|
||||
Array array;
|
||||
|
||||
// TODO Most strings should not be allocated by Value, but reference to string allocated previously
|
||||
// Wrapper for std::string is needed that will allocate only when needed, middle ground between:
|
||||
// std::string - always owning string type
|
||||
// std::string_view - not-owning string type
|
||||
std::string s{};
|
||||
std::variant<
|
||||
Nil,
|
||||
Bool,
|
||||
Number,
|
||||
Symbol,
|
||||
Intrinsic,
|
||||
Block,
|
||||
Array,
|
||||
Chord
|
||||
> data = Nil{};
|
||||
|
||||
Value();
|
||||
Value(Value const&) = default;
|
||||
Value(Value &&) = default;
|
||||
Value& operator=(Value const&) = default;
|
||||
Value& operator=(Value &&) = default;
|
||||
~Value() = default;
|
||||
|
||||
/// Contructs Intrinsic, used to simplify definition of intrinsics
|
||||
inline Value(Intrinsic::Function_Pointer intr) : data(Intrinsic(intr))
|
||||
{
|
||||
}
|
||||
|
||||
inline Value(Intrinsic const& intr) : data(intr)
|
||||
{
|
||||
}
|
||||
|
||||
/// Returns truth judgment for current type, used primarly for if function
|
||||
bool truthy() const;
|
||||
@ -74,7 +78,7 @@ struct Value
|
||||
bool falsy() const;
|
||||
|
||||
/// Calls contained value if it can be called
|
||||
Result<Value> operator()(Interpreter &i, std::vector<Value> args);
|
||||
Result<Value> operator()(Interpreter &i, std::vector<Value> args) const;
|
||||
|
||||
/// Index contained value if it can be called
|
||||
Result<Value> index(Interpreter &i, unsigned position) const;
|
||||
@ -87,55 +91,19 @@ struct Value
|
||||
std::partial_ordering operator<=>(Value const& other) const;
|
||||
};
|
||||
|
||||
template<Value::Type>
|
||||
struct Member_For_Value_Type {};
|
||||
/// Forward variant operations to variant member
|
||||
template<typename T>
|
||||
inline T const* get_if(Value const& v) { return get_if<T const>(v.data); }
|
||||
|
||||
template<> struct Member_For_Value_Type<Value::Type::Bool>
|
||||
{ static constexpr auto value = &Value::b; };
|
||||
|
||||
template<> struct Member_For_Value_Type<Value::Type::Number>
|
||||
{ static constexpr auto value = &Value::n; };
|
||||
|
||||
template<> struct Member_For_Value_Type<Value::Type::Symbol>
|
||||
{ static constexpr auto value = &Value::s; };
|
||||
|
||||
template<> struct Member_For_Value_Type<Value::Type::Intrinsic>
|
||||
{ static constexpr auto value = &Value::intr; };
|
||||
|
||||
template<> struct Member_For_Value_Type<Value::Type::Block>
|
||||
{ static constexpr auto value = &Value::blk; };
|
||||
|
||||
template<> struct Member_For_Value_Type<Value::Type::Array>
|
||||
{ static constexpr auto value = &Value::array; };
|
||||
|
||||
template<> struct Member_For_Value_Type<Value::Type::Music>
|
||||
{ static constexpr auto value = &Value::chord; };
|
||||
template<typename T>
|
||||
inline T* get_if(Value& v) { return get_if<T>(v.data); }
|
||||
|
||||
/// Returns type name of Value type
|
||||
std::string_view type_name(Value::Type t);
|
||||
std::string_view type_name(Value const& v);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Value const& v);
|
||||
template<> struct std::hash<Value> { std::size_t operator()(Value const&) const; };
|
||||
|
||||
/// Returns if type can be indexed
|
||||
static constexpr bool is_indexable(Value::Type type)
|
||||
{
|
||||
return type == Value::Type::Array || type == Value::Type::Block;
|
||||
}
|
||||
|
||||
/// Returns if type can be called
|
||||
static constexpr bool is_callable(Value::Type type)
|
||||
{
|
||||
return type == Value::Type::Block || type == Value::Type::Intrinsic;
|
||||
}
|
||||
|
||||
|
||||
/// 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)
|
||||
{
|
||||
return args.size() == 2 && (is_indexable(args[0].type) != is_indexable(args[1].type));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Result<Value> wrap_value(Result<T> &&value)
|
||||
{
|
||||
@ -147,4 +115,57 @@ Value wrap_value(auto &&value)
|
||||
return Value::from(std::move(value));
|
||||
}
|
||||
|
||||
template<typename Desired>
|
||||
constexpr Desired& get_ref(Value &v)
|
||||
{
|
||||
if constexpr (std::is_same_v<Desired, Value>) {
|
||||
return v;
|
||||
} else {
|
||||
if (auto result = get_if<Desired>(v)) { return *result; }
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Desired>
|
||||
constexpr bool holds_alternative(Value const& v)
|
||||
{
|
||||
if constexpr (std::is_same_v<Desired, Value>) {
|
||||
return true;
|
||||
} else {
|
||||
return get_if<Desired>(v.data) != nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Values>
|
||||
concept With_Index_Operator = requires (Values &values, size_t i) {
|
||||
{ values[i] } -> std::convertible_to<Value>;
|
||||
{ values.size() } -> std::convertible_to<size_t>;
|
||||
};
|
||||
|
||||
template<typename ...T>
|
||||
constexpr auto match(With_Index_Operator auto& values) -> std::optional<std::tuple<T&...>>
|
||||
{
|
||||
return [&]<std::size_t ...I>(std::index_sequence<I...>) -> std::optional<std::tuple<T&...>> {
|
||||
if (sizeof...(T) == values.size() && (holds_alternative<T>(values[I]) && ...)) {
|
||||
return {{ get_ref<T>(values[I])... }};
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
} (std::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
|
||||
template<typename ...T, typename ...Values>
|
||||
constexpr auto match(Values& ...values) -> std::optional<std::tuple<T&...>>
|
||||
{
|
||||
static_assert(sizeof...(T) == sizeof...(Values), "Provided parameters and expected types list must have the same length");
|
||||
|
||||
return [&]<std::size_t ...I>(std::index_sequence<I...>) -> std::optional<std::tuple<T&...>> {
|
||||
if ((holds_alternative<T>(values) && ...)) {
|
||||
return {{ get_ref<T>(values)... }};
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
} (std::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user