From cb5eedb2a55fdce765a7fcb1ed1f80453a2959c7 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Sun, 21 Aug 2022 20:01:59 +0200 Subject: [PATCH] Unexpected empty source error improved --- include/musique.hh | 84 ++++++++++++++++++++++++---------------------- src/errors.cc | 36 +++++++++++++------- src/parser.cc | 7 ++-- 3 files changed, 72 insertions(+), 55 deletions(-) diff --git a/include/musique.hh b/include/musique.hh index daf6d88..c422de5 100644 --- a/include/musique.hh +++ b/include/musique.hh @@ -41,6 +41,47 @@ using i64 = std::int64_t; using usize = std::size_t; using isize = std::ptrdiff_t; +/// \brief Location describes code position in `file line column` format. +/// It's used both to represent position in source files provided +// to interpreter and internal interpreter usage. +struct Location +{ + std::string_view filename = ""; ///< File that location is pointing to + usize line = 1; ///< Line number (1 based) that location is pointing to + usize column = 1; ///< Column number (1 based) that location is pointing to + + /// Advances line and column numbers based on provided rune + /// + /// If rune is newline, then column is reset to 1, and line number is incremented. + /// Otherwise column number is incremented. + /// + /// @param rune Rune from which column and line numbers advancements are made. + Location& advance(u32 rune); + + bool operator==(Location const& rhs) const = default; + + //! Creates location at default filename with specified line and column number + static Location at(usize line, usize column); + + // Used to describe location of function call in interpreter (internal use only) +#if defined(__cpp_lib_source_location) + static Location caller(std::source_location loc = std::source_location::current()); +#elif (__has_builtin(__builtin_FILE) and __has_builtin(__builtin_LINE)) + static Location caller(char const* file = __builtin_FILE(), usize line = __builtin_LINE()); +#else +#error Cannot implement Location::caller function + /// Returns location of call in interpreter source code. + /// + /// Example of reporting where `foo()` was beeing called: + /// @code + /// void foo(Location loc = Location::caller()) { std::cout << loc << '\n'; } + /// @endcode + static Location caller(); +#endif +}; + +std::ostream& operator<<(std::ostream& os, Location const& location); + /// Error handling related functions and definitions namespace errors { @@ -53,6 +94,8 @@ namespace errors /// When parser was expecting code but encountered end of file struct Unexpected_Empty_Source { + enum { Block_Without_Closing_Bracket } reason; + std::optional start; }; /// When user passed numeric literal too big for numeric type @@ -183,47 +226,6 @@ namespace pretty template struct Overloaded : Lambdas... { using Lambdas::operator()...; }; -/// \brief Location describes code position in `file line column` format. -/// It's used both to represent position in source files provided -// to interpreter and internal interpreter usage. -struct Location -{ - std::string_view filename = ""; ///< File that location is pointing to - usize line = 1; ///< Line number (1 based) that location is pointing to - usize column = 1; ///< Column number (1 based) that location is pointing to - - /// Advances line and column numbers based on provided rune - /// - /// If rune is newline, then column is reset to 1, and line number is incremented. - /// Otherwise column number is incremented. - /// - /// @param rune Rune from which column and line numbers advancements are made. - Location& advance(u32 rune); - - bool operator==(Location const& rhs) const = default; - - //! Creates location at default filename with specified line and column number - static Location at(usize line, usize column); - - // Used to describe location of function call in interpreter (internal use only) -#if defined(__cpp_lib_source_location) - static Location caller(std::source_location loc = std::source_location::current()); -#elif (__has_builtin(__builtin_FILE) and __has_builtin(__builtin_LINE)) - static Location caller(char const* file = __builtin_FILE(), usize line = __builtin_LINE()); -#else -#error Cannot implement Location::caller function - /// Returns location of call in interpreter source code. - /// - /// Example of reporting where `foo()` was beeing called: - /// @code - /// void foo(Location loc = Location::caller()) { std::cout << loc << '\n'; } - /// @endcode - static Location caller(); -#endif -}; - -std::ostream& operator<<(std::ostream& os, Location const& location); - /// Guards that program exits if condition does not hold void assert(bool condition, std::string message, Location loc = Location::caller()); diff --git a/src/errors.cc b/src/errors.cc index 9b69c2b..31fa5d5 100644 --- a/src/errors.cc +++ b/src/errors.cc @@ -155,7 +155,7 @@ 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 = [&] { + auto const print_error_line = [&] (std::optional loc) { if (loc) { Lines::the.print(os, std::string(loc->filename), loc->line, loc->line); os << '\n'; @@ -167,7 +167,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err) os << "I cannot '" << err.name << "' due to lack of MIDI " << (err.is_input ? "input" : "output") << "connection\n"; os << "\n"; - print_error_line(); + print_error_line(loc); 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"; @@ -178,7 +178,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err) os << "I can't find it in surrounding scope or in one of parent's scopes\n"; os << "\n"; - print_error_line(); + print_error_line(loc); os << "Variables can only be references in scope (block) where they been created\n"; os << "or from parent blocks to variable block\n"; @@ -189,7 +189,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err) os << " Character printed: '" << utf8::Print{err.invalid_character} << "'\n"; os << "\n"; - print_error_line(); + print_error_line(loc); os << "Musique only accepts characters that are unicode letters or ascii numbers and punctuation\n"; }, @@ -199,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"; - print_error_line(); + print_error_line(loc); if (err.reason == std::errc::result_out_of_range) { os << "Declared number is outside of valid range of numbers that can be represented.\n"; @@ -212,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"; - print_error_line(); + print_error_line(loc); os << pretty::begin_comment << "This error is considered an internal one. It should not be displayed to the end user.\n"; os << "\n"; @@ -223,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"; - print_error_line(); + print_error_line(loc); if (err.what == "var") { os << "If you want to create variable inside expression try wrapping them inside parentheses like this:\n"; @@ -234,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"; - print_error_line(); + print_error_line(loc); if (err.type_name == "chord") { os << "Try renaming to different name or appending with something that is not part of chord literal like 'x'\n"; @@ -252,7 +252,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err) { os << "I tried to call function '" << err.name << "' but you gave me wrong types for it!\n"; - print_error_line(); + print_error_line(loc); os << "Make sure that all values matches one of supported signatures listed below!\n"; os << '\n'; @@ -267,7 +267,7 @@ 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 << "Make sure that both values matches one of supported signatures listed below!\n"; os << '\n'; - print_error_line(); + print_error_line(loc); if (err.name == "+") { os << "Addition only supports:\n"; } @@ -285,7 +285,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err) os << "Value of type " << err.type << " cannot be called.\n"; os << "\n"; - print_error_line(); + print_error_line(loc); 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"; @@ -299,9 +299,21 @@ std::ostream& operator<<(std::ostream& os, Error const& err) os << "when correct code for such expression would be [4;3]\n"; os << pretty::end; }, + [&](errors::Unexpected_Empty_Source const& err) { + switch (err.reason) { + break; case errors::Unexpected_Empty_Source::Block_Without_Closing_Bracket: + os << "Reached end of input when waiting for closing of a block ']'\n"; + os << "Expected block end after:\n\n"; + print_error_line(loc); + + if (err.start) { + os << "Which was introduced by block starting here:\n\n"; + print_error_line(err.start); + } + } + }, [&](errors::Undefined_Operator const&) { unimplemented(); }, [&](errors::Unexpected_Keyword const&) { unimplemented(); }, - [&](errors::Unexpected_Empty_Source const&) { unimplemented(); } }, err.details); return os; diff --git a/src/parser.cc b/src/parser.cc index f276b69..09b7b0a 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -272,8 +272,11 @@ Result Parser::parse_atomic_expression() } } return Error { - .details = errors::Unexpected_Empty_Source {}, - .location = {} + .details = errors::Unexpected_Empty_Source { + .reason = errors::Unexpected_Empty_Source::Block_Without_Closing_Bracket, + .start = opening.location + }, + .location = tokens.back().location }; } consume();