From cb13dc9591c9f7f021357a7344a0dc52f37c7a9f Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Sat, 17 Sep 2022 00:31:55 +0200 Subject: [PATCH] Refactored operators, loosing binary requirements due to them Due to addition of turning binary operators into functions, they can now be called with more or less then 2 arguments, which would trigger assertions. Not all were converted to support new call syntax though --- include/musique.hh | 9 +++ include/musique_internal.hh | 44 +++++++++++ src/builtin_operators.cc | 149 +++++++++++++++++++----------------- 3 files changed, 132 insertions(+), 70 deletions(-) diff --git a/include/musique.hh b/include/musique.hh index db7eb04..1f975cc 100644 --- a/include/musique.hh +++ b/include/musique.hh @@ -318,6 +318,15 @@ struct [[nodiscard("This value may contain critical error, so it should NOT be i { } + template + requires requires (Arg a) { + { Error { .details = std::move(a) } }; + } + inline Result(Arg a) + : Storage(tl::unexpected(Error { .details = std::move(a) })) + { + } + // Internal function used for definition of Try macro inline auto value() && { diff --git a/include/musique_internal.hh b/include/musique_internal.hh index f6234cf..4db056b 100644 --- a/include/musique_internal.hh +++ b/include/musique_internal.hh @@ -2,6 +2,9 @@ #define Musique_Internal_HH #include +#include +#include +#include /// Allows creation of guards that ensure proper type template @@ -100,4 +103,45 @@ Result ensure_midi_connection_available(Interpreter&, Midi_Connection_Type constexpr std::size_t hash_combine(std::size_t lhs, std::size_t rhs) { return lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2); } + +template +Result wrap_value(Result &&value) +{ + return std::move(value).map([](auto &&value) { return Value::from(std::move(value)); }); +} + +Value wrap_value(auto &&value) +{ + return Value::from(std::move(value)); +} + +/// Generic algorithms support +namespace algo +{ + /// Check if predicate is true for all successive pairs of elements + constexpr bool pairwise_all( + std::ranges::forward_range auto &&range, + auto &&binary_predicate) + { + auto it = std::begin(range); + auto const end_it = std::end(range); + for (auto next_it = std::next(it); it != end_it && next_it != end_it; ++it, ++next_it) { + if (not binary_predicate(*it, *next_it)) { + return false; + } + } + return true; + } + + /// Fold that stops iteration on error value via Result type + template + constexpr Result fold(auto&& range, T init, auto &&reducer) + { + for (auto &&value : range) { + init = Try(reducer(std::move(init), value)); + } + return init; + } +} + #endif diff --git a/src/builtin_operators.cc b/src/builtin_operators.cc index 14d25d7..5008500 100644 --- a/src/builtin_operators.cc +++ b/src/builtin_operators.cc @@ -2,15 +2,10 @@ #include /// Intrinsic implementation primitive to ease operation vectorization -/// @invariant args.size() == 2 -Result vectorize(auto &&operation, Interpreter &interpreter, std::vector args) +Result vectorize(auto &&operation, Interpreter &interpreter, Value lhs, Value rhs) { - assert(args.size() == 2, "Vectorization primitive only supports two arguments"); Array array; - auto lhs = std::move(args.front()); - auto rhs = std::move(args.back()); - if (is_indexable(lhs.type) && !is_indexable(rhs.type)) { Array array; for (auto i = 0u; i < lhs.size(); ++i) { @@ -27,6 +22,26 @@ Result vectorize(auto &&operation, Interpreter &interpreter, std::vector< return Value::from(std::move(array)); } +/// Intrinsic implementation primitive to ease operation vectorization +/// @invariant args.size() == 2 +Result vectorize(auto &&operation, Interpreter &interpreter, std::vector args) +{ + assert(args.size() == 2, "Vectorization primitive only supports two arguments"); + return vectorize(std::move(operation), interpreter, std::move(args.front()), std::move(args.back())); +} + + +std::optional symetric(Value::Type t1, Value::Type t2, Value &lhs, Value &rhs, auto binary_predicate) +{ + if (lhs.type == t1 && rhs.type == t2) { + return binary_predicate(std::move(lhs), std::move(rhs)); + } else if (lhs.type == t2 && rhs.type == t1) { + return binary_predicate(std::move(rhs), std::move(lhs)); + } else { + return std::nullopt; + } +} + /// Creates implementation of plus/minus operator that support following operations: /// number, number -> number (standard math operations) /// n: number, m: music -> music @@ -95,40 +110,39 @@ template static Result binary_operator(Interpreter& interpreter, std::vector args) { static constexpr char Name[] = { Chars..., '\0' }; - - using NN = Shape; - - if (NN::typecheck(args)) { - auto [lhs, rhs] = NN::move_from(args); - if constexpr (is_template_v) { - return Value::from(Try(Binary_Operation{}(lhs, rhs))); - } else { - return Value::from(Binary_Operation{}(lhs, rhs)); - } + if (args.empty()) { + return Value::from(Number(1)); } - - if (may_be_vectorized(args)) { - return vectorize(binary_operator, interpreter, args); - } - - return Error { - .details = errors::Unsupported_Types_For { - .type = errors::Unsupported_Types_For::Operator, - .name = Name, - .possibilities = { - "(number, number) -> number", - "(array, number) -> array", - "(number, array) -> array" + auto init = std::move(args.front()); + return algo::fold(std::span(args).subspan(1), std::move(init), + [&interpreter](Value lhs, Value &rhs) -> 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)); } - }, - .location = {}, // TODO fill location - }; + + if (is_indexable(lhs.type) != is_indexable(rhs.type)) { + return vectorize(binary_operator, interpreter, std::move(lhs), std::move(rhs)); + } + + return errors::Unsupported_Types_For { + .type = errors::Unsupported_Types_For::Operator, + .name = Name, + .possibilities = { + "(number, number) -> number", + "(array, number) -> array", + "(number, array) -> array" + } + }; + } + ); } template -static Result equality_operator(Interpreter &interpreter, std::vector args) +static Result comparison_operator(Interpreter &interpreter, std::vector args) { - assert(args.size() == 2, "(in)Equality only allows for 2 operands"); // TODO(assert) + if (args.size() != 2) { + return Value::from(algo::pairwise_all(std::move(args), Binary_Predicate{})); + } if (is_indexable(args[1].type) && !is_indexable(args[0].type)) { std::swap(args[1], args[0]); @@ -155,43 +169,39 @@ static Result equality_operator(Interpreter &interpreter, std::vector -static Result comparison_operator(Interpreter&, std::vector args) -{ - assert(args.size() == 2, "Operator handler cannot accept any shape different then 2 arguments"); - return Value::from(Binary_Predicate{}(args.front(), args.back())); -} - static Result multiplication_operator(Interpreter &i, std::vector args) { - using MN = Shape; - using NM = Shape; - - Number repeat; Chord what; bool typechecked = false; - - // TODO add automatic symetry resolution for cases like this - if (NM::typecheck(args)) { typechecked = true; std::tie(repeat, what) = NM::move_from(args); } - else if (MN::typecheck(args)) { typechecked = true; std::tie(what, repeat) = MN::move_from(args); } - - if (typechecked) { - return Value::from(Array { - .elements = std::vector( - repeat.floor().as_int(), Value::from(std::move(what)) - ) - }); + if (args.empty()) { + return Value::from(Number(1)); } - // If binary_operator returns an error that lists all possible overloads - // of this operator we must inject overloads that we provided above - auto result = binary_operator, '*'>(i, std::move(args)); - if (!result.has_value()) { - auto &details = result.error().details; - if (auto p = std::get_if(&details)) { - p->possibilities.push_back("(repeat: number, what: music) -> array of music"); - p->possibilities.push_back("(what: music, repeat: number) -> array of music"); + 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)) + }); + }); + + if (result.has_value()) { + return *std::move(result); + } } - } - return result; + + // If binary_operator returns an error that lists all possible overloads + // of this operator we must inject overloads that we provided above + auto result = binary_operator, '*'>(i, { std::move(lhs), std::move(rhs) }); + if (!result.has_value()) { + auto &details = result.error().details; + if (auto p = std::get_if(&details)) { + p->possibilities.push_back("(repeat: number, what: music) -> array of music"); + p->possibilities.push_back("(what: music, repeat: number) -> array of music"); + } + } + + return result; + }); } using Operator_Entry = std::tuple; @@ -213,14 +223,13 @@ static constexpr auto Operators = std::array { Operator_Entry { "%", binary_operator, '%'> }, Operator_Entry { "**", binary_operator }, + Operator_Entry { "!=", comparison_operator> }, Operator_Entry { "<", comparison_operator> }, - Operator_Entry { ">", comparison_operator> }, Operator_Entry { "<=", comparison_operator> }, + Operator_Entry { "==", comparison_operator> }, + Operator_Entry { ">", comparison_operator> }, Operator_Entry { ">=", comparison_operator> }, - Operator_Entry { "==", equality_operator> }, - Operator_Entry { "!=", equality_operator> }, - Operator_Entry { ".", +[](Interpreter &i, std::vector args) -> Result { if (args.size() == 2 && is_indexable(args[0].type)) {