Improved error location reporting

This commit is contained in:
Robert Bendun 2022-08-21 19:12:12 +02:00
parent 99482d4c7c
commit 183907a0ed
5 changed files with 68 additions and 18 deletions

View File

@ -297,6 +297,17 @@ struct [[nodiscard("This value may contain critical error, so it should NOT be i
} }
} }
/// Fill error location if it's empty and we have an error
inline Result<T> with_location(Location location) &&
{
if (!Storage::has_value()) {
if (auto& target = Storage::error().location; !target || target == Location{}) {
target = location;
}
}
return *this;
}
inline tl::expected<T, Error> to_expected() && inline tl::expected<T, Error> to_expected() &&
{ {
return *static_cast<Storage*>(this); return *static_cast<Storage*>(this);

View File

@ -156,8 +156,9 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
auto const loc = err.location; auto const loc = err.location;
auto const print_error_line = [&] { auto const print_error_line = [&] {
if (loc->filename != "") { if (loc) {
Lines::the.print(os, std::string(loc->filename), loc->line, loc->line); Lines::the.print(os, std::string(loc->filename), loc->line, loc->line);
os << '\n';
} }
}; };
@ -179,7 +180,6 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
print_error_line(); print_error_line();
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";
os << "or from parent blocks to variable block\n"; os << "or from parent blocks to variable block\n";
}, },
@ -202,7 +202,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
print_error_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 << "Declared number is outside of valid range of numbers that can be represented.\n";
os << "Only numbers in range [" << Min << ", " << Max << "] are supported\n"; os << "Only numbers in range [" << Min << ", " << Max << "] are supported\n";
} }
}, },
@ -214,7 +214,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
print_error_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 << "This error is considered an internal one. It should not be displayed to the end user.\n";
os << "\n"; os << "\n";
os << "This error message is temporary and will be replaced by better one in the future\n"; os << "This error message is temporary and will be replaced by better one in the future\n";
os << pretty::end; os << pretty::end;
@ -226,7 +226,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
print_error_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 << "If you want to create variable inside expression try wrapping them inside parentheses like this:\n";
os << " 10 + (var i = 20)\n"; os << " 10 + (var i = 20)\n";
} }
}, },
@ -237,7 +237,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
print_error_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 << "Try renaming to different name or appending with something that is not part of chord literal like 'x'\n";
os << pretty::begin_comment << os << pretty::begin_comment <<
"\nMusical notation names are reserved for chord and note notations,\n" "\nMusical notation names are reserved for chord and note notations,\n"
@ -251,6 +251,9 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
case errors::Unsupported_Types_For::Function: case errors::Unsupported_Types_For::Function:
{ {
os << "I tried to call function '" << err.name << "' but you gave me wrong types for it!\n"; os << "I tried to call function '" << err.name << "' but you gave me wrong types for it!\n";
print_error_line();
os << "Make sure that all values matches one of supported signatures listed below!\n"; os << "Make sure that all values matches one of supported signatures listed below!\n";
os << '\n'; os << '\n';
@ -264,6 +267,8 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
os << "I tried and failed to evaluate operator '" << err.name << "' due to values with wrong types provided\n"; os << "I tried and failed to evaluate operator '" << err.name << "' due to values with wrong types provided\n";
os << "Make sure that both values matches one of supported signatures listed below!\n"; os << "Make sure that both values matches one of supported signatures listed below!\n";
os << '\n'; os << '\n';
print_error_line();
if (err.name == "+") { os << "Addition only supports:\n"; } if (err.name == "+") { os << "Addition only supports:\n"; }
else { os << "Operator '" << err.name << "' only supports:\n"; } else { os << "Operator '" << err.name << "' only supports:\n"; }
@ -276,7 +281,24 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
} }
}, },
[&](errors::Not_Callable const&) { unimplemented(); }, [&](errors::Not_Callable const& err) {
os << "Value of type " << err.type << " cannot be called.\n";
os << "\n";
print_error_line();
os << "Only values of this types can be called:\n";
os << " - musical values like c, c47, (c&g) can be called to provide octave and duration\n";
os << " - blocks can be called to compute their body like this: [i | i + 1] 3\n";
os << " - builtin functions like if, par, floor\n";
os << "\n";
os << pretty::begin_comment;
os << "Parhaps you forgot to include semicolon between successive elements?\n";
os << "Common problem is writing [4 3] as list with elements 4 and 3,\n";
os << "when correct code for such expression would be [4;3]\n";
os << pretty::end;
},
[&](errors::Undefined_Operator const&) { unimplemented(); }, [&](errors::Undefined_Operator const&) { unimplemented(); },
[&](errors::Unexpected_Keyword const&) { unimplemented(); }, [&](errors::Unexpected_Keyword const&) { unimplemented(); },
[&](errors::Unexpected_Empty_Source const&) { unimplemented(); } [&](errors::Unexpected_Empty_Source const&) { unimplemented(); }

View File

@ -87,18 +87,20 @@ Result<Value> Interpreter::eval(Ast &&ast)
Value *v = env->find(std::string(lhs.token.source)); Value *v = env->find(std::string(lhs.token.source));
assert(v, "Cannot resolve variable: "s + std::string(lhs.token.source)); // TODO(assert) assert(v, "Cannot resolve variable: "s + std::string(lhs.token.source)); // TODO(assert)
return *v = Try(eval(std::move(rhs))); return *v = Try(eval(std::move(rhs)).with_location(ast.token.location));
} }
if (ast.token.source == "and" || ast.token.source == "or") { if (ast.token.source == "and" || ast.token.source == "or") {
auto lhs = std::move(ast.arguments.front()); auto lhs = std::move(ast.arguments.front());
auto rhs = std::move(ast.arguments.back()); auto rhs = std::move(ast.arguments.back());
auto result = Try(eval(std::move(lhs))); auto lhs_loc = lhs.location, rhs_loc = rhs.location;
auto result = Try(eval(std::move(lhs)).with_location(std::move(lhs_loc)));
if (ast.token.source == "or" ? result.truthy() : result.falsy()) { if (ast.token.source == "or" ? result.truthy() : result.falsy()) {
return result; return result;
} else { } else {
return eval(std::move(rhs)); return eval(std::move(rhs)).with_location(rhs_loc);
} }
} }
@ -115,12 +117,15 @@ Result<Value> Interpreter::eval(Ast &&ast)
auto lhs = std::move(ast.arguments.front()); auto lhs = std::move(ast.arguments.front());
auto rhs = std::move(ast.arguments.back()); 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, assert(lhs.type == Ast::Type::Literal && lhs.token.type == Token::Type::Symbol,
"Currently LHS of assigment must be an identifier"); // TODO(assert) "Currently LHS of assigment must be an identifier"); // TODO(assert)
Value *v = env->find(std::string(lhs.token.source)); Value *v = env->find(std::string(lhs.token.source));
assert(v, "Cannot resolve variable: "s + std::string(lhs.token.source)); // TODO(assert) assert(v, "Cannot resolve variable: "s + std::string(lhs.token.source)); // TODO(assert)
return *v = Try(op->second(*this, { *v, Try(eval(std::move(rhs))) })); return *v = Try(op->second(*this, {
*v, Try(eval(std::move(rhs)).with_location(rhs_loc))
}).with_location(ast.token.location));
} }
return Error { return Error {
@ -132,10 +137,11 @@ Result<Value> Interpreter::eval(Ast &&ast)
std::vector<Value> values; std::vector<Value> values;
values.reserve(ast.arguments.size()); values.reserve(ast.arguments.size());
for (auto& a : ast.arguments) { for (auto& a : ast.arguments) {
values.push_back(Try(eval(std::move(a)))); auto const a_loc = a.location;
values.push_back(Try(eval(std::move(a)).with_location(a_loc)));
} }
return op->second(*this, std::move(values)); return op->second(*this, std::move(values)).with_location(ast.token.location);
} }
break; break;
@ -153,6 +159,7 @@ Result<Value> Interpreter::eval(Ast &&ast)
case Ast::Type::Call: case Ast::Type::Call:
{ {
auto call_location = ast.arguments.front().location;
Value func = Try(eval(std::move(ast.arguments.front()))); Value func = Try(eval(std::move(ast.arguments.front())));
std::vector<Value> values; std::vector<Value> values;
@ -160,7 +167,8 @@ Result<Value> Interpreter::eval(Ast &&ast)
for (auto& a : std::span(ast.arguments).subspan(1)) { for (auto& a : std::span(ast.arguments).subspan(1)) {
values.push_back(Try(eval(std::move(a)))); values.push_back(Try(eval(std::move(a))));
} }
return std::move(func)(*this, std::move(values)); return std::move(func)(*this, std::move(values))
.with_location(std::move(call_location));
} }
case Ast::Type::Variable_Declaration: case Ast::Type::Variable_Declaration:

View File

@ -53,6 +53,17 @@ static std::string_view pop(std::span<char const*> &span)
std::exit(1); std::exit(1);
} }
void print_repl_help()
{
std::cout <<
"List of all available commands of Musique interactive mode:\n"
":exit - quit interactive mode\n"
":help - prints this help message\n"
":!<command> - allows for execution of any shell command\n"
":clear - clears screen\n"
;
}
/// Trim spaces from left an right /// Trim spaces from left an right
static void trim(std::string_view &s) static void trim(std::string_view &s)
{ {
@ -301,6 +312,7 @@ static Result<void> Main(std::span<char const*> args)
if (command == "exit") { break; } if (command == "exit") { break; }
if (command == "clear") { std::cout << "\x1b[1;1H\x1b[2J" << std::flush; continue; } if (command == "clear") { std::cout << "\x1b[1;1H\x1b[2J" << std::flush; continue; }
if (command.starts_with('!')) { Ignore(system(command.data() + 1)); continue; } if (command.starts_with('!')) { Ignore(system(command.data() + 1)); continue; }
if (command == "help") { print_repl_help(); continue; }
std::cerr << "musique: error: unrecognized REPL command '" << command << '\'' << std::endl; std::cerr << "musique: error: unrecognized REPL command '" << command << '\'' << std::endl;
continue; continue;
} }

View File

@ -168,11 +168,8 @@ Result<Value> Value::operator()(Interpreter &i, std::vector<Value> args)
return *this; return *this;
} }
default: default:
// TODO Fill location
return Error { return Error {
.details = errors::Not_Callable { .details = errors::Not_Callable { .type = type_name(type) },
.type = type_name(type)
},
.location = std::nullopt, .location = std::nullopt,
}; };
} }