Report nice error when without MIDI connection
This commit is contained in:
parent
b06ad84514
commit
ab6ed8f45c
@ -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<void> play(Chord);
|
||||
|
||||
/// Add to global interpreter scope all builtin function definitions
|
||||
///
|
||||
|
@ -1,6 +1,8 @@
|
||||
#ifndef 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
|
||||
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
|
||||
|
2
lib/midi
2
lib/midi
@ -1 +1 @@
|
||||
Subproject commit ed5d959e60fd021ae4e545e0b39fe90cd1ec35a1
|
||||
Subproject commit f42b663f0d08fc629c7deb26cd32ee06fba76d83
|
@ -5,7 +5,6 @@
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
void Interpreter::register_callbacks()
|
||||
{
|
||||
assert(callbacks == nullptr, "This field should be uninitialized");
|
||||
@ -80,7 +79,7 @@ static inline Result<void> 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<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)
|
||||
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) {
|
||||
|
@ -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<decltype(Number::num)>::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";
|
||||
|
@ -202,9 +202,9 @@ void Interpreter::leave_scope()
|
||||
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();
|
||||
|
||||
@ -229,4 +229,37 @@ void Interpreter::play(Chord chord)
|
||||
}
|
||||
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 {};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user