From 831de5abe513003962c6fc0b02c58dc47dd4e408 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Thu, 18 Aug 2022 22:21:04 +0200 Subject: [PATCH] Builtin operator handlers definiton now in separate file Interpreter constructor and file in general is very unreadable and busy now. To limit noise separate builtin operators from src/interpreter.cc into their own file src/builtin_operators.cc This also allows to see which functions are used as implementation detail of which operations. --- Makefile | 27 ++--- src/builtin_operators.cc | 196 +++++++++++++++++++++++++++++++ src/interpreter.cc | 247 +-------------------------------------- src/musique.hh | 64 ++++++++++ 4 files changed, 276 insertions(+), 258 deletions(-) create mode 100644 src/builtin_operators.cc diff --git a/Makefile b/Makefile index 3ac869d..dec47b6 100644 --- a/Makefile +++ b/Makefile @@ -8,19 +8,20 @@ CXX=g++ LDFLAGS=-L./lib/midi/ LDLIBS=-lmidi-alsa -lasound -lpthread -Obj= \ - context.o \ - environment.o \ - errors.o \ - interpreter.o \ - lexer.o \ - lines.o \ - location.o \ - number.o \ - parser.o \ - pretty.o \ - unicode.o \ - unicode_tables.o \ +Obj= \ + builtin_operators.o \ + context.o \ + environment.o \ + errors.o \ + interpreter.o \ + lexer.o \ + lines.o \ + location.o \ + number.o \ + parser.o \ + pretty.o \ + unicode.o \ + unicode_tables.o \ value.o Tests= \ diff --git a/src/builtin_operators.cc b/src/builtin_operators.cc new file mode 100644 index 0000000..61e1420 --- /dev/null +++ b/src/builtin_operators.cc @@ -0,0 +1,196 @@ +#include + +/// 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"); + 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) { + array.elements.push_back( + Try(operation(interpreter, { Try(lhs.index(interpreter, i)), rhs }))); + } + return Value::from(std::move(array)); + } + + for (auto i = 0u; i < rhs.size(); ++i) { + array.elements.push_back( + Try(operation(interpreter, { lhs, Try(rhs.index(interpreter, i)) }))); + } + return Value::from(std::move(array)); +} + +/// Creates implementation of plus/minus operator that support following operations: +/// number, number -> number (standard math operations) +/// n: number, m: music -> music +/// m: music, n: number -> music moves m by n semitones (+ goes up, - goes down) +template +static Result plus_minus_operator(Interpreter &interpreter, std::vector args) +{ + static_assert(std::is_same_v> || std::is_same_v>, + "Error reporting depends on only one of this two types beeing provided"); + + using NN = Shape; + using MN = Shape; + using NM = Shape; + + if (NN::typecheck(args)) { + auto [a, b] = NN::move_from(args); + return Value::from(Binary_Operation{}(std::move(a), std::move(b))); + } + + if (MN::typecheck(args)) { + auto [chord, offset] = MN::move_from(args); + for (auto ¬e : chord.notes) { + note.base = Binary_Operation{}(note.base, offset.as_int()); + note.simplify_inplace(); + } + return Value::from(std::move(chord)); + } + + if (NM::typecheck(args)) { + auto [offset, chord] = NM::move_from(args); + for (auto ¬e : chord.notes) { + note.base = Binary_Operation{}(offset.as_int(), note.base); + note.simplify_inplace(); + } + return Value::from(std::move(chord)); + } + + if (may_be_vectorized(args)) { + return vectorize(plus_minus_operator, interpreter, std::move(args)); + } + + // TODO Limit possibilities based on provided types + static_assert(std::is_same_v, Binary_Operation> || std::is_same_v, Binary_Operation>, + "Error message printing only supports operators given above"); + return Error { + .details = errors::Unsupported_Types_For { + .type = errors::Unsupported_Types_For::Operator, + .name = std::is_same_v, Binary_Operation> ? "+" : "-", + .possibilities = { + "(number, number) -> number", + "(music, number) -> music", + "(number, music) -> music", + "(array, number|music) -> array", + "(number|music, array) -> array", + } + }, + .location = {}, // TODO fill location + }; +} + + +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); + return Value::from(Binary_Operation{}(lhs, rhs)); + } + + 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" + } + }, + .location = {}, // TODO fill location + }; +} + +template +static Result equality_operator(Interpreter&, std::vector args) +{ + assert(args.size() == 2, "(in)Equality only allows for 2 operands"); // TODO(assert) + return Value::from(Binary_Predicate{}(std::move(args.front()), std::move(args.back()))); +} + +template +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())); +} + +/// Operators definition table +static constexpr auto Operators = std::array { + std::tuple { "+", plus_minus_operator> }, + std::tuple { "-", plus_minus_operator> }, + std::tuple { "*", binary_operator, '*'> }, + std::tuple { "/", binary_operator, '/'> }, + + std::tuple { "<", comparison_operator> }, + std::tuple { ">", comparison_operator> }, + std::tuple { "<=", comparison_operator> }, + std::tuple { ">=", comparison_operator> }, + + std::tuple { "==", equality_operator> }, + std::tuple { "!=", equality_operator> }, + + std::tuple { ".", + +[](Interpreter &i, std::vector args) -> Result { + assert(args.size() == 2, "Operator . requires two arguments"); // TODO(assert) + assert(args.back().type == Value::Type::Number, "Only numbers can be used for indexing"); // TODO(assert) + return std::move(args.front()).index(i, std::move(args.back()).n.as_int()); + } + }, + + std::tuple { "&", + +[](Interpreter&, std::vector args) -> Result { + 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; + + // Append one set of notes to another to make bigger chord! + l.reserve(l.size() + r.size()); + std::move(r.begin(), r.end(), std::back_inserter(l)); + + return Value::from(lhs); + } + + return Error { + .details = errors::Unsupported_Types_For { + .type = errors::Unsupported_Types_For::Operator, + .name = "&", + .possibilities = { + "(music, music) -> music", + } + }, + .location = {} + }; + } + }, +}; + +// All operators should be defined here except 'and' and 'or' which handle evaluation differently +// and are need unevaluated expressions for their proper evaluation. Exclusion of them is marked +// as subtraction of total excluded operators from expected constant +static_assert(Operators.size() == Operators_Count - 2, "All operators handlers are defined here"); + +void Interpreter::register_builtin_operators() +{ + // Set all predefined operators into operators array + for (auto &[name, fptr] : Operators) { operators[name] = fptr; } +} diff --git a/src/interpreter.cc b/src/interpreter.cc index e7b9cb0..6ed6df8 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -68,60 +68,6 @@ void Interpreter::register_callbacks() }); } -/// 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...); } -}; - -/// 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 bool may_be_vectorized(std::vector const& args) -{ - return args.size() == 2 && (is_indexable(args[0].type) != is_indexable(args[1].type)); -} - static Result into_flat_array(Interpreter &i, std::span args) { Array array; @@ -149,136 +95,6 @@ static Result into_flat_array(Interpreter &i, std::vector args) return into_flat_array(i, std::span(args)); } -/// 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"); - 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) { - array.elements.push_back( - Try(operation(interpreter, { Try(lhs.index(interpreter, i)), rhs }))); - } - return Value::from(std::move(array)); - } - - for (auto i = 0u; i < rhs.size(); ++i) { - array.elements.push_back( - Try(operation(interpreter, { lhs, Try(rhs.index(interpreter, i)) }))); - } - return Value::from(std::move(array)); -} - -/// Creates implementation of plus/minus operator that support following operations: -/// number, number -> number (standard math operations) -/// n: number, m: music -> music -/// m: music, n: number -> music moves m by n semitones (+ goes up, - goes down) -template -static Result plus_minus_operator(Interpreter &interpreter, std::vector args) -{ - static_assert(std::is_same_v> || std::is_same_v>, - "Error reporting depends on only one of this two types beeing provided"); - - using NN = Shape; - using MN = Shape; - using NM = Shape; - - if (NN::typecheck(args)) { - auto [a, b] = NN::move_from(args); - return Value::from(Binary_Operation{}(std::move(a), std::move(b))); - } - - if (MN::typecheck(args)) { - auto [chord, offset] = MN::move_from(args); - for (auto ¬e : chord.notes) { - note.base = Binary_Operation{}(note.base, offset.as_int()); - note.simplify_inplace(); - } - return Value::from(std::move(chord)); - } - - if (NM::typecheck(args)) { - auto [offset, chord] = NM::move_from(args); - for (auto ¬e : chord.notes) { - note.base = Binary_Operation{}(offset.as_int(), note.base); - note.simplify_inplace(); - } - return Value::from(std::move(chord)); - } - - if (may_be_vectorized(args)) { - return vectorize(plus_minus_operator, interpreter, std::move(args)); - } - - // TODO Limit possibilities based on provided types - static_assert(std::is_same_v, Binary_Operation> || std::is_same_v, Binary_Operation>, - "Error message printing only supports operators given above"); - return Error { - .details = errors::Unsupported_Types_For { - .type = errors::Unsupported_Types_For::Operator, - .name = std::is_same_v, Binary_Operation> ? "+" : "-", - .possibilities = { - "(number, number) -> number", - "(music, number) -> music", - "(number, music) -> music", - "(array, number|music) -> array", - "(number|music, array) -> array", - } - }, - .location = {}, // TODO fill location - }; -} - - -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); - return Value::from(Binary_Operation{}(lhs, rhs)); - } - - 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" - } - }, - .location = {}, // TODO fill location - }; -} - -template -static Result equality_operator(Interpreter&, std::vector args) -{ - assert(args.size() == 2, "(in)Equality only allows for 2 operands"); // TODO(assert) - return Value::from(Binary_Predicate{}(std::move(args.front()), std::move(args.back()))); -} - -template -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())); -} /// Registers constants like `fn = full note = 1/1` static inline void register_note_length_constants() @@ -710,68 +526,9 @@ error: global.force_define("pgmchange", pgmchange); global.force_define("program_change", pgmchange); } - - // Operators definition table - static constexpr auto Operators = std::array { - std::tuple { "+", plus_minus_operator> }, - std::tuple { "-", plus_minus_operator> }, - std::tuple { "*", binary_operator, '*'> }, - std::tuple { "/", binary_operator, '/'> }, - - std::tuple { "<", comparison_operator> }, - std::tuple { ">", comparison_operator> }, - std::tuple { "<=", comparison_operator> }, - std::tuple { ">=", comparison_operator> }, - - std::tuple { "==", equality_operator> }, - std::tuple { "!=", equality_operator> }, - - std::tuple { ".", - +[](Interpreter &i, std::vector args) -> Result { - assert(args.size() == 2, "Operator . requires two arguments"); // TODO(assert) - assert(args.back().type == Value::Type::Number, "Only numbers can be used for indexing"); // TODO(assert) - return std::move(args.front()).index(i, std::move(args.back()).n.as_int()); - } - }, - - std::tuple { "&", - +[](Interpreter&, std::vector args) -> Result { - 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; - - // Append one set of notes to another to make bigger chord! - l.reserve(l.size() + r.size()); - std::move(r.begin(), r.end(), std::back_inserter(l)); - - return Value::from(lhs); - } - - return Error { - .details = errors::Unsupported_Types_For { - .type = errors::Unsupported_Types_For::Operator, - .name = "&", - .possibilities = { - "(music, music) -> music", - } - }, - .location = {} - }; - } - }, - }; - - // All operators should be defined here except 'and' and 'or' which handle evaluation differently - // and are need unevaluated expressions for their proper evaluation. Exclusion of them is marked - // as subtraction of total excluded operators from expected constant - static_assert(Operators.size() == Operators_Count - 2, "All operators handlers are defined here"); - - // Set all predefined operators into operators array - for (auto &[name, fptr] : Operators) { operators[name] = fptr; } } + + register_builtin_operators(); } Interpreter::~Interpreter() diff --git a/src/musique.hh b/src/musique.hh index 4b7f83a..71444b1 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -952,6 +952,16 @@ struct Interpreter /// Play note resolving any missing parameters with context via `midi_connection` member. void play(Chord); + + /// Add to global interpreter scope all builtin function definitions + /// + /// Invoked during construction + void register_builtin_functions(); + + /// Add to interpreter operators table all operators + /// + /// Invoked during construction + void register_builtin_operators(); }; namespace errors @@ -959,3 +969,57 @@ namespace errors [[noreturn]] void all_tokens_were_not_parsed(std::span); } + +/// 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...); } +}; + +/// 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)); +}