From 6368659d9566c7dcc43e7dc1d207c732a39a3102 Mon Sep 17 00:00:00 2001 From: Suguru ARAKAWA Date: Fri, 5 Jul 2024 01:07:34 +0900 Subject: [PATCH] feat: revise syntax error messages. --- include/mizugaki/parser/sql_parser.h | 11 ++ include/mizugaki/parser/sql_parser_code.h | 69 ++++++++++ .../mizugaki/parser/sql_parser_diagnostic.h | 64 +++++----- src/CMakeLists.txt | 1 + .../parser/details/sequence_options.h | 1 + src/mizugaki/parser/sql_driver.cpp | 31 ++++- src/mizugaki/parser/sql_driver.h | 19 ++- src/mizugaki/parser/sql_parser.cpp | 7 + src/mizugaki/parser/sql_parser.yy | 112 ++++++++++++++-- src/mizugaki/parser/sql_parser_diagnostic.cpp | 65 ++++++++++ src/mizugaki/parser/sql_scanner.ll | 12 +- test/CMakeLists.txt | 1 + .../mizugaki/parser/sql_parser_error_test.cpp | 120 ++++++++++++++++++ .../parser/sql_parser_function_test.cpp | 9 ++ test/mizugaki/parser/sql_parser_misc_test.cpp | 6 - 15 files changed, 476 insertions(+), 52 deletions(-) create mode 100644 include/mizugaki/parser/sql_parser_code.h create mode 100644 src/mizugaki/parser/sql_parser_diagnostic.cpp create mode 100644 test/mizugaki/parser/sql_parser_error_test.cpp diff --git a/include/mizugaki/parser/sql_parser.h b/include/mizugaki/parser/sql_parser.h index c310251..97976d7 100644 --- a/include/mizugaki/parser/sql_parser.h +++ b/include/mizugaki/parser/sql_parser.h @@ -21,6 +21,16 @@ class sql_parser { /// @brief the result type. using result_type = sql_parser_result; + /// @brief default number of next token candidates to display on error. + static constexpr std::size_t default_max_expected_candidates = 5; + + /** + * @brief sets the max number of next token candidates to display on error. + * @param count the max number of candidates, or 0 to disable to display + * @see default_max_expected_candidates + */ + sql_parser max_expected_candidates(std::size_t count) noexcept; + /** * @brief sets the debug level. * @param level the debug level @@ -47,6 +57,7 @@ class sql_parser { [[nodiscard]] result_type operator()(::takatori::util::maybe_shared_ptr document) const; private: + std::size_t max_expected_candidates_ { default_max_expected_candidates }; int debug_ {}; }; diff --git a/include/mizugaki/parser/sql_parser_code.h b/include/mizugaki/parser/sql_parser_code.h new file mode 100644 index 0000000..3811266 --- /dev/null +++ b/include/mizugaki/parser/sql_parser_code.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +namespace mizugaki::parser { + +/** + * @brief represents diagnostic code of SQL analyzer. + */ +enum class sql_parser_code { + /// @brief unknown diagnostic. + unknown = 0, + + /// @brief diagnostics generated by parser system. + system, + + /// @brief violates syntax rule restrictions. + syntax_error, + + /// @brief the target feature is not supported. + unsupported_feature, + + /// @brief the document includes unrecognized characters. + invalid_character, + + /// @brief the token format is not appropriate for syntax. + invalid_token, + + /// @brief the occurred token is not appropriate for syntax rules. + unexpected_token, + + /// @brief the document includes unexpected EOF. + unexpected_eof, +}; + +/** + * @brief returns string representation of the value. + * @param value the target value + * @return the corresponded string representation + */ +inline constexpr std::string_view to_string_view(sql_parser_code value) noexcept { + using namespace std::string_view_literals; + using kind = sql_parser_code; + switch (value) { + case kind::unknown: return "unknown"sv; + case kind::system: return "system"sv; + case kind::syntax_error: return "syntax_error"sv; + case kind::unsupported_feature: return "unsupported_feature"sv; + case kind::invalid_character: return "invalid_character"sv; + case kind::invalid_token: return "invalid_token"sv; + case kind::unexpected_token: return "unexpected_token"sv; + case kind::unexpected_eof: return "unexpected_eof"sv; + } + std::abort(); +} + +/** + * @brief appends string representation of the given value. + * @param out the target output + * @param value the target value + * @return the output + */ +inline std::ostream& operator<<(std::ostream& out, sql_parser_code value) { + return out << to_string_view(value); +} + +} // namespace mizugaki::parser diff --git a/include/mizugaki/parser/sql_parser_diagnostic.h b/include/mizugaki/parser/sql_parser_diagnostic.h index a432e17..4f6033e 100644 --- a/include/mizugaki/parser/sql_parser_diagnostic.h +++ b/include/mizugaki/parser/sql_parser_diagnostic.h @@ -8,6 +8,8 @@ #include +#include "sql_parser_code.h" + namespace mizugaki::parser { /** @@ -15,6 +17,9 @@ namespace mizugaki::parser { */ class sql_parser_diagnostic { public: + /// @brief the diagnostic code type. + using code_type = sql_parser_code; + /// @brief the diagnostic message type. using message_type = std::string; @@ -31,75 +36,76 @@ class sql_parser_diagnostic { /** * @brief creates a new instance + * @param code the diagnostic code * @param message the diagnostic message * @param document the source document * @param region the diagnostic region on the document */ explicit sql_parser_diagnostic( + code_type code, message_type message, ::takatori::util::maybe_shared_ptr document = {}, - region_type region = {}) noexcept : - message_(std::move(message)), - document_ { std::move(document) }, - region_ { region } - {} + region_type region = {}) noexcept; /** * @brief returns whether or not this diagnostic information is valid. * @return true if this is valid * @return false otherwise */ - [[nodiscard]] bool is_valid() const noexcept { - return !message_.empty(); - } + [[nodiscard]] bool is_valid() const noexcept; /// @copydoc is_valid() - [[nodiscard]] explicit operator bool() const noexcept { - return is_valid(); - } + [[nodiscard]] explicit operator bool() const noexcept; + + /** + * @brief returns the diagnostic code. + * @return the diagnostic code + * @return sql_parser_code::unknown if this is an empty diagnostic + */ + [[nodiscard]] code_type& code() noexcept; + + /// @copydoc code() + [[nodiscard]] code_type const& code() const noexcept; /** * @brief returns the diagnostic message. * @return the diagnostic message * @return empty if this is an empty diagnostic */ - [[nodiscard]] message_type& message() noexcept { - return message_; - } + [[nodiscard]] message_type& message() noexcept; /// @copydoc message() - [[nodiscard]] message_type const& message() const noexcept { - return message_; - } + [[nodiscard]] message_type const& message() const noexcept; /** * @brief returns the source document. * @return the source document * @return empty if it is not sure */ - [[nodiscard]] ::takatori::util::maybe_shared_ptr& document() noexcept { - return document_; - } + [[nodiscard]] ::takatori::util::maybe_shared_ptr& document() noexcept; /// @copydoc document() - [[nodiscard]] ::takatori::util::maybe_shared_ptr const& document() const noexcept { - return document_; - } + [[nodiscard]] ::takatori::util::maybe_shared_ptr const& document() const noexcept; /** * @brief returns the region of this diagnostic. * @return the diagnostic region */ - [[nodiscard]] constexpr region_type& region() noexcept { - return region_; - } + [[nodiscard]] region_type& region() noexcept; /// @copydoc region() - [[nodiscard]] constexpr region_type const& region() const noexcept { - return region_; - } + [[nodiscard]] region_type const& region() const noexcept; + + /** + * @brief returns the contents of the diagnostic region. + * @return the contents + * @return empty string if the region is empty (may represent EOF) + * @attention the returned contents will be disabled after document() was disposed + */ + [[nodiscard]] std::string_view contents() const; private: + code_type code_ { code_type::unknown }; message_type message_ {}; ::takatori::util::maybe_shared_ptr document_ {}; region_type region_ {}; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 98f6285..f43522a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -148,6 +148,7 @@ add_library(mizugaki # SQL parser mizugaki/parser/sql_parser.cpp + mizugaki/parser/sql_parser_diagnostic.cpp mizugaki/parser/sql_parser_result.cpp mizugaki/parser/sql_scanner.cpp mizugaki/parser/sql_driver.cpp diff --git a/src/mizugaki/parser/details/sequence_options.h b/src/mizugaki/parser/details/sequence_options.h index 87578a9..bbdd4dd 100644 --- a/src/mizugaki/parser/details/sequence_options.h +++ b/src/mizugaki/parser/details/sequence_options.h @@ -33,6 +33,7 @@ inline bool set(sql_driver& driver, sequence_options& options, sequence_option_f if (options.saw.contains(field.kind)) { using ::takatori::util::string_builder; driver.error( + sql_parser_code::syntax_error, field.region, string_builder {} << "duplicate declaration of " << to_string_view(field.kind) diff --git a/src/mizugaki/parser/sql_driver.cpp b/src/mizugaki/parser/sql_driver.cpp index f4ee9ee..bddd45f 100644 --- a/src/mizugaki/parser/sql_driver.cpp +++ b/src/mizugaki/parser/sql_driver.cpp @@ -52,8 +52,12 @@ void sql_driver::success(std::vector> statem document_); } -void sql_driver::error(sql_driver::location_type location, sql_parser_result::message_type message) { +void sql_driver::error( + diagnostic_code_type code, + location_type location, + result_type::message_type message) { result_ = sql_parser_diagnostic { + code, std::move(message), document_, location, @@ -64,6 +68,10 @@ void sql_driver::add_comment(location_type location) { comments_.emplace_back(location); } +std::size_t &sql_driver::max_expected_candidates() noexcept { + return max_expected_candidates_; +} + std::vector& sql_driver::comments() noexcept { return comments_; } @@ -121,6 +129,27 @@ std::size_t sql_driver::to_size(ast::common::chars const& str) { // NOLINT: non- return result; } +std::string_view sql_driver::image(location_type location) { + if (location.size() == 0) { + return {}; + } + return document_->contents(location.first(), location.size()); +} + +bool sql_driver::check_regular_identifier(const ast::common::chars& str) { + if (str.size() >= 2) { + return str[0] != '_' || str[1] != '_'; + } + return true; +} + +bool sql_driver::check_delimited_identifier(const ast::common::chars &str) { + if (str.size() >= 3) { + return str[1] != '_' || str[2] != '_'; + } + return true; +} + ast::common::chars sql_driver::parse_regular_identifier(ast::common::chars str) { // NOLINT return str; } diff --git a/src/mizugaki/parser/sql_driver.h b/src/mizugaki/parser/sql_driver.h index 33cb66d..78de60b 100644 --- a/src/mizugaki/parser/sql_driver.h +++ b/src/mizugaki/parser/sql_driver.h @@ -10,6 +10,8 @@ #include #include +#include + namespace mizugaki::parser { @@ -18,6 +20,8 @@ class sql_driver { using document_type = ::takatori::document::document; using result_type = sql_parser_result; + using diagnostic_code_type = sql_parser_code; + using location_type = ast::node_region; template @@ -33,10 +37,15 @@ class sql_driver { void success(std::vector> statements); - void error(location_type location, result_type::message_type message); + void error( + diagnostic_code_type code, + location_type location, + result_type::message_type message); void add_comment(location_type location); + [[nodiscard]] std::size_t& max_expected_candidates() noexcept; + [[nodiscard]] std::vector& comments() noexcept; [[nodiscard]] std::vector const& comments() const noexcept; @@ -69,6 +78,12 @@ class sql_driver { [[nodiscard]] std::size_t to_size(ast::common::chars const& str); + [[nodiscard]] bool check_regular_identifier(ast::common::chars const& str); + + [[nodiscard]] bool check_delimited_identifier(ast::common::chars const& str); + + [[nodiscard]] std::string_view image(location_type location); + [[nodiscard]] ast::common::chars parse_regular_identifier(ast::common::chars str); [[nodiscard]] ast::common::chars parse_delimited_identifier(ast::common::chars const& str); @@ -95,6 +110,8 @@ class sql_driver { ::takatori::util::maybe_shared_ptr document_; std::vector comments_; result_type result_ {}; + + std::size_t max_expected_candidates_ {}; }; } // namespace sandbox diff --git a/src/mizugaki/parser/sql_parser.cpp b/src/mizugaki/parser/sql_parser.cpp index 32e44ae..5995d11 100644 --- a/src/mizugaki/parser/sql_parser.cpp +++ b/src/mizugaki/parser/sql_parser.cpp @@ -12,6 +12,11 @@ namespace mizugaki::parser { using ::takatori::document::basic_document; +sql_parser sql_parser::max_expected_candidates(std::size_t count) noexcept { + max_expected_candidates_ = count; + return *this; +} + sql_parser& sql_parser::set_debug(int level) noexcept { debug_ = level; return *this; @@ -31,6 +36,8 @@ sql_parser::result_type sql_parser::operator()(takatori::util::maybe_shared_ptr< sql_scanner scanner { input }; sql_driver driver { std::move(document) }; + driver.max_expected_candidates() = max_expected_candidates_; + sql_parser_generated parser { scanner, driver }; #if YYDEBUG diff --git a/src/mizugaki/parser/sql_parser.yy b/src/mizugaki/parser/sql_parser.yy index bbc06f7..a30b4f2 100644 --- a/src/mizugaki/parser/sql_parser.yy +++ b/src/mizugaki/parser/sql_parser.yy @@ -8,7 +8,7 @@ %define api.namespace { mizugaki::parser } %define api.parser.class { sql_parser_generated } %define api.token.prefix {} -%define parse.error verbose /* FIXME: 'detailed' requires bison 3.6 */ +%define parse.error custom %define parse.lac full %locations @@ -143,6 +143,7 @@ %code { #include + #include #include #include @@ -156,7 +157,94 @@ } void sql_parser_generated::error(location_type const& location, std::string const& message) { - driver.error(location, message); + driver.error(sql_parser_code::system, location, message); + } + + void sql_parser_generated::report_syntax_error(context const& ctxt) const { + using ::takatori::util::string_builder; + using kind = symbol_kind::symbol_kind_type; + auto symbol = ctxt.token(); + auto location = ctxt.location(); + auto image = driver.image(location); + + // check erroneous token kind + if (symbol == kind::S_ERROR) { + driver.error( + sql_parser_code::invalid_character, + location, + string_builder {} + << "unrecognized character: " << "\"" << image << "\"" + << string_builder::to_string); + return; + } + if (symbol == kind::S_UNCLOSED_BLOCK_COMMENT) { + driver.error( + sql_parser_code::unexpected_eof, + location, + "block comment (/* ~ */) is not closed"); + return; + } + if (symbol == kind::S_REGULAR_IDENTIFIER_RESTRICTED || + symbol == kind::S_DELIMITED_IDENTIFIER_RESTRICTED) { + driver.error( + sql_parser_code::invalid_token, + location, + string_builder {} + << "identifier must not start with two underscores: " << image + << string_builder::to_string); + return; + } + + auto max_candidates = driver.max_expected_candidates(); + std::string candidates {}; + if (max_candidates > 0) { + std::vector buffer {}; + buffer.resize(max_candidates + 1); + auto r = ctxt.expected_tokens(buffer.data(), static_cast(buffer.size())); + if (r == 0) { + if (buffer[0] == kind::S_YYEMPTY) { + buffer.resize(0); + } + } else { + buffer.resize(r); + } + if (!buffer.empty()) { + candidates.reserve(256); + for (std::size_t i = 0; i < buffer.size() - 1; ++i) { + if (i > 0) { + candidates.append(", "); + } + auto sym = symbol_name(buffer[i]); + candidates.append(sym); + } + if (buffer.size() > max_candidates) { + candidates.append(", ..."); + } + } + } + + if (symbol == kind::S_YYEOF) { + string_builder message {}; + message << "reached end of file"; + if (max_candidates > 0) { + message << ": expected one of {" << candidates << "}"; + } + driver.error( + sql_parser_code::unexpected_eof, + location, + message << string_builder::to_string); + return; + } + + string_builder message {}; + message << "appeared unexpected token: " << "\"" << image << "\""; + if (max_candidates > 0) { + message << ", expected one of {" << candidates << "}"; + } + driver.error( + sql_parser_code::unexpected_token, + location, + message << string_builder::to_string); } } // namespace mizugaki::parser @@ -681,6 +769,9 @@ %token REGULAR_IDENTIFIER %token DELIMITED_IDENTIFIER +%token REGULAR_IDENTIFIER_RESTRICTED +%token DELIMITED_IDENTIFIER_RESTRICTED + %token UNSIGNED_INTEGER %token EXACT_NUMERIC_LITERAL %token APPROXIMATE_NUMERIC_LITERAL @@ -2992,8 +3083,11 @@ value_expression_primary @$); } else { // syntax error - driver.error(@q, "syntax error: must be a type"); - $$ = nullptr; + driver.error( + sql_parser_code::syntax_error, + @q, + "qualifier of static method invocation must be a type"); + YYABORT; } } // FIXME: user defined aggregate functions @@ -4206,15 +4300,7 @@ identifier_chain ; identifier - : REGULAR_IDENTIFIER[t] - { - auto token = $t; - if (token.size() >= 2 && token[0] == '_' && token[1] == '_') { - driver.error(@$, "identifier starting with '__' is reserved for internal use"); - YYABORT; - } - $$ = driver.to_regular_identifier(std::move(token), @$); - } + : REGULAR_IDENTIFIER[t] { $$ = driver.to_regular_identifier($t, @$); } | DELIMITED_IDENTIFIER[t] { $$ = driver.to_delimited_identifier($t, @$); } // FIXME: move to individual name | contextual_identifier[n] diff --git a/src/mizugaki/parser/sql_parser_diagnostic.cpp b/src/mizugaki/parser/sql_parser_diagnostic.cpp new file mode 100644 index 0000000..3369bb7 --- /dev/null +++ b/src/mizugaki/parser/sql_parser_diagnostic.cpp @@ -0,0 +1,65 @@ +#include + +namespace mizugaki::parser { + +using ::takatori::util::maybe_shared_ptr; + +sql_parser_diagnostic::sql_parser_diagnostic( + code_type code, + message_type message, + maybe_shared_ptr document, + region_type region) noexcept: + code_ { code }, + message_(std::move(message)), + document_ { std::move(document) }, + region_ { region } +{} + +bool sql_parser_diagnostic::is_valid() const noexcept { + return code_ != code_type::unknown || !message_.empty(); +} + +sql_parser_diagnostic::operator bool() const noexcept { + return is_valid(); +} + +sql_parser_diagnostic::code_type& sql_parser_diagnostic::code() noexcept { + return code_; +} + +sql_parser_diagnostic::code_type const& sql_parser_diagnostic::code() const noexcept { + return code_; +} + +sql_parser_diagnostic::message_type& sql_parser_diagnostic::message() noexcept { + return message_; +} + +sql_parser_diagnostic::message_type const& sql_parser_diagnostic::message() const noexcept { + return message_; +} + +maybe_shared_ptr& sql_parser_diagnostic::document() noexcept { + return document_; +} + +maybe_shared_ptr const& sql_parser_diagnostic::document() const noexcept { + return document_; +} + +sql_parser_diagnostic::region_type& sql_parser_diagnostic::region() noexcept { + return region_; +} + +sql_parser_diagnostic::region_type const& sql_parser_diagnostic::region() const noexcept { + return region_; +} + +std::string_view sql_parser_diagnostic::contents() const { + if (region_.size() == 0) { + return {}; + } + return document_->contents(region_.first(), region_.size()); +} + +} // namespace mizugaki::parser diff --git a/src/mizugaki/parser/sql_scanner.ll b/src/mizugaki/parser/sql_scanner.ll index ece5159..dc7ef24 100644 --- a/src/mizugaki/parser/sql_scanner.ll +++ b/src/mizugaki/parser/sql_scanner.ll @@ -434,11 +434,19 @@ host_parameter_name ":"{identifier} "BOOL_OR" { return parser_type::make_BOOL_OR(location()); } {identifier} { - return parser_type::make_REGULAR_IDENTIFIER(get_image(driver), location()); + auto token = get_image(driver); + if (!driver.check_regular_identifier(token)) { + return parser_type::make_REGULAR_IDENTIFIER_RESTRICTED(location()); + } + return parser_type::make_REGULAR_IDENTIFIER(std::move(token), location()); } {delimited_identifier} { - return parser_type::make_DELIMITED_IDENTIFIER(get_image(driver), location()); + auto token = get_image(driver); + if (!driver.check_delimited_identifier(token)) { + return parser_type::make_DELIMITED_IDENTIFIER_RESTRICTED(location()); + } + return parser_type::make_DELIMITED_IDENTIFIER(std::move(token), location()); } {host_parameter_name} { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f2056e8..4d05faa 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -51,6 +51,7 @@ add_test_executable(mizugaki/parser/sql_parser_function_test.cpp) add_test_executable(mizugaki/parser/sql_parser_literal_test.cpp) add_test_executable(mizugaki/parser/sql_parser_type_test.cpp) add_test_executable(mizugaki/parser/sql_parser_misc_test.cpp) +add_test_executable(mizugaki/parser/sql_parser_error_test.cpp) # SQL analyzer add_test_executable(mizugaki/analyzer/sql_analyzer_test.cpp) diff --git a/test/mizugaki/parser/sql_parser_error_test.cpp b/test/mizugaki/parser/sql_parser_error_test.cpp new file mode 100644 index 0000000..bc53e25 --- /dev/null +++ b/test/mizugaki/parser/sql_parser_error_test.cpp @@ -0,0 +1,120 @@ +#include + +#include + +#include + +#include + +#include "utils.h" + +namespace mizugaki::parser { + +using namespace testing; + +class sql_parser_error_test : public ::testing::Test { +protected: + sql_parser_diagnostic parse_erroneous(std::string content) { + sql_parser parser; + auto result = parser("-", std::move(content)); + if (result.has_value()) { + ADD_FAILURE() << *result.value(); + return {}; + } + if (!result.has_diagnostic()) { + ADD_FAILURE() << "no diagnostics"; + return {}; + } + return std::move(result.diagnostic()); + } + + void print_result(sql_parser_diagnostic const& diagnostic) { + auto&& document = *diagnostic.document(); + std::cout << " source: " << document.contents(0, document.size()) << std::endl; + std::cout << " code: " << diagnostic.code() << std::endl; + std::cout << " message: " << diagnostic.message() << std::endl; + std::cout << " region: " << diagnostic.region() << std::endl; + std::cout << "contents: " << diagnostic.contents() << std::endl; + std::cout << std::endl; + } +}; + +TEST_F(sql_parser_error_test, invalid_character) { + std::string content { R"(SELECT $1)" }; + auto r = parse_erroneous(content); + ASSERT_TRUE(r); + + EXPECT_EQ(r.code(), sql_parser_code::invalid_character); + EXPECT_EQ(r.contents(), "$"); + print_result(r); +} + +TEST_F(sql_parser_error_test, invalid_character_cstyle_not_eq) { + std::string content { R"(SELECT 1 != 2)" }; + auto r = parse_erroneous(content); + ASSERT_TRUE(r); + + EXPECT_EQ(r.code(), sql_parser_code::invalid_character); + EXPECT_EQ(r.contents(), "!"); + print_result(r); +} + +TEST_F(sql_parser_error_test, unexpected_eof_in_block_comments) { + std::string content { R"(SELECT /* ... )" }; + auto r = parse_erroneous(content); + ASSERT_TRUE(r); + + EXPECT_EQ(r.code(), sql_parser_code::unexpected_eof); + EXPECT_EQ(r.contents(), "/*"); + print_result(r); +} + +TEST_F(sql_parser_error_test, regular_identifier_restricted) { + std::string content { R"(SELECT __x)" }; + auto r = parse_erroneous(content); + ASSERT_TRUE(r); + + EXPECT_EQ(r.code(), sql_parser_code::invalid_token); + EXPECT_EQ(r.contents(), "__x"); + print_result(r); +} + +TEST_F(sql_parser_error_test, delimited_identifier_restricted) { + std::string content { R"(SELECT "__x")" }; + auto r = parse_erroneous(content); + EXPECT_EQ(r.contents(), R"("__x")"); + ASSERT_TRUE(r); + + EXPECT_EQ(r.code(), sql_parser_code::invalid_token); + print_result(r); +} + +TEST_F(sql_parser_error_test, unexpected_token) { + std::string content { R"(SELECT 1 _ 2)" }; + auto r = parse_erroneous(content); + ASSERT_TRUE(r); + + EXPECT_EQ(r.code(), sql_parser_code::unexpected_token); + EXPECT_EQ(r.contents(), "_"); + print_result(r); +} + +TEST_F(sql_parser_error_test, unexpected_eof_in_grammar) { + std::string content { R"(SELECT * FROM)" }; + auto r = parse_erroneous(content); + ASSERT_TRUE(r); + + EXPECT_EQ(r.code(), sql_parser_code::unexpected_eof); + print_result(r); +} + +TEST_F(sql_parser_error_test, unexpected_token_less_candidates) { + std::string content { R"(VALUES 1)" }; + auto r = parse_erroneous(content); + ASSERT_TRUE(r); + + EXPECT_EQ(r.code(), sql_parser_code::unexpected_token); + print_result(r); +} + +} // namespace mizugaki::parser diff --git a/test/mizugaki/parser/sql_parser_function_test.cpp b/test/mizugaki/parser/sql_parser_function_test.cpp index c802130..be716f4 100644 --- a/test/mizugaki/parser/sql_parser_function_test.cpp +++ b/test/mizugaki/parser/sql_parser_function_test.cpp @@ -803,4 +803,13 @@ TEST_F(sql_parser_function_test, static_method_invocation_multiple_arguments) { })); } +TEST_F(sql_parser_function_test, static_method_invocation_not_type) { + auto result = parse("1::f(a)"); + EXPECT_FALSE(result); + + auto error = result.diagnostic(); + ASSERT_TRUE(error); + EXPECT_EQ(error.code(), sql_parser_code::syntax_error); +} + } // namespace mizugaki::parser diff --git a/test/mizugaki/parser/sql_parser_misc_test.cpp b/test/mizugaki/parser/sql_parser_misc_test.cpp index 4aa6de2..73ca7d3 100644 --- a/test/mizugaki/parser/sql_parser_misc_test.cpp +++ b/test/mizugaki/parser/sql_parser_misc_test.cpp @@ -131,12 +131,6 @@ TEST_F(sql_parser_misc_test, identifier_single_underscore) { })); } -TEST_F(sql_parser_misc_test, identifier_double_underscore) { - sql_parser parser; - auto result = parser("-", R"(TABLE __x;)"); - EXPECT_FALSE(result) << diagnostics(result); -} - TEST_F(sql_parser_misc_test, keyword_value) { sql_parser parser; auto result = parser("-", R"(TABLE VALUE;)");