diff --git a/include/iris/x4/core/context.hpp b/include/iris/x4/core/context.hpp index b72d4896a..06f0dfd2e 100644 --- a/include/iris/x4/core/context.hpp +++ b/include/iris/x4/core/context.hpp @@ -342,7 +342,7 @@ template void make_context(T const&&) = delete; // dangling -// Remove the contained reference of the leftmost context having the id `ID_To_Remove`. +// Remove the *leftmost* context having the id `ID_To_Remove`. template [[nodiscard]] constexpr decltype(auto) // may return existing reference in some cases remove_first_context(context const& ctx) noexcept @@ -444,6 +444,78 @@ remove_first_context(unused_type const&) noexcept template void remove_first_context(context const&&) = delete; // dangling + +// Remove *all* contexts having any of the ids `IDs_To_Remove...`. +template +[[nodiscard]] constexpr decltype(auto) // may return existing reference in some cases +remove_all_contexts(context const& ctx) noexcept +{ + static_assert(sizeof...(IDs_To_Remove) > 0); + + if constexpr (iris::is_in_v) { // Match + if constexpr (std::same_as) { + // Existing context found; removing it will result in an + // empty context, so create a monostate placeholder. + return unused_type{}; + + } else { + // Existing context found; remove it and continue the search. + return x4::remove_all_contexts(ctx.next); + } + + } else { // No match + if constexpr (std::same_as) { + // No match at all. Return as-is. + return ctx; + + } else { + // No match. Continue the replacement recursively. + using NewNext_ = decltype(x4::remove_all_contexts(ctx.next)); + using NewNext = std::conditional_t< + std::is_reference_v, + NewNext_, + std::remove_const_t + >; + + if constexpr (std::same_as, std::remove_cvref_t>) { + // Avoid creating copy on exact same type + return ctx; + + } else { + // If the recursive replacement resulted in a monostate context, + // prevent appending it; return the context without `next`. + if constexpr (std::same_as, unused_type>) { + return context{ctx.val}; + + } else if constexpr (std::is_reference_v) { + // Assert `decltype(auto)` is working as intended; i.e., no dangling reference + static_assert(std::is_lvalue_reference_v); + + return context{ + ctx.val, x4::remove_all_contexts(ctx.next) + }; + + } else { // prvalue context + return context{ + ctx.val, x4::remove_all_contexts(ctx.next) + }; + } + } + } + } +} + +template +[[nodiscard]] constexpr unused_type const& +remove_all_contexts(unused_type const&) noexcept +{ + return unused; +} + +template +void remove_all_contexts(context const&&) = delete; // dangling + + // Replaces the contained reference of the leftmost context // having the id `ID_To_Replace`. If no such context exists, // append a new one. diff --git a/include/iris/x4/directive.hpp b/include/iris/x4/directive.hpp index 896262bdb..8df3e5f96 100644 --- a/include/iris/x4/directive.hpp +++ b/include/iris/x4/directive.hpp @@ -24,5 +24,6 @@ #include #include #include +#include #endif diff --git a/include/iris/x4/directive/without.hpp b/include/iris/x4/directive/without.hpp new file mode 100644 index 000000000..aadf1a87e --- /dev/null +++ b/include/iris/x4/directive/without.hpp @@ -0,0 +1,73 @@ +#ifndef IRIS_X4_DIRECTIVE_WITHOUT_HPP +#define IRIS_X4_DIRECTIVE_WITHOUT_HPP + +/*============================================================================= + Copyright (c) 2026 The Iris Project Contributors + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +=============================================================================*/ + +#include +#include + +#include +#include +#include +#include + +namespace iris::x4 { + +template +struct without_directive : proxy_parser> +{ + template Se, class Context, X4Attribute Attr> + [[nodiscard]] constexpr bool + parse(It& first, Se const& last, Context const& ctx, Attr& attr) const + noexcept( + x4::is_nothrow_parsable_v< + Subject, It, Se, + std::remove_cvref_t(ctx))>, + Attr + > + ) + { + return this->subject.parse(first, last, x4::remove_all_contexts(ctx), attr); + } + + [[nodiscard]] constexpr std::string get_x4_info() const + { + return std::format("without<...>[{}]", get_info{}(this->subject)); + } +}; + +namespace detail { + +template +struct without_gen +{ + template + [[nodiscard]] constexpr without_directive, IDs...> + operator[](Subject&& subject) const // TODO: MSVC 2022 does not properly handle static operator[] + noexcept(std::is_nothrow_constructible_v, IDs...>, Subject>) + { + return without_directive, IDs...>{ + std::forward(subject) + }; + } +}; + +} // detail + +namespace parsers::directive { + +template +[[maybe_unused]] inline constexpr detail::without_gen without{}; + +} // parsers::directive + +using parsers::directive::without; + +} // iris::x4 + +#endif diff --git a/test/x4/CMakeLists.txt b/test/x4/CMakeLists.txt index 73485d3f9..47821e52d 100644 --- a/test/x4/CMakeLists.txt +++ b/test/x4/CMakeLists.txt @@ -90,6 +90,7 @@ x4_define_tests( unused with with_local + without x3_rule_problem alloy_wrong_substitute_test_case ) diff --git a/test/x4/without.cpp b/test/x4/without.cpp new file mode 100644 index 000000000..3e09178e3 --- /dev/null +++ b/test/x4/without.cpp @@ -0,0 +1,95 @@ +/*============================================================================= + Copyright (c) 2026 The Iris Project Contributors + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +=============================================================================*/ + +#include "iris_x4_test.hpp" + +#include +#include +#include +#include + +#include +#include + +template +struct context_checker : x4::parser> +{ + template Se, class Context, x4::X4Attribute Attr> + [[nodiscard]] static constexpr bool parse(It&, Se const&, Context const&, Attr const&) + { + STATIC_CHECK(std::same_as); + return true; + } +}; + +static_assert(x4::X4ExplicitParser, char const*, char const*>); + +struct OK {}; +struct Bad0 {}; +struct NonExistent {}; + +TEST_CASE("without") +{ + using x4::without; + using x4::with; + using x4::context; + + constexpr int dummy = 0; + char const* first = nullptr, *const last = nullptr; + + { + (void)x4::with(dummy)[ + x4::without[ + context_checker{} + ] + ].parse(first, last, unused, unused); + } + { + (void)x4::with(dummy)[ + x4::without[ + context_checker>{} + ] + ].parse(first, last, unused, unused); + } + + { + (void)x4::with(dummy)[ + x4::with(dummy)[ + x4::without[ + context_checker>{} + ] + ] + ].parse(first, last, unused, unused); + } + { + (void)x4::with(dummy)[ + x4::with(dummy)[ + x4::without[ + context_checker>{} + ] + ] + ].parse(first, last, unused, unused); + } + { + (void)x4::with(dummy)[ + x4::with(dummy)[ + x4::without[ + context_checker const&>>{} + ] + ] + ].parse(first, last, unused, unused); + } + { + (void)x4::with(dummy)[ + x4::with(dummy)[ + x4::without[ + context_checker{} + ] + ] + ].parse(first, last, unused, unused); + } +}