diff --git a/include/iris/x4/char/case_compare.hpp b/include/iris/x4/char/case_compare.hpp index 7a4baa9e4..1a01fa83d 100644 --- a/include/iris/x4/char/case_compare.hpp +++ b/include/iris/x4/char/case_compare.hpp @@ -69,7 +69,7 @@ template [[nodiscard]] constexpr auto get_case_compare(Context const&) noexcept { - if constexpr (has_context_of_v) { + if constexpr (has_context_of) { return no_case_compare{}; } else { return case_compare{}; diff --git a/include/iris/x4/char/char_class.hpp b/include/iris/x4/char/char_class.hpp index 7fafac400..8c9e7f5f3 100644 --- a/include/iris/x4/char/char_class.hpp +++ b/include/iris/x4/char/char_class.hpp @@ -25,6 +25,7 @@ #include #include +#include #include @@ -216,14 +217,16 @@ constexpr void builtin_skip_over(It& first, Se const& last) noexcept // Forward declaration in "skip_over.hpp" // Need to sync this implementation with `skip_gen` ("skip.hpp") template Se, class Context> - requires std::same_as, builtin_skipper_kind> + requires has_context_of constexpr void skip_over(It& first, Se const& last, Context const& ctx) noexcept { switch (x4::get(ctx)) { case builtin_skipper_kind::no_skip: return; case builtin_skipper_kind::blank: return detail::builtin_skip_over(first, last); case builtin_skipper_kind::space: return detail::builtin_skip_over(first, last); - default: assert(false && "unsupported builtin skipper type"); break; + default: + assert(false && "unsupported builtin skipper type"); + std::unreachable(); } } diff --git a/include/iris/x4/core/action.hpp b/include/iris/x4/core/action.hpp index 5df81b141..0f9ba9fb3 100644 --- a/include/iris/x4/core/action.hpp +++ b/include/iris/x4/core/action.hpp @@ -188,7 +188,7 @@ struct action : proxy_parser, Subject> first = std::move(local_it); return true; } - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(local_it); @@ -228,7 +228,7 @@ struct action : proxy_parser, Subject> first = std::move(local_it); return true; } - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(local_it); @@ -249,7 +249,7 @@ struct action : proxy_parser, Subject> first = std::move(local_it); return true; } - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(local_it); diff --git a/include/iris/x4/core/context.hpp b/include/iris/x4/core/context.hpp index f691ba961..6059a522e 100644 --- a/include/iris/x4/core/context.hpp +++ b/include/iris/x4/core/context.hpp @@ -27,34 +27,44 @@ template struct context; -template -struct has_context; +namespace detail { template -constexpr bool has_context_v = has_context::value; +struct has_context_impl; template -struct has_context +struct has_context_impl : std::false_type {}; template -struct has_context, ID_To_Search> +struct has_context_impl, ID_To_Search> : std::true_type -{}; +{ + using context_value_type = T; +}; template requires (!std::same_as) -struct has_context, ID_To_Search> - : has_context, ID_To_Search> +struct has_context_impl, ID_To_Search> + : has_context_impl, ID_To_Search> {}; +} // detail + +template +concept has_context = detail::has_context_impl::value; + +template +concept has_context_of = + has_context && + std::same_as::context_value_type>, T>; template [[nodiscard]] constexpr decltype(auto) get(context const& ctx) noexcept { - if constexpr (has_context_v, ID_To_Get>) { + if constexpr (has_context, ID_To_Get>) { return ctx.get(std::type_identity{}); } else { return (unused); // return lvalue @@ -74,22 +84,6 @@ void get(context const&&) = delete; // dangling template using get_context_plain_t = std::remove_cvref_t(std::declval()))>; - -template -struct has_context_of : std::false_type {}; - -template - requires has_context_v -struct has_context_of -{ - static_assert(!std::is_reference_v); - static_assert(!std::is_const_v); - static constexpr bool value = std::same_as, T>; -}; - -template -constexpr bool has_context_of_v = has_context_of::value; - namespace detail { template @@ -99,7 +93,7 @@ template concept AllowUnusedContextID = requires { requires ID::allow_unused; }; template -concept HasNoDuplicateContext = !UniqueContextID || !has_context_v; +concept HasNoDuplicateContext = !UniqueContextID || !has_context; template concept ContextValueType = @@ -513,7 +507,7 @@ replace_first_context_impl( (void)new_val; // == unused return x4::remove_first_context(ctx); - } else if constexpr (!IsAppend && !has_context_v, ID_To_Replace>) { + } else if constexpr (!IsAppend && !has_context, ID_To_Replace>) { return context const&>{new_val, ctx}; } else if constexpr (std::same_as) { // Match diff --git a/include/iris/x4/core/detail/parse_alternative.hpp b/include/iris/x4/core/detail/parse_alternative.hpp index 4a1fd50b9..048fbb961 100644 --- a/include/iris/x4/core/detail/parse_alternative.hpp +++ b/include/iris/x4/core/detail/parse_alternative.hpp @@ -42,7 +42,7 @@ namespace iris::x4::detail { template [[nodiscard]] constexpr bool alternative_should_stop(Context const& ctx) noexcept { - if constexpr (has_context_v) { + if constexpr (has_context) { return x4::has_expectation_failure(ctx); } else { return false; diff --git a/include/iris/x4/core/expectation.hpp b/include/iris/x4/core/expectation.hpp index ae865f1cc..a83886be3 100644 --- a/include/iris/x4/core/expectation.hpp +++ b/include/iris/x4/core/expectation.hpp @@ -39,7 +39,7 @@ template constexpr bool has_expectation_failure(Context const& ctx) noexcept { static_assert( - has_context_v, + has_context, "Context type was not specified for `x4::contexts::expectation_failure`. " "You probably forgot: `x4::with(failure)[p]`. " "Note that you must also bind the context to your skipper." @@ -55,7 +55,7 @@ constexpr void set_expectation_failure( ) { static_assert( - has_context_v, + has_context, "Context type was not specified for `x4::contexts::expectation_failure`. " "You probably forgot: `x4::with(failure)[p]`. " "Note that you must also bind the context to your skipper." @@ -70,7 +70,7 @@ constexpr void set_expectation_failure_if_empty( ) { static_assert( - has_context_v, + has_context, "Context type was not specified for `x4::contexts::expectation_failure`. " "You probably forgot: `x4::with(failure)[p]`. " "Note that you must also bind the context to your skipper." @@ -84,7 +84,7 @@ template constexpr decltype(auto) get_expectation_failure(Context const& ctx) noexcept { static_assert( - has_context_v, + has_context, "Context type was not specified for `x4::contexts::expectation_failure`. " "You probably forgot: `x4::with(failure)[p]`. " "Note that you must also bind the context to your skipper." @@ -97,7 +97,7 @@ template constexpr void clear_expectation_failure(Context const& ctx) noexcept { static_assert( - has_context_v, + has_context, "Context type was not specified for `x4::contexts::expectation_failure`. " "You probably forgot: `x4::with(failure)[p]`. " "Note that you must also bind the context to your skipper." diff --git a/include/iris/x4/core/skip_over.hpp b/include/iris/x4/core/skip_over.hpp index e76c36101..2e28e6117 100644 --- a/include/iris/x4/core/skip_over.hpp +++ b/include/iris/x4/core/skip_over.hpp @@ -57,7 +57,10 @@ struct builtin_skipper_traits } // detail template Se, class Context> - requires X4Subject> + requires + has_context && + (!has_context_of) && + X4Subject> constexpr void skip_over(It& first, Se const& last, Context const& ctx) { auto const& skipper = x4::get(ctx); @@ -68,10 +71,10 @@ constexpr void skip_over(It& first, Se const& last, Context const& ctx) // Declare a concrete alias type; MSVC prints the alias instead of actual type, // which makes the compilation error significantly shorter. - using SkipperOnlyContext = std::remove_cvref_t(ctx))>; - auto const& local_ctx = detail::named_context(x4::remove_first_context(ctx)); + using NoSkipperContext = std::remove_cvref_t(ctx))>; + auto const& local_ctx = detail::named_context(x4::remove_first_context(ctx)); - if constexpr (x4::has_context_v) { + if constexpr (has_context) { auto& error_handler = x4::get(ctx); error_handler.on_skip(skipper_state::pre_skip); @@ -86,12 +89,12 @@ constexpr void skip_over(It& first, Se const& last, Context const& ctx) // Implemented in `char_class.hpp` template Se, class Context> - requires std::same_as, builtin_skipper_kind> + requires has_context_of constexpr void skip_over(It& first, Se const& last, Context const& ctx) noexcept; template Se, class Context> - requires (!has_context_v) + requires (!has_context) constexpr void skip_over(It&, Se const&, Context const&) noexcept { } diff --git a/include/iris/x4/debug/error_handler.hpp b/include/iris/x4/debug/error_handler.hpp index d46999e08..7a178ae14 100644 --- a/include/iris/x4/debug/error_handler.hpp +++ b/include/iris/x4/debug/error_handler.hpp @@ -105,7 +105,7 @@ struct has_on_expectation_failure : std::true_type "`on_error` is obsolete due to its confusing name; use `on_expectation_failure` instead." ); static_assert( - has_context_v, + has_context, "[BUG] The `on_expectation_failure` callback on the user-provided error handler " "is well-defined, but the actual `Context` passed to the `parse(...)` function " "does not contain a reference bound to `x4::contexts::expectation_failure`. " diff --git a/include/iris/x4/directive/expect.hpp b/include/iris/x4/directive/expect.hpp index 825fe9532..43f75dbde 100644 --- a/include/iris/x4/directive/expect.hpp +++ b/include/iris/x4/directive/expect.hpp @@ -80,7 +80,7 @@ struct parse_into_container_impl> ) { static_assert( - has_context_v, + has_context, "Context type was not specified for `x4::contexts::expectation_failure`. " "You probably forgot: `x4::with(failure)[p]`. " "Note that you must also bind the context to your skipper." diff --git a/include/iris/x4/directive/lexeme.hpp b/include/iris/x4/directive/lexeme.hpp index ef3f7379c..870decf23 100644 --- a/include/iris/x4/directive/lexeme.hpp +++ b/include/iris/x4/directive/lexeme.hpp @@ -19,17 +19,26 @@ #include #include +#include + namespace iris::x4 { namespace detail { template -[[nodiscard]] constexpr decltype(auto) make_lexeme_context(Context const& ctx) noexcept +[[nodiscard]] constexpr decltype(auto) make_lexeme_context(Context const& ctx, builtin_skipper_kind& skipper_kind) noexcept { + static_assert(!has_context_of); + assert(skipper_kind == builtin_skipper_kind::no_skip); + // Declare a concrete alias type; MSVC prints the alias instead of actual type, // which makes the compilation error significantly shorter. - using T = std::remove_cvref_t(ctx))>; - return detail::named_context(x4::remove_first_context(ctx)); + + // Note: we must use "append" here since the position of the skipper should be + // super stable in the context as the exact type is often referenced by + // `IRIS_X4_INSTANTIATE`. + using T = std::remove_cvref_t(ctx, skipper_kind))>; + return detail::named_context(x4::replace_first_or_append_context(ctx, skipper_kind)); } } // detail @@ -43,22 +52,31 @@ struct lexeme_directive : proxy_parser, Subject> [[nodiscard]] constexpr bool parse(It& first, Se const& last, Context const& ctx, Attr& attr) const { - auto it = first; - x4::skip_over(it, last, ctx); // pre-skip - - bool const ok = this->subject.parse( - it, last, - detail::make_lexeme_context(ctx), // no skipper - attr - ); + auto local_it = first; + x4::skip_over(local_it, last, ctx); // pre-skip + + bool ok; + if constexpr (has_context_of) { + builtin_skipper_kind& skipper_kind = x4::get(ctx); + auto const old_skipper_kind = skipper_kind; + skipper_kind = builtin_skipper_kind::no_skip; + ok = this->subject.parse(local_it, last, ctx, attr); + skipper_kind = old_skipper_kind; + + } else { + // This value could be reset by some nested parsers, so it can't be const + /* constexpr */ builtin_skipper_kind skipper_kind = builtin_skipper_kind::no_skip; + ok = this->subject.parse(local_it, last, detail::make_lexeme_context(ctx, skipper_kind), attr); + } + if (ok) { - first = std::move(it); + first = std::move(local_it); return true; } - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) - first = std::move(it); + first = std::move(local_it); } } return false; diff --git a/include/iris/x4/directive/matches.hpp b/include/iris/x4/directive/matches.hpp index a06b61060..5f46f7a85 100644 --- a/include/iris/x4/directive/matches.hpp +++ b/include/iris/x4/directive/matches.hpp @@ -38,7 +38,7 @@ struct matches_directive : unary_parser, Subject> { bool const matched = this->subject.parse(first, last, ctx, unused); - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) return false; } diff --git a/include/iris/x4/directive/no_skip.hpp b/include/iris/x4/directive/no_skip.hpp index 7aa376604..eba080130 100644 --- a/include/iris/x4/directive/no_skip.hpp +++ b/include/iris/x4/directive/no_skip.hpp @@ -20,17 +20,26 @@ #include #include +#include + namespace iris::x4 { namespace detail { template -[[nodiscard]] constexpr decltype(auto) make_no_skip_context(Context const& ctx) noexcept +[[nodiscard]] constexpr decltype(auto) make_no_skip_context(Context const& ctx, builtin_skipper_kind& skipper_kind) noexcept { + static_assert(!has_context_of); + assert(skipper_kind == builtin_skipper_kind::no_skip); + // Declare a concrete alias type; MSVC prints the alias instead of actual type, // which makes the compilation error significantly shorter. - using T = std::remove_cvref_t(ctx))>; - return detail::named_context(x4::remove_first_context(ctx)); + + // Note: we must use "append" here since the position of the skipper should be + // super stable in the context as the exact type is often referenced by + // `IRIS_X4_INSTANTIATE`. + using T = std::remove_cvref_t(ctx, skipper_kind))>; + return detail::named_context(x4::replace_first_or_append_context(ctx, skipper_kind)); } } // detail @@ -46,7 +55,20 @@ struct no_skip_directive : proxy_parser, Subject> parse(It& first, Se const& last, Context const& ctx, Attr& attr) const { // No pre-skip here, in contrast to `lexeme` - return this->subject.parse(first, last, detail::make_no_skip_context(ctx), attr); + + if constexpr (has_context_of) { + builtin_skipper_kind& skipper_kind = x4::get(ctx); + auto const old_skipper_kind = skipper_kind; + skipper_kind = builtin_skipper_kind::no_skip; + bool const ok = this->subject.parse(first, last, ctx, attr); + skipper_kind = old_skipper_kind; + return ok; + + } else { + // This value could be reset by some nested parsers, so it can't be const + /* constexpr */ builtin_skipper_kind skipper_kind = builtin_skipper_kind::no_skip; + return this->subject.parse(first, last, detail::make_no_skip_context(ctx, skipper_kind), attr); + } } }; diff --git a/include/iris/x4/directive/repeat.hpp b/include/iris/x4/directive/repeat.hpp index 1b52ec9a6..d2326d23c 100644 --- a/include/iris/x4/directive/repeat.hpp +++ b/include/iris/x4/directive/repeat.hpp @@ -116,7 +116,7 @@ struct repeat_directive : proxy_parser, Subjec if (detail::parse_into_container(this->subject, local_it, last, ctx, chunk_buf.container())) { // We can't merge here; it will lead to partial status } else { - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(local_it); @@ -137,7 +137,7 @@ struct repeat_directive : proxy_parser, Subjec } } - if constexpr (has_context_v) { + if constexpr (has_context) { return !x4::has_expectation_failure(ctx); } else { return true; @@ -153,7 +153,7 @@ struct repeat_directive : proxy_parser, Subjec typename Bounds::value_type i{}; for (; !bounds_.got_min(i); ++i) { if (!detail::parse_into_container(this->subject, local_it, last, ctx, x4::assume_container(unused_attr))) { - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(local_it); @@ -171,7 +171,7 @@ struct repeat_directive : proxy_parser, Subjec } } - if constexpr (has_context_v) { + if constexpr (has_context) { return !x4::has_expectation_failure(ctx); } else { return true; diff --git a/include/iris/x4/directive/skip.hpp b/include/iris/x4/directive/skip.hpp index 2a6296ddc..2e7076ee4 100644 --- a/include/iris/x4/directive/skip.hpp +++ b/include/iris/x4/directive/skip.hpp @@ -81,8 +81,10 @@ struct skip_directive : proxy_parser, Subject> namespace detail { template -[[nodiscard]] constexpr decltype(auto) make_builtin_skipper_context(Context const& ctx, builtin_skipper_kind& skipper_kind) noexcept +[[nodiscard]] constexpr decltype(auto) make_builtin_skip_context(Context const& ctx, builtin_skipper_kind& skipper_kind) noexcept { + static_assert(!has_context_of); + // Declare a concrete alias type; MSVC prints the alias instead of actual type, // which makes the compilation error significantly shorter. @@ -101,33 +103,23 @@ struct builtin_skip_directive : proxy_parser; using base_type::base_type; - // Has existing builtin skipper - template Se, class Context, X4Attribute Attr> - [[nodiscard]] constexpr bool - parse(It& first, Se const& last, Context const& ctx, Attr& attr) const - { - builtin_skipper_kind& skipper_kind = x4::get(ctx); - auto const old_skipper_kind = skipper_kind; - skipper_kind = Kind; - - bool const ok = this->subject.parse(first, last, ctx, attr); - - skipper_kind = old_skipper_kind; - return ok; - } - - // No existing builtin skipper template Se, class Context, X4Attribute Attr> - requires - std::same_as, unused_type> || - (!std::same_as, builtin_skipper_kind>) [[nodiscard]] constexpr bool parse(It& first, Se const& last, Context const& ctx, Attr& attr) const { - // This value could be reset by some nested parsers, so it can't be const - /* constexpr */ builtin_skipper_kind skipper_kind = Kind; - - return this->subject.parse(first, last, detail::make_builtin_skipper_context(ctx, skipper_kind), attr); + if constexpr (has_context_of) { + builtin_skipper_kind& skipper_kind = x4::get(ctx); + auto const old_skipper_kind = skipper_kind; + skipper_kind = Kind; + bool const ok = this->subject.parse(first, last, ctx, attr); + skipper_kind = old_skipper_kind; + return ok; + + } else { + // This value could be reset by some nested parsers, so it can't be const + /* constexpr */ builtin_skipper_kind skipper_kind = Kind; + return this->subject.parse(first, last, detail::make_builtin_skip_context(ctx, skipper_kind), attr); + } } [[nodiscard]] constexpr std::string get_x4_info() const diff --git a/include/iris/x4/operator/and_predicate.hpp b/include/iris/x4/operator/and_predicate.hpp index e8f2760b3..580af38a2 100644 --- a/include/iris/x4/operator/and_predicate.hpp +++ b/include/iris/x4/operator/and_predicate.hpp @@ -35,7 +35,7 @@ struct and_predicate : unary_parser, Subject> auto it = first; if (this->subject.parse(it, last, ctx, unused)) return true; - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(it); diff --git a/include/iris/x4/operator/delimited_list.hpp b/include/iris/x4/operator/delimited_list.hpp index c07b0f03a..9ece74e08 100644 --- a/include/iris/x4/operator/delimited_list.hpp +++ b/include/iris/x4/operator/delimited_list.hpp @@ -81,7 +81,7 @@ struct delimited_list : parser> first = last_parse_it; } - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(last_parse_it); @@ -110,7 +110,7 @@ struct delimited_list : parser> first = last_parse_it; } - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(last_parse_it); diff --git a/include/iris/x4/operator/difference.hpp b/include/iris/x4/operator/difference.hpp index 3d255472b..90e3120ed 100644 --- a/include/iris/x4/operator/difference.hpp +++ b/include/iris/x4/operator/difference.hpp @@ -41,7 +41,7 @@ struct difference : binary_parser, Left, Right> // We don't need to advance the iterator on this situation. return false; } - if constexpr (has_context_v) { + if constexpr (has_context) { // In case of `Left - expect[r]`, // if Right yielded expectation error, // the whole difference expression (*this) should also yield error. @@ -60,7 +60,7 @@ struct difference : binary_parser, Left, Right> first = std::move(it); return true; } - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(it); diff --git a/include/iris/x4/operator/kleene.hpp b/include/iris/x4/operator/kleene.hpp index 4ac694d3c..cbfda25f8 100644 --- a/include/iris/x4/operator/kleene.hpp +++ b/include/iris/x4/operator/kleene.hpp @@ -47,7 +47,7 @@ struct kleene : unary_parser, Subject> chunk_buf.merge(); } - if constexpr (has_context_v) { + if constexpr (has_context) { return !x4::has_expectation_failure(ctx); } else { return true; @@ -62,7 +62,7 @@ struct kleene : unary_parser, Subject> while (detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(unused_attr))) /* loop */; - if constexpr (has_context_v) { + if constexpr (has_context) { return !x4::has_expectation_failure(ctx); } else { return true; diff --git a/include/iris/x4/operator/not_predicate.hpp b/include/iris/x4/operator/not_predicate.hpp index 5c3e3de55..7fb90bded 100644 --- a/include/iris/x4/operator/not_predicate.hpp +++ b/include/iris/x4/operator/not_predicate.hpp @@ -35,7 +35,7 @@ struct not_predicate : unary_parser, Subject> { It local_first = first; - if constexpr (has_context_v) { + if constexpr (has_context) { bool const matched = this->subject.parse(local_first, last, ctx, unused); if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) diff --git a/include/iris/x4/operator/optional.hpp b/include/iris/x4/operator/optional.hpp index f59aaf657..ff4fa227d 100644 --- a/include/iris/x4/operator/optional.hpp +++ b/include/iris/x4/operator/optional.hpp @@ -53,7 +53,7 @@ struct optional : unary_parser, Subject> } traits::attribute_traits::reset(attr); - if constexpr (has_context_v) { + if constexpr (has_context) { return !x4::has_expectation_failure(ctx); } else { return true; @@ -76,7 +76,7 @@ struct optional : unary_parser, Subject> } traits::attribute_traits::reset(attr); - if constexpr (has_context_v) { + if constexpr (has_context) { return !x4::has_expectation_failure(ctx); } else { return true; @@ -107,7 +107,7 @@ struct optional : unary_parser, Subject> } } - if constexpr (has_context_v) { + if constexpr (has_context) { return !x4::has_expectation_failure(ctx); } else { return true; diff --git a/include/iris/x4/operator/plus.hpp b/include/iris/x4/operator/plus.hpp index 5110176f6..7f2f5833f 100644 --- a/include/iris/x4/operator/plus.hpp +++ b/include/iris/x4/operator/plus.hpp @@ -53,7 +53,7 @@ struct plus : unary_parser, Subject> chunk_buf.merge(); } - if constexpr (has_context_v) { + if constexpr (has_context) { return !x4::has_expectation_failure(ctx); } else { return true; @@ -72,7 +72,7 @@ struct plus : unary_parser, Subject> while (detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(unused_attr))) /* loop */; - if constexpr (has_context_v) { + if constexpr (has_context) { return !x4::has_expectation_failure(ctx); } else { return true; diff --git a/include/iris/x4/operator/sequence.hpp b/include/iris/x4/operator/sequence.hpp index e89a7a5f4..f59382e22 100644 --- a/include/iris/x4/operator/sequence.hpp +++ b/include/iris/x4/operator/sequence.hpp @@ -156,7 +156,7 @@ struct sequence : nary_parser, Ps...> first = std::move(local_it); return true; } - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(local_it); @@ -180,7 +180,7 @@ struct sequence : nary_parser, Ps...> first = std::move(local_it); return true; } - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(local_it); @@ -206,7 +206,7 @@ struct sequence : nary_parser, Ps...> first = std::move(local_it); return true; } - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(local_it); @@ -226,7 +226,7 @@ struct sequence : nary_parser, Ps...> first = std::move(local_it); return true; } - if constexpr (has_context_v) { + if constexpr (has_context) { if (x4::has_expectation_failure(ctx)) { // don't rollback iterator (mimicking exception-like behavior) first = std::move(local_it); diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index b4f9a8347..2ea9ecae7 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -353,20 +353,47 @@ call_rule_definition( #define IRIS_ZZ_X4_FIRST(x, ...) x -#define IRIS_ZZ_X4_INSTANTIATE_I(rule_name, It, Se, Context) \ +#define IRIS_ZZ_X4_INSTANTIATE_I(export_macro, rule_name, It, Se, Context) \ namespace rules { \ - template bool parse_rule( \ + template export_macro bool parse_rule( \ IRIS_PP_CAT(rule_name, _id), \ It&, Se const&, Context const&, \ ::iris::x4::detail::rule_attr_ref \ ); \ } /* rules */ -// `IRIS_X4_INSTANTIATE(rule_name, It, Context)` -// `IRIS_X4_INSTANTIATE(rule_name, It, Se, Context)` +// Explicitly instantiates `parse_rule` for the given iterator, sentinel, and +// context types. This allows the grammar definition to live entirely in a +// .cpp file, keeping the X4 dependency of public headers to a minimum. +// +// The rule must be declared with `IRIS_X4_DECLARE_PUBLIC` and defined with +// `IRIS_X4_DEFINE_PUBLIC`. +// // Note: If a type contains a comma, wrap the entire type with parentheses. +// +// Usage: +// - `IRIS_X4_INSTANTIATE(rule_name, It, Context)` +// - `IRIS_X4_INSTANTIATE(rule_name, It, Se, Context)` #define IRIS_X4_INSTANTIATE(rule_name, It, SeOrContext, ...) \ IRIS_ZZ_X4_INSTANTIATE_I( \ + , /* export_macro */ \ + rule_name, \ + IRIS_PP_UNPAREN_IF_PAREN(It), \ + IRIS_PP_UNPAREN_IF_PAREN(IRIS_ZZ_X4_FIRST(__VA_OPT__(SeOrContext,) It)), \ + IRIS_PP_UNPAREN_IF_PAREN(IRIS_ZZ_X4_FIRST(__VA_ARGS__ __VA_OPT__(,) SeOrContext)) \ + ) + +// Similar to `IRIS_X4_INSTANTIATE`, but also exports the instantiation +// across DLL boundaries using `export_macro`. +// +// Note: If a type contains a comma, wrap the entire type with parentheses. +// +// Usage: +// - `IRIS_X4_INSTANTIATE_EXPORT(export_macro, rule_name, It, Context)` +// - `IRIS_X4_INSTANTIATE_EXPORT(export_macro, rule_name, It, Se, Context)` +#define IRIS_X4_INSTANTIATE_EXPORT(export_macro, rule_name, It, SeOrContext, ...) \ + IRIS_ZZ_X4_INSTANTIATE_I( \ + export_macro, \ rule_name, \ IRIS_PP_UNPAREN_IF_PAREN(It), \ IRIS_PP_UNPAREN_IF_PAREN(IRIS_ZZ_X4_FIRST(__VA_OPT__(SeOrContext,) It)), \