From ab6ed8f45c31d4bf6fd2465c0f3c26ae607bd847 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Fri, 19 Aug 2022 16:06:33 +0200 Subject: [PATCH] Report nice error when without MIDI connection --- include/musique.hh | 13 ++++++++++++- include/musique_internal.hh | 5 +++++ lib/midi | 2 +- src/builtin_functions.cc | 9 +++++---- src/errors.cc | 29 +++++++++++++++++++++++------ src/interpreter.cc | 37 +++++++++++++++++++++++++++++++++++-- 6 files changed, 81 insertions(+), 14 deletions(-) diff --git a/include/musique.hh b/include/musique.hh index bbff201..51166ef 100644 --- a/include/musique.hh +++ b/include/musique.hh @@ -112,6 +112,16 @@ namespace errors std::string name; }; + /// When user tries to invoke some MIDI action but haven't established MIDI connection + struct Operation_Requires_Midi_Connection + { + /// If its input or output connection missing + bool is_input; + + /// Name of the operation that was beeing invoked + std::string name; + }; + /// Collection of messages that are considered internal and should not be printed to the end user. namespace internal { @@ -136,6 +146,7 @@ namespace errors Literal_As_Identifier, Missing_Variable, Not_Callable, + Operation_Requires_Midi_Connection, Undefined_Operator, Unexpected_Empty_Source, Unexpected_Keyword, @@ -952,7 +963,7 @@ struct Interpreter void leave_scope(); /// Play note resolving any missing parameters with context via `midi_connection` member. - void play(Chord); + Result play(Chord); /// Add to global interpreter scope all builtin function definitions /// diff --git a/include/musique_internal.hh b/include/musique_internal.hh index 5cc4b51..d494f70 100644 --- a/include/musique_internal.hh +++ b/include/musique_internal.hh @@ -1,6 +1,8 @@ #ifndef Musique_Internal_HH #define Musique_Internal_HH +#include + /// 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) { @@ -60,4 +62,7 @@ struct Interpreter::Incoming_Midi_Callbacks } }; +enum class Midi_Connection_Type { Output, Input }; +Result ensure_midi_connection_available(Interpreter&, Midi_Connection_Type, std::string_view operation_name); + #endif diff --git a/lib/midi b/lib/midi index ed5d959..f42b663 160000 --- a/lib/midi +++ b/lib/midi @@ -1 +1 @@ -Subproject commit ed5d959e60fd021ae4e545e0b39fe90cd1ec35a1 +Subproject commit f42b663f0d08fc629c7deb26cd32ee06fba76d83 diff --git a/src/builtin_functions.cc b/src/builtin_functions.cc index f05f03c..5c9700c 100644 --- a/src/builtin_functions.cc +++ b/src/builtin_functions.cc @@ -5,7 +5,6 @@ #include #include - void Interpreter::register_callbacks() { assert(callbacks == nullptr, "This field should be uninitialized"); @@ -80,7 +79,7 @@ static inline Result play_notes(Interpreter &interpreter, T args) break; case Value::Type::Music: - interpreter.play(arg.chord); + Try(interpreter.play(arg.chord)); break; default: @@ -294,9 +293,11 @@ error: global.force_define("oct", &ctx_read_write_property<&Context::octave>); global.force_define("par", +[](Interpreter &i, std::vector args) -> Result { + 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) if (args.size() == 1) { - i.play(std::move(args.front()).chord); + Try(i.play(std::move(args.front()).chord)); return Value{}; } @@ -309,7 +310,7 @@ error: } for (auto it = std::next(args.begin()); it != args.end(); ++it) { - i.play(std::move(*it).chord); + Try(i.play(std::move(*it).chord)); } for (auto const& note : chord.notes) { diff --git a/src/errors.cc b/src/errors.cc index 35f48f7..e94eaa8 100644 --- a/src/errors.cc +++ b/src/errors.cc @@ -134,6 +134,7 @@ void unreachable(Location loc) std::ostream& operator<<(std::ostream& os, Error const& err) { std::string_view short_description = visit(Overloaded { + [](errors::Operation_Requires_Midi_Connection const&) { return "Operation requires MIDI connection"; }, [](errors::Missing_Variable const&) { return "Cannot find variable"; }, [](errors::Failed_Numeric_Parsing const&) { return "Failed to parse a number"; }, [](errors::Not_Callable const&) { return "Value not callable"; }, @@ -154,13 +155,29 @@ std::ostream& operator<<(std::ostream& os, Error const& err) error_heading(os, err.location, Error_Level::Error, short_description); auto const loc = err.location; + auto const print_error_line = [&] { + if (loc->filename != "") { + Lines::the.print(os, std::string(loc->filename), loc->line, loc->line); + } + }; + visit(Overloaded { + [&](errors::Operation_Requires_Midi_Connection const& err) { + os << "I cannot '" << err.name << "' due to lack of MIDI " << (err.is_input ? "input" : "output") << "connection\n"; + os << "\n"; + + print_error_line(); + + os << "You can connect to given MIDI device by specifing port when running musique command like:\n"; + os << (err.is_input ? " --input" : " --output") << " PORT\n"; + os << "You can list all available ports with --list flag\n"; + }, [&](errors::Missing_Variable const& err) { os << "I encountered '" << err.name << "' that looks like variable but\n"; os << "I can't find it in surrounding scope or in one of parent's scopes\n"; os << "\n"; - Lines::the.print(os, std::string(loc->filename), loc->line, loc->line); + print_error_line(); os << "\n"; os << "Variables can only be references in scope (block) where they been created\n"; @@ -172,7 +189,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err) os << " Character printed: '" << utf8::Print{err.invalid_character} << "'\n"; os << "\n"; - Lines::the.print(os, std::string(loc->filename), loc->line, loc->line); + print_error_line(); os << "Musique only accepts characters that are unicode letters or ascii numbers and punctuation\n"; }, @@ -182,7 +199,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err) constexpr auto Min = std::numeric_limits::min(); os << "I tried to parse numeric literal, but I failed.\n\n"; - Lines::the.print(os, std::string(loc->filename), loc->line, loc->line); + print_error_line(); if (err.reason == std::errc::result_out_of_range) { os << "\nDeclared number is outside of valid range of numbers that can be represented.\n"; @@ -195,7 +212,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err) os << " Token type: " << ut.type << '\n'; os << " Token source: " << ut.source << "\n\n"; - Lines::the.print(os, std::string(loc->filename), loc->line, loc->line); + print_error_line(); os << pretty::begin_comment << "\nThis error is considered an internal one. It should not be displayed to the end user.\n"; os << "\n"; @@ -206,7 +223,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err) [&](errors::Expected_Expression_Separator_Before const& err) { os << "I failed to parse following code, due to missing semicolon before it!\n\n"; - Lines::the.print(os, std::string(loc->filename), loc->line, loc->line); + print_error_line(); if (err.what == "var") { os << "\nIf you want to create variable inside expression try wrapping them inside parentheses like this:\n"; @@ -217,7 +234,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err) [&](errors::Literal_As_Identifier const& err) { os << "I expected an identifier in " << err.context << ", but found" << (err.type_name.empty() ? "" : " ") << err.type_name << " value = '" << err.source << "'\n\n"; - Lines::the.print(os, std::string(loc->filename), loc->line, loc->line); + print_error_line(); if (err.type_name == "chord") { os << "\nTry renaming to different name or appending with something that is not part of chord literal like 'x'\n"; diff --git a/src/interpreter.cc b/src/interpreter.cc index ecbbf5a..a320946 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -202,9 +202,9 @@ void Interpreter::leave_scope() env = env->leave(); } -void Interpreter::play(Chord chord) +Result Interpreter::play(Chord chord) { - assert(midi_connection, "To play midi Interpreter requires instance of MIDI connection"); + Try(ensure_midi_connection_available(*this, Midi_Connection_Type::Output, "play")); auto &ctx = context_stack.back(); @@ -229,4 +229,37 @@ void Interpreter::play(Chord chord) } midi_connection->send_note_off(0, *note.into_midi_note(), 127); } + + return {}; +} + +Result ensure_midi_connection_available(Interpreter &i, Midi_Connection_Type m, std::string_view operation_name) +{ + switch (m) { + break; case Midi_Connection_Type::Output: + if (i.midi_connection == nullptr || !i.midi_connection->supports_output()) { + return Error { + .details = errors::Operation_Requires_Midi_Connection { + .is_input = false, + .name = std::string(operation_name), + }, + .location = {} + }; + } + + break; case Midi_Connection_Type::Input: + if (i.midi_connection == nullptr || !i.midi_connection->supports_input()) { + return Error { + .details = errors::Operation_Requires_Midi_Connection { + .is_input = false, + .name = std::string(operation_name), + }, + .location = {} + }; + } + break; default: + unreachable(); + } + + return {}; }