Report nice error when without MIDI connection

This commit is contained in:
Robert Bendun 2022-08-19 16:06:33 +02:00
parent b06ad84514
commit ab6ed8f45c
6 changed files with 81 additions and 14 deletions

View File

@ -112,6 +112,16 @@ namespace errors
std::string name; 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. /// Collection of messages that are considered internal and should not be printed to the end user.
namespace internal namespace internal
{ {
@ -136,6 +146,7 @@ namespace errors
Literal_As_Identifier, Literal_As_Identifier,
Missing_Variable, Missing_Variable,
Not_Callable, Not_Callable,
Operation_Requires_Midi_Connection,
Undefined_Operator, Undefined_Operator,
Unexpected_Empty_Source, Unexpected_Empty_Source,
Unexpected_Keyword, Unexpected_Keyword,
@ -952,7 +963,7 @@ struct Interpreter
void leave_scope(); void leave_scope();
/// Play note resolving any missing parameters with context via `midi_connection` member. /// Play note resolving any missing parameters with context via `midi_connection` member.
void play(Chord); Result<void> play(Chord);
/// Add to global interpreter scope all builtin function definitions /// Add to global interpreter scope all builtin function definitions
/// ///

View File

@ -1,6 +1,8 @@
#ifndef Musique_Internal_HH #ifndef Musique_Internal_HH
#define Musique_Internal_HH #define Musique_Internal_HH
#include <musique.hh>
/// Binary operation may be vectorized when there are two argument which one is indexable and other is not /// 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<Value> const& args) static inline bool may_be_vectorized(std::vector<Value> const& args)
{ {
@ -60,4 +62,7 @@ struct Interpreter::Incoming_Midi_Callbacks
} }
}; };
enum class Midi_Connection_Type { Output, Input };
Result<void> ensure_midi_connection_available(Interpreter&, Midi_Connection_Type, std::string_view operation_name);
#endif #endif

@ -1 +1 @@
Subproject commit ed5d959e60fd021ae4e545e0b39fe90cd1ec35a1 Subproject commit f42b663f0d08fc629c7deb26cd32ee06fba76d83

View File

@ -5,7 +5,6 @@
#include <memory> #include <memory>
#include <iostream> #include <iostream>
void Interpreter::register_callbacks() void Interpreter::register_callbacks()
{ {
assert(callbacks == nullptr, "This field should be uninitialized"); assert(callbacks == nullptr, "This field should be uninitialized");
@ -80,7 +79,7 @@ static inline Result<void> play_notes(Interpreter &interpreter, T args)
break; break;
case Value::Type::Music: case Value::Type::Music:
interpreter.play(arg.chord); Try(interpreter.play(arg.chord));
break; break;
default: default:
@ -294,9 +293,11 @@ error:
global.force_define("oct", &ctx_read_write_property<&Context::octave>); global.force_define("oct", &ctx_read_write_property<&Context::octave>);
global.force_define("par", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> { global.force_define("par", +[](Interpreter &i, std::vector<Value> args) -> Result<Value> {
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) assert(args.size() >= 1, "par only makes sense for at least one argument"); // TODO(assert)
if (args.size() == 1) { if (args.size() == 1) {
i.play(std::move(args.front()).chord); Try(i.play(std::move(args.front()).chord));
return Value{}; return Value{};
} }
@ -309,7 +310,7 @@ error:
} }
for (auto it = std::next(args.begin()); it != args.end(); ++it) { 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) { for (auto const& note : chord.notes) {

View File

@ -134,6 +134,7 @@ void unreachable(Location loc)
std::ostream& operator<<(std::ostream& os, Error const& err) std::ostream& operator<<(std::ostream& os, Error const& err)
{ {
std::string_view short_description = visit(Overloaded { 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::Missing_Variable const&) { return "Cannot find variable"; },
[](errors::Failed_Numeric_Parsing const&) { return "Failed to parse a number"; }, [](errors::Failed_Numeric_Parsing const&) { return "Failed to parse a number"; },
[](errors::Not_Callable const&) { return "Value not callable"; }, [](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); error_heading(os, err.location, Error_Level::Error, short_description);
auto const loc = err.location; 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 { 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) { [&](errors::Missing_Variable const& err) {
os << "I encountered '" << err.name << "' that looks like variable but\n"; 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 << "I can't find it in surrounding scope or in one of parent's scopes\n";
os << "\n"; os << "\n";
Lines::the.print(os, std::string(loc->filename), loc->line, loc->line); print_error_line();
os << "\n"; os << "\n";
os << "Variables can only be references in scope (block) where they been created\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 << " Character printed: '" << utf8::Print{err.invalid_character} << "'\n";
os << "\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"; 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<decltype(Number::num)>::min(); constexpr auto Min = std::numeric_limits<decltype(Number::num)>::min();
os << "I tried to parse numeric literal, but I failed.\n\n"; 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) { if (err.reason == std::errc::result_out_of_range) {
os << "\nDeclared number is outside of valid range of numbers that can be represented.\n"; 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 type: " << ut.type << '\n';
os << " Token source: " << ut.source << "\n\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 << pretty::begin_comment << "\nThis error is considered an internal one. It should not be displayed to the end user.\n";
os << "\n"; os << "\n";
@ -206,7 +223,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
[&](errors::Expected_Expression_Separator_Before const& err) { [&](errors::Expected_Expression_Separator_Before const& err) {
os << "I failed to parse following code, due to missing semicolon before it!\n\n"; 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") { if (err.what == "var") {
os << "\nIf you want to create variable inside expression try wrapping them inside parentheses like this:\n"; 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) { [&](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"; 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") { 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"; os << "\nTry renaming to different name or appending with something that is not part of chord literal like 'x'\n";

View File

@ -202,9 +202,9 @@ void Interpreter::leave_scope()
env = env->leave(); env = env->leave();
} }
void Interpreter::play(Chord chord) Result<void> 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(); 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); midi_connection->send_note_off(0, *note.into_midi_note(), 127);
} }
return {};
}
Result<void> 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 {};
} }