From 6d719da80a5e3394d57c6115395a5e9b1d2ea9e1 Mon Sep 17 00:00:00 2001 From: AWE Henry Date: Mon, 15 Jan 2024 10:45:56 +0800 Subject: [PATCH] Update interpreter 1. Debug mode 2. Bug fix 3. Fix typo --- include/papilio/format.hpp | 2 +- include/papilio/format/fundamental.hpp | 2 +- include/papilio/script/interpreter.hpp | 134 +++++++++++++++++-------- src/script/interpreter.cpp | 5 - test/test_script_interpreter.cpp | 45 ++++++++- 5 files changed, 137 insertions(+), 51 deletions(-) diff --git a/include/papilio/format.hpp b/include/papilio/format.hpp index cd7ee03..5614573 100644 --- a/include/papilio/format.hpp +++ b/include/papilio/format.hpp @@ -52,7 +52,7 @@ namespace detail basic_format_parse_context parse_ctx(fmt, args); Context fmt_ctx(loc, out, args); - script::interpreter intp; + script::basic_interpreter intp; intp.format(parse_ctx, fmt_ctx); return fmt_ctx.out(); diff --git a/include/papilio/format/fundamental.hpp b/include/papilio/format/fundamental.hpp index b7cdbad..3c1c5d4 100644 --- a/include/papilio/format/fundamental.hpp +++ b/include/papilio/format/fundamental.hpp @@ -60,7 +60,7 @@ class std_formatter_parser using iterator = typename ParseContext::iterator; using result_type = std_formatter_data; - using interpreter_type = script::interpreter; + using interpreter_type = script::basic_interpreter; std::pair parse(ParseContext& ctx, std::u32string_view types) { diff --git a/include/papilio/script/interpreter.hpp b/include/papilio/script/interpreter.hpp index bc98bf4..e0ee833 100644 --- a/include/papilio/script/interpreter.hpp +++ b/include/papilio/script/interpreter.hpp @@ -464,15 +464,20 @@ PAPILIO_EXPORT class interpreter_base protected: [[noreturn]] - static void throw_end_of_string(); + static void throw_end_of_string() + { + throw make_error(script_error_code::end_of_string); + } [[noreturn]] static void throw_error(script_error_code ec); }; -PAPILIO_EXPORT template -class interpreter : public interpreter_base +PAPILIO_EXPORT template +class basic_interpreter : public interpreter_base { + using base = interpreter_base; + public: using char_type = typename FormatContext::char_type; using variable_type = basic_variable; @@ -485,7 +490,28 @@ class interpreter : public interpreter_base using parse_context = basic_format_parse_context; using iterator = typename parse_context::iterator; - interpreter() = default; + class script_error_ex : public base::script_error + { + public: + script_error_ex(script_error_code ec, iterator it) + : script_error(ec), m_it(std::move(it)) {} + + [[nodiscard]] + iterator get_iter() const noexcept + { + return m_it; + } + + private: + iterator m_it; + }; + + basic_interpreter() = default; + + static constexpr bool debug() noexcept + { + return Debug; + } std::pair access(parse_context& ctx) const { @@ -497,14 +523,14 @@ class interpreter : public interpreter_base iterator start = ctx.begin(); const iterator stop = ctx.end(); - bool cond_result; + bool cond_result = false; std::tie(cond_result, start) = parse_condition(ctx, start, stop); start = skip_ws(start, stop); if(start == stop) [[unlikely]] throw_end_of_string(); - if(*start != U'\'') - throw_error(script_error_code::invalid_string); + if(*start != U'\'') [[unlikely]] + throw_error(script_error_code::invalid_string, start); ++start; string_container_type result_1; @@ -519,8 +545,8 @@ class interpreter : public interpreter_base ++start; start = skip_ws(start, stop); - if(*start != U'\'') - throw_error(script_error_code::invalid_string); + if(*start != U'\'') [[unlikely]] + throw_error(script_error_code::invalid_string, start); ++start; if(!cond_result) @@ -559,8 +585,8 @@ class interpreter : public interpreter_base ++parse_it; if(parse_it == parse_ctx.end()) [[unlikely]] throw_end_of_string(); - if(*parse_it != U'}') - throw_error(script_error_code::unclosed_brace); + if(*parse_it != U'}') [[unlikely]] + throw_error(script_error_code::unclosed_brace, parse_it); context_t::append(fmt_ctx, U'}'); ++parse_it; @@ -593,8 +619,8 @@ class interpreter : public interpreter_base parse_it = next_it; if(parse_it == parse_ctx.end()) [[unlikely]] throw_end_of_string(); - if(*parse_it != U'}') - throw_error(script_error_code::unclosed_brace); + if(*parse_it != U'}') [[unlikely]] + throw_error(script_error_code::unclosed_brace, parse_it); ++parse_it; } else @@ -611,10 +637,10 @@ class interpreter : public interpreter_base arg.format(parse_ctx, fmt_ctx); parse_it = parse_ctx.begin(); - if(parse_it == parse_ctx.end()) + if(parse_it == parse_ctx.end()) [[unlikely]] throw_end_of_string(); - if(*parse_it != U'}') - throw_error(script_error_code::unclosed_brace); + if(*parse_it != U'}') [[unlikely]] + throw_error(script_error_code::unclosed_brace, parse_it); ++parse_it; } } @@ -668,8 +694,8 @@ class interpreter : public interpreter_base enum class op_id { - equal, // != - not_equal, // = and == + equal, // = and == + not_equal, // != greater_equal, // >= less_equal, // <= greater, // > @@ -705,7 +731,7 @@ class interpreter : public interpreter_base if(start != stop) { if(*start != U'=') - throw_error(script_error_code::invalid_operator); + throw_error(script_error_code::invalid_operator, start); ++start; } @@ -716,8 +742,8 @@ class interpreter : public interpreter_base ++start; if(start == stop) throw_end_of_string(); - if(*start != U'=') - throw_error(script_error_code::invalid_operator); + if(*start != U'=') [[unlikely]] + throw_error(script_error_code::invalid_operator, start); ++start; return std::make_pair(op_id::not_equal, start); } @@ -743,7 +769,7 @@ class interpreter : public interpreter_base } } - throw_error(script_error_code::invalid_operator); + throw_error(script_error_code::invalid_operator, start); } static bool execute_op(op_id op, const variable_type& lhs, const variable_type& rhs) @@ -784,10 +810,10 @@ class interpreter : public interpreter_base { ++start; auto [arg, next_it] = access_impl(ctx, start, stop); - if(next_it == stop) + if(next_it == stop) [[unlikely]] throw_end_of_string(); - if(*next_it != U'}') - throw_error(script_error_code::unclosed_brace); + if(*next_it != U'}') [[unlikely]] + throw_error(script_error_code::unclosed_brace, next_it); ++next_it; return std::make_pair( @@ -844,7 +870,7 @@ class interpreter : public interpreter_base } } - throw_error(script_error_code::invalid_condition); + throw_error(script_error_code::invalid_condition, start); } static std::pair parse_condition(parse_context& ctx, iterator start, iterator stop) @@ -864,7 +890,7 @@ class interpreter : public interpreter_base if(next_it == stop) throw_end_of_string(); if(*next_it != condition_end) - throw_error(script_error_code::invalid_condition); + throw_error(script_error_code::invalid_condition, next_it); ++next_it; return std::make_pair(!var.template as(), next_it); @@ -885,7 +911,7 @@ class interpreter : public interpreter_base } else if(is_op_ch(ch)) { - op_id op; + op_id op{}; std::tie(op, next_it) = parse_op(next_it, stop); next_it = skip_ws(next_it, stop); @@ -893,10 +919,10 @@ class interpreter : public interpreter_base auto [var_2, next_it_2] = parse_variable(ctx, next_it, stop); next_it = skip_ws(next_it_2, stop); - if(next_it == stop) + if(next_it == stop) [[unlikely]] throw_end_of_string(); - if(*next_it != U':') - throw_error(script_error_code::invalid_condition); + if(*next_it != U':') [[unlikely]] + throw_error(script_error_code::invalid_condition, next_it); ++next_it; return std::make_pair( @@ -904,9 +930,11 @@ class interpreter : public interpreter_base next_it ); } + + throw_error(script_error_code::invalid_condition, next_it); } - throw_error(script_error_code::invalid_condition); + throw_error(script_error_code::invalid_condition, start); } // Parses integer value @@ -996,7 +1024,7 @@ class interpreter : public interpreter_base return std::make_pair(ctx.get_args()[idx], start); } - throw_error(script_error_code::invalid_field_name); + throw_error(script_error_code::invalid_field_name, start); } static std::pair parse_chained_access( @@ -1017,7 +1045,7 @@ class interpreter : public interpreter_base string_ref_type attr_name(str_start, str_end); if(attr_name.empty()) - throw_error(script_error_code::invalid_attribute); + throw_error(script_error_code::invalid_attribute, str_end); current = current.attribute(attr_name); @@ -1027,8 +1055,10 @@ class interpreter : public interpreter_base { ++start; auto [idx, next_it] = parse_indexing_value(start, stop); - if(next_it == stop || *next_it != U']') - throw_error(script_error_code::invalid_index); + if(next_it == stop) [[unlikely]] + throw_end_of_string(); + if(*next_it != U']') [[unlikely]] + throw_error(script_error_code::invalid_index, next_it); ++next_it; current = current.index(idx); @@ -1097,7 +1127,7 @@ class interpreter : public interpreter_base } } - throw_error(script_error_code::invalid_index); + throw_error(script_error_code::invalid_index, start); } static char32_t get_esc_ch(char32_t ch) noexcept @@ -1150,8 +1180,8 @@ class interpreter : public interpreter_base string_container_type result(start, it); ++it; - if(it == stop) - throw_error(script_error_code::invalid_string); + if(it == stop) [[unlikely]] + throw_error(script_error_code::invalid_string, it); result.push_back(utf::codepoint(get_esc_ch(*it++))); @@ -1161,8 +1191,8 @@ class interpreter : public interpreter_base if(ch == U'\\') { ++it; - if(it == stop) - throw_error(script_error_code::invalid_string); + if(it == stop) [[unlikely]] + throw_error(script_error_code::invalid_string, it); result.push_back(utf::codepoint(get_esc_ch(*it))); } @@ -1190,5 +1220,27 @@ class interpreter : public interpreter_base std::next(it) // +1 to skip '\'' ); } + + using base::throw_end_of_string; + + static script_error_ex make_error_ex(script_error_code ec, iterator it) + { + return script_error_ex(ec, std::move(it)); + } + + [[noreturn]] + static void throw_error(script_error_code ec, iterator it) + { + if constexpr(debug()) + { + throw make_error_ex(ec, std::move(it)); + } + else + { + base::throw_error(ec); + } + } }; + +PAPILIO_EXPORT using interpreter = basic_interpreter; } // namespace papilio::script diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 13a07de..027a446 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -45,11 +45,6 @@ interpreter_base::script_error interpreter_base::make_error(script_error_code ec return script_error(ec); } -void interpreter_base::throw_end_of_string() -{ - throw make_error(script_error_code::end_of_string); -} - void interpreter_base::throw_error(script_error_code ec) { throw make_error(ec); diff --git a/test/test_script_interpreter.cpp b/test/test_script_interpreter.cpp index 1bb3958..b03f7f7 100644 --- a/test/test_script_interpreter.cpp +++ b/test/test_script_interpreter.cpp @@ -252,7 +252,7 @@ auto test_access(std::string_view fmt, Args&&... args) format_parse_context parse_ctx(fmt, fmt_args); parse_ctx.advance_to(parse_ctx.begin() + 1); // skip '{' - script::interpreter intp; + script::basic_interpreter intp; auto [result, it] = intp.access(parse_ctx); EXPECT_NE(it, parse_ctx.end()); @@ -339,7 +339,7 @@ auto run_script(std::string_view fmt, Args&&... args) format_parse_context parse_ctx(fmt, fmt_args); parse_ctx.advance_to(parse_ctx.begin() + 2); // skip "{$" - script::interpreter intp; + script::basic_interpreter intp; auto [arg, it] = intp.run(parse_ctx); EXPECT_NE(it, parse_ctx.end()); @@ -410,7 +410,7 @@ TEST(interpreter, format) using namespace script; { - interpreter intp; + interpreter intp; std::string buf; mutable_format_args args; @@ -457,6 +457,45 @@ TEST(interpreter, exception) EXPECT_EQ(get_err("{$ 'str': 'incomplete}").error_code(), end_of_string); } +TEST(interpreter, debug) +{ + using namespace papilio; + using namespace script; + using enum script_error_code; + + using intp_t = basic_interpreter; + + auto helper = [](utf::string_ref fmt) + { + mutable_format_args args; + format_parse_context parse_ctx(fmt, args); + std::string str; + format_context fmt_ctx(std::back_inserter(str), args); + intp_t intp; + intp.format(parse_ctx, fmt_ctx); + + FAIL() << "unreachable"; + }; + +#define PAPILIO_TEST_INTERPRETER_DEBUG(fmt, ec, pos) \ + do \ + { \ + utf::string_ref fmt_str = fmt; \ + try \ + { \ + return helper(fmt_str); \ + } \ + catch(const intp_t::script_error_ex& e) \ + { \ + EXPECT_EQ(e.error_code(), ec); \ + EXPECT_EQ(std::distance(fmt_str.begin(), e.get_iter()), pos); \ + } \ + } while(0) + + PAPILIO_TEST_INTERPRETER_DEBUG("{$ 'str'}", invalid_condition, 8); + PAPILIO_TEST_INTERPRETER_DEBUG("{$ 'str':}", invalid_string, 9); +} + int main(int argc, char* argv[]) { testing::InitGoogleTest(&argc, argv);