diff --git a/Makefile b/Makefile index c0fa0b5..7744d55 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ include scripts/windows.mk # http://www.music.mcgill.ca/~gary/rtmidi/#compiling bin/rtmidi.o: lib/rtmidi/RtMidi.cpp lib/rtmidi/RtMidi.h @echo "CXX $@" - @$(CXX) $< -c -O2 -o $@ $(CPPFLAGS) + @$(CXX) $< -c -O2 -o $@ $(CPPFLAGS) -std=c++20 doc: Doxyfile musique/*.cc musique/*.hh doxygen diff --git a/config.mk b/config.mk index 514be89..4bb0e4a 100644 --- a/config.mk +++ b/config.mk @@ -1,9 +1,9 @@ MAKEFLAGS="-j $(grep -c ^processor /proc/cpuinfo)" -CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result -Wno-maybe-uninitialized +CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -I. -Ilib/bestline/ -Ilib/rtmidi/ LDFLAGS=-flto -LDLIBS= -lpthread -static-libgcc -static-libstdc++ +LDLIBS= -lpthread RELEASE_FLAGS=-O2 DEBUG_FLAGS=-O0 -ggdb -fsanitize=undefined -DDebug @@ -16,7 +16,7 @@ LDLIBS:=-lwinmm $(LDLIBS) else CC=gcc CXX=g++ -CPPFLAGS:=$(CPPFLAGS) -D __LINUX_ALSA__ -LDLIBS:=-lasound $(LDLIBS) +CPPFLAGS:=$(CPPFLAGS) -D __MACOSX_CORE__ +LDLIBS:=-framework CoreMIDI -framework CoreAudio -framework CoreFoundation $(LDLIBS) endif diff --git a/musique/algo.hh b/musique/algo.hh index b592c46..e1785a9 100644 --- a/musique/algo.hh +++ b/musique/algo.hh @@ -5,13 +5,14 @@ #include #include #include +#include /// 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 &&range, auto &&binary_predicate) { auto it = std::begin(range); @@ -34,6 +35,22 @@ namespace algo } return init; } + + /// Equavilent of std::lexicographical_compare_three_way + /// + /// It's missing from MacOS C++ standard library + constexpr auto lexicographical_compare(auto&& lhs_range, auto&& rhs_range) + { + auto lhs = lhs_range.begin(); + auto rhs = rhs_range.begin(); + decltype(*lhs <=> *rhs) result = std::partial_ordering::equivalent; + for (; lhs != lhs_range.end() && rhs != rhs_range.end(); ++lhs, ++rhs) { + if ((result = *lhs <=> *rhs) != 0) { + return result; + } + } + return result; + } } /// Flattens one layer: `[[[1], 2], 3]` becomes `[[1], 2, 3]` diff --git a/musique/common.hh b/musique/common.hh index 800b12b..5cc92b9 100644 --- a/musique/common.hh +++ b/musique/common.hh @@ -54,4 +54,9 @@ constexpr std::size_t hash_combine(std::size_t lhs, std::size_t rhs) { template static constexpr bool always_false = false; +template +concept Three_Way_Comparable = requires (T const& lhs, T const& rhs) { + { lhs <=> rhs }; +}; + #endif diff --git a/musique/errors.cc b/musique/errors.cc index 0fabeee..39a7e10 100644 --- a/musique/errors.cc +++ b/musique/errors.cc @@ -81,7 +81,7 @@ static void encourage_contact(std::ostream &os) << pretty::end << std::flush; } -void assert(bool condition, std::string message, Location loc) +void ensure(bool condition, std::string message, Location loc) { if (condition) return; #if Debug diff --git a/musique/errors.hh b/musique/errors.hh index 9036e55..769345d 100644 --- a/musique/errors.hh +++ b/musique/errors.hh @@ -15,13 +15,8 @@ #include #include -// To make sure, that we don't collide with macro -#ifdef assert -#undef assert -#endif - /// Guards that program exits if condition does not hold -void assert(bool condition, std::string message, Location loc = Location::caller()); +void ensure(bool condition, std::string message, Location loc = Location::caller()); /// Marks part of code that was not implemented yet [[noreturn]] void unimplemented(std::string_view message = {}, Location loc = Location::caller()); diff --git a/musique/format.cc b/musique/format.cc index e91f7b1..b721106 100644 --- a/musique/format.cc +++ b/musique/format.cc @@ -1,8 +1,8 @@ +#include #include #include #include #include -#include Result format(Interpreter &i, Value const& value) { diff --git a/musique/interpreter/builtin_functions.cc b/musique/interpreter/builtin_functions.cc index 9e17c1c..ab0e255 100644 --- a/musique/interpreter/builtin_functions.cc +++ b/musique/interpreter/builtin_functions.cc @@ -14,7 +14,7 @@ void Interpreter::register_callbacks() { - assert(callbacks == nullptr, "This field should be uninitialized"); + ensure(callbacks == nullptr, "This field should be uninitialized"); callbacks = std::make_unique(); callbacks->add_callbacks(*midi_connection, *this); } @@ -67,8 +67,8 @@ static inline std::optional create_chord(std::vector &chord, Interp } if (auto arg_chord = get_if(arg)) { - std::ranges::copy_if( - arg_chord->notes, + std::copy_if( + arg_chord->notes.begin(), arg_chord->notes.end(), std::back_inserter(chord), [](Note const& n) { return n.base.has_value(); } ); @@ -80,7 +80,7 @@ static inline std::optional create_chord(std::vector &chord, Interp continue; } - assert(false, "this type is not supported inside chord"); // TODO(assert) + ensure(false, "this type is not supported inside chord"); // TODO(assert) } return {}; @@ -90,7 +90,7 @@ static inline std::optional create_chord(std::vector &chord, Interp template static Result ctx_read_write_property(Interpreter &interpreter, std::vector args) { - assert(args.size() <= 1, "Ctx get or set is only supported (wrong number of arguments)"); // TODO(assert) + ensure(args.size() <= 1, "Ctx get or set is only supported (wrong number of arguments)"); // TODO(assert) using Member_Type = std::remove_cvref_t().*(Mem_Ptr))>; @@ -98,7 +98,7 @@ static Result ctx_read_write_property(Interpreter &interpreter, std::vect return Number(interpreter.context_stack.back().*(Mem_Ptr)); } - assert(std::holds_alternative(args.front().data), "Ctx only holds numeric values"); + ensure(std::holds_alternative(args.front().data), "Ctx only holds numeric values"); if constexpr (std::is_same_v) { interpreter.context_stack.back().*(Mem_Ptr) = std::get(args.front().data); @@ -118,7 +118,7 @@ static Result into_flat_array(Interpreter &interpreter, std::span for (auto &arg : args) { std::visit(Overloaded { [&target](Array &&array) -> std::optional { - std::ranges::move(array.elements, std::back_inserter(target.elements)); + std::move(array.elements.begin(), array.elements.end(), std::back_inserter(target.elements)); return {}; }, [&target, &interpreter](Block &&block) -> std::optional { @@ -300,10 +300,10 @@ static inline Result builtin_play(Interpreter &i, Container args) static Result builtin_par(Interpreter &i, std::vector args) { Try(ensure_midi_connection_available(i, Midi_Connection_Type::Output, "par")); - assert(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert) + ensure(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert) if (args.size() == 1) { auto chord = get_if(args.front()); - assert(chord, "Par expects music value as first argument"); // TODO(assert) + ensure(chord, "Par expects music value as first argument"); // TODO(assert) Try(i.play(std::move(*chord))); return Value{}; } @@ -311,7 +311,7 @@ static Result builtin_par(Interpreter &i, std::vector args) { // Create chord that should sustain during playing of all other notes auto &ctx = i.context_stack.back(); auto chord = get_if(args.front()); - assert(chord, "par expects music value as first argument"); // TODO(assert) + ensure(chord, "par expects music value as first argument"); // TODO(assert) std::for_each(chord->notes.begin(), chord->notes.end(), [&](Note ¬e) { note = ctx.fill(note); }); @@ -674,7 +674,7 @@ 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"); // TODO(assert) + ensure(args.size() == 1, "typeof expects only one argument"); // TODO(assert) return Symbol(type_name(args.front())); } @@ -701,7 +701,7 @@ static Result builtin_shuffle(Interpreter &i, std::vector args) { static std::mt19937 rnd{std::random_device{}()}; auto array = Try(flatten(i, std::move(args))); - std::ranges::shuffle(array, rnd); + std::shuffle(array.begin(), array.end(), rnd); return array; } @@ -709,7 +709,7 @@ static Result builtin_shuffle(Interpreter &i, std::vector args) static Result builtin_permute(Interpreter &i, std::vector args) { auto array = Try(flatten(i, std::move(args))); - std::ranges::next_permutation(array); + std::next_permutation(array.begin(), array.end()); return array; } @@ -717,7 +717,7 @@ static Result builtin_permute(Interpreter &i, std::vector args) static Result builtin_sort(Interpreter &i, std::vector args) { auto array = Try(flatten(i, std::move(args))); - std::ranges::sort(array); + std::sort(array.begin(), array.end()); return array; } @@ -725,7 +725,7 @@ static Result builtin_sort(Interpreter &i, std::vector args) static Result builtin_reverse(Interpreter &i, std::vector args) { auto array = Try(flatten(i, std::move(args))); - std::ranges::reverse(array); + std::reverse(array.begin(), array.end()); return array; } @@ -733,7 +733,7 @@ static Result builtin_reverse(Interpreter &i, std::vector args) static Result builtin_min(Interpreter &i, std::vector args) { auto array = Try(deep_flat(i, args)); - if (auto min = std::ranges::min_element(array); min != array.end()) + if (auto min = std::min_element(array.begin(), array.end()); min != array.end()) return *min; return Value{}; } @@ -742,7 +742,7 @@ static Result builtin_min(Interpreter &i, std::vector args) static Result builtin_max(Interpreter &i, std::vector args) { auto array = Try(deep_flat(i, args)); - if (auto max = std::ranges::max_element(array); max != array.end()) + if (auto max = std::max_element(array.begin(), array.end()); max != array.end()) return *max; return Value{}; } diff --git a/musique/interpreter/builtin_operators.cc b/musique/interpreter/builtin_operators.cc index d8f7d9f..38306db 100644 --- a/musique/interpreter/builtin_operators.cc +++ b/musique/interpreter/builtin_operators.cc @@ -5,6 +5,8 @@ #include #include +#include + /// Intrinsic implementation primitive to ease operation vectorization static Result vectorize(auto &&operation, Interpreter &interpreter, Value lhs, Value rhs) { @@ -20,7 +22,7 @@ static Result vectorize(auto &&operation, Interpreter &interpreter, Value return array; } - assert(rhs_coll != nullptr, "Trying to vectorize two non-collections"); + ensure(rhs_coll != nullptr, "Trying to vectorize two non-collections"); Array array; for (auto i = 0u; i < rhs_coll->size(); ++i) { @@ -34,7 +36,7 @@ static Result vectorize(auto &&operation, Interpreter &interpreter, Value /// @invariant args.size() == 2 static Result vectorize(auto &&operation, Interpreter &interpreter, std::vector args) { - assert(args.size() == 2, "Vectorization primitive only supports two arguments"); + ensure(args.size() == 2, "Vectorization primitive only supports two arguments"); return vectorize(std::move(operation), interpreter, std::move(args.front()), std::move(args.back())); } diff --git a/musique/interpreter/env.cc b/musique/interpreter/env.cc index ba1cc23..9bd749d 100644 --- a/musique/interpreter/env.cc +++ b/musique/interpreter/env.cc @@ -7,7 +7,7 @@ std::shared_ptr Env::global = nullptr; std::shared_ptr Env::make() { auto new_env = new Env(); - assert(new_env, "Cannot construct new env"); + ensure(new_env, "Cannot construct new env"); return std::shared_ptr(new_env); } diff --git a/musique/interpreter/interpreter.cc b/musique/interpreter/interpreter.cc index 9534e45..1132676 100644 --- a/musique/interpreter/interpreter.cc +++ b/musique/interpreter/interpreter.cc @@ -35,7 +35,7 @@ Interpreter::Interpreter() context_stack.emplace_back(); // Environment initlialization - assert(!bool(Env::global), "Only one instance of interpreter can be at one time"); + ensure(!bool(Env::global), "Only one instance of interpreter can be at one time"); env = Env::global = Env::make(); // Builtins initialization @@ -83,16 +83,16 @@ Result Interpreter::eval(Ast &&ast) case Ast::Type::Binary: { - assert(ast.arguments.size() == 2, "Expected arguments of binary operation to be 2 long"); + ensure(ast.arguments.size() == 2, "Expected arguments of binary operation to be 2 long"); if (ast.token.source == "=") { auto lhs = std::move(ast.arguments.front()); auto rhs = std::move(ast.arguments.back()); - assert(lhs.type == Ast::Type::Literal && lhs.token.type == Token::Type::Symbol, + ensure(lhs.type == Ast::Type::Literal && lhs.token.type == Token::Type::Symbol, "Currently LHS of assigment must be an identifier"); // TODO(assert) Value *v = env->find(std::string(lhs.token.source)); - assert(v, "Cannot resolve variable: "s + std::string(lhs.token.source)); // TODO(assert) + ensure(v, "Cannot resolve variable: "s + std::string(lhs.token.source)); // TODO(assert) return *v = Try(eval(std::move(rhs)).with_location(ast.token.location)); } @@ -124,11 +124,11 @@ Result Interpreter::eval(Ast &&ast) auto lhs = std::move(ast.arguments.front()); auto rhs = std::move(ast.arguments.back()); auto const rhs_loc = rhs.location; - assert(lhs.type == Ast::Type::Literal && lhs.token.type == Token::Type::Symbol, + ensure(lhs.type == Ast::Type::Literal && lhs.token.type == Token::Type::Symbol, "Currently LHS of assigment must be an identifier"); // TODO(assert) Value *v = env->find(std::string(lhs.token.source)); - assert(v, "Cannot resolve variable: "s + std::string(lhs.token.source)); // TODO(assert) + ensure(v, "Cannot resolve variable: "s + std::string(lhs.token.source)); // TODO(assert) return *v = Try(op->second(*this, { *v, Try(eval(std::move(rhs)).with_location(rhs_loc)) }).with_location(ast.token.location)); @@ -179,9 +179,9 @@ Result Interpreter::eval(Ast &&ast) case Ast::Type::Variable_Declaration: { - assert(ast.arguments.size() == 2, "Only simple assigments are supported now"); - assert(ast.arguments.front().type == Ast::Type::Literal, "Only names are supported as LHS arguments now"); - assert(ast.arguments.front().token.type == Token::Type::Symbol, "Only names are supported as LHS arguments now"); + ensure(ast.arguments.size() == 2, "Only simple assigments are supported now"); + ensure(ast.arguments.front().type == Ast::Type::Literal, "Only names are supported as LHS arguments now"); + ensure(ast.arguments.front().token.type == Token::Type::Symbol, "Only names are supported as LHS arguments now"); env->force_define(std::string(ast.arguments.front().token.source), Try(eval(std::move(ast.arguments.back())))); return Value{}; } @@ -191,10 +191,10 @@ Result Interpreter::eval(Ast &&ast) { Block block; if (ast.type == Ast::Type::Lambda) { - auto parameters = std::span(ast.arguments.begin(), std::prev(ast.arguments.end())); + auto parameters = std::span(ast.arguments.data(), ast.arguments.size() - 1); block.parameters.reserve(parameters.size()); for (auto ¶m : parameters) { - assert(param.type == Ast::Type::Literal && param.token.type == Token::Type::Symbol, "Not a name in parameter section of Ast::lambda"); + ensure(param.type == Ast::Type::Literal && param.token.type == Token::Type::Symbol, "Not a name in parameter section of Ast::lambda"); block.parameters.push_back(std::string(std::move(param).token.source)); } } @@ -217,7 +217,7 @@ void Interpreter::enter_scope() void Interpreter::leave_scope() { - assert(env != Env::global, "Cannot leave global scope"); + ensure(env != Env::global, "Cannot leave global scope"); env = env->leave(); } diff --git a/musique/interpreter/interpreter.hh b/musique/interpreter/interpreter.hh index 461e722..83cb729 100644 --- a/musique/interpreter/interpreter.hh +++ b/musique/interpreter/interpreter.hh @@ -4,6 +4,7 @@ #include #include #include +#include /// Given program tree evaluates it into Value struct Interpreter diff --git a/musique/lexer/lexer.cc b/musique/lexer/lexer.cc index 079b3f3..fa8ca08 100644 --- a/musique/lexer/lexer.cc +++ b/musique/lexer/lexer.cc @@ -226,7 +226,7 @@ auto Lexer::consume_if(auto first, auto second) -> bool void Lexer::rewind() { - assert(last_rune_length != 0, "cannot rewind to not existing rune"); + ensure(last_rune_length != 0, "cannot rewind to not existing rune"); source = { source.data() - last_rune_length, source.size() + last_rune_length }; token_length -= last_rune_length; location = prev_location; diff --git a/musique/lexer/lines.cc b/musique/lexer/lines.cc index 114e17c..7be61c6 100644 --- a/musique/lexer/lines.cc +++ b/musique/lexer/lines.cc @@ -23,7 +23,7 @@ void Lines::add_file(std::string filename, std::string_view source) void Lines::add_line(std::string const& filename, std::string_view source, unsigned line_number) { - assert(line_number != 0, "Line number = 0 is invalid"); + ensure(line_number != 0, "Line number = 0 is invalid"); if (lines[filename].size() <= line_number) lines[filename].resize(line_number); lines[filename][line_number - 1] = source; diff --git a/musique/main.cc b/musique/main.cc index ffe3762..657b1e0 100644 --- a/musique/main.cc +++ b/musique/main.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,7 @@ extern "C" { #include } #else +#include extern "C" { #include } @@ -126,15 +128,13 @@ struct Runner midi::Rt_Midi midi; Interpreter interpreter; - std::thread midi_input_event_loop; - std::stop_source stop_source; /// Setup interpreter and midi connection with given port Runner(std::optional input_port, std::optional output_port) : midi() , interpreter{} { - assert(the == nullptr, "Only one instance of runner is supported"); + ensure(the == nullptr, "Only one instance of runner is supported"); the = this; bool const midi_go = bool(input_port) || bool(output_port); @@ -149,11 +149,6 @@ struct Runner std::cout << "Connected MIDI input to port " << *input_port << ". Ready for incoming messages!" << std::endl; midi.connect_input(*input_port); } - if (midi_go) { - interpreter.register_callbacks(); - midi_input_event_loop = std::thread([this] { handle_midi_event_loop(); }); - midi_input_event_loop.detach(); - } Env::global->force_define("say", +[](Interpreter &interpreter, std::vector args) -> Result { for (auto it = args.begin(); it != args.end(); ++it) { @@ -166,21 +161,11 @@ struct Runner }); } - ~Runner() - { - stop_source.request_stop(); - } - Runner(Runner const&) = delete; Runner(Runner &&) = delete; Runner& operator=(Runner const&) = delete; Runner& operator=(Runner &&) = delete; - void handle_midi_event_loop() - { - midi.input_event_loop(stop_source.get_token()); - } - /// Run given source std::optional run(std::string_view source, std::string_view filename, bool output = false) { @@ -221,7 +206,7 @@ bool is_tty() #ifdef _WIN32 return _isatty(STDOUT_FILENO); #else - return isatty(STDOUT_FILENO); + return isatty(fileno(stdout)); #endif } diff --git a/musique/midi/midi.hh b/musique/midi/midi.hh index 02a9db5..ac3ee18 100644 --- a/musique/midi/midi.hh +++ b/musique/midi/midi.hh @@ -3,7 +3,6 @@ #include #include #include -#include #include // Documentation of midi messages available at http://midi.teragonaudio.com/tech/midispec.htm @@ -48,8 +47,6 @@ namespace midi void send_program_change(uint8_t channel, uint8_t program) override; void send_controller_change(uint8_t channel, uint8_t controller_number, uint8_t value) override; - void input_event_loop(std::stop_token); - std::optional input; std::optional output; }; diff --git a/musique/midi/rt_midi.cc b/musique/midi/rt_midi.cc index ac9adb5..a8da393 100644 --- a/musique/midi/rt_midi.cc +++ b/musique/midi/rt_midi.cc @@ -36,9 +36,11 @@ try { void midi::Rt_Midi::connect_output(unsigned target) try { - assert(not output.has_value(), "Reconeccting is not supported yet"); + ensure(not output.has_value(), "Reconeccting is not supported yet"); output.emplace(); - output->openPort(target, "Musique output port"); + output->openVirtualPort("Musique output port"); + + // output->openPort(target, "Musique output port"); } catch (RtMidiError &error) { // TODO(error) std::cerr << "Failed to use MIDI connection: " << error.getMessage() << std::endl; @@ -47,7 +49,7 @@ try { void midi::Rt_Midi::connect_input(unsigned target) try { - assert(not input.has_value(), "Reconeccting is not supported yet"); + ensure(not input.has_value(), "Reconeccting is not supported yet"); input.emplace(); input->openPort(target, "Musique input port"); } catch (RtMidiError &error) { @@ -104,6 +106,3 @@ void midi::Rt_Midi::send_controller_change(uint8_t channel, uint8_t controller_n send_message(*output, std::array { std::uint8_t(Control_Change + channel), controller_number, value }); } -void midi::Rt_Midi::input_event_loop(std::stop_token) -{ -} diff --git a/musique/parser/parser.cc b/musique/parser/parser.cc index fff86a0..3398280 100644 --- a/musique/parser/parser.cc +++ b/musique/parser/parser.cc @@ -118,7 +118,7 @@ Result Parser::parse_variable_declaration() return std::move(lvalue).error(); } - assert(expect(Token::Type::Operator, ":="), "This function should always be called with valid sequence"); + ensure(expect(Token::Type::Operator, ":="), "This function should always be called with valid sequence"); consume(); return Ast::variable_declaration(lvalue->location, { *std::move(lvalue) }, Try(parse_expression())); } @@ -133,7 +133,7 @@ Result Parser::parse_infix_expression() || expect(Token::Type::Keyword, "or"); if (next_is_operator) { - assert(not expect(Token::Type::Operator, "."), "This should be handled by parse_index_expression"); + ensure(not expect(Token::Type::Operator, "."), "This should be handled by parse_index_expression"); auto op = consume(); Ast ast; @@ -161,7 +161,7 @@ Result Parser::parse_rhs_of_infix_expression(Ast lhs) return lhs; } - assert(not expect(Token::Type::Operator, "."), "This should be handled by parse_index_expression"); + ensure(not expect(Token::Type::Operator, "."), "This should be handled by parse_index_expression"); auto op = consume(); if (precedense(lhs.token.source) >= precedense(op.source)) { @@ -249,7 +249,7 @@ Result Parser::parse_atomic_expression() if (not expect(Token::Type::Close_Block)) { if (expect(Token::Type::Parameter_Separator)) { if (is_lambda) { - assert(false, "There should be error message that you cannot put multiple parameter separators in one block"); + ensure(false, "There should be error message that you cannot put multiple parameter separators in one block"); } else { // This may be a result of user trying to specify parameters from things that cannot be parameters (like chord literals) // or accidential hit of "|" on the keyboard. We can detect first case by ensuring that all tokens between this place @@ -467,7 +467,7 @@ Ast Ast::binary(Token token, Ast lhs, Ast rhs) Ast Ast::call(std::vector call) { - assert(!call.empty(), "Call must have at least pice of code that is beeing called"); + ensure(!call.empty(), "Call must have at least pice of code that is beeing called"); Ast ast; ast.type = Type::Call; diff --git a/musique/try.hh b/musique/try.hh index 4b31e20..6163e4a 100644 --- a/musique/try.hh +++ b/musique/try.hh @@ -46,13 +46,13 @@ struct Try_Traits> static std::nullopt_t yield_value(std::optional&& err) { - assert(not err.has_value(), "Trying to yield value from optional that contains error"); + ensure(not err.has_value(), "Trying to yield value from optional that contains error"); return std::nullopt; } static Error yield_error(std::optional&& err) { - assert(err.has_value(), "Trying to yield value from optional that NOT constains error"); + ensure(err.has_value(), "Trying to yield value from optional that NOT constains error"); return std::move(*err); } }; @@ -70,7 +70,7 @@ struct Try_Traits> static auto yield_value(Result val) { - assert(val.has_value(), "Trying to yield value from expected that contains error"); + ensure(val.has_value(), "Trying to yield value from expected that contains error"); if constexpr (std::is_void_v) { } else { return std::move(*val); @@ -79,7 +79,7 @@ struct Try_Traits> static Error yield_error(Result&& val) { - assert(not val.has_value(), "Trying to yield error from expected with value"); + ensure(not val.has_value(), "Trying to yield error from expected with value"); return std::move(val.error()); } }; diff --git a/musique/value/block.cc b/musique/value/block.cc index db9fb34..f8128e3 100644 --- a/musique/value/block.cc +++ b/musique/value/block.cc @@ -16,7 +16,7 @@ static inline std::optional guard_index(unsigned index, unsigned size) // TODO Add memoization Result Block::index(Interpreter &i, unsigned position) const { - assert(parameters.size() == 0, "cannot index into block with parameters (for now)"); + ensure(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); diff --git a/musique/value/chord.cc b/musique/value/chord.cc index abdcc64..7ff6884 100644 --- a/musique/value/chord.cc +++ b/musique/value/chord.cc @@ -17,7 +17,7 @@ Chord::Chord(std::vector &¬es) Chord Chord::from(std::string_view source) { auto note = Note::from(source); - assert(note.has_value(), "don't know how this could happen"); + ensure(note.has_value(), "don't know how this could happen"); Chord chord; source.remove_prefix(1 + (source[1] == '#')); @@ -120,7 +120,7 @@ Result Chord::operator()(Interpreter& interpreter, std::vector arg std::move(current.begin(), current.end(), std::back_inserter(array)); - assert(not array.empty(), "At least *this should be in this array"); + ensure(not array.empty(), "At least *this should be in this array"); return array; } diff --git a/musique/value/function.hh b/musique/value/function.hh index 45e244c..3190eb9 100644 --- a/musique/value/function.hh +++ b/musique/value/function.hh @@ -12,6 +12,7 @@ struct Function virtual Result operator()(Interpreter &i, std::vector params) const = 0; constexpr bool operator==(Function const&) const = default; + constexpr std::strong_ordering operator<=>(Function const&) const = default; }; #endif // MUSIQUE_VALUE_FUNCTION_HH diff --git a/musique/value/intrinsic.hh b/musique/value/intrinsic.hh index a91ad58..a8f0b4d 100644 --- a/musique/value/intrinsic.hh +++ b/musique/value/intrinsic.hh @@ -26,6 +26,8 @@ struct Intrinsic : Function /// Compares if function pointers are equal bool operator==(Intrinsic const&) const = default; + + std::strong_ordering operator<=>(Intrinsic const& rhs) const = default; }; #endif // MUSIQUE_VALUE_INTRINSIC_HH diff --git a/musique/value/note.cc b/musique/value/note.cc index 36fe2b3..867a649 100644 --- a/musique/value/note.cc +++ b/musique/value/note.cc @@ -70,7 +70,7 @@ std::optional Note::into_midi_note() const u8 Note::into_midi_note(i8 default_octave) const { - assert(bool(this->base), "Pause don't translate into MIDI"); + ensure(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; diff --git a/musique/value/number.cc b/musique/value/number.cc index c3efd47..3a5f505 100644 --- a/musique/value/number.cc +++ b/musique/value/number.cc @@ -19,7 +19,7 @@ auto Number::as_int() const -> i64 { // We don't perform GCD simplification in place due to constness auto self = simplify(); - assert(self.den == 1 || self.num == 0, "Implicit coarce to integer while holding fractional number is not allowed"); + ensure(self.den == 1 || self.num == 0, "Implicit coarce to integer while holding fractional number is not allowed"); return self.num; } @@ -203,7 +203,7 @@ static consteval auto compute_powers(Number::value_type multiplier) -> std::arra static Number::value_type pow10(usize n) { static constexpr auto Powers = compute_powers(10); - assert(n < Powers.size(), "Trying to compute power of 10 grater then current type can hold"); + ensure(n < Powers.size(), "Trying to compute power of 10 grater then current type can hold"); return Powers[n]; } diff --git a/musique/value/value.cc b/musique/value/value.cc index 355bccd..5400413 100644 --- a/musique/value/value.cc +++ b/musique/value/value.cc @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -7,9 +8,25 @@ #include #include +#include Value::Value() = default; +std::strong_ordering operator<=>(std::string const& lhs, std::string const& rhs) +{ + if (auto cmp = lhs.size() <=> rhs.size(); cmp == 0) { + if (auto cmp = strncmp(lhs.c_str(), rhs.c_str(), lhs.size()); cmp == 0) { + return std::strong_ordering::equal; + } else if (cmp < 0) { + return std::strong_ordering::less; + } else { + return std::strong_ordering::greater; + } + } else { + return cmp; + } +} + Result Value::from(Token t) { switch (t.type) { @@ -28,7 +45,7 @@ Result Value::from(Token t) case Token::Type::Chord: if (t.source.size() == 1 || (t.source.size() == 2 && t.source.back() == '#')) { auto maybe_note = Note::from(t.source); - assert(maybe_note.has_value(), "Somehow parser passed invalid note literal"); + ensure(maybe_note.has_value(), "Somehow parser passed invalid note literal"); return *maybe_note; } @@ -104,7 +121,7 @@ Result Value::index(Interpreter &i, unsigned position) const if (auto collection = get_if(data)) { return collection->index(i, position); } - assert(false, "Block indexing is not supported for this type"); // TODO(assert) + ensure(false, "Block indexing is not supported for this type"); // TODO(assert) unreachable(); } @@ -141,7 +158,7 @@ usize Value::size() const return collection->size(); } - assert(false, "This type does not support Value::size()"); // TODO(assert) + ensure(false, "This type does not support Value::size()"); // TODO(assert) unreachable(); } @@ -151,18 +168,12 @@ std::partial_ordering Value::operator<=>(Value const& rhs) const 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() - ); + return algo::lexicographical_compare(lhs.elements, rhs.elements); }, [](Chord const& lhs, Chord const& rhs) { - return std::lexicographical_compare_three_way( - lhs.notes.begin(), lhs.notes.end(), - rhs.notes.begin(), rhs.notes.end() - ); + return algo::lexicographical_compare(lhs.notes, rhs.notes); }, - [](T const& lhs, T const& rhs) -> std::partial_ordering requires std::three_way_comparable { + [](T const& lhs, T const& rhs) -> std::partial_ordering requires Three_Way_Comparable { return lhs <=> rhs; }, [](auto&&...) { return std::partial_ordering::unordered; }