diff --git a/include/iris/x4/auxiliary/attr.hpp b/include/iris/x4/auxiliary/attr.hpp index 62e6f63db..03dbb06b5 100644 --- a/include/iris/x4/auxiliary/attr.hpp +++ b/include/iris/x4/auxiliary/attr.hpp @@ -43,8 +43,6 @@ struct attr_parser : parser> using attribute_type = T; using held_value_type = HeldValueT; - static constexpr bool handles_container = traits::is_container_v; - template requires (!std::is_same_v, attr_parser>) && @@ -77,8 +75,6 @@ struct attr_parser : parser> using attribute_type = T; - static constexpr bool handles_container = traits::is_container_v; - template Se, class Context, X4UnusedAttribute UnusedAttr> [[nodiscard]] static constexpr bool parse(It&, Se const&, Context const&, UnusedAttr const&) noexcept diff --git a/include/iris/x4/core/action.hpp b/include/iris/x4/core/action.hpp index fba6c57ab..d0a3195f9 100644 --- a/include/iris/x4/core/action.hpp +++ b/include/iris/x4/core/action.hpp @@ -27,8 +27,6 @@ namespace iris::x4 { namespace detail { -struct raw_attribute_t; - template struct action_context; @@ -207,17 +205,6 @@ struct action : proxy_parser> first = saved_first; return false; } - - // attr==raw, action wants iterator_range (see raw.hpp) - template Se, class Context> - [[nodiscard]] constexpr bool - parse_main(It& first, Se const& last, Context const& ctx, detail::raw_attribute_t&) const - noexcept(false) // construction of `subrange` is never noexcept as per the standard - { - // synthesize the attribute since one is not supplied - std::ranges::subrange rng; // This must be It-It pair, NOT It-Se pair - return this->parse_main(first, last, ctx, rng); - } }; template diff --git a/include/iris/x4/core/action_context.hpp b/include/iris/x4/core/action_context.hpp index 972497620..33cefcc81 100644 --- a/include/iris/x4/core/action_context.hpp +++ b/include/iris/x4/core/action_context.hpp @@ -80,13 +80,6 @@ struct _as_var_fn static void operator()(Context const&&) = delete; // dangling }; -struct _where_fn -{ - template - static constexpr void - operator()(Context const&) = delete; // `_where` is obsolete. Use `raw[...]` directive. -}; - struct _attr_fn { template @@ -112,7 +105,6 @@ inline namespace cpos { inline constexpr detail::_rule_var_fn _val{}; [[maybe_unused]] inline constexpr detail::_as_var_fn _as_var{}; -[[maybe_unused]] inline constexpr detail::_where_fn _where{}; // obsolete [[maybe_unused]] inline constexpr detail::_attr_fn _attr{}; } // cpos diff --git a/include/iris/x4/core/detail/parse_alternative.hpp b/include/iris/x4/core/detail/parse_alternative.hpp index 7162d07ec..5176df180 100644 --- a/include/iris/x4/core/detail/parse_alternative.hpp +++ b/include/iris/x4/core/detail/parse_alternative.hpp @@ -67,7 +67,7 @@ template struct pass_parser_attribute { using attribute_type = parser_traits::attribute_type; - using substitute_type = traits::variant_find_substitute_t; + using substitute_type = traits::variant_find_holdable_type::type; using type = std::conditional_t< std::same_as, diff --git a/include/iris/x4/core/detail/parse_into_container.hpp b/include/iris/x4/core/detail/parse_into_container.hpp index 2593828b7..2b37abb06 100644 --- a/include/iris/x4/core/detail/parse_into_container.hpp +++ b/include/iris/x4/core/detail/parse_into_container.hpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include @@ -24,205 +24,114 @@ #include #include -namespace iris::x4::detail { - -template -struct parser_accepts_container - : traits::is_substitute::attribute_type, Container> -{}; - -template -constexpr bool parser_accepts_container_v = parser_accepts_container::value; - -template -struct parse_into_container_base_impl -{ - // Parser has attribute (synthesize; Attribute is a container) - template Se, class Context, X4Attribute Attr> - requires (!parser_accepts_container_v>) - [[nodiscard]] static constexpr bool - call_synthesize( - Parser const& parser, It& first, Se const& last, - Context const& ctx, Attr& attr - ) // never noexcept (requires container insertion) - { - static_assert(!std::same_as, unused_container_type>); - - typename traits::container_value>::type val{}; // value-initialize - - //static_assert(Parsable); - if (!parser.parse(first, last, ctx, val)) return false; +namespace iris::x4 { - // push the parsed value into our attribute - traits::push_back(unwrap_recursive(attr), std::move(val)); - return true; - } +template +struct optional; - // unused_container_type - template Se, class Context> - requires (!parser_accepts_container_v) - [[nodiscard]] static constexpr bool - call_synthesize( - Parser const& parser, It& first, Se const& last, - Context const& ctx, unused_container_type const& - ) noexcept(is_nothrow_parsable_v) - { - //static_assert(Parsable); - return parser.parse(first, last, ctx, unused); - } +} // iris::x4 - // Parser has attribute (synthesize; Attribute is a container) - template Se, class Context, X4Attribute Attr> - requires parser_accepts_container_v> - [[nodiscard]] static constexpr bool - call_synthesize( - Parser const& parser, It& first, Se const& last, - Context const& ctx, Attr& attr - ) noexcept(is_nothrow_parsable_v>) - { - //static_assert(Parsable>); - return parser.parse(first, last, ctx, attr); - } - - // ------------------------------------------------------ - - // Parser has attribute && it is NOT tuple-like - template Se, class Context, X4Attribute Attr> - requires - has_attribute_v && - (!alloy::is_tuple_like_v) - [[nodiscard]] static constexpr bool - call( - Parser const& parser, It& first, Se const& last, - Context const& ctx, Attr& attr - ) - { - // TODO: reduce call stack while keeping maintainability - return parse_into_container_base_impl::call_synthesize(parser, first, last, ctx, attr); - } - - // Parser has attribute && it is tuple-like - template Se, class Context, X4Attribute Attr> - requires - has_attribute_v && - alloy::is_tuple_like_v - [[nodiscard]] static constexpr bool - call( - Parser const& parser, It& first, Se const& last, - Context const& ctx, Attr& attr - ) noexcept(noexcept(parse_into_container_base_impl::call_synthesize(parser, first, last, ctx, alloy::get<0>(attr)))) - { - static_assert(traits::has_size_v, "Expecting a single element tuple-like"); - // TODO: reduce call stack while keeping maintainability - return parse_into_container_base_impl::call_synthesize(parser, first, last, ctx, alloy::get<0>(attr)); - } +namespace iris::x4::detail { - // Parser has no attribute (pass unused) - template Se, class Context, X4Attribute Attr> - requires (!has_attribute_v) - [[nodiscard]] static constexpr bool - call( - Parser const& parser, It& first, Se const& last, - Context const& ctx, Attr& /* attr */ - ) noexcept(is_nothrow_parsable_v) - { - // static_assert(Parsable); - return parser.parse(first, last, ctx, unused_container); - } +template +struct parser_accepts_container +{ + static constexpr bool value = + parser_traits::template handles_container && + !requires (Container& c, typename parser_traits::attribute_type&& v) { + traits::push_back(c, std::move(v)); + }; }; -template -struct parse_into_container_impl : parse_into_container_base_impl {}; - +template + requires traits::is_variant_v::attribute_type> +struct parser_accepts_container +{ + using alternative_type = traits::variant_find_holdable_type< + typename parser_traits::attribute_type, + Container + >::type; + + static constexpr bool value = + parser_traits::template handles_container && + !requires (Container& c, alternative_type&& v) { + traits::push_back(c, std::move(v)); + }; +}; template - requires Parser::handles_container -struct parse_into_container_impl +struct parse_into_container_impl_default { - template Se, class Context, X4Attribute Attr> - static constexpr bool pass_attibute_as_is = std::disjunction_v< - parser_accepts_container, - - std::negation::attribute_type - >::actual_type, - typename traits::container_value::type - >> - >; - - template Se, class Context, X4Attribute Attr> - requires (!pass_attibute_as_is) - [[nodiscard]] static constexpr bool - call( - Parser const& parser, It& first, Se const& last, - Context const& ctx, Attr& attr - ) noexcept(noexcept(parse_into_container_base_impl::call( - parser, first, last, ctx, attr - ))) + template Se, class Context, X4NonUnusedAttribute Attr> + static constexpr bool call(Parser const& parser, It& first, Se const& last, Context& ctx, Attr& attr) + // never noexcept (requires container insertion) { - return parse_into_container_base_impl::call( - parser, first, last, ctx, attr - ); + using unwrapped_attribute_type = iris::unwrap_recursive_type; + auto& unwrapped_attr = iris::unwrap_recursive(attr); + + if constexpr (traits::is_container_v) { // Attr is a container + if constexpr (parser_accepts_container::value) { + // `Parser` accepts the exact `Container`; let parser append directly + auto&& appender = x4::make_container_appender(unwrapped_attr); + return parser.parse(first, last, ctx, appender); + + } else { + // `Parser` DOES NOT accept the exact `Container`; parse into `value_type` and append it. + typename traits::container_value::type value{}; // value-initialize + if (!parser.parse(first, last, ctx, value)) return false; + traits::push_back(unwrapped_attr, std::move(value)); + return true; + } + + } else { + if constexpr (traits::is_size_one_sequence_v) { + // attribute is single element tuple-like; unwrap and try again + return parse_into_container_impl_default::call(parser, first, last, ctx, alloy::get<0>(unwrapped_attr)); + } else { + //attr = nullptr; + static_assert(false, "[BUG] parse_into_container accepts a container, a variant of container or a single element tuple-like of container"); + return false; + } + } } +}; - template Se, class Context> - requires pass_attibute_as_is - [[nodiscard]] static constexpr bool - call( - Parser const& parser, It& first, Se const& last, - Context const& ctx, unused_container_type - ) noexcept(is_nothrow_parsable_v) - { - static_assert(Parsable); - return parser.parse(first, last, ctx, unused_container); - } +// internal customization point +template +struct parse_into_container_impl + : parse_into_container_impl_default +{}; - template Se, class Context, X4Attribute Attr> - requires pass_attibute_as_is - [[nodiscard]] static constexpr bool - call( - Parser const& parser, It& first, Se const& last, - Context const& ctx, Attr& attr - ) // never noexcept (requires container insertion) - { - static_assert(!std::same_as, unused_type>); - static_assert(!std::same_as, unused_container_type>); - static_assert(Parsable); +template +struct parse_into_container_noexcept : std::false_type {}; - auto&& appender = x4::make_container_appender(attr); - return parser.parse(first, last, ctx, appender); - } -}; +template + requires X4UnusedAttribute || (!has_attribute_v) +struct parse_into_container_noexcept : is_nothrow_parsable {}; -template< - class Parser, std::forward_iterator It, std::sentinel_for Se, - class Context, X4Attribute Attr -> +template Se, class Context, X4Attribute Attr> [[nodiscard]] constexpr bool parse_into_container( Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr -) noexcept(noexcept(parse_into_container_impl::call(parser, first, last, ctx, attr))) +) noexcept(parse_into_container_noexcept::value) { - static_assert( - !std::same_as, - "`unused_type` should not be passed to `parse_into_container`. Use `x4::assume_container(attr)`" - ); - - if constexpr (traits::is_variant_v) { - // e.g. `char` when the caller is `+char_` - using attribute_type = parser_traits::attribute_type; - - // e.g. `std::string` when the attribute_type is `char` - using substitute_type = traits::variant_find_substitute_t::type>; - - // instead of creating a temporary `substitute_type`, append directly into the emplaced alternative - auto& variant_alt = attr.template emplace(); - return parse_into_container_impl::call(parser, first, last, ctx, variant_alt); + if constexpr (X4UnusedAttribute || !has_attribute_v) { // handle unused types first + return parser.parse(first, last, ctx, unused); } else { - return parse_into_container_impl::call(parser, first, last, ctx, attr); + if constexpr (traits::is_variant_v) { + // e.g. `char` when the caller is `+char_` + using attribute_type = parser_traits::attribute_type; + + // e.g. `std::string` when the attribute_type is `char` + using substitute_type = traits::variant_find_holdable_type::type>::type; + + // instead of creating a temporary `substitute_type`, append directly into the emplaced alternative + auto& variant_alt = attr.template emplace(); + return parse_into_container_impl::call(parser, first, last, ctx, variant_alt); + } else { + return parse_into_container_impl::call(parser, first, last, ctx, attr); + } } } diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index 61242686f..6c534f0dd 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include @@ -97,15 +97,14 @@ struct pass_sequence_attribute {}; template -struct partition_attribute -{ - using attr_category = traits::attribute_category_t; - - static_assert( - std::same_as, - "The parser expects tuple-like attribute type" - ); +struct partition_attribute {}; +template Attr> + requires + has_attribute_v && + has_attribute_v +struct partition_attribute +{ static constexpr std::size_t l_size = parser_traits::sequence_size; static constexpr std::size_t r_size = parser_traits::sequence_size; @@ -243,7 +242,7 @@ template Se, class parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) noexcept(is_nothrow_parsable_v) { - static_assert(Parsable); + // static_assert(Parsable); return parser.parse(first, last, ctx, attr); } @@ -281,49 +280,27 @@ parse_sequence(Parser const& parser, It& first, Se const& last, Context const& c template struct parse_into_container_impl> { - template - static constexpr bool is_container_substitute = traits::is_substitute_v< - typename sequence::attribute_type, - typename traits::container_value::type - >; - template Se, class Context, X4Attribute Attr> - requires is_container_substitute - [[nodiscard]] static constexpr bool - call( - sequence const& parser, It& first, Se const& last, - Context const& ctx, Attr& attr - ) noexcept(noexcept(parse_into_container_base_impl>::call( - parser, first, last, ctx, attr - ))) - { - return parse_into_container_base_impl>::call( - parser, first, last, ctx, attr - ); - } - - template Se, class Context, X4Attribute Attr> - requires (!is_container_substitute) [[nodiscard]] static constexpr bool call( sequence const& parser, It& first, Se const& last, Context const& ctx, Attr& attr ) // never noexcept (requires container insertion) { - static_assert( - std::same_as, traits::container_attr> || - std::same_as, traits::unused_attr> - ); - - if constexpr ( - std::same_as, unused_type> || - std::same_as, unused_container_type> - ) { - return detail::parse_sequence(parser, first, last, ctx, x4::assume_container(attr)); - + if constexpr (traits::is_container_v) { + constexpr bool sequence_attribute_can_directly_hold_value_type = traits::can_hold< + typename sequence::attribute_type, + typename traits::container_value::type + >::value; + if constexpr (sequence_attribute_can_directly_hold_value_type) { + return parse_into_container_impl_default>::call(parser, first, last, ctx, attr); + + } else { + auto&& appender = x4::make_container_appender(x4::assume_container(attr)); + return detail::parse_sequence(parser, first, last, ctx, appender); + } } else { - auto&& appender = x4::make_container_appender(x4::assume_container(attr)); - return detail::parse_sequence(parser, first, last, ctx, appender); + return parse_into_container_impl_default>::call(parser, first, last, ctx, attr); } } }; diff --git a/include/iris/x4/core/list_like_parser.hpp b/include/iris/x4/core/list_like_parser.hpp new file mode 100644 index 000000000..aad76b3fc --- /dev/null +++ b/include/iris/x4/core/list_like_parser.hpp @@ -0,0 +1,140 @@ +#ifndef IRIS_X4_CORE_LIST_LIKE_PARSER_HPP +#define IRIS_X4_CORE_LIST_LIKE_PARSER_HPP + +#include + +#include +#include + +#include // export +#include // export +#include +#include + +#include +#include + +#include +#include + +#include + +namespace iris::x4 { + +namespace list_like_parser { + +namespace detail { + +template + // non-variant `ExposedAttr` +struct unwrap_container_candidate +{ + using type = traits::synthesized_value< + unwrap_recursive_type< + typename unwrap_container_appender::type + > + >::type; +}; + +template + requires traits::is_variant_v> +struct unwrap_container_candidate +{ + using type = traits::variant_find_holdable_type< + unwrap_recursive_type, ParserAttr + >::type; +}; + +template +struct chunk_buffer_impl +{ + using type = unwrap_container_candidate::type; + static_assert(traits::X4Container::type>); +}; + +template +[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept +{ + return std::forward(value); +} + +template + requires traits::is_size_one_sequence_v> +[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept +{ + return std::forward_like(alloy::get<0>(std::forward(value))); +} + +template +struct unwrap_single_element_plain +{ + using type = std::remove_cvref_t; +}; + +template + requires traits::is_size_one_sequence_v> +struct unwrap_single_element_plain +{ + using type = std::remove_cvref_t>; +}; + +} // detail + + +template +using chunk_buffer = detail::chunk_buffer_impl::type; + + +template +[[nodiscard]] constexpr auto& get_container(ExposedAttr& attr) +{ + using unwrapped_attr_type = detail::unwrap_single_element_plain< + unwrap_recursive_type + >::type; + auto& unwrapped_attr = detail::unwrap_single_element(iris::unwrap_recursive(attr)); + + if constexpr (traits::is_variant_v) { + using container_alternative = traits::variant_find_holdable_type< + unwrapped_attr_type, ParserAttr + >::type; + + if (iris::holds_alternative(unwrapped_attr)) { + return iris::unsafe_get(unwrapped_attr); + } else { + return unwrapped_attr.template emplace(); + } + + } else { + return unwrapped_attr; + } +} + + +template +constexpr void successful_merge_into(ChunkBuf& chunk_buf, ExposedAttr& container_attr) +{ + traits::append( + container_attr, + std::make_move_iterator(traits::begin(chunk_buf)), + std::make_move_iterator(traits::end(chunk_buf)) + ); + traits::clear(chunk_buf); +} + +template + requires traits::is_variant_v::type> +constexpr void successful_merge_into(ChunkBuf& chunk_buf, ExposedAttr& container_attr) +{ + traits::append( + container_attr, + std::make_move_iterator(traits::begin(chunk_buf)), + std::make_move_iterator(traits::end(chunk_buf)) + ); + traits::clear(chunk_buf); +} + +} // list_like_parser + +} // iris::x4 + +#endif diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index c87d7f8f4..24612e6c1 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -153,49 +153,35 @@ move_to(Source&& src, Dest& dest) } template Dest> - requires traits::is_size_one_sequence_v && traits::variant_has_substitute_v + requires std::is_assignable_v constexpr void move_to(Source&& src, Dest& dest) noexcept(std::is_nothrow_assignable_v) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); - // dest is a variant, src is a single element tuple-like that the variant - // *can* directly hold. - static_assert(std::is_assignable_v); + // e.g. Dest is `iris::rvariant` and Source is `int` + // e.g. Dest is `iris::rvariant>` and Source is `alloy::tuple` dest = std::forward(src); } template Dest> - requires traits::is_size_one_sequence_v && (!traits::variant_has_substitute_v) + requires (!std::is_assignable_v) && traits::is_size_one_sequence_v constexpr void move_to(Source&& src, Dest& dest) noexcept(noexcept(dest = std::forward_like(alloy::get<0>(std::forward(src))))) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); - // dest is a variant, src is a single element tuple-like that the variant - // cannot directly hold. We'll try to unwrap the single element tuple-like. - - // Make sure that the Dest variant can really hold Source static_assert( - traits::variant_has_substitute_v>, + std::is_assignable_v(alloy::get<0>(std::forward(src))))>, "Error! The destination variant (Dest) cannot hold the source type (Source)" ); - // TODO: preliminarily invoke static_assert to check if the assignment is valid - dest = std::forward_like(alloy::get<0>(std::forward(src))); -} + // forward_like is *required*, since when Source is `alloy::tuple` `alloy::get<0>(std::forward(src))` returns `int&` whereas we want `int&&` instead -template Dest> - requires (!traits::is_size_one_sequence_v) -constexpr void -move_to(Source&& src, Dest& dest) - noexcept(std::is_nothrow_assignable_v) -{ - static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); - static_assert(std::is_assignable_v); - dest = std::forward(src); + // e.g. Dest is `iris::rvariant` and Source is `alloy::tuple` + dest = std::forward_like(alloy::get<0>(std::forward(src))); } template Dest> @@ -221,26 +207,17 @@ move_to(It first, Se last, Dest& dest) static_assert(!std::same_as, unused_type>); static_assert(!std::same_as, unused_container_type>); - // Be careful, this may result in converting surprisingly incompatible types, - // for example, `std::vector` and `std::set`. Such types must be - // handled *before* invoking `move_to`. - - if constexpr (is_ttp_specialization_of_v, container_appender>) { - traits::append(dest, first, last); - - } else { + if constexpr (!is_ttp_specialization_of_v) { if (!traits::is_empty(dest)) { traits::clear(dest); } - traits::append(dest, first, last); // try to reuse underlying memory buffer } -} -template Se, std::ranges::subrange_kind Kind> -constexpr void -move_to(It first, Se last, std::ranges::subrange& rng) -{ - rng = std::ranges::subrange(std::move(first), std::move(last)); + // Be careful, this may result in converting surprisingly incompatible types, + // for example, `std::vector` and `std::set`. Such types must be + // handled *before* invoking `move_to`. + + traits::append(dest, first, last); // try to reuse underlying memory buffer } template Se, traits::CategorizedAttr Dest> @@ -278,10 +255,14 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); - if constexpr (std::is_rvalue_reference_v) { - x4::move_to(std::make_move_iterator(std::ranges::begin(src)), std::make_move_iterator(std::ranges::end(src)), dest); + if constexpr (std::same_as, typename traits::container_value::type>) { + traits::push_back(dest, std::forward(src)); } else { - x4::move_to(std::ranges::begin(src), std::ranges::end(src), dest); + if constexpr (std::is_rvalue_reference_v) { + x4::move_to(std::make_move_iterator(std::ranges::begin(src)), std::make_move_iterator(std::ranges::end(src)), dest); + } else { + x4::move_to(std::ranges::begin(src), std::ranges::end(src), dest); + } } } diff --git a/include/iris/x4/core/parser.hpp b/include/iris/x4/core/parser.hpp index 7cd67f132..efdcbf68c 100644 --- a/include/iris/x4/core/parser.hpp +++ b/include/iris/x4/core/parser.hpp @@ -46,7 +46,6 @@ struct parser : private detail::parser_base static_assert(!std::is_reference_v); using derived_type = Derived; - static constexpr bool handles_container = false; static constexpr bool has_action = false; static constexpr bool need_rcontext = false; @@ -117,9 +116,11 @@ struct proxy_parser : unary_parser using attribute_type = parser_traits::attribute_type; static constexpr bool has_attribute = x4::has_attribute_v; - static constexpr bool handles_container = Subject::handles_container; static constexpr std::size_t sequence_size = parser_traits::sequence_size; + template + static constexpr bool handles_container = parser_traits::template handles_container; + using unary_parser::unary_parser; }; diff --git a/include/iris/x4/core/parser_traits.hpp b/include/iris/x4/core/parser_traits.hpp index b5f98abdc..acb25898f 100644 --- a/include/iris/x4/core/parser_traits.hpp +++ b/include/iris/x4/core/parser_traits.hpp @@ -1,4 +1,4 @@ -#ifndef IRIS_ZZ_X4_CORE_PARSER_TRAITS_HPP +#ifndef IRIS_ZZ_X4_CORE_PARSER_TRAITS_HPP #define IRIS_ZZ_X4_CORE_PARSER_TRAITS_HPP /*============================================================================= @@ -11,6 +11,8 @@ #include +#include + #include #include @@ -41,6 +43,23 @@ struct get_attribute_type using type = Parser::attribute_type; }; +template +struct get_handles_container +{ + static constexpr bool value = traits::can_hold< + typename get_attribute_type::type, + Container + >::value; +}; + +template + requires + requires { Parser::template handles_container; } +struct get_handles_container +{ + static constexpr bool value = Parser::template handles_container; +}; + } // detail @@ -112,7 +131,9 @@ struct parser_traits static constexpr std::size_t sequence_size = detail::get_sequence_size::value; - static constexpr bool handles_container = Parser::handles_container; + template + static constexpr bool handles_container = detail::get_handles_container::value; + static constexpr bool has_action = Parser::has_action; static constexpr bool need_rcontext = Parser::need_rcontext; }; diff --git a/include/iris/x4/directive.hpp b/include/iris/x4/directive.hpp index 20fe1595c..898383ef1 100644 --- a/include/iris/x4/directive.hpp +++ b/include/iris/x4/directive.hpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include diff --git a/include/iris/x4/directive/as.hpp b/include/iris/x4/directive/as.hpp index a61d946f4..c4f2c4a6c 100644 --- a/include/iris/x4/directive/as.hpp +++ b/include/iris/x4/directive/as.hpp @@ -1,4 +1,4 @@ -#ifndef IRIS_ZZ_X4_DIRECTIVE_AS_HPP +#ifndef IRIS_ZZ_X4_DIRECTIVE_AS_HPP #define IRIS_ZZ_X4_DIRECTIVE_AS_HPP /*============================================================================= @@ -54,6 +54,11 @@ struct as_directive : unary_parser> static constexpr bool has_attribute = !std::same_as; static constexpr bool has_action = false; // Explicitly re-enable attribute detection in `x4::rule` + // `as_directive` should NOT inherit underlying parser's `handles_container` + // because `as_directive` is an atomic parser. The default implementation of + // `parser_traits>::handles_container` must transparently + // handle this case. + private: static constexpr bool need_as_var = Subject::has_action; @@ -119,12 +124,12 @@ struct as_directive : unary_parser> return true; } - template Se, class Context, X4NonUnusedAttribute OuterAttr> - requires - (!std::same_as, T>) && - (!X4Movable) - constexpr void - parse(It&, Se const&, Context const&, OuterAttr&) const = delete; // `T` is not movable to the exposed attribute + //template Se, class Context, X4NonUnusedAttribute OuterAttr> + // requires + // (!std::same_as, T>) && + // (!X4Movable) + //constexpr void + //parse(It&, Se const&, Context const&, OuterAttr&) const = delete; // `T` is not movable to the exposed attribute }; namespace detail { diff --git a/include/iris/x4/directive/raw.hpp b/include/iris/x4/directive/raw.hpp deleted file mode 100644 index 00a27f987..000000000 --- a/include/iris/x4/directive/raw.hpp +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef IRIS_ZZ_X4_DIRECTIVE_RAW_HPP -#define IRIS_ZZ_X4_DIRECTIVE_RAW_HPP - -/*============================================================================= - Copyright (c) 2014 Joel de Guzman - Copyright (c) 2025 Nana Sakisaka - 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 -#include - -namespace iris::x4 { - -namespace detail { - -// Pseudo attribute type indicating that the parser wants the -// iterator range pointing to the [first, last) matching characters from -// the input iterators. -struct raw_attribute_t {}; - -} // detail - -template -struct raw_directive : unary_parser> -{ - using attribute_type = detail::raw_attribute_t; - - static constexpr bool handles_container = true; - - template Se, class Context, X4NonUnusedAttribute Attr> - [[nodiscard]] constexpr bool - parse(It& first, Se const& last, Context const& ctx, Attr& attr) const - // never noexcept; construction of `std::ranges::subrange` is never noexcept - { - static_assert(Parsable); - - x4::skip_over(first, last, ctx); - It local_it = first; - if (!this->subject.parse(local_it, last, ctx, unused)) return false; - - x4::move_to(first, local_it, attr); - first = local_it; - return true; - } - - template Se, class Context, X4UnusedAttribute UnusedAttr> - [[nodiscard]] constexpr bool - parse(It& first, Se const& last, Context const& ctx, UnusedAttr const&) const - noexcept(is_nothrow_parsable_v) - { - return this->subject.parse(first, last, ctx, unused); - } -}; - -namespace detail { - -struct raw_gen -{ - template - [[nodiscard]] constexpr raw_directive> - operator[](Subject&& subject) const - noexcept(is_parser_nothrow_constructible_v>, Subject>) - { - return {as_parser(std::forward(subject))}; - } -}; - -} // detail - -namespace parsers::directive { - -[[maybe_unused]] inline constexpr detail::raw_gen raw{}; - -} // parsers::directive - -using parsers::directive::raw; - -} // iris::x4 - -namespace iris::x4::traits { - -template Se, class Context> -struct pseudo_attribute -{ - using actual_type = std::ranges::subrange; - - [[nodiscard]] static constexpr actual_type - make_actual_type(It& first, Se const& last, Context const&, x4::detail::raw_attribute_t) - { - return {first, last}; - } -}; - -} // iris::x4::traits - -#endif diff --git a/include/iris/x4/directive/repeat.hpp b/include/iris/x4/directive/repeat.hpp index 5bf43d63d..e5cff7659 100644 --- a/include/iris/x4/directive/repeat.hpp +++ b/include/iris/x4/directive/repeat.hpp @@ -13,8 +13,8 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ==============================================================================*/ -#include -#include +#include +#include #include #include @@ -39,7 +39,7 @@ struct exact_count // handles repeat(exact)[p] [[nodiscard]] constexpr bool got_max(T i) const noexcept { return i >= exact_value; } [[nodiscard]] constexpr bool got_min(T i) const noexcept { return i >= exact_value; } - T exact_value; + T exact_value{}; }; template @@ -49,8 +49,8 @@ struct finite_count // handles repeat(min, max)[p] [[nodiscard]] constexpr bool got_max(T i) const noexcept { return i >= max_value; } [[nodiscard]] constexpr bool got_min(T i) const noexcept { return i >= min_value; } - T min_value; - T max_value; + T min_value{}; + T max_value{}; }; template @@ -60,7 +60,7 @@ struct infinite_count // handles repeat(min, inf)[p] [[nodiscard]] constexpr bool got_max(T /*i*/) const noexcept { return false; } [[nodiscard]] constexpr bool got_min(T i) const noexcept { return i >= min_value; } - T min_value; + T min_value{}; }; template @@ -87,9 +87,13 @@ template struct repeat_directive : proxy_parser> { using base_type = proxy_parser; - using attribute_type = traits::build_container::attribute_type>::type; + using attribute_type = traits::default_container::attribute_type>::type; - static constexpr bool handles_container = true; + template + static constexpr bool handles_container = std::disjunction_v< + traits::can_hold::attribute_type, Container>, + traits::can_hold::attribute_type, typename traits::container_value::type> + >; template requires std::is_constructible_v && std::is_constructible_v @@ -99,15 +103,55 @@ struct repeat_directive : proxy_parser(bounds)) {} - template Se, class Context, X4Attribute Attr> + template Se, class Context, X4NonUnusedAttribute Attr> [[nodiscard]] constexpr bool parse(It& first, Se const& last, Context const& ctx, Attr& attr) const - // never noexcept (requires container insertion) + // never noexcept; requires container insertion + { + auto& container_attr = list_like_parser::get_container(attr); + list_like_parser::chunk_buffer chunk_buf; + + It local_it = first; + typename Bounds::value_type i{}; + for (; !bounds_.got_min(i); ++i) { + if (detail::parse_into_container(this->subject, local_it, last, ctx, chunk_buf)) { + // We can't merge here; it will lead to partial status + } else { + return false; + } + } + list_like_parser::successful_merge_into(chunk_buf, container_attr); + + first = local_it; + // parse some more up to the maximum specified + for (; !bounds_.got_max(i); ++i) { + if (detail::parse_into_container(this->subject, first, last, ctx, chunk_buf)) { + list_like_parser::successful_merge_into(chunk_buf, container_attr); + } else { + break; + } + } + + if constexpr (has_context_v) { + return !x4::has_expectation_failure(ctx); + } else { + return true; + } + } + + template Se, class Context, X4UnusedAttribute UnusedAttr> + [[nodiscard]] constexpr bool + parse(It& first, Se const& last, Context const& ctx, UnusedAttr& unused_attr) const + noexcept( + noexcept(detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(unused_attr))) && + std::is_nothrow_copy_assignable_v && + is_nothrow_parsable_v + ) { It local_it = first; 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(attr))) { + if (!detail::parse_into_container(this->subject, local_it, last, ctx, x4::assume_container(unused_attr))) { return false; } } @@ -115,7 +159,7 @@ struct repeat_directive : proxy_parsersubject, first, last, ctx, x4::assume_container(attr))) { + if (!detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(unused_attr))) { break; } } @@ -136,12 +180,7 @@ namespace detail { struct repeat_gen { template - [[nodiscard, deprecated("`repeat[p]` has the exact same meaning as `*p`. Use `*p` instead.")]] - constexpr auto operator[](Subject&& subject) const - noexcept(noexcept(*as_parser(std::forward(subject)))) - { - return *as_parser(std::forward(subject)); - } + constexpr void operator[](Subject&& subject) = delete; // `repeat[p]` has the exact same meaning as `*p`. Use `*p` instead. template struct [[nodiscard]] repeat_gen_impl diff --git a/include/iris/x4/operator/difference.hpp b/include/iris/x4/operator/difference.hpp index 4d42381f0..5a1387010 100644 --- a/include/iris/x4/operator/difference.hpp +++ b/include/iris/x4/operator/difference.hpp @@ -25,8 +25,6 @@ struct difference : binary_parser> { using attribute_type = parser_traits::attribute_type; - static constexpr bool handles_container = Left::handles_container; - using binary_parser::binary_parser; template Se, class Context, X4Attribute Attr> diff --git a/include/iris/x4/operator/kleene.hpp b/include/iris/x4/operator/kleene.hpp index 0289ce968..12c00e9da 100644 --- a/include/iris/x4/operator/kleene.hpp +++ b/include/iris/x4/operator/kleene.hpp @@ -12,13 +12,10 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) =============================================================================*/ -#include -#include +#include #include #include -#include - #include #include #include @@ -28,16 +25,39 @@ namespace iris::x4 { template struct kleene : unary_parser> { - using attribute_type = traits::build_container::attribute_type>::type; + using attribute_type = traits::default_container::attribute_type>::type; - static constexpr bool handles_container = true; + template + static constexpr bool handles_container = std::disjunction_v< + std::bool_constant::template handles_container>, + traits::can_hold::attribute_type, typename traits::container_value::type> + >; - template Se, class Context, X4Attribute Attr> + template Se, class Context, X4NonUnusedAttribute Attr> [[nodiscard]] constexpr bool parse(It& first, Se const& last, Context const& ctx, Attr& attr) const - noexcept(noexcept(detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(attr)))) + // never noexcept; requires container insertion + { + auto& container_attr = list_like_parser::get_container(attr); + list_like_parser::chunk_buffer chunk_buf; + + while (detail::parse_into_container(this->subject, first, last, ctx, chunk_buf)) { + list_like_parser::successful_merge_into(chunk_buf, container_attr); + } + + if constexpr (has_context_v) { + return !x4::has_expectation_failure(ctx); + } else { + return true; + } + } + + template Se, class Context, X4UnusedAttribute UnusedAttr> + [[nodiscard]] constexpr bool + parse(It& first, Se const& last, Context const& ctx, UnusedAttr& unused_attr) const + noexcept(noexcept(detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(unused_attr)))) { - while (detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(attr))) + while (detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(unused_attr))) /* loop */; if constexpr (has_context_v) { diff --git a/include/iris/x4/operator/list.hpp b/include/iris/x4/operator/list.hpp index c6d9039b9..465f527dd 100644 --- a/include/iris/x4/operator/list.hpp +++ b/include/iris/x4/operator/list.hpp @@ -12,12 +12,9 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) =============================================================================*/ -#include -#include -#include +#include #include - -#include +#include #include #include @@ -28,30 +25,65 @@ namespace iris::x4 { template struct list : binary_parser> { - using attribute_type = traits::build_container::attribute_type>::type; + using attribute_type = traits::default_container::attribute_type>::type; - static constexpr bool handles_container = true; + template + static constexpr bool handles_container = std::disjunction_v< + std::bool_constant::template handles_container>, + traits::can_hold::attribute_type, typename traits::container_value::type> + >; using binary_parser::binary_parser; - template Se, class Context, X4Attribute Attr> + template Se, class Context, X4NonUnusedAttribute Attr> [[nodiscard]] constexpr bool parse(It& first, Se const& last, Context const& ctx, Attr& attr) const + // never noexcept; requires container insertion + { + auto& container_attr = list_like_parser::get_container(attr); + list_like_parser::chunk_buffer chunk_buf; + + // In order to succeed, we need to match at least one element + if (detail::parse_into_container(this->left, first, last, ctx, chunk_buf)) { + list_like_parser::successful_merge_into(chunk_buf, container_attr); + } else { + return false; + } + + It last_parse_it = first; + while ( + this->right.parse(last_parse_it, last, ctx, unused) && + detail::parse_into_container(this->left, last_parse_it, last, ctx, chunk_buf) + ) { + list_like_parser::successful_merge_into(chunk_buf, container_attr); + first = last_parse_it; + } + + if constexpr (has_context_v) { + return !x4::has_expectation_failure(ctx); + } else { + return true; + } + } + + template Se, class Context, X4UnusedAttribute UnusedAttr> + [[nodiscard]] constexpr bool + parse(It& first, Se const& last, Context const& ctx, UnusedAttr& unused_attr) const noexcept( - noexcept(detail::parse_into_container(this->left, first, last, ctx, x4::assume_container(attr))) && + noexcept(detail::parse_into_container(this->left, first, last, ctx, x4::assume_container(unused_attr))) && std::is_nothrow_copy_assignable_v && is_nothrow_parsable_v ) { // In order to succeed we need to match at least one element - if (!detail::parse_into_container(this->left, first, last, ctx, x4::assume_container(attr))) { + if (!detail::parse_into_container(this->left, first, last, ctx, x4::assume_container(unused_attr))) { return false; } It last_parse_it = first; while ( this->right.parse(last_parse_it, last, ctx, unused) && - detail::parse_into_container(this->left, last_parse_it, last, ctx, x4::assume_container(attr)) + detail::parse_into_container(this->left, last_parse_it, last, ctx, x4::assume_container(unused_attr)) ) { // TODO: can we reduce this copy assignment? first = last_parse_it; diff --git a/include/iris/x4/operator/optional.hpp b/include/iris/x4/operator/optional.hpp index 498b30f87..cc820f58d 100644 --- a/include/iris/x4/operator/optional.hpp +++ b/include/iris/x4/operator/optional.hpp @@ -32,7 +32,11 @@ struct optional : unary_parser> { using attribute_type = traits::build_optional::attribute_type>::type; - static constexpr bool handles_container = true; + template + static constexpr bool handles_container = std::disjunction_v< + std::bool_constant::template handles_container>, + traits::can_hold::attribute_type, typename traits::container_value::type> + >; // catch-all overload template< diff --git a/include/iris/x4/operator/plus.hpp b/include/iris/x4/operator/plus.hpp index 4b16ca713..e2c4a4099 100644 --- a/include/iris/x4/operator/plus.hpp +++ b/include/iris/x4/operator/plus.hpp @@ -12,12 +12,9 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) =============================================================================*/ -#include +#include #include #include -#include - -#include #include #include @@ -28,20 +25,49 @@ namespace iris::x4 { template struct plus : unary_parser> { - using attribute_type = traits::build_container::attribute_type>::type; + using attribute_type = traits::default_container::attribute_type>::type; - static constexpr bool handles_container = true; + template + static constexpr bool handles_container = std::disjunction_v< + std::bool_constant::template handles_container>, + traits::can_hold::attribute_type, typename traits::container_value::type> + >; - template Se, class Context, X4Attribute Attr> + template Se, class Context, X4NonUnusedAttribute Attr> [[nodiscard]] constexpr bool parse(It& first, Se const& last, Context const& ctx, Attr& attr) const - noexcept(noexcept(detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(attr)))) + // never noexcept; requires container insertion + { + auto& container_attr = list_like_parser::get_container(attr); + list_like_parser::chunk_buffer chunk_buf; + + if (detail::parse_into_container(this->subject, first, last, ctx, chunk_buf)) { + list_like_parser::successful_merge_into(chunk_buf, container_attr); + } else { + return false; + } + + while (detail::parse_into_container(this->subject, first, last, ctx, chunk_buf)) { + list_like_parser::successful_merge_into(chunk_buf, container_attr); + } + + if constexpr (has_context_v) { + return !x4::has_expectation_failure(ctx); + } else { + return true; + } + } + + template Se, class Context, X4UnusedAttribute UnusedAttr> + [[nodiscard]] constexpr bool + parse(It& first, Se const& last, Context const& ctx, UnusedAttr& unused_attr) const + noexcept(noexcept(detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(unused_attr)))) { - if (!detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(attr))) { + if (!detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(unused_attr))) { return false; } - while (detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(attr))) + while (detail::parse_into_container(this->subject, first, last, ctx, x4::assume_container(unused_attr))) /* loop */; if constexpr (has_context_v) { diff --git a/include/iris/x4/operator/sequence.hpp b/include/iris/x4/operator/sequence.hpp index 67109b1a9..ac4ce5786 100644 --- a/include/iris/x4/operator/sequence.hpp +++ b/include/iris/x4/operator/sequence.hpp @@ -11,11 +11,13 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) =============================================================================*/ +#include #include #include -#include +#include #include +#include #include @@ -28,6 +30,50 @@ namespace iris::x4 { +namespace detail { + +template +struct container_can_hold_element : std::is_same +{}; + +template + requires + (!std::same_as) && + (!traits::X4Container) && + requires (Container& c, Elem&& elem) { + traits::push_back(c, std::move(elem)); + } +struct container_can_hold_element + : std::true_type +{}; + +template + requires + (!std::same_as) && + traits::X4Container && + requires (Container& c, ContainerElem&& container_elem) { + x4::move_to( + std::make_move_iterator(traits::begin(container_elem)), + std::make_move_iterator(traits::end(container_elem)), + c + ); + } +struct container_can_hold_element + : std::true_type +{}; + +template +struct container_can_hold_sequence : container_can_hold_element +{}; + +template +struct container_can_hold_sequence> + // this should not delegate to `container_can_hold_sequence`; we don't want recursive expansion here. + : std::conjunction...> +{}; + +} // detail + template struct sequence : binary_parser> { @@ -36,6 +82,14 @@ struct sequence : binary_parser> static constexpr std::size_t sequence_size = parser_traits::sequence_size + parser_traits::sequence_size; + template + static constexpr bool handles_container = + ( + parser_traits::template handles_container && + parser_traits::template handles_container + ) || + detail::container_can_hold_sequence::value; + using binary_parser::binary_parser; template Se, class Context, X4UnusedAttribute UnusedAttr> diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index b2c22fc06..406ceb4e2 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -312,7 +312,6 @@ struct rule_definition : parser, unused_type>; - static constexpr bool handles_container = traits::is_container_v>; static constexpr bool force_attribute = ForceAttr; template @@ -396,7 +395,6 @@ struct rule : parser> using attribute_type = RuleAttr; static constexpr bool has_attribute = !std::is_same_v, unused_type>; - static constexpr bool handles_container = traits::is_container_v>; static constexpr bool force_attribute = ForceAttr; std::string_view name = "unnamed"; diff --git a/include/iris/x4/string/literal_string.hpp b/include/iris/x4/string/literal_string.hpp index 848e5ed7a..14bc1bf79 100644 --- a/include/iris/x4/string/literal_string.hpp +++ b/include/iris/x4/string/literal_string.hpp @@ -42,7 +42,6 @@ struct literal_string : parser> static_assert(!std::is_same_v, "`literal_string` with `unused_container_type` is not supported"); static constexpr bool has_attribute = !std::is_same_v; - static constexpr bool handles_container = has_attribute; template requires diff --git a/include/iris/x4/symbols.hpp b/include/iris/x4/symbols.hpp index d5a3e4b85..ab325d5af 100644 --- a/include/iris/x4/symbols.hpp +++ b/include/iris/x4/symbols.hpp @@ -62,7 +62,6 @@ struct symbols_parser_impl : parser using attribute_type = value_type; static constexpr bool has_attribute = !std::is_same_v; - static constexpr bool handles_container = traits::is_container_v; constexpr symbols_parser_impl(std::string_view name = "symbols") requires(IsShared) diff --git a/include/iris/x4/traits/attribute_category.hpp b/include/iris/x4/traits/attribute_category.hpp index aac482c69..10049137c 100644 --- a/include/iris/x4/traits/attribute_category.hpp +++ b/include/iris/x4/traits/attribute_category.hpp @@ -10,7 +10,6 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) =============================================================================*/ -#include #include #include #include @@ -34,7 +33,6 @@ struct container_attr {}; struct tuple_attr {}; struct variant_attr {}; struct optional_attr {}; -struct subrange_attr {}; template struct attribute_category @@ -108,13 +106,6 @@ struct attribute_category using type = optional_attr; }; -template - requires is_subrange_v> -struct attribute_category -{ - using type = subrange_attr; -}; - template requires traits::is_container_v> struct attribute_category diff --git a/include/iris/x4/traits/can_hold.hpp b/include/iris/x4/traits/can_hold.hpp new file mode 100644 index 000000000..469046c7f --- /dev/null +++ b/include/iris/x4/traits/can_hold.hpp @@ -0,0 +1,114 @@ +#ifndef IRIS_X4_TRAITS_CAN_HOLD_HPP +#define IRIS_X4_TRAITS_CAN_HOLD_HPP + +/*============================================================================= + Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2025 Nana Sakisaka + 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::traits { + +template +struct can_hold; + +template +struct is_variant; + +namespace detail { + +template>> +struct is_all_substitute_for_tuple_impl {}; + +template +struct is_all_substitute_for_tuple_impl> + : std::conjunction, alloy::tuple_element_t>...> {}; + +template +struct is_all_substitute_for_tuple : std::false_type {}; + +template + requires is_same_size_sequence_v +struct is_all_substitute_for_tuple : is_all_substitute_for_tuple_impl {}; + +template +struct value_type_can_hold + : can_hold::type, typename container_value::type> +{}; + +// This "implementation" exists for short-circuiting `can_hold` for certain trivial combinations +template +struct can_hold_impl : std::false_type {}; + +template + requires + alloy::is_tuple_like_v && + alloy::is_tuple_like_v +struct can_hold_impl + : detail::is_all_substitute_for_tuple +{}; + +template + requires + is_container_v && + is_container_v +struct can_hold_impl + : detail::value_type_can_hold +{}; + +template + requires is_variant::value && X4UnusedAttribute +struct can_hold_impl + : std::false_type +{}; + +template + requires (!is_variant::value) && X4UnusedAttribute +struct can_hold_impl + : std::false_type +{}; + +template + requires is_variant::value && (!X4UnusedAttribute) +struct can_hold_impl + : std::is_assignable +{}; + +template +struct can_hold_impl, std::optional> + : can_hold +{}; + +} // detail + +template +struct can_hold + : detail::can_hold_impl +{ + static_assert(X4Attribute); + static_assert(X4Attribute); +}; + +template +struct can_hold + : std::true_type +{ + static_assert(X4Attribute); +}; + +} // iris::x4::traits + +#endif diff --git a/include/iris/x4/traits/container_traits.hpp b/include/iris/x4/traits/container_traits.hpp index b48db67e7..bf17a1233 100644 --- a/include/iris/x4/traits/container_traits.hpp +++ b/include/iris/x4/traits/container_traits.hpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -80,7 +81,11 @@ struct remove_value_const> // Customization point template -struct container_value +struct container_value {}; + +template + requires requires { typename Container::value_type; } +struct container_value : detail::remove_value_const {}; @@ -137,8 +142,8 @@ struct push_back_fn template static constexpr void operator()(Container&, unused_type const&) noexcept { - static_assert(!std::is_same_v, unused_type>); - static_assert(!std::is_same_v, unused_container_type>); + static_assert(!std::same_as, unused_type>); + static_assert(!std::same_as, unused_container_type>); } template @@ -170,8 +175,8 @@ struct push_back_fn static constexpr void operator()(Container& c, T&& val) noexcept(noexcept(push_back_container::call(c, std::forward(val)))) { - static_assert(!std::is_same_v, unused_type>); - static_assert(!std::is_same_v, unused_container_type>); + static_assert(!std::same_as, unused_type>); + static_assert(!std::same_as, unused_container_type>); push_back_container::call(c, std::forward(val)); } }; @@ -208,6 +213,9 @@ struct append_fn static constexpr void operator()(Container& c, It first, Se last) noexcept(noexcept(c.insert(first, last))) { + // appending incompatible type into a container can result in unexpected behavior + // e.g. appending `int` into `vector>` compiles, but gets resolved into `vector::vector(size_t)` + static_assert(std::constructible_from::type, std::iter_value_t>); c.insert(first, last); } @@ -221,6 +229,9 @@ struct append_fn static constexpr void operator()(Container& c, It first, Se last) noexcept(noexcept(c.insert(std::ranges::end(c), first, last))) { + // appending incompatible type into a container can result in unexpected behavior + // e.g. appending `int` into `vector>` compiles, but gets resolved into `vector::vector(size_t)` + static_assert(std::constructible_from::type, std::iter_value_t>); c.insert(std::ranges::end(c), first, last); } @@ -229,8 +240,12 @@ struct append_fn static constexpr void operator()(Container& c, It first, Se last) noexcept(noexcept(append_container::call(c, first, last))) { - static_assert(!std::is_same_v, unused_type>); - static_assert(!std::is_same_v, unused_container_type>); + static_assert(!std::same_as, unused_type>); + static_assert(!std::same_as, unused_container_type>); + + // appending incompatible type into a container can result in unexpected behavior + // e.g. appending `int` into `vector>` compiles, but gets resolved into `vector::vector(size_t)` + static_assert(std::constructible_from::type, std::iter_value_t>); append_container::call(c, first, last); } }; @@ -308,8 +323,8 @@ struct is_empty_fn [[nodiscard]] static constexpr bool operator()(Container const& c) noexcept { - static_assert(!std::is_same_v); - static_assert(!std::is_same_v); + static_assert(!std::same_as); + static_assert(!std::same_as); return std::ranges::empty(c); } @@ -321,8 +336,8 @@ struct is_empty_fn operator()(Container const& c) noexcept(noexcept(is_empty_container::call(c))) { - static_assert(!std::is_same_v); - static_assert(!std::is_same_v); + static_assert(!std::same_as); + static_assert(!std::same_as); return is_empty_container::call(c); } }; @@ -438,13 +453,13 @@ template std::default_initializable && requires(T& c) { - typename T::value_type; // required + typename container_value::type; traits::begin(c); requires std::forward_iterator; traits::end(c); requires std::sentinel_for; traits::is_empty(c); - traits::push_back(c, std::declval()); + traits::push_back(c, std::declval::type>()); traits::append( c, std::declval(), @@ -468,52 +483,52 @@ concept X4Container = is_container_v>; // Customization point template -struct build_container +struct default_container { using type = std::vector; }; template -struct build_container> : build_container {}; +struct default_container> : default_container {}; template<> -struct build_container +struct default_container { using type = unused_container_type; }; template<> -struct build_container +struct default_container { using type = unused_container_type; }; template<> -struct build_container +struct default_container { using type = std::basic_string; }; template<> -struct build_container +struct default_container { using type = std::basic_string; }; template<> -struct build_container +struct default_container { using type = std::basic_string; }; template<> -struct build_container +struct default_container { using type = std::basic_string; }; template<> -struct build_container +struct default_container { using type = std::basic_string; }; diff --git a/include/iris/x4/traits/subrange_traits.hpp b/include/iris/x4/traits/subrange_traits.hpp deleted file mode 100644 index 70422b1f7..000000000 --- a/include/iris/x4/traits/subrange_traits.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef IRIS_ZZ_X4_TRAITS_SUBRANGE_TRAITS_HPP -#define IRIS_ZZ_X4_TRAITS_SUBRANGE_TRAITS_HPP - -/*============================================================================= - Copyright (c) 2001-2014 Joel de Guzman - Copyright (c) 2025 Nana Sakisaka - 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 - -namespace iris::x4::traits { - -template -struct is_subrange : std::false_type {}; - -template -constexpr bool is_subrange_v = is_subrange::value; - -template Se, std::ranges::subrange_kind Kind> -struct is_subrange> : std::true_type {}; - -} // iris::x4::traits - -#endif diff --git a/include/iris/x4/traits/substitution.hpp b/include/iris/x4/traits/substitution.hpp deleted file mode 100644 index f675231ea..000000000 --- a/include/iris/x4/traits/substitution.hpp +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef IRIS_ZZ_X4_TRAITS_SUBSTITUTION_HPP -#define IRIS_ZZ_X4_TRAITS_SUBSTITUTION_HPP - -/*============================================================================= - Copyright (c) 2001-2014 Joel de Guzman - Copyright (c) 2025 Nana Sakisaka - 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::traits { - -template -struct is_variant; - -// Find out if T can be a (strong) substitute for Attribute -template -struct is_substitute; - -template -constexpr bool is_substitute_v = is_substitute::value; - -template -struct variant_has_substitute; - -namespace detail { - -template>> -struct is_all_substitute_for_tuple_impl {}; - -template -struct is_all_substitute_for_tuple_impl> - : std::conjunction, alloy::tuple_element_t>...> {}; - -template -struct is_all_substitute_for_tuple : std::false_type {}; - -template - requires is_same_size_sequence_v -struct is_all_substitute_for_tuple : is_all_substitute_for_tuple_impl {}; - -template -struct value_type_is_substitute - : is_substitute::type, typename container_value::type> -{}; - -template -struct is_substitute_impl : std::false_type {}; - -template - requires std::conjunction_v< - alloy::is_tuple_like, - alloy::is_tuple_like - > -struct is_substitute_impl - : is_all_substitute_for_tuple -{}; - -template - requires - is_container_v> && - is_container_v> -struct is_substitute_impl - : value_type_is_substitute -{}; - -template - requires is_variant>::value -struct is_substitute_impl - : variant_has_substitute -{}; - -} // detail - -template -struct is_substitute - : std::disjunction< - std::is_same, - detail::is_substitute_impl - > -{}; - -template -struct is_substitute - : std::false_type -{}; - -// for reference T -template -struct is_substitute - : is_substitute -{}; - -// for reference Attribute -template -struct is_substitute - : is_substitute -{}; - -template -struct is_substitute, std::optional> - : is_substitute -{}; - -} // iris::x4::traits - -#endif diff --git a/include/iris/x4/traits/variant_traits.hpp b/include/iris/x4/traits/variant_traits.hpp index 34203ab6f..f3fdc5415 100644 --- a/include/iris/x4/traits/variant_traits.hpp +++ b/include/iris/x4/traits/variant_traits.hpp @@ -12,10 +12,11 @@ #include -#include +#include #include +#include #include namespace iris::x4::traits { @@ -34,20 +35,40 @@ struct is_variant> : std::true_type {}; namespace detail { -template -struct variant_find_substitute_impl; +template +struct any_of_unwrapped_exactly_same; -template -struct variant_find_substitute_impl +template +struct any_of_unwrapped_exactly_same + : std::false_type +{}; + +template + requires std::same_as> +struct any_of_unwrapped_exactly_same + : std::true_type +{}; + +template + requires (!std::same_as>) +struct any_of_unwrapped_exactly_same + : any_of_unwrapped_exactly_same +{}; + +template +struct variant_find_holdable_type_impl; + +template +struct variant_find_holdable_type_impl { - using type = Attr; + using type = T; }; -template -struct variant_find_substitute_impl +template +struct variant_find_holdable_type_impl { using type = std::conditional_t< - is_substitute_v>, + can_hold, T>::value, // Given some type `T`, when both `T` and `recursive_wrapper` is seen // during attribute resolution, X4 should ideally materialize the latter @@ -60,63 +81,36 @@ struct variant_find_substitute_impl // First, // no need to unwrap due to the reason described above - typename variant_find_substitute_impl::type + typename variant_find_holdable_type_impl::type >; }; } // detail -template -struct variant_find_substitute; +template +struct variant_find_holdable_type; -template -using variant_find_substitute_t = typename variant_find_substitute::type; - -template -struct variant_find_substitute +template +struct variant_find_holdable_type { - using type = Attr; + static_assert(is_variant_v); + using type = Variant; }; -// Recursively find the first type from the variant that can be a substitute for `Attr`. -// If none is found, returns `Attr`. -template - requires (!std::same_as, Attr>) -struct variant_find_substitute, Attr> +template + requires (!std::same_as, T>) && detail::any_of_unwrapped_exactly_same::value +struct variant_find_holdable_type, T> { - using type = typename detail::variant_find_substitute_impl::type; + using type = T; }; - -template -struct variant_has_substitute; - -template -constexpr bool variant_has_substitute_v = variant_has_substitute::value; - -template -struct variant_has_substitute - : std::true_type -{}; - -template -struct variant_has_substitute - : std::true_type -{}; - -template -struct variant_has_substitute - : std::true_type -{}; - -// Recursively find the first type from the variant that can be a substitute for `T`. -// Returns boolean value whether it was found. -template - requires (!std::same_as, Attr>) -struct variant_has_substitute, Attr> - : std::disjunction...> -{}; +template + requires (!std::same_as, T>) && (!detail::any_of_unwrapped_exactly_same::value) +struct variant_find_holdable_type, T> +{ + using type = typename detail::variant_find_holdable_type_impl::type; +}; } // iris::x4::traits diff --git a/modules/iris b/modules/iris index cec583eed..fe9e3a14d 160000 --- a/modules/iris +++ b/modules/iris @@ -1 +1 @@ -Subproject commit cec583eed7ab2fb70e7ef6aa52ab840bc03e92ca +Subproject commit fe9e3a14dd412910b5a6dd6f14590bb6ced68ddc diff --git a/test/x4/CMakeLists.txt b/test/x4/CMakeLists.txt index 9ef0da995..ab7fdac55 100644 --- a/test/x4/CMakeLists.txt +++ b/test/x4/CMakeLists.txt @@ -44,7 +44,6 @@ x4_define_tests( and_predicate as attr - attribute attribute_type_check bool char @@ -66,14 +65,15 @@ x4_define_tests( list lit matches + move_to not_predicate no_case no_skip omit optional parser + partial_success plus - raw real1 real2 real3 @@ -86,6 +86,7 @@ x4_define_tests( seek sequence skip + substitution symbols1 symbols2 symbols3 diff --git a/test/x4/alternative.cpp b/test/x4/alternative.cpp index 5b4adf757..6c1ea50a5 100644 --- a/test/x4/alternative.cpp +++ b/test/x4/alternative.cpp @@ -233,7 +233,7 @@ TEST_CASE("alternative") using attribute_type = x4::parser_traits::attribute_type; STATIC_CHECK(std::same_as>); - using substitute_type = x4::traits::variant_find_substitute_t; + using substitute_type = x4::traits::variant_find_holdable_type::type; STATIC_CHECK(std::same_as>); Attr var; @@ -247,7 +247,7 @@ TEST_CASE("alternative") using attribute_type = x4::parser_traits::attribute_type; STATIC_CHECK(std::same_as); - using substitute_type = x4::traits::variant_find_substitute_t; + using substitute_type = x4::traits::variant_find_holdable_type::type; STATIC_CHECK(std::same_as); Attr var; diff --git a/test/x4/attr.cpp b/test/x4/attr.cpp index bba786535..e85d21a7c 100644 --- a/test/x4/attr.cpp +++ b/test/x4/attr.cpp @@ -142,11 +142,35 @@ TEST_CASE("attr") REQUIRE(parse("s", "s" >> attr(std::string("123")), s)); CHECK(s == "123"); } + + // container of container + + // vector> + { + std::vector> vecs; + std::vector vec{1, 2, 3}; + x4::move_to(std::move(vec), vecs); + CHECK(vecs == std::vector>{std::vector{1, 2, 3}}); + } + { + std::vector> vecs; + REQUIRE(parse("", attr(std::vector{1, 2, 3}) >> attr(std::vector{4, 5, 6}), vecs)); + CHECK(vecs == std::vector{std::vector{1, 2, 3}, std::vector{4, 5, 6}}); + } + + // vector + { + std::vector strs; + std::string str = "abc"; + x4::move_to(std::move(str), strs); + CHECK(strs == std::vector{std::string("abc")}); + } { std::vector strs; REQUIRE(parse("", attr(std::string("123")) >> attr(std::string("456")), strs)); CHECK(strs == std::vector{"123", "456"}); } + { std::string s; REQUIRE(parse("", attr(std::string("123")) >> attr(std::string("456")), s)); diff --git a/test/x4/attribute.cpp b/test/x4/attribute.cpp deleted file mode 100644 index 2dee8dbd9..000000000 --- a/test/x4/attribute.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/*============================================================================= - Copyright (c) 2025 Nana Sakisaka - 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 -#include -#include -#include - -#include - -#include -#include -#include -#include - -// NOLINTBEGIN(readability-container-size-empty) - -namespace { - -struct strong_int -{ - int value = 0; - int assigned_count = 0; - - strong_int() = default; - strong_int(strong_int const&) = default; - strong_int(strong_int&&) noexcept = default; - - explicit strong_int(int value) : value(value) {} - - strong_int& operator=(strong_int const& other) - { - value = other.value; - ++assigned_count; - return *this; - } - - strong_int& operator=(strong_int&& other) noexcept - { - value = other.value; - ++assigned_count; - return *this; - } - - strong_int& operator=(int new_value) - { - value = new_value; - ++assigned_count; - return *this; - } - - bool operator==(strong_int const& other) const - { - return value == other.value; - } - - friend std::ostream& operator<<(std::ostream& os, strong_int const& si) - { - return os << si.value; - } -}; - -} // anonymous - -TEST_CASE("attribute_alternative_hold") -{ - using x4::attr; - using x4::eps; - using x4::omit; - using x4::int_; - - using x4::string; - using x4::lit; - using x4::standard::char_; - using x4::standard::space; - - // Sanity checks - { - int i = -1; - REQUIRE(parse("", attr(42), i)); - CHECK(i == 42); - } - { - std::string str; - REQUIRE(parse("", attr("foo"), str)); - CHECK(str == "foo"); - } - { - std::string str; - REQUIRE(parse("foo", string("foo"), str)); - CHECK(str == "foo"); - } - - // Non-string container attribute - // Related to: https://github.com/boostorg/spirit/issues/378 - { - static_assert(x4::traits::CategorizedAttr, x4::traits::container_attr>); - static_assert(x4::traits::X4Container>); - static_assert(x4::traits::is_container_v>); - - { - std::vector ints; - REQUIRE(parse("1 2", eps(false) | attr(98) >> attr(99), ints).is_partial_match()); - CHECK(ints == std::vector{98, 99}); - } - { - std::vector ints; - REQUIRE(parse("1 2", int_ >> int_ >> eps(false) | attr(98) >> attr(99), space, ints).is_partial_match()); - // If we don't properly "hold" the value on the failed branch of - // `x4::alternative`, we would see {1, 2, 98, 99} here. - CHECK(ints == std::vector{98, 99}); - } - // Failed parse should not modify the exposed attribute - { - std::vector ints; - REQUIRE(!parse("1 2", int_ >> int_ >> eps(false) | attr(98) >> attr(99) >> eps(false), space, ints)); - // Wrong implementation yields {1, 2, 98, 99} or {98, 99} - CHECK(ints == std::vector{}); - } - { - std::vector ints; - REQUIRE(parse("1 2", attr(std::vector{3, 4}) >> eps(false) | attr(98) >> attr(99), space, ints).is_partial_match()); - // Wrong implementation yields {3, 4, 98, 99} - CHECK(ints == std::vector{98, 99}); - } - } - - // String container attribute - // Intended for testing `detail::string_parse` - { - static_assert(x4::traits::CategorizedAttr); - static_assert(x4::traits::X4Container); - static_assert(x4::traits::is_container_v); - - { - std::string str; - REQUIRE(parse("foodie", "fox" | string("foodie"), str)); - CHECK(str == "foodie"); - } - { - constexpr auto fox = char_('f') >> char_('o') >> char_('x'); - - std::string str; - REQUIRE(parse("foodie", fox | string("foodie"), str)); - // If we don't properly "hold" the value on the failed branch of - // `x4::alternative`, we would see "fofoodie" here. - CHECK(str == "foodie"); - } - { - constexpr auto foo = char_('f') >> char_('o') >> char_('o'); - - std::string str; - REQUIRE(parse("foodie", foo >> eps(false) | string("foodie"), str)); - // Wrong implementation yields "foofoodie" - CHECK(str == "foodie"); - } - { - std::string str; - REQUIRE(parse("foodie", attr("bookworm") >> eps(false) | string("foodie"), str)); - // Wrong implementation yields "bookwormfoodie" - CHECK(str == "foodie"); - } - // Failed parse should not modify the exposed attribute - { - std::string str; - REQUIRE(!parse("foodie", attr("bookworm") >> eps(false) | string("foodie") >> eps(false), str)); - // Wrong implementation yields "bookwormfoodie" or "foodie" - CHECK(str == ""); - } - - { - std::string str; - REQUIRE(parse("foodie", string("food") >> "fan" | string("foodie"), str)); - // Wrong implementation yields "foodfoodie" - CHECK(str == "foodie"); - } - } - - // Plain attribute - { - static_assert(x4::traits::CategorizedAttr); - static_assert(!x4::traits::X4Container); - static_assert(!x4::traits::is_container_v); - - { - strong_int si; - REQUIRE(parse("1", int_ | attr(strong_int{9}), si)); - CHECK(si == strong_int{1}); - CHECK(si.assigned_count == 1); - } - { - strong_int si; - REQUIRE(parse("1", int_ >> eps(false) | int_, si)); - CHECK(si == strong_int{1}); - // Wrong implementation yields 2, because `x4::alternative` wrongly mutates the exposed variable - CHECK(si.assigned_count == 1); - } - } - - // Tuple attribute - { - using pair_int = std::pair; - - static_assert(x4::traits::CategorizedAttr); - static_assert(!x4::traits::X4Container); - static_assert(!x4::traits::is_container_v); - - { - pair_int pi; - REQUIRE(parse("1 2", int_ >> int_ | attr(pair_int{98, 99}), space, pi)); - CHECK(pi == pair_int{1, 2}); - } - { - pair_int pi; - REQUIRE(parse("1 2", - int_ >> int_ >> eps(false) | attr(pair_int{98, 99}) >> omit[int_ >> int_], - space, pi - )); - CHECK(pi == pair_int{98, 99}); - } - } -} - -// NOLINTEND(readability-container-size-empty) diff --git a/test/x4/container_support.cpp b/test/x4/container_support.cpp index 0722b7425..e7efada1a 100644 --- a/test/x4/container_support.cpp +++ b/test/x4/container_support.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -33,57 +34,22 @@ namespace x4 = iris::x4; -// check if we did not break user defined specializations -namespace check_substitute { - -template struct foo {}; -template struct bar { using type = T; }; -template struct is_bar : std::false_type {}; -template struct is_bar> : std::true_type {}; - -} // check_substitute - -namespace iris::x4::traits { - -using namespace check_substitute; - -template -struct is_substitute, foo> - : is_substitute -{}; - -template - requires is_bar::value && is_bar::value -struct is_substitute - : is_substitute -{}; - -} // iris::x4::traits - -namespace check_substitute { - -using x4::traits::is_substitute_v; -static_assert( is_substitute_v, foo>); -static_assert(!is_substitute_v, foo>); -static_assert( is_substitute_v, bar>); -static_assert(!is_substitute_v, bar>); - -} // check_substitute - -namespace { - constexpr x4::rule> pair_rule("pair"); constexpr x4::rule string_rule("string"); -constexpr auto pair_rule_def = string_rule >> x4::lit('=') >> string_rule; constexpr auto string_rule_def = x4::lexeme[*x4::standard::alnum]; +constexpr auto pair_rule_def = string_rule >> x4::lit('=') >> string_rule; -IRIS_X4_DEFINE(pair_rule) IRIS_X4_DEFINE(string_rule) +IRIS_X4_DEFINE(pair_rule) + +constexpr auto as_string_parser = x4::as(x4::lexeme[*x4::standard::alnum]); +constexpr auto as_pair_parser = x4::as>(as_string_parser >> x4::lit('=') >> as_string_parser); template void test_map_support() { + // rule version { constexpr auto rule = pair_rule % x4::lit(','); Container actual; @@ -103,11 +69,33 @@ void test_map_support() Container container; CHECK(parse("k1=v1,k2=v2,k2=v3", cic_rule, container)); } + + // as version + { + constexpr auto rule = as_pair_parser % x4::lit(','); + Container actual; + REQUIRE(parse("k1=v1,k2=v2,k2=v3", rule, actual)); + CHECK(actual.size() == 2); + CHECK(actual == Container{{"k1", "v1"}, {"k2", "v2"}}); + } + { + // test sequences parsing into containers + constexpr auto seq_rule = as_pair_parser >> ',' >> as_pair_parser >> ',' >> as_pair_parser; + Container container; + CHECK(parse("k1=v1,k2=v2,k2=v3", seq_rule, container)); + } + { + // test parsing container into container + constexpr auto cic_rule = as_pair_parser >> +(',' >> as_pair_parser); + Container container; + CHECK(parse("k1=v1,k2=v2,k2=v3", cic_rule, container)); + } } template void test_multimap_support() { + // rule version { constexpr auto rule = pair_rule % x4::lit(','); Container actual; @@ -127,11 +115,33 @@ void test_multimap_support() Container container; CHECK(parse("k1=v1,k2=v2,k2=v3", cic_rule, container)); } + + // as version + { + constexpr auto rule = as_pair_parser % x4::lit(','); + Container actual; + REQUIRE(parse("k1=v1,k2=v2,k2=v3", rule, actual)); + CHECK(actual.size() == 3); + CHECK(actual == Container{ {"k1", "v1"}, {"k2", "v2"}, {"k2", "v3"} }); + } + { + // test sequences parsing into containers + constexpr auto seq_rule = as_pair_parser >> ',' >> as_pair_parser >> ',' >> as_pair_parser; + Container container; + CHECK(parse("k1=v1,k2=v2,k2=v3", seq_rule, container)); + } + { + // test parsing container into container + constexpr auto cic_rule = as_pair_parser >> +(',' >> as_pair_parser); + Container container; + CHECK(parse("k1=v1,k2=v2,k2=v3", cic_rule, container)); + } } template void test_sequence_support() { + // rule version { constexpr auto rule = string_rule % x4::lit(','); Container actual; @@ -151,11 +161,33 @@ void test_sequence_support() Container container; CHECK(parse("e1,e2,e2", cic_rule, container)); } + + // as version + { + constexpr auto rule = as_string_parser % x4::lit(','); + Container actual; + REQUIRE(parse("e1,e2,e2", rule, actual)); + CHECK(actual.size() == 3); + CHECK(actual == Container{ "e1", "e2", "e2" }); + } + { + // test sequences parsing into containers + constexpr auto seq_rule = as_string_parser >> ',' >> as_string_parser >> ',' >> as_string_parser; + Container container; + CHECK(parse("e1,e2,e2", seq_rule, container)); + } + { + // test parsing container into container + constexpr auto cic_rule = as_string_parser >> +(',' >> as_string_parser); + Container container; + CHECK(parse("e1,e2,e2", cic_rule, container)); + } } template void test_set_support() { + // rule version { constexpr auto rule = string_rule % x4::lit(','); Container actual; @@ -175,11 +207,33 @@ void test_set_support() Container container; CHECK(parse("e1,e2,e2", cic_rule, container)); } + + // as version + { + constexpr auto rule = as_string_parser % x4::lit(','); + Container actual; + REQUIRE(parse("e1,e2,e2", rule, actual)); + CHECK(actual.size() == 2); + CHECK(actual == Container{ "e1", "e2" }); + } + { + // test sequences parsing into containers + constexpr auto seq_rule = as_string_parser >> ',' >> as_string_parser >> ',' >> as_string_parser; + Container container; + CHECK(parse("e1,e2,e2", seq_rule, container)); + } + { + // test parsing container into container + constexpr auto cic_rule = as_string_parser >> +(',' >> as_string_parser); + Container container; + CHECK(parse("e1,e2,e2", cic_rule, container)); + } } template void test_multiset_support() { + // rule version { constexpr auto rule = string_rule % x4::lit(','); Container actual; @@ -199,11 +253,33 @@ void test_multiset_support() Container container; CHECK(parse("e1,e2,e2", cic_rule, container)); } + + // as version + { + constexpr auto rule = as_string_parser % x4::lit(','); + Container actual; + REQUIRE(parse("e1,e2,e2", rule, actual)); + CHECK(actual.size() == 3); + CHECK(actual == Container{"e1", "e2", "e2"}); + } + { + // test sequences parsing into containers + constexpr auto seq_rule = as_string_parser >> ',' >> as_string_parser >> ',' >> as_string_parser; + Container container; + CHECK(parse("e1,e2,e2", seq_rule, container)); + } + { + // test parsing container into container + constexpr auto cic_rule = as_string_parser >> +(',' >> as_string_parser); + Container container; + CHECK(parse("e1,e2,e2", cic_rule, container)); + } } template void test_string_support() { + // rule version { constexpr auto rule = string_rule % x4::lit(','); Container container; @@ -223,9 +299,28 @@ void test_string_support() Container container; CHECK(parse("e1,e2,e2", cic_rule, container)); } -} -} // anonymous + // as version + { + constexpr auto rule = as_string_parser % x4::lit(','); + Container container; + REQUIRE(parse("e1,e2,e2", rule, container)); + CHECK(container.size() == 6); + CHECK(container == Container{"e1e2e2"}); + } + { + // test sequences parsing into containers + constexpr auto seq_rule = as_string_parser >> ',' >> as_string_parser >> ',' >> as_string_parser; + Container container; + CHECK(parse("e1,e2,e2", seq_rule, container)); + } + { + // test parsing container into container + constexpr auto cic_rule = as_string_parser >> +(',' >> as_string_parser); + Container container; + CHECK(parse("e1,e2,e2", cic_rule, container)); + } +} TEST_CASE("container_support") { @@ -276,25 +371,25 @@ TEST_CASE("container_support") STATIC_CHECK(is_container_v>>); STATIC_CHECK(is_associative_v>>); - STATIC_CHECK(is_container_v>); - STATIC_CHECK(is_associative_v>); - STATIC_CHECK(is_container_v>>); - STATIC_CHECK(is_associative_v>>); + STATIC_CHECK(is_container_v>); + STATIC_CHECK(is_associative_v>); + STATIC_CHECK(is_container_v>>); + STATIC_CHECK(is_associative_v>>); - STATIC_CHECK(is_container_v>); - STATIC_CHECK(is_associative_v>); - STATIC_CHECK(is_container_v>>); - STATIC_CHECK(is_associative_v>>); + STATIC_CHECK(is_container_v>); + STATIC_CHECK(is_associative_v>); + STATIC_CHECK(is_container_v>>); + STATIC_CHECK(is_associative_v>>); - STATIC_CHECK(is_container_v>); - STATIC_CHECK(is_associative_v>); - STATIC_CHECK(is_container_v>>); - STATIC_CHECK(is_associative_v>>); + STATIC_CHECK(is_container_v>); + STATIC_CHECK(is_associative_v>); + STATIC_CHECK(is_container_v>>); + STATIC_CHECK(is_associative_v>>); - STATIC_CHECK(is_container_v>); - STATIC_CHECK(is_associative_v>); - STATIC_CHECK(is_container_v>>); - STATIC_CHECK(is_associative_v>>); + STATIC_CHECK(is_container_v>); + STATIC_CHECK(is_associative_v>); + STATIC_CHECK(is_container_v>>); + STATIC_CHECK(is_associative_v>>); // ------------------------------------------------------------------ @@ -310,9 +405,9 @@ TEST_CASE("container_support") test_multiset_support>(); test_multiset_support>(); - test_map_support>(); - test_map_support>(); + test_map_support>(); + test_map_support>(); - test_multimap_support>(); - test_multimap_support>(); + test_multimap_support>(); + test_multimap_support>(); } diff --git a/test/x4/expect.cpp b/test/x4/expect.cpp index 86fe5b483..4d6d04d72 100644 --- a/test/x4/expect.cpp +++ b/test/x4/expect.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -152,7 +151,6 @@ TEST_CASE("expectation_failure_context_uninstantiated_in_expect_less_parse") using x4::no_case; using x4::no_skip; using x4::omit; - using x4::raw; using x4::repeat; using x4::seek; using x4::skip; @@ -216,7 +214,6 @@ TEST_CASE("expectation_failure_context_uninstantiated_in_expect_less_parse") (void)no_skip[int_ >> int_].parse(first, last, unused, dummy_ints); (void)omit[eps].parse(first, last, unused, unused); - (void)raw[eps].parse(first, last, unused, unused); (void)repeat(1)[eps].parse(first, last, unused, unused); (void)seek[eps].parse(first, last, unused, unused); (void)skip(space)[eps].parse(first, last, unused, unused); @@ -252,7 +249,7 @@ TEST_CASE("expectation_failure_context_uninstantiated_in_expect_less_parse") (void)(-(int_ >> int_)).parse(first, last, unused, dummy_optional_ints); (void)(+eps(false)).parse(first, last, unused, unused); - (void)(+int_).parse(first, last, unused, dummy_int); + (void)(+int_).parse(first, last, unused, dummy_ints); (void)(eps >> eps).parse(first, last, unused, unused); (void)(int_ >> int_).parse(first, last, unused, dummy_ints); @@ -292,7 +289,6 @@ TEST_CASE("expect") using x4::no_case; using x4::no_skip; using x4::omit; - using x4::raw; using x4::skip; using x4::seek; using x4::repeat; @@ -660,15 +656,6 @@ TEST_CASE("expect") }); } - // raw - { - X4_TEST_SUCCESS_PASS("ab", raw[lit('a') > 'b']); - X4_TEST_FAILURE("ab", raw[lit('a') > 'c'], { - CHECK(which == "'c'"sv); - CHECK(where == "b"sv); - }); - } - // repeat { X4_TEST_SUCCESS_PASS("ababac", repeat(1, 3)[lit('a') >> 'b'] >> "ac" | +alpha); diff --git a/test/x4/iris_x4_test.hpp b/test/x4/iris_x4_test.hpp index d5bbb77a2..5b7a38981 100644 --- a/test/x4/iris_x4_test.hpp +++ b/test/x4/iris_x4_test.hpp @@ -174,6 +174,19 @@ constexpr synth_parser synth{}; constexpr synth_parser synth_move_only{}; +template +struct custom_container +{ + using value_type = T; + T* begin() { return nullptr; } + T* end() { return nullptr; } + bool empty() const { return true; } + void clear() {} + void push_back(T const&) {} + template + void insert(T*, It, Se) {} +}; + } // x4_test using x4_test::parse; diff --git a/test/x4/iterator.cpp b/test/x4/iterator.cpp index 16070ee10..14f9dc8d6 100644 --- a/test/x4/iterator.cpp +++ b/test/x4/iterator.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -290,7 +289,6 @@ TEST_CASE("rollback on failed parse (directive)") using x4::no_case; using x4::no_skip; using x4::omit; - using x4::raw; using x4::repeat; using x4::seek; using x4::skip; @@ -465,34 +463,6 @@ TEST_CASE("rollback on failed parse (directive)") CHECK(dummy_int == -1); // `omit` never yields an attribute } - { - constexpr auto input = "foo"sv; - auto first = input.begin(); - std::ranges::subrange dummy_subrange{input.end(), input.end()}; - REQUIRE_FALSE(raw[eps(false)].parse(first, input.end(), unused, dummy_subrange)); - CHECK(first == input.begin()); - CHECK(dummy_subrange.begin() == input.end()); - CHECK(dummy_subrange.end() == input.end()); - } - { - constexpr auto input = "foo"sv; - auto first = input.begin(); - std::ranges::subrange dummy_subrange{input.end(), input.end()}; - REQUIRE_FALSE(raw[int_].parse(first, input.end(), unused, dummy_subrange)); - CHECK(first == input.begin()); - CHECK(dummy_subrange.begin() == input.end()); - CHECK(dummy_subrange.end() == input.end()); - } - { - constexpr auto input = "42"sv; - auto first = input.begin(); - std::ranges::subrange dummy_subrange{input.end(), input.end()}; - REQUIRE_FALSE(raw[int_ >> eps(false)].parse(first, input.end(), unused, dummy_subrange)); - CHECK(first == input.begin()); - CHECK(dummy_subrange.begin() == input.end()); - CHECK(dummy_subrange.end() == input.end()); - } - { constexpr auto input = "foo"sv; auto first = input.begin(); @@ -513,7 +483,7 @@ TEST_CASE("rollback on failed parse (directive)") std::vector dummy_bools; REQUIRE_FALSE(repeat(1)[true_ >> true_].parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); - CHECK(dummy_bools == std::vector{true}); // sequence parser has side effect + CHECK(dummy_bools == std::vector{}); } { constexpr auto input = "true123"sv; @@ -521,7 +491,7 @@ TEST_CASE("rollback on failed parse (directive)") std::vector dummy_bools; REQUIRE_FALSE(repeat(2)[true_].parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); - CHECK(dummy_bools == std::vector{true}); // sequence parser has side effect + CHECK(dummy_bools == std::vector{}); } { @@ -912,7 +882,6 @@ TEST_CASE("rollback on failed parse (rule)") TEST_CASE("transform iterator") { - using x4::raw; using x4::eps; using x4::eoi; using x4::standard::upper; @@ -931,13 +900,6 @@ TEST_CASE("transform iterator") CHECK("ABCDE" == str); } - { - std::ranges::subrange> str; - - REQUIRE(parse(std::ranges::begin(rng), std::ranges::end(rng), raw[+upper >> eoi], str)); - CHECK(std::ranges::equal(std::string("ABCDE"), str)); - } - CHECK(parse(std::ranges::begin(rng), std::ranges::end(rng), (repeat(6)[upper] | repeat(5)[upper]) >> eoi)); } diff --git a/test/x4/kleene.cpp b/test/x4/kleene.cpp index 789244748..693868dad 100644 --- a/test/x4/kleene.cpp +++ b/test/x4/kleene.cpp @@ -19,27 +19,6 @@ #include #include -struct x_attr {}; - -namespace iris::x4::traits { - -template<> -struct container_value -{ - using type = char; // value type of container -}; - -template<> -struct push_back_container -{ - static constexpr void call(x_attr& /*c*/, char /*val*/) noexcept - { - // push back value type into container - } -}; - -} // x4::traits - TEST_CASE("kleene") { using x4::char_; @@ -131,7 +110,7 @@ TEST_CASE("kleene") } { - x_attr x; + x4_test::custom_container x; (void)parse("abcde", *char_, x); } diff --git a/test/x4/move_to.cpp b/test/x4/move_to.cpp new file mode 100644 index 000000000..b7f168bde --- /dev/null +++ b/test/x4/move_to.cpp @@ -0,0 +1,58 @@ +#include "iris_x4_test.hpp" + +#include + +#include +#include +#include +#include + +#include + +struct X {}; +struct Y {}; +struct Z {}; + +struct Xs +{ + std::vector xs; +}; +IRIS_ALLOY_ADAPT_STRUCT(Xs, xs); + +using XYZ = iris::rvariant; + + +TEST_CASE("move_to") +{ + // TODO: add more test + + // Currently not permitted by design, but may be changed in the future + // if legitimate rationale is discovered + { + //XYZ xyz; + //xyz = std::vector{}; + static_assert(!std::is_assignable_v>); + STATIC_CHECK(!x4::X4Movable, XYZ>); + } + + // tuple contains reference + { + int n = 0; + alloy::tuple ref_tuple{ n }; + x4::move_to(42, ref_tuple); + CHECK(n == 42); + } + { + int n = 42; + alloy::tuple ref_tuple{ n }; + alloy::tuple dest{ 0 }; + x4::move_to(std::move(ref_tuple), dest); + CHECK(alloy::get<0>(dest) == 42); + } + { + x4_test::move_only mo; + alloy::tuple ref_tuple{ mo }; + alloy::tuple dest; + x4::move_to(std::move(ref_tuple), dest); // move "far" ownership + } +} diff --git a/test/x4/partial_success.cpp b/test/x4/partial_success.cpp new file mode 100644 index 000000000..9219b6284 --- /dev/null +++ b/test/x4/partial_success.cpp @@ -0,0 +1,446 @@ +/*============================================================================= + Copyright (c) 2025 Nana Sakisaka + 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 +#include +#include +#include + +#include + +// list-like +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// NOLINTBEGIN(readability-container-size-empty) + +using namespace std::string_view_literals; + +struct strong_int +{ + int value = 0; + int assigned_count = 0; + + strong_int() = default; + strong_int(strong_int const&) = default; + strong_int(strong_int&&) noexcept = default; + + explicit strong_int(int value) : value(value) {} + + strong_int& operator=(strong_int const& other) + { + value = other.value; + ++assigned_count; + return *this; + } + + strong_int& operator=(strong_int&& other) noexcept + { + value = other.value; + ++assigned_count; + return *this; + } + + strong_int& operator=(int new_value) + { + value = new_value; + ++assigned_count; + return *this; + } + + bool operator==(strong_int const& other) const + { + return value == other.value; + } + + friend std::ostream& operator<<(std::ostream& os, strong_int const& si) + { + return os << si.value; + } +}; + +TEST_CASE("partial success (alternative)") +{ + using x4::attr; + using x4::eps; + using x4::omit; + using x4::int_; + + using x4::string; + using x4::lit; + using x4::standard::char_; + using x4::standard::space; + + // Sanity checks + { + int i = -1; + REQUIRE(parse("", attr(42), i)); + CHECK(i == 42); + } + { + std::string str; + REQUIRE(parse("", attr("foo"), str)); + CHECK(str == "foo"); + } + { + std::string str; + REQUIRE(parse("foo", string("foo"), str)); + CHECK(str == "foo"); + } + + // Non-string container attribute + // Related to: https://github.com/boostorg/spirit/issues/378 + { + static_assert(x4::traits::CategorizedAttr, x4::traits::container_attr>); + static_assert(x4::traits::X4Container>); + static_assert(x4::traits::is_container_v>); + + { + std::vector ints; + REQUIRE(parse("1 2", eps(false) | attr(98) >> attr(99), ints).is_partial_match()); + CHECK(ints == std::vector{98, 99}); + } + { + std::vector ints; + REQUIRE(parse("1 2", int_ >> int_ >> eps(false) | attr(98) >> attr(99), space, ints).is_partial_match()); + // If we don't properly "hold" the value on the failed branch of + // `x4::alternative`, we would see {1, 2, 98, 99} here. + CHECK(ints == std::vector{98, 99}); + } + // Failed parse should not modify the exposed attribute + { + std::vector ints; + REQUIRE(!parse("1 2", int_ >> int_ >> eps(false) | attr(98) >> attr(99) >> eps(false), space, ints)); + // Wrong implementation yields {1, 2, 98, 99} or {98, 99} + CHECK(ints == std::vector{}); + } + { + std::vector ints; + REQUIRE(parse("1 2", attr(std::vector{3, 4}) >> eps(false) | attr(98) >> attr(99), space, ints).is_partial_match()); + // Wrong implementation yields {3, 4, 98, 99} + CHECK(ints == std::vector{98, 99}); + } + } + + // String container attribute + // Intended for testing `detail::string_parse` + { + static_assert(x4::traits::CategorizedAttr); + static_assert(x4::traits::X4Container); + static_assert(x4::traits::is_container_v); + + { + std::string str; + REQUIRE(parse("foodie", "fox" | string("foodie"), str)); + CHECK(str == "foodie"); + } + { + constexpr auto fox = char_('f') >> char_('o') >> char_('x'); + + std::string str; + REQUIRE(parse("foodie", fox | string("foodie"), str)); + // If we don't properly "hold" the value on the failed branch of + // `x4::alternative`, we would see "fofoodie" here. + CHECK(str == "foodie"); + } + { + constexpr auto foo = char_('f') >> char_('o') >> char_('o'); + + std::string str; + REQUIRE(parse("foodie", foo >> eps(false) | string("foodie"), str)); + // Wrong implementation yields "foofoodie" + CHECK(str == "foodie"); + } + { + std::string str; + REQUIRE(parse("foodie", attr("bookworm") >> eps(false) | string("foodie"), str)); + // Wrong implementation yields "bookwormfoodie" + CHECK(str == "foodie"); + } + // Failed parse should not modify the exposed attribute + { + std::string str; + REQUIRE(!parse("foodie", attr("bookworm") >> eps(false) | string("foodie") >> eps(false), str)); + // Wrong implementation yields "bookwormfoodie" or "foodie" + CHECK(str == ""); + } + + { + std::string str; + REQUIRE(parse("foodie", string("food") >> "fan" | string("foodie"), str)); + // Wrong implementation yields "foodfoodie" + CHECK(str == "foodie"); + } + } + + // Plain attribute + { + static_assert(x4::traits::CategorizedAttr); + static_assert(!x4::traits::X4Container); + static_assert(!x4::traits::is_container_v); + + { + strong_int si; + REQUIRE(parse("1", int_ | attr(strong_int{9}), si)); + CHECK(si == strong_int{1}); + CHECK(si.assigned_count == 1); + } + { + strong_int si; + REQUIRE(parse("1", int_ >> eps(false) | int_, si)); + CHECK(si == strong_int{1}); + // Wrong implementation yields 2, because `x4::alternative` wrongly mutates the exposed variable + CHECK(si.assigned_count == 1); + } + } + + // Tuple attribute + { + using pair_int = std::pair; + + static_assert(x4::traits::CategorizedAttr); + static_assert(!x4::traits::X4Container); + static_assert(!x4::traits::is_container_v); + + { + pair_int pi; + REQUIRE(parse("1 2", int_ >> int_ | attr(pair_int{98, 99}), space, pi)); + CHECK(pi == pair_int{1, 2}); + } + { + pair_int pi; + REQUIRE(parse("1 2", + int_ >> int_ >> eps(false) | attr(pair_int{98, 99}) >> omit[int_ >> int_], + space, pi + )); + CHECK(pi == pair_int{98, 99}); + } + } +} + +TEST_CASE("partial success (list-like)") +{ + using x4::char_encoding::standard; + + using x4::standard::char_; + using x4::standard::string; + using x4::standard::lit; + using x4::repeat; + + constexpr auto a = char_('a'); + constexpr auto b = char_('b'); + constexpr auto c = char_('c'); + constexpr auto OO = string("OO"); + + constexpr auto abc = a >> b >> c; // tuple + constexpr auto aOOc = a >> OO >> c; // tuple + + // abc ---------------------------------------------- + { + using Subject = x4::sequence< + x4::sequence< + x4::literal_char, + x4::literal_char + >, + x4::literal_char + >; + static_assert(std::same_as, Subject>); + STATIC_CHECK(std::same_as>); + STATIC_CHECK(x4::detail::container_can_hold_sequence>::value); + + using Container = std::string; + + STATIC_CHECK(x4::parser_traits::template handles_container); + STATIC_CHECK(x4::parser_traits>::template handles_container); + STATIC_CHECK(x4::parser_traits>::template handles_container); + STATIC_CHECK(x4::parser_traits>>::template handles_container); + } + + // kleene + { + std::string abcs; + REQUIRE(parse("abcabx", *abc >> "abx", abcs)); + CHECK(abcs == "abc"sv); // wrong implementation yields "abcab" + } + { + std::string abcs; + REQUIRE(parse("abcabcabx", *abc >> "abx", abcs)); + CHECK(abcs == "abcabc"sv); // wrong implementation yields "abcabcab" + } + + // plus + { + std::string abcs; + REQUIRE(parse("abcabx", +abc >> "abx", abcs)); + CHECK(abcs == "abc"sv); // wrong implementation yields "abcab" + } + { + std::string abcs; + REQUIRE(parse("abcabcabx", +abc >> "abx", abcs)); + CHECK(abcs == "abcabc"sv); // wrong implementation yields "abcabcab" + } + + // list + { + std::string abcs; + REQUIRE(parse("abc,abx", abc % ',' >> ",abx", abcs)); + CHECK(abcs == "abc"sv); // wrong implementation yields "abcab" + } + { + std::string abcs; + REQUIRE(parse("abc,abc,abx", abc % ',' >> ",abx", abcs)); + CHECK(abcs == "abcabc"sv); // wrong implementation yields "abcabcab" + } + + // repeat [exact] + { + std::string abcs; + REQUIRE(!parse("abcabx", repeat(2)[abc], abcs)); + CHECK(abcs == ""sv); // wrong implementation yields "abc" + } + { + std::string abcs; + REQUIRE(!parse("abcabcabx", repeat(3)[abc], abcs)); + CHECK(abcs == ""sv); // wrong implementation yields "abcabc" + } + + // repeat [min, max] + { + std::string abcs; + REQUIRE(parse("abcabx", repeat(0, 2)[abc] >> "abx", abcs)); + CHECK(abcs == "abc"sv); // wrong implementation yields "abcab" + } + { + std::string abcs; + REQUIRE(parse("abcabcabx", repeat(0, 3)[abc] >> "abx", abcs)); + CHECK(abcs == "abcabc"sv); // wrong implementation yields "abcabcab" + } + + // repeat [min, inf] + { + std::string abcs; + REQUIRE(parse("abcabx", repeat(0, x4::repeat_inf)[abc] >> "abx", abcs)); + CHECK(abcs == "abc"sv); // wrong implementation yields "abcab" + } + { + std::string abcs; + REQUIRE(parse("abcabcabx", repeat(0, x4::repeat_inf)[abc] >> "abx", abcs)); + CHECK(abcs == "abcabc"sv); // wrong implementation yields "abcabcab" + } + + + // aXXc ---------------------------------------------- + { + using Subject = x4::sequence< + x4::sequence< + x4::literal_char, + x4::literal_string + >, + x4::literal_char + >; + static_assert(std::same_as, Subject>); + STATIC_CHECK(std::same_as>); + STATIC_CHECK(x4::detail::container_can_hold_sequence>::value); + + using Container = std::string; + + STATIC_CHECK(x4::parser_traits::template handles_container); + STATIC_CHECK(x4::parser_traits>::template handles_container); + STATIC_CHECK(x4::parser_traits>::template handles_container); + STATIC_CHECK(x4::parser_traits>>::template handles_container); + } + + // kleene + { + std::string aOOcs; + REQUIRE(parse("aOOcaOOx", *aOOc >> "aOOx", aOOcs)); + CHECK(aOOcs == "aOOc"sv); // wrong implementation yields "aOOcab" + } + { + std::string aOOcs; + REQUIRE(parse("aOOcaOOcaOOx", *aOOc >> "aOOx", aOOcs)); + CHECK(aOOcs == "aOOcaOOc"sv); // wrong implementation yields "aOOcaOOcab" + } + + // plus + { + std::string aOOcs; + REQUIRE(parse("aOOcaOOx", +aOOc >> "aOOx", aOOcs)); + CHECK(aOOcs == "aOOc"sv); // wrong implementation yields "aOOcab" + } + { + std::string aOOcs; + REQUIRE(parse("aOOcaOOcaOOx", +aOOc >> "aOOx", aOOcs)); + CHECK(aOOcs == "aOOcaOOc"sv); // wrong implementation yields "aOOcaOOcab" + } + + // list + { + std::string aOOcs; + REQUIRE(parse("aOOc,aOOx", aOOc % ',' >> ",aOOx", aOOcs)); + CHECK(aOOcs == "aOOc"sv); // wrong implementation yields "aOOcab" + } + { + std::string aOOcs; + REQUIRE(parse("aOOc,aOOc,aOOx", aOOc % ',' >> ",aOOx", aOOcs)); + CHECK(aOOcs == "aOOcaOOc"sv); // wrong implementation yields "aOOcaOOcab" + } + + // repeat [exact] + { + std::string aOOcs; + REQUIRE(!parse("aOOcaOOx", repeat(2)[aOOc], aOOcs)); + CHECK(aOOcs == ""sv); // wrong implementation yields "aOOc" + } + { + std::string aOOcs; + REQUIRE(!parse("aOOcaOOcaOOx", repeat(3)[aOOc], aOOcs)); + CHECK(aOOcs == ""sv); // wrong implementation yields "aOOcaOOc" + } + + // repeat [min, max] + { + std::string aOOcs; + REQUIRE(parse("aOOcaOOx", repeat(0, 2)[aOOc] >> "aOOx", aOOcs)); + CHECK(aOOcs == "aOOc"sv); // wrong implementation yields "aOOcab" + } + { + std::string aOOcs; + REQUIRE(parse("aOOcaOOcaOOx", repeat(0, 3)[aOOc] >> "aOOx", aOOcs)); + CHECK(aOOcs == "aOOcaOOc"sv); // wrong implementation yields "aOOcaOOcab" + } + + // repeat [min, inf] + { + std::string aOOcs; + REQUIRE(parse("aOOcaOOx", repeat(0, x4::repeat_inf)[aOOc] >> "aOOx", aOOcs)); + CHECK(aOOcs == "aOOc"sv); // wrong implementation yields "aOOcab" + } + { + std::string aOOcs; + REQUIRE(parse("aOOcaOOcaOOx", repeat(0, x4::repeat_inf)[aOOc] >> "aOOx", aOOcs)); + CHECK(aOOcs == "aOOcaOOc"sv); // wrong implementation yields "aOOcaOOcab" + } +} + +// NOLINTEND(readability-container-size-empty) diff --git a/test/x4/plus.cpp b/test/x4/plus.cpp index d51da684e..55ece085f 100644 --- a/test/x4/plus.cpp +++ b/test/x4/plus.cpp @@ -23,27 +23,6 @@ #include #include -struct x_attr {}; - -namespace iris::x4::traits { - -template<> -struct container_value -{ - using type = char; // value type of container -}; - -template<> -struct push_back_container -{ - static constexpr void call(x_attr& /*c*/, char /*val*/) noexcept - { - // push back value type into container - } -}; - -} // x4::traits - TEST_CASE("plus") { using x4::char_; @@ -129,7 +108,7 @@ TEST_CASE("plus") // attribute customization { - x_attr x; + x4_test::custom_container x; (void)parse("abcde", +char_, x); } diff --git a/test/x4/raw.cpp b/test/x4/raw.cpp deleted file mode 100644 index d7e7a08ac..000000000 --- a/test/x4/raw.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/*============================================================================= - Copyright (c) 2001-2014 Joel de Guzman - Copyright (c) 2025 Nana Sakisaka - 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 -#include -#include -#include -#include - -#include - -#include - -#include -#include - -namespace { - -using x4::rule; - -rule direct_rule = "direct_rule"; -rule indirect_rule = "indirect_rule"; - -auto const direct_rule_def = x4::int_; -auto const indirect_rule_def = direct_rule; - -IRIS_X4_DEFINE(direct_rule) -IRIS_X4_DEFINE(indirect_rule) - -} // anonymous - -TEST_CASE("raw") -{ - using namespace x4::standard; - using x4::raw; - using x4::eps; - using x4::lit; - using x4::_attr; - using x4::int_; - using x4::char_; - - IRIS_X4_ASSERT_CONSTEXPR_CTORS(raw['x']); - - { - std::ranges::subrange range; - REQUIRE(parse("spirit_test_123", raw[alpha >> *(alnum | '_')], range)); - CHECK(std::string(range.begin(), range.end()) == "spirit_test_123"); - } - { - std::ranges::subrange range; - REQUIRE(parse(" spirit", raw[*alpha], space, range)); - CHECK(std::string(range.begin(), range.end()) == "spirit"); - } - - { - std::string str; - REQUIRE(parse("spirit_test_123", raw[alpha >> *(alnum | '_')], str)); - CHECK(str == "spirit_test_123"); - } - { - std::string str; - REQUIRE(parse("x123", alpha >> raw[+alnum], str)); - CHECK(str == "x123"); - } - - { - std::ranges::subrange range; - CHECK(parse("x", raw[alpha])); - CHECK(parse("x", raw[alpha], range)); - CHECK(parse("x", raw[alpha] >> eps, range)); - } - - { - std::ranges::subrange range; - REQUIRE(parse("x", raw[alpha][ ([&](auto&& ctx){ range = _attr(ctx); }) ])); - REQUIRE(range.size() == 1); - CHECK(*range.begin() == 'x'); - } - - { - std::ranges::subrange range; - CHECK(parse("x123x", lit('x') >> raw[+digit] >> lit('x'))); - CHECK(parse("x123x", lit('x') >> raw[+digit] >> lit('x'), range)); - CHECK(std::string(range.begin(), range.end()) == "123"); - } - - { - using range = std::ranges::subrange; - iris::rvariant attr; - - REQUIRE(parse("test", (int_ | raw[*char_]), attr)); - auto const& rng = iris::get(attr); - CHECK(std::string(rng.begin(), rng.end()) == "test"); - } - - { - std::vector> attr; - REQUIRE(parse("123abcd", raw[int_] >> raw[*char_], attr)); - CHECK(attr.size() == 2); - CHECK(std::string(attr[0].begin(), attr[0].end()) == "123"); - CHECK(std::string(attr[1].begin(), attr[1].end()) == "abcd"); - } - - { - std::pair> attr; - REQUIRE(parse("123abcd", int_ >> raw[*char_], attr)); - CHECK(attr.first == 123); - CHECK(std::string(attr.second.begin(), attr.second.end()) == "abcd"); - } - - { - // test with simple rule - std::ranges::subrange range; - REQUIRE(parse("123", raw[direct_rule], range)); - CHECK(std::string(range.begin(), range.end()) == "123"); - } - - { - // test with complex rule - std::ranges::subrange range; - REQUIRE(parse("123", raw[indirect_rule], range)); - CHECK(std::string(range.begin(), range.end()) == "123"); - } -} diff --git a/test/x4/rule3.cpp b/test/x4/rule3.cpp index 7ad893611..128b2d2ea 100644 --- a/test/x4/rule3.cpp +++ b/test/x4/rule3.cpp @@ -52,8 +52,8 @@ namespace check_recursive { struct node_array; using node_t = iris::rvariant< - int, - iris::recursive_wrapper + int, + iris::recursive_wrapper >; struct node_array : std::vector @@ -158,6 +158,7 @@ TEST_CASE("rule3") { using namespace check_recursive; + node_t v; REQUIRE(parse("[4,2]", grammar, v)); CHECK((node_t{node_array{{4}, {2}}} == v)); diff --git a/test/x4/rule4.cpp b/test/x4/rule4.cpp index 840a9a60f..4824331fa 100644 --- a/test/x4/rule4.cpp +++ b/test/x4/rule4.cpp @@ -11,7 +11,9 @@ #include #include +#include #include +#include #include #include #include diff --git a/test/x4/sequence.cpp b/test/x4/sequence.cpp index fc3628a70..b92d6cb0a 100644 --- a/test/x4/sequence.cpp +++ b/test/x4/sequence.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,7 @@ TEST_CASE("sequence") using x4::rule; using x4::_attr; using x4::eps; + using x4::as; IRIS_X4_ASSERT_CONSTEXPR_CTORS(char_ >> char_); @@ -129,13 +131,27 @@ TEST_CASE("sequence") // unwrap it). It's odd that the RHS (r) does not really have a // single element tuple, so the original comment is not accurate. - using attr_type = alloy::tuple; - attr_type tpl; + // rule version + { + using attr_type = alloy::tuple; + attr_type tpl; + + auto r = rule{} = char_ >> ',' >> int_; + + REQUIRE(parse("test:x,1", "test:" >> r, tpl)); + CHECK((tpl == attr_type('x', 1))); + } - auto r = rule{} = char_ >> ',' >> int_; + // as version + { + using attr_type = alloy::tuple; + attr_type tpl; + + auto r = as(char_ >> ',' >> int_); - REQUIRE(parse("test:x,1", "test:" >> r, tpl)); - CHECK((tpl == attr_type('x', 1))); + REQUIRE(parse("test:x,1", "test:" >> r, tpl)); + CHECK((tpl == attr_type('x', 1))); + } } { @@ -143,13 +159,27 @@ TEST_CASE("sequence") // has a single element tuple as its attribute. This is a correction // of the test above. - using attr_type = alloy::tuple; - attr_type tpl; + // rule version + { + using attr_type = alloy::tuple; + attr_type tpl; + + auto r = rule{} = int_; + + REQUIRE(parse("test:1", "test:" >> r, tpl)); + CHECK((tpl == attr_type(1))); + } + + // as version + { + using attr_type = alloy::tuple; + attr_type tpl; - auto r = rule{} = int_; + auto r = as(int_); - REQUIRE(parse("test:1", "test:" >> r, tpl)); - CHECK((tpl == attr_type(1))); + REQUIRE(parse("test:1", "test:" >> r, tpl)); + CHECK((tpl == attr_type(1))); + } } // unused means we don't care about the attribute @@ -262,16 +292,33 @@ TEST_CASE("sequence") } { - std::vector v; + // rule version + { + std::vector v; - auto e = rule{} = *~char_(','); - auto l = rule>{} = e >> *(',' >> e); + auto e = rule{} = *~char_(','); + auto l = rule>{} = e >> *(',' >> e); - REQUIRE(parse("abc1,abc2,abc3", l, v)); - REQUIRE(v.size() == 3); - CHECK(v[0] == "abc1"); - CHECK(v[1] == "abc2"); - CHECK(v[2] == "abc3"); + REQUIRE(parse("abc1,abc2,abc3", l, v)); + REQUIRE(v.size() == 3); + CHECK(v[0] == "abc1"); + CHECK(v[1] == "abc2"); + CHECK(v[2] == "abc3"); + } + + // as version + { + std::vector v; + + auto e = as(*~char_(',')); + auto l = as>(e >> *(',' >> e)); + + REQUIRE(parse("abc1,abc2,abc3", l, v)); + REQUIRE(v.size() == 3); + CHECK(v[0] == "abc1"); + CHECK(v[1] == "abc2"); + CHECK(v[2] == "abc3"); + } } // do the same with a plain string object @@ -282,12 +329,25 @@ TEST_CASE("sequence") } { - std::string s; - auto e = rule{} = *~char_(','); - auto l = rule{} = e >> *(',' >> e); + // rule version + { + std::string s; + auto e = rule{} = *~char_(','); + auto l = rule{} = e >> *(',' >> e); + + REQUIRE(parse("abc1,abc2,abc3", l, s)); + CHECK(s == "abc1abc2abc3"); + } + + // as version + { + std::string s; + auto e = as(*~char_(',')); + auto l = as(e >> *(',' >> e)); - REQUIRE(parse("abc1,abc2,abc3", l, s)); - CHECK(s == "abc1abc2abc3"); + REQUIRE(parse("abc1,abc2,abc3", l, s)); + CHECK(s == "abc1abc2abc3"); + } } { diff --git a/test/x4/substitution.cpp b/test/x4/substitution.cpp new file mode 100644 index 000000000..5d8d24aac --- /dev/null +++ b/test/x4/substitution.cpp @@ -0,0 +1,30 @@ +#include "iris_x4_test.hpp" + +#include + +#include +#include + +#include + +template +inline constexpr bool can_hold_v = x4::traits::can_hold::value; + +TEST_CASE("can_hold") +{ + // identical types + STATIC_CHECK(can_hold_v); + STATIC_CHECK(can_hold_v, std::vector>); + STATIC_CHECK(can_hold_v, alloy::tuple>); + STATIC_CHECK(can_hold_v, iris::rvariant>); + + // `iris::rvariant` is "broader" than `int` + STATIC_CHECK( can_hold_v, int>); + STATIC_CHECK(!can_hold_v>); + + // container types + STATIC_CHECK(can_hold_v>, std::vector>); + + // tuple-like types + STATIC_CHECK(can_hold_v>, alloy::tuple>); +}