From 6a98178690e245e6fa6d8351762b395acad40371 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Sun, 25 Sep 2022 00:10:42 +0200 Subject: [PATCH] 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 --- musique/accessors.hh | 69 +++ musique/common.hh | 1 + musique/format.cc | 82 ++- musique/guard.hh | 15 +- musique/interpreter/builtin_functions.cc | 369 +++++++------- musique/interpreter/builtin_operators.cc | 152 +++--- musique/interpreter/incoming_midi.hh | 10 +- musique/main.cc | 10 +- musique/unicode_tables.cc | 4 +- musique/value/array.cc | 40 ++ musique/value/array.hh | 23 +- musique/value/block.cc | 59 +++ musique/value/block.hh | 19 +- musique/value/chord.cc | 141 ++++++ musique/value/chord.hh | 14 +- musique/value/collection.hh | 23 + musique/value/function.hh | 17 + musique/value/intrinsic.cc | 7 + musique/value/intrinsic.hh | 31 ++ musique/value/note.cc | 129 +++++ musique/value/typecheck.hh | 47 -- musique/value/value.cc | 602 ++++------------------- musique/value/value.hh | 177 ++++--- 23 files changed, 1060 insertions(+), 981 deletions(-) create mode 100644 musique/accessors.hh create mode 100644 musique/value/array.cc create mode 100644 musique/value/block.cc create mode 100644 musique/value/chord.cc create mode 100644 musique/value/collection.hh create mode 100644 musique/value/function.hh create mode 100644 musique/value/intrinsic.cc create mode 100644 musique/value/intrinsic.hh create mode 100644 musique/value/note.cc delete mode 100644 musique/value/typecheck.hh diff --git a/musique/accessors.hh b/musique/accessors.hh new file mode 100644 index 0000000..a4d3846 --- /dev/null +++ b/musique/accessors.hh @@ -0,0 +1,69 @@ +#ifndef MUSIQUE_ACCESSORS_HH +#define MUSIQUE_ACCESSORS_HH + +#include +#include + +template +constexpr Desired* get_if(std::variant &v) +{ + return std::visit([](Actual &act) -> Desired* { + if constexpr (std::is_same_v) { + return &act; + } else if constexpr (std::is_base_of_v) { + return static_cast(&act); + } else { + return nullptr; + } + }, v); +} + +template +constexpr Desired const* get_if(std::variant const& v) +{ + return std::visit([](Actual const& act) -> Desired const* { + if constexpr (std::is_same_v) { + return &act; + } else if constexpr (std::is_base_of_v) { + return static_cast(&act); + } else { + return nullptr; + } + }, v); +} + +template +constexpr Desired& get_ref(std::variant &v) +{ + if (auto result = get_if(v)) { return *result; } + unreachable(); +} + +#if 0 +template +constexpr auto match(Values& values) -> std::optional> +{ + return [&](std::index_sequence) -> std::optional> { + if (sizeof...(T) == values.size() && (std::holds_alternative(values[I].data) && ...)) { + return {{ std::get(values[I].data)... }}; + } else { + return std::nullopt; + } + } (std::make_index_sequence{}); +} + +template +constexpr auto match_ref(Values& values) -> std::optional> +{ + return [&](std::index_sequence) -> std::optional> { + if (sizeof...(T) == values.size() && (get_if(values[I].data) && ...)) { + return {{ get_ref(values[I].data)... }}; + } else { + return std::nullopt; + } + } (std::make_index_sequence{}); +} +#endif + +#endif + diff --git a/musique/common.hh b/musique/common.hh index c9fd875..8f419a6 100644 --- a/musique/common.hh +++ b/musique/common.hh @@ -24,6 +24,7 @@ using isize = std::ptrdiff_t; /// Combine several lambdas into one for visiting std::variant template struct Overloaded : Lambdas... { using Lambdas::operator()...; }; +template Overloaded(Ts...) -> Overloaded; /// Returns if provided thingy is a given template template typename Template, typename> diff --git a/musique/format.cc b/musique/format.cc index 885131b..e91f7b1 100644 --- a/musique/format.cc +++ b/musique/format.cc @@ -18,56 +18,42 @@ Value_Formatter Value_Formatter::nest(Context nested) const std::optional 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: - for (auto const& [key, val] : Env::global->variables) { - if (val.type == Value::Type::Intrinsic && val.intr == value.intr) { - os << ""; - return {}; + return std::visit(Overloaded { + [&](Intrinsic const& intrinsic) -> std::optional { + for (auto const& [key, val] : Env::global->variables) { + if (auto other = get_if(val); intrinsic == *other) { + os << ""; + return {}; + } } - } - os << ""; - - - break; case Value::Type::Array: - os << '['; - for (auto i = 0u; i < value.array.elements.size(); ++i) { - if (i > 0) { - os << "; "; + os << ""; + return {}; + }, + [&](Array const& array) -> std::optional { + os << '['; + for (auto i = 0u; i < array.elements.size(); ++i) { + if (i > 0) { + os << "; "; + } + Try(nest(Inside_Block).format(os, interpreter, array.elements[i])); } - Try(nest(Inside_Block).format(os, interpreter, value.array.elements[i])); - } - os << ']'; - - break; case Value::Type::Block: - os << '['; - for (auto i = 0u; i < value.blk.size(); ++i) { - if (i > 0) { - os << "; "; + os << ']'; + return {}; + }, + [&](Block const& block) -> std::optional { + os << '['; + for (auto i = 0u; i < block.size(); ++i) { + if (i > 0) { + os << "; "; + } + Try(nest(Inside_Block).format(os, interpreter, Try(block.index(interpreter, i)))); } - Try(nest(Inside_Block).format(os, interpreter, Try(value.index(interpreter, i)))); + os << ']'; + return {}; + }, + [&](auto&&) -> std::optional { + os << value; + return {}; } - os << ']'; - - break; case Value::Type::Music: - os << value.chord; - } - - return {}; + }, value.data); } diff --git a/musique/guard.hh b/musique/guard.hh index e7b14f6..d896f1f 100644 --- a/musique/guard.hh +++ b/musique/guard.hh @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -33,9 +34,19 @@ struct Guard return yield_error(); } - inline std::optional operator()(bool(*predicate)(Value::Type), Value const& v) const + template + inline Result match(Value &v) const { - return predicate(v.type) ? std::optional{} : yield_result(); + if (auto p = get_if(v)) { + return p; + } else { + return yield_error(); + } + } + + inline std::optional operator()(bool(*predicate)(Value const&), Value const& v) const + { + return predicate(v) ? std::optional{} : yield_result(); } }; diff --git a/musique/interpreter/builtin_functions.cc b/musique/interpreter/builtin_functions.cc index a60ba73..edc150b 100644 --- a/musique/interpreter/builtin_functions.cc +++ b/musique/interpreter/builtin_functions.cc @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -26,12 +25,6 @@ concept With_Index_Method = requires (T t, Interpreter interpreter, usize positi { t.index(interpreter, position) } -> std::convertible_to>; }; -/// Check if type has index operator -template -concept With_Index_Operator = requires (T t, unsigned i) { - { t[i] } -> std::convertible_to; -}; - /// Check if type has either (index operator or method) and size() method template concept Iterable = (With_Index_Method || With_Index_Operator) && requires (T const t) { @@ -40,7 +33,7 @@ concept Iterable = (With_Index_Method || With_Index_Operator) && requires /// Create chord out of given notes template -static inline std::optional create_chord(std::vector &chord, Interpreter &interpreter, T args) +static inline std::optional create_chord(std::vector &chord, Interpreter &interpreter, T&& args) { for (auto i = 0u; i < args.size(); ++i) { Value arg; @@ -50,19 +43,21 @@ static inline std::optional create_chord(std::vector &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(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(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 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(args.front().data), "Ctx only holds numeric values"); if constexpr (std::is_same_v) { - interpreter.context_stack.back().*(Mem_Ptr) = args.front().n; + interpreter.context_stack.back().*(Mem_Ptr) = std::get(args.front().data); } else { - interpreter.context_stack.back().*(Mem_Ptr) = static_cast(args.front().n.as_int()); + interpreter.context_stack.back().*(Mem_Ptr) = static_cast( + std::get(args.front().data).as_int() + ); } return Value{}; } /// Iterate over array and it's subarrays to create one flat array -static Result into_flat_array(Interpreter &i, std::span args) +static Result into_flat_array(Interpreter &interpreter, std::span 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))); - } - break; - - default: - array.elements.push_back(std::move(arg)); - } + std::visit(Overloaded { + [&target](Array &&array) -> std::optional { + std::ranges::move(array.elements, std::back_inserter(target.elements)); + return {}; + }, + [&target, &interpreter](Block &&block) -> std::optional { + for (auto i = 0u; i < block.size(); ++i) { + target.elements.push_back(Try(block.index(interpreter, i))); + } + return {}; + }, + [&target, &arg](auto&&) -> std::optional { + target.elements.push_back(std::move(arg)); + return {}; + }, + }, std::move(arg.data)); } - return array; + return target; } static Result into_flat_array(Interpreter &i, std::vector args) @@ -119,7 +118,6 @@ static Result into_flat_array(Interpreter &i, std::vector args) return into_flat_array(i, std::span(args)); } - /// Helper to convert method to it's name template 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 static Result apply_numeric_transform(Interpreter &i, std::vector args) { - using N = Shape; - if (N::typecheck(args)) { - return Value::from((std::get<0>(N::move_from(args)).*Method)()); + if (args.size()) { + if (auto number = get_if(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(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,28 +164,21 @@ enum class Range_Direction { Up, Down }; template static Result builtin_range(Interpreter&, std::vector args) { - using N = Shape; - using NN = Shape; - using NNN = Shape; - 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(args)) { std::tie(stop) = *a; } + else if (auto a = match(args)) { std::tie(start, stop) = *a; } + else if (auto a = match(args)) { std::tie(start, stop, step) = *a; } else { - return Error { - .details = errors::Unsupported_Types_For { - .type = errors::Unsupported_Types_For::Function, - .name = "range", - .possibilities = { - "(stop: number) -> array of number", - "(start: number, stop: number) -> array of number", - "(start: number, stop: number, step: number) -> array of number", - } - }, - .location = {} + return errors::Unsupported_Types_For { + .type = errors::Unsupported_Types_For::Function, + .name = "range", + .possibilities = { + "(stop: number) -> array of number", + "(start: number, stop: number) -> array of number", + "(start: number, stop: number, step: number) -> array of number", + } }; } @@ -203,17 +197,14 @@ static Result builtin_range(Interpreter&, std::vector args) /// Send MIDI Program Change message static auto builtin_program_change(Interpreter &i, std::vector args) -> Result { - using Program = Shape; - using Channel_Program = Shape; - - if (Program::typecheck(args)) { - auto [program] = Program::move_from(args); + if (auto a = match(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(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 args) -> R /// @invariant default_action is play one static inline std::optional 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(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(v)) { + Try(sequential_play(i, Try(i.eval(std::move(block->body))))); + } + else if (auto chord = get_if(v)) { + return i.play(*chord); } return {}; @@ -291,16 +279,20 @@ static Result builtin_par(Interpreter &i, std::vector 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(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(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 builtin_par(Interpreter &i, std::vector 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 builtin_sim(Interpreter &interpreter, std::vector ar std::optional operator()(std::vector &track, Value &arg) { - if (arg.type == Value::Type::Music) { - track.push_back(std::move(arg).chord); + if (auto chord = get_if(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(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 builtin_primes(Interpreter&, std::vector args) { - using N = Shape; - - if (N::typecheck(args)) { + if (auto a = match(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 builtin_primes(Interpreter&, std::vector 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 builtin_for(Interpreter &i, std::vector args) .possibilities = { "(array, callback) -> any" } }; - if (args.size() != 2) { + if (auto a = match(args)) { + auto& [collection, func] = *a; + Value result{}; + 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(); } - - Try(guard(is_indexable, args[0])); - Try(guard(is_callable, args[1])); - - Value result{}; - for (size_t n = 0; n < args[0].size(); ++n) { - result = Try(args[1](i, { Try(args[0].index(i, n)) })); - } - return result; } /// Fold container @@ -543,14 +531,13 @@ static Result builtin_fold(Interpreter &interpreter, std::vector a return guard.yield_error(); } - Try(guard(is_indexable, array)); - Try(guard(is_callable, callback)); + auto collection = Try(guard.match(array)); + auto function = Try(guard.match(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 builtin_if(Interpreter &i, std::vector args) { } if (args.front().truthy()) { - Try(guard(is_callable, args[1])); - return args[1](i, {}); + auto fun = Try(guard.match(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(args[2])); + return (*fun)(i, {}); } return Value{}; @@ -590,19 +577,19 @@ static Result builtin_try(Interpreter &interpreter, std::vector 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(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(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(args.back())); + return std::move(*callable)(interpreter, {}); } } @@ -623,17 +610,14 @@ static Result builtin_update(Interpreter &i, std::vector args) return guard.yield_error(); } - using Eager_And_Number = Shape; - using Lazy_And_Number = Shape; - - 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(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(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 builtin_update(Interpreter &i, std::vector args) /// Return typeof variable static Result builtin_typeof(Interpreter&, std::vector 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 builtin_len(Interpreter &i, std::vector 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(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 builtin_partition(Interpreter &i, std::vector args) return guard.yield_error(); } - auto predicate = std::move(args.front()); - Try(guard(is_callable, predicate)); + auto& predicate = *Try(guard.match(args.front())); auto array = Try(flatten(i, std::span(args).subspan(1))); Array tuple[2] = {}; @@ -738,7 +724,7 @@ static Result builtin_partition(Interpreter &i, std::vector 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,20 +738,23 @@ static Result builtin_rotate(Interpreter &i, std::vector args) .possibilities = { "(number, ...array) -> array" } }; - if (args.empty() || args.front().type != Value::Type::Number) { - return guard.yield_error(); + + if (args.size()) { + if (auto const offset_source = get_if(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(); + std::rotate(array.begin(), array.begin() + offset, array.end()); + } else if (offset < 0) { + offset = -offset % array.size(); + std::rotate(array.rbegin(), array.rbegin() + offset, array.rend()); + } + return Value::from(std::move(array)); + } } - auto offset = std::move(args.front()).n.as_int(); - auto array = Try(flatten(i, std::span(args).subspan(1))); - if (offset > 0) { - offset = offset % array.size(); - std::rotate(array.begin(), array.begin() + offset, array.end()); - } else if (offset < 0) { - offset = -offset % array.size(); - std::rotate(array.rbegin(), array.rbegin() + offset, array.rend()); - } - return Value::from(std::move(array)); + return guard.yield_error(); } /// Returns unique collection of arguments @@ -821,18 +810,14 @@ static Result builtin_chord(Interpreter &i, std::vector args) /// Send MIDI message Note On static Result builtin_note_on(Interpreter &i, std::vector args) { - using Channel_Note_Velocity = Shape; - using Channel_Music_Velocity = Shape; - - if (Channel_Note_Velocity::typecheck(args)) { - auto [chan, note, vel] = Channel_Note_Velocity::move_from(args); + if (auto a = match(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(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 builtin_note_on(Interpreter &i, std::vector args) /// Send MIDI message Note Off static Result builtin_note_off(Interpreter &i, std::vector args) { - using Channel_Note = Shape; - using Channel_Music = Shape; - - if (Channel_Note::typecheck(args)) { - auto [chan, note] = Channel_Note::move_from(args); + if (auto a = match(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(args)) { + auto& [chan, chord] = *a; for (auto note : chord.notes) { note = i.context_stack.back().fill(note); @@ -889,27 +871,23 @@ static Result builtin_note_off(Interpreter &i, std::vector args) /// Add handler for incoming midi messages static Result builtin_incoming(Interpreter &i, std::vector 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 = {} - }; + if (auto a = match(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") { + i.callbacks->note_off = std::move(args[1]); + } else { + + } + return Value{}; } - std::string const& symbol = args[0].s; - - if (symbol == "note_on" || symbol == "noteon") { - i.callbacks->note_on = std::move(args[1]); - } else if (symbol == "note_off" || symbol == "noteoff") { - i.callbacks->note_off = std::move(args[1]); - } else { - - } - return Value{}; + return errors::Unsupported_Types_For { + .type = errors::Unsupported_Types_For::Function, + .name = "incoming", + .possibilities = { "(symbol, function) -> nil" } + }; } /// Interleaves arguments @@ -919,7 +897,7 @@ static Result builtin_mix(Interpreter &i, std::vector args) std::unordered_map 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); // Algorithm description: // Repeat until all arguments were exhausted: @@ -929,12 +907,13 @@ static Result builtin_mix(Interpreter &i, std::vector 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(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 builtin_call(Interpreter &i, std::vector 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(args.front())); args.erase(args.begin()); - return callable(i, std::move(args)); } diff --git a/musique/interpreter/builtin_operators.cc b/musique/interpreter/builtin_operators.cc index 144947c..0ba68fb 100644 --- a/musique/interpreter/builtin_operators.cc +++ b/musique/interpreter/builtin_operators.cc @@ -3,25 +3,29 @@ #include #include #include -#include +#include /// Intrinsic implementation primitive to ease operation vectorization static Result vectorize(auto &&operation, Interpreter &interpreter, Value lhs, Value rhs) { + auto lhs_coll = get_if(lhs); + auto rhs_coll = get_if(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 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 symetric(Value::Type t1, Value::Type t2, Value &lhs, Value &rhs, auto binary) +template +inline std::optional 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 auto&& p1, - std::predicate auto&& p2, - Value &lhs, Value &rhs, - auto binary) -> std::optional -{ - 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(lhs, rhs)) { + return std::apply(std::move(binary), *a); + } else if (auto a = match(rhs, lhs)) { + return std::apply(std::move(binary), *a); } else { return std::nullopt; } @@ -79,24 +66,24 @@ static Result plus_minus_operator(Interpreter &interpreter, std::vector Result { - 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(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(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(lhs) != holds_alternative(rhs)) { return vectorize(plus_minus_operator, interpreter, std::move(lhs), std::move(rhs)); } @@ -128,11 +115,11 @@ static Result binary_operator(Interpreter& interpreter, std::vector Result { - 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(lhs, rhs)) { + return wrap_value(std::apply(Binary_Operation{}, *a)); } - if (is_indexable(lhs.type) != is_indexable(rhs.type)) { + if (holds_alternative(lhs) != holds_alternative(rhs)) { return vectorize(binary_operator, interpreter, std::move(lhs), std::move(rhs)); } @@ -156,24 +143,26 @@ static Result comparison_operator(Interpreter &interpreter, std::vector Result { + auto lhs_coll = get_if(args.front()); + auto rhs_coll = get_if(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 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 multiplication_operator(Interpreter &i, std::vector auto init = std::move(args.front()); return algo::fold(std::span(args).subspan(1), std::move(init), [&i](Value lhs, Value &rhs) -> Result { { - auto result = symetric(Value::Type::Number, Value::Type::Music, lhs, rhs, [](Value lhs, Value rhs) { - return Value::from(Array { - .elements = std::vector(lhs.n.floor().as_int(), std::move(rhs)) - }); + auto result = symetric(lhs, rhs, [](Number lhs, Chord &rhs) { + return Value::from(Array { std::vector(lhs.floor().as_int(), Value::from(std::move(rhs))) }); }); if (result.has_value()) { @@ -214,7 +201,7 @@ static Result multiplication_operator(Interpreter &i, std::vector }); } -using Operator_Entry = std::tuple; +using Operator_Entry = std::tuple; using power = decltype([](Number lhs, Number rhs) -> Result { return lhs.pow(rhs); @@ -237,32 +224,33 @@ static constexpr auto Operators = std::array { Operator_Entry { ">=", comparison_operator> }, Operator_Entry { ".", - +[](Interpreter &i, std::vector args) -> Result { - 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 args) -> Result { + if (auto a = match(args)) { + auto& [coll, pos] = *a; + return coll.index(interpreter, pos.as_int()); + } + if (auto a = match(args)) { + auto& [coll, pos] = *a; + return coll.index(interpreter, pos ? 1 : 0); + } + if (auto a = match(args)) { + auto& [source, positions] = *a; - if (args[1].type == Value::Type::Bool) { - return std::move(args.front()).index(i, args.back().b ? 1 : 0); - } + std::vector result; + for (size_t n = 0; n < positions.size(); ++n) { + auto const v = Try(positions.index(interpreter, n)); - if (is_indexable(args[1].type)) { - std::vector 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()))); + auto index = std::visit(Overloaded { + [](Number n) -> std::optional { return n.floor().as_int(); }, + [](Bool b) -> std::optional { return b ? 1 : 0; }, + [](auto &&) -> std::optional { return std::nullopt; } + }, v.data); - break; case Value::Type::Bool: default: - if (v.truthy()) { - result.push_back(Try(args[0].index(i, n))); - } - } + 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; - 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(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(a)); for (auto n = 0u; n < array.size(); ++n) { result.elements.push_back(Try(array.index(i, n))); } diff --git a/musique/interpreter/incoming_midi.hh b/musique/interpreter/incoming_midi.hh index a111953..14d8482 100644 --- a/musique/interpreter/incoming_midi.hh +++ b/musique/interpreter/incoming_midi.hh @@ -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(callback->data)) { std::vector 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(args[1].data).num % 12), + .octave = std::get(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(callback->data)) { auto result = (*callback)(*interpreter, { Value::from(Number(source_args))... }); // We discard this since callback is running in another thread. (void) result; diff --git a/musique/main.cc b/musique/main.cc index 44d6e53..a863023 100644 --- a/musique/main.cc +++ b/musique/main.cc @@ -135,7 +135,7 @@ struct Runner Env::global->force_define("say", +[](Interpreter &interpreter, std::vector args) -> Result { 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(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 Main(std::span args) +static std::optional Main(std::span 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; } } diff --git a/musique/unicode_tables.cc b/musique/unicode_tables.cc index 912922f..d39b2c5 100644 --- a/musique/unicode_tables.cc +++ b/musique/unicode_tables.cc @@ -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}, diff --git a/musique/value/array.cc b/musique/value/array.cc new file mode 100644 index 0000000..4bac376 --- /dev/null +++ b/musique/value/array.cc @@ -0,0 +1,40 @@ +#include +#include + +Array::Array() = default; +Array::Array(Array const&) = default; +Array::Array(Array &&) = default; +Array::~Array() = default; + +Array::Array(std::vector&& elements) + : elements{std::move(elements)} +{ +} + +Result 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 << ']'; +} diff --git a/musique/value/array.hh b/musique/value/array.hh index 4d257c4..0398412 100644 --- a/musique/value/array.hh +++ b/musique/value/array.hh @@ -1,27 +1,40 @@ #ifndef MUSIQUE_ARRAY_HH #define MUSIQUE_ARRAY_HH -#include #include +#include +#include struct Interpreter; struct Value; /// Eager Array -struct Array +struct Array : Collection { /// Elements that are stored in array std::vector elements; + Array(); + Array(std::vector&&); + Array(Array const&); + Array(Array &&); + ~Array() override; + + Array& operator=(Array const&) = default; + Array& operator=(Array &&) = default; + /// Index element of an array - Result index(Interpreter &i, unsigned position) const; + Result 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 diff --git a/musique/value/block.cc b/musique/value/block.cc new file mode 100644 index 0000000..db9fb34 --- /dev/null +++ b/musique/value/block.cc @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include + +/// Helper that produces error when trying to access container with too few elements for given index +static inline std::optional 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 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 Block::operator()(Interpreter &i, std::vector 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 + .name = "", + .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; +} diff --git a/musique/value/block.hh b/musique/value/block.hh index 9c6f5f5..440c3e9 100644 --- a/musique/value/block.hh +++ b/musique/value/block.hh @@ -1,20 +1,21 @@ #ifndef MUSIQUE_BLOCK_HH #define MUSIQUE_BLOCK_HH -#include -#include - #include +#include +#include +#include +#include struct Env; struct Interpreter; struct Value; -using Intrinsic = Result(*)(Interpreter &i, std::vector); - /// 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 context; /// Calling block - Result operator()(Interpreter &i, std::vector params); + Result operator()(Interpreter &i, std::vector params) const override; /// Indexing block - Result index(Interpreter &i, unsigned position) const; + Result index(Interpreter &i, unsigned position) const override; /// Count of elements in block - usize size() const; + usize size() const override; }; #endif diff --git a/musique/value/chord.cc b/musique/value/chord.cc new file mode 100644 index 0000000..738fb6c --- /dev/null +++ b/musique/value/chord.cc @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include + +Chord::Chord(Note note) + : notes{ note } +{ +} + +Chord::Chord(std::vector &¬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 Chord::operator()(Interpreter& interpreter, std::vector args) const +{ + std::vector array; + std::vector 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 { + 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(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(value)) { + Try(update(current[i], *number)); + continue; + } + } + next(); + continue; + } + + if (auto number = get_if(arg)) { + for (auto &chord : current) { + Try(update(chord, *number)); + } + next(); + continue; + } + + if (auto chord = get_if(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 << ']'; +} diff --git a/musique/value/chord.hh b/musique/value/chord.hh index b4b876c..f4189f6 100644 --- a/musique/value/chord.hh +++ b/musique/value/chord.hh @@ -1,22 +1,32 @@ +#ifndef MUSIQUE_VALUE_CHORD_HH +#define MUSIQUE_VALUE_CHORD_HH + #include #include +#include struct Interpreter; struct Value; /// Represantation of simultaneously played notes, aka musical chord -struct Chord +struct Chord : Function { std::vector notes; ///< Notes composing a chord + Chord() = default; + explicit Chord(Note note); + explicit Chord(std::vector &¬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 operator()(Interpreter &i, std::vector args); + Result operator()(Interpreter &i, std::vector args) const override; }; std::ostream& operator<<(std::ostream& os, Chord const& chord); + +#endif // MUSIQUE_VALUE_CHORD_HH diff --git a/musique/value/collection.hh b/musique/value/collection.hh new file mode 100644 index 0000000..a5f30c1 --- /dev/null +++ b/musique/value/collection.hh @@ -0,0 +1,23 @@ +#ifndef MUSIQUE_VALUE_COLLECTION_HH +#define MUSIQUE_VALUE_COLLECTION_HH + +#include + +struct Interpreter; +struct Value; + +/// Abstraction of Collection +struct Collection +{ + virtual ~Collection() = default; + + /// Return element at position inside collection + virtual Result 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 diff --git a/musique/value/function.hh b/musique/value/function.hh new file mode 100644 index 0000000..45e244c --- /dev/null +++ b/musique/value/function.hh @@ -0,0 +1,17 @@ +#ifndef MUSIQUE_VALUE_FUNCTION_HH +#define MUSIQUE_VALUE_FUNCTION_HH + +#include + +struct Value; +struct Interpreter; + +struct Function +{ + constexpr virtual ~Function() = default; + virtual Result operator()(Interpreter &i, std::vector params) const = 0; + + constexpr bool operator==(Function const&) const = default; +}; + +#endif // MUSIQUE_VALUE_FUNCTION_HH diff --git a/musique/value/intrinsic.cc b/musique/value/intrinsic.cc new file mode 100644 index 0000000..2058bfb --- /dev/null +++ b/musique/value/intrinsic.cc @@ -0,0 +1,7 @@ +#include +#include + +Result Intrinsic::operator()(Interpreter& interpreter, std::vector args) const +{ + return function_pointer(interpreter, std::move(args)); +} diff --git a/musique/value/intrinsic.hh b/musique/value/intrinsic.hh new file mode 100644 index 0000000..eafa687 --- /dev/null +++ b/musique/value/intrinsic.hh @@ -0,0 +1,31 @@ +#ifndef MUSIQUE_VALUE_INTRINSIC_HH +#define MUSIQUE_VALUE_INTRINSIC_HH + +#include +#include + +struct Interpreter; +struct Value; + +struct Intrinsic : Function +{ + using Function_Pointer = Result(*)(Interpreter &i, std::vector); + Function_Pointer function_pointer = nullptr; + + constexpr Intrinsic() = default; + + constexpr Intrinsic(Function_Pointer fp) + : function_pointer{fp} + { + } + + constexpr ~Intrinsic() = default; + + /// Calls underlying function pointer + Result operator()(Interpreter&, std::vector) const; + + /// Compares if function pointers are equal + bool operator==(Intrinsic const&) const = default; +}; + +#endif // MUSIQUE_VALUE_INTRINSIC_HH diff --git a/musique/value/note.cc b/musique/value/note.cc new file mode 100644 index 0000000..36fe2b3 --- /dev/null +++ b/musique/value/note.cc @@ -0,0 +1,129 @@ +#include + +/// 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::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 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; +} + diff --git a/musique/value/typecheck.hh b/musique/value/typecheck.hh deleted file mode 100644 index e3370e2..0000000 --- a/musique/value/typecheck.hh +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef MUSIQUE_TYPECHECK_HH -#define MUSIQUE_TYPECHECK_HH - -#include - -/// Intrinsic implementation primitive providing a short way to check if arguments match required type signature -static inline bool typecheck(std::vector const& args, auto const& ...expected_types) -{ - return (args.size() == sizeof...(expected_types)) && - [&args, expected_types...](std::index_sequence) { - return ((expected_types == args[I].type) && ...); - } (std::make_index_sequence{}); -} - -/// Intrinsic implementation primitive providing a short way to move values based on matched type signature -static inline bool typecheck_front(std::vector const& args, auto const& ...expected_types) -{ - return (args.size() >= sizeof...(expected_types)) && - [&args, expected_types...](std::index_sequence) { - return ((expected_types == args[I].type) && ...); - } (std::make_index_sequence{}); -} - -/// Intrinsic implementation primitive providing a short way to move values based on matched type signature -template -static inline auto move_from(std::vector& args) -{ - return [&args](std::index_sequence) { - return std::tuple { (std::move(args[I]).*(Member_For_Value_Type::value)) ... }; - } (std::make_index_sequence{}); -} - -/// Shape abstraction to define what types are required once -template -struct Shape -{ - static inline auto move_from(std::vector& args) { return ::move_from(args); } - static inline auto typecheck(std::vector& args) { return ::typecheck(args, Types...); } - static inline auto typecheck_front(std::vector& args) { return ::typecheck_front(args, Types...); } - - static inline auto typecheck_and_move(std::vector& args) - { - return typecheck(args) ? std::optional { move_from(args) } : std::nullopt; - } -}; - -#endif diff --git a/musique/value/value.cc b/musique/value/value.cc index 472112f..bf0a5e2 100644 --- a/musique/value/value.cc +++ b/musique/value/value.cc @@ -1,5 +1,6 @@ -#include +#include #include +#include #include #include #include @@ -7,47 +8,7 @@ #include #include -/// 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::from(Token t) { @@ -81,126 +42,102 @@ Result 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 &&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::operator()(Interpreter &i, std::vector args) +Result Value::operator()(Interpreter &i, std::vector 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(data)) { + return (*func)(i, std::move(args)); } + + return errors::Not_Callable { .type = type_name(*this) }; } + Result 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(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 { + [](T const& lhs, T const& rhs) -> bool requires (!std::is_same_v) { + 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(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::lexicographical_compare_three_way( - array.elements.begin(), array.elements.end(), - rhs.array.elements.begin(), rhs.array.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(); + return std::visit(Overloaded { + [](Nil, Nil) { return std::partial_ordering::equivalent; }, + [](Array const& lhs, Array const& rhs) { + return std::lexicographical_compare_three_way( + lhs.elements.begin(), lhs.elements.end(), + rhs.elements.begin(), rhs.elements.end() + ); + }, + [](Chord const& lhs, Chord const& rhs) { + return std::lexicographical_compare_three_way( + lhs.notes.begin(), lhs.notes.end(), + rhs.notes.begin(), rhs.notes.end() + ); + }, + [](T const& lhs, T const& rhs) -> std::partial_ordering requires std::three_way_comparable { + 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 << ""; - - case Value::Type::Block: - return os << ""; - - 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 Block::operator()(Interpreter &i, std::vector 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 - .name = "", - .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 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 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 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::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 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 << ""; }, + [&](Block const&) { os << ""; }, + [&](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 Chord::operator()(Interpreter& interpreter, std::vector args) -{ - std::vector array; - std::vector 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 { - 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> flatten(Interpreter &interpreter, std::span args) { std::vector 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(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> flatten(Interpreter &i, std::vector args) std::size_t std::hash::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{}(value.n); - break; case Value::Type::Symbol: value_hash = std::hash{}(value.s); - break; case Value::Type::Bool: value_hash = std::hash{}(value.b); - break; case Value::Type::Intrinsic: value_hash = ptrdiff_t(value.intr); - break; case Value::Type::Block: value_hash = hash_combine(std::hash{}(value.blk.body), value.blk.parameters.size()); + 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{}(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>{}(n.base)); + h = hash_combine(h, std::hash>{}(n.length)); + h = hash_combine(h, std::hash>{}(n.octave)); + return h; + }); + }, + [](T const& t) { return std::hash{}(t); }, + }, value.data); - 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) { - h = hash_combine(h, std::hash>{}(n.base)); - h = hash_combine(h, std::hash>{}(n.length)); - h = hash_combine(h, std::hash>{}(n.octave)); - return h; - }); - } - - return hash_combine(value_hash, size_t(value.type)); + return hash_combine(value_hash, size_t(value.data.index())); } diff --git a/musique/value/value.hh b/musique/value/value.hh index c14e6a7..8cb83c0 100644 --- a/musique/value/value.hh +++ b/musique/value/value.hh @@ -1,13 +1,23 @@ #ifndef MUSIQUE_VALUE_HH #define MUSIQUE_VALUE_HH +#include +#include +#include +#include #include #include #include -#include +#include #include -#include -#include + +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 &&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 operator()(Interpreter &i, std::vector args); + Result operator()(Interpreter &i, std::vector args) const; /// Index contained value if it can be called Result index(Interpreter &i, unsigned position) const; @@ -87,55 +91,19 @@ struct Value std::partial_ordering operator<=>(Value const& other) const; }; -template -struct Member_For_Value_Type {}; +/// Forward variant operations to variant member +template +inline T const* get_if(Value const& v) { return get_if(v.data); } -template<> struct Member_For_Value_Type -{ static constexpr auto value = &Value::b; }; - -template<> struct Member_For_Value_Type -{ static constexpr auto value = &Value::n; }; - -template<> struct Member_For_Value_Type -{ static constexpr auto value = &Value::s; }; - -template<> struct Member_For_Value_Type -{ static constexpr auto value = &Value::intr; }; - -template<> struct Member_For_Value_Type -{ static constexpr auto value = &Value::blk; }; - -template<> struct Member_For_Value_Type -{ static constexpr auto value = &Value::array; }; - -template<> struct Member_For_Value_Type -{ static constexpr auto value = &Value::chord; }; +template +inline T* get_if(Value& v) { return get_if(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 { 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 const& args) -{ - return args.size() == 2 && (is_indexable(args[0].type) != is_indexable(args[1].type)); -} - template Result wrap_value(Result &&value) { @@ -147,4 +115,57 @@ Value wrap_value(auto &&value) return Value::from(std::move(value)); } +template +constexpr Desired& get_ref(Value &v) +{ + if constexpr (std::is_same_v) { + return v; + } else { + if (auto result = get_if(v)) { return *result; } + unreachable(); + } +} + +template +constexpr bool holds_alternative(Value const& v) +{ + if constexpr (std::is_same_v) { + return true; + } else { + return get_if(v.data) != nullptr; + } +} + +template +concept With_Index_Operator = requires (Values &values, size_t i) { + { values[i] } -> std::convertible_to; + { values.size() } -> std::convertible_to; +}; + +template +constexpr auto match(With_Index_Operator auto& values) -> std::optional> +{ + return [&](std::index_sequence) -> std::optional> { + if (sizeof...(T) == values.size() && (holds_alternative(values[I]) && ...)) { + return {{ get_ref(values[I])... }}; + } else { + return std::nullopt; + } + } (std::make_index_sequence{}); +} + +template +constexpr auto match(Values& ...values) -> std::optional> +{ + static_assert(sizeof...(T) == sizeof...(Values), "Provided parameters and expected types list must have the same length"); + + return [&](std::index_sequence) -> std::optional> { + if ((holds_alternative(values) && ...)) { + return {{ get_ref(values)... }}; + } else { + return std::nullopt; + } + } (std::make_index_sequence{}); +} + #endif