diff --git a/include/iris/x4/core/detail/parse_alternative.hpp b/include/iris/x4/core/detail/parse_alternative.hpp index 5176df180..2c67a3157 100644 --- a/include/iris/x4/core/detail/parse_alternative.hpp +++ b/include/iris/x4/core/detail/parse_alternative.hpp @@ -108,7 +108,7 @@ struct pass_non_variant_attribute // Unwrap single element sequences template - requires traits::is_size_one_sequence_v + requires traits::is_single_element_tuple_like::value struct pass_non_variant_attribute { using attr_type = typename std::remove_reference_t< diff --git a/include/iris/x4/core/detail/parse_into_container.hpp b/include/iris/x4/core/detail/parse_into_container.hpp index 2b37abb06..4b10d30e3 100644 --- a/include/iris/x4/core/detail/parse_into_container.hpp +++ b/include/iris/x4/core/detail/parse_into_container.hpp @@ -84,7 +84,7 @@ struct parse_into_container_impl_default } } else { - if constexpr (traits::is_size_one_sequence_v) { + if constexpr (traits::is_single_element_tuple_like::value) { // 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 { diff --git a/include/iris/x4/core/detail/parse_sequence.hpp b/include/iris/x4/core/detail/parse_sequence.hpp index 6c534f0dd..95f359921 100644 --- a/include/iris/x4/core/detail/parse_sequence.hpp +++ b/include/iris/x4/core/detail/parse_sequence.hpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -37,64 +38,12 @@ struct sequence; namespace iris::x4::detail { -struct pass_sequence_attribute_unused -{ - using type = unused_type; - - template - [[nodiscard]] static constexpr unused_type - call(T&) noexcept - { - return unused_type{}; - } -}; - -template -struct pass_sequence_attribute_size_one_view -{ - using type = alloy::tuple_element_t<0, Attr>; - - [[nodiscard]] static constexpr type - call(Attr& attribute) - noexcept(noexcept(alloy::get<0>(attribute))) - { - return alloy::get<0>(attribute); - } -}; - -template -struct pass_through_sequence_attribute -{ - using type = Attr&; - - template - [[nodiscard]] static constexpr Attr_& - call(Attr_& attribute) noexcept - { - return attribute; - } -}; - -template -struct pass_sequence_attribute : std::conditional_t< - traits::is_size_one_view_v, - pass_sequence_attribute_size_one_view, - pass_through_sequence_attribute +// Note: `ExpectedAttr` parameter exists for a better debugging experience +template< + class LParser, class RParser, traits::CategorizedAttr ActualAttr, + class ExpectedAttr = typename traits::detail::attribute_of_sequence::type > -{}; - -template -struct pass_sequence_attribute, Attr> - : pass_through_sequence_attribute -{}; - -template - requires requires { - typename Parser::proxy_backend_type; - } -struct pass_sequence_attribute - : pass_sequence_attribute -{}; +inline constexpr bool has_same_sequence_size_v = parser_traits::sequence_size + parser_traits::sequence_size == alloy::tuple_size_v; template struct partition_attribute {}; @@ -108,38 +57,26 @@ 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; - static constexpr std::size_t actual_size = alloy::tuple_size_v; - static constexpr std::size_t expected_size = l_size + r_size; - // If you got an error here, then you are trying to pass // a tuple-like with the wrong number of elements // as that expected by the (sequence) parser. - static_assert( - actual_size >= expected_size, - "Sequence size of the passed attribute is less than expected." - ); - static_assert( - actual_size <= expected_size, - "Sequence size of the passed attribute is greater than expected." - ); + static_assert(has_same_sequence_size_v, "sequence size mismatch"); using view = alloy::tuple_ref_t; - using splitted = alloy::tuple_split_t; - using l_part = alloy::tuple_element_t<0, splitted>; - using r_part = alloy::tuple_element_t<1, splitted>; - using l_pass = pass_sequence_attribute; - using r_pass = pass_sequence_attribute; + using split = alloy::tuple_split_t; + using l_part = traits::unwrap_if_single_element_tuple_like>::type; + using r_part = traits::unwrap_if_single_element_tuple_like>::type; [[nodiscard]] static constexpr l_part left(Attr& s) // TODO: noexcept { - return alloy::get<0>(alloy::tuple_split(alloy::tuple_ref(s))); + return traits::do_unwrap_if_single_element_tuple_like(alloy::get<0>(alloy::tuple_split(alloy::tuple_ref(s)))); } [[nodiscard]] static constexpr r_part right(Attr& s) // TODO: noexcept { - return alloy::get<1>(alloy::tuple_split(alloy::tuple_ref(s))); + return traits::do_unwrap_if_single_element_tuple_like(alloy::get<1>(alloy::tuple_split(alloy::tuple_ref(s)))); } }; @@ -149,9 +86,6 @@ template has_attribute_v struct partition_attribute { - using l_pass = pass_sequence_attribute_unused; - using r_pass = pass_sequence_attribute; - [[nodiscard]] static constexpr unused_type left(Attr&) noexcept { return unused; @@ -169,9 +103,6 @@ template (!has_attribute_v) struct partition_attribute { - using l_pass = pass_sequence_attribute; - using r_pass = pass_sequence_attribute_unused; - [[nodiscard]] static constexpr Attr& left(Attr& s) noexcept { return s; @@ -189,9 +120,6 @@ template (!has_attribute_v) struct partition_attribute { - using l_pass = pass_sequence_attribute_unused; - using r_pass = pass_sequence_attribute_unused; - [[nodiscard]] static constexpr unused_type left(Attr&) noexcept { return unused; @@ -203,39 +131,6 @@ struct partition_attribute } }; -// Default overload, no constraints on attribute category -template Se, class Context, class Attr> -[[nodiscard]] constexpr bool -parse_sequence(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) - // TODO: noexcept -{ - static_assert(X4Attribute); - - using partition = partition_attribute< - typename Parser::left_type, - typename Parser::right_type, - Attr - >; - - auto&& l_part = partition::left(attr); - auto&& r_part = partition::right(attr); - auto&& l_attr = partition::l_pass::call(l_part); - auto&& r_attr = partition::r_pass::call(r_part); - - auto&& l_attr_appender = x4::make_container_appender(l_attr); - auto&& r_attr_appender = x4::make_container_appender(r_attr); - - It local_it = first; - if (parser.left.parse(local_it, last, ctx, l_attr_appender) && - parser.right.parse(local_it, last, ctx, r_attr_appender) - ) { - first = std::move(local_it); - return true; - } - - return false; -} - template Se, class Context, X4Attribute Attr> requires (parser_traits::sequence_size > 1) [[nodiscard]] constexpr bool @@ -255,28 +150,78 @@ parse_sequence_impl(Parser const& parser, It& first, Se const& last, Context con return detail::parse_into_container(parser, first, last, ctx, attr); } -template< - class Parser, std::forward_iterator It, std::sentinel_for Se, class Context, - traits::CategorizedAttr ContainerAttr -> +template Se, class Context, class Attr> [[nodiscard]] constexpr bool -parse_sequence(Parser const& parser, It& first, Se const& last, Context const& ctx, ContainerAttr& container_attr) - noexcept( - std::is_nothrow_copy_assignable_v && - noexcept(detail::parse_sequence_impl(parser.left, first, last, ctx, container_attr)) && - noexcept(detail::parse_sequence_impl(parser.right, first, last, ctx, container_attr)) - ) +parse_sequence(Parser const& parser, It& first, Se const& last, Context const& ctx, Attr& attr) + // TODO: noexcept { - It local_it = first; - if (detail::parse_sequence_impl(parser.left, local_it, last, ctx, container_attr) && - detail::parse_sequence_impl(parser.right, local_it, last, ctx, container_attr) + static_assert(X4Attribute); + + if constexpr (traits::is_container_v) { + It local_it = first; + if (detail::parse_sequence_impl(parser.left, local_it, last, ctx, attr) && + detail::parse_sequence_impl(parser.right, local_it, last, ctx, attr) + ) { + first = std::move(local_it); + return true; + } + return false; + } else if constexpr ( + traits::is_single_element_tuple_like::value && + has_attribute_v && + has_attribute_v ) { - first = std::move(local_it); - return true; + // Both sub-parsers have attributes (expected_size >= 2), but Attr is a + // single-element tuple-like (size 1). Unwrap to get<0>(attr) and recurse, + // so the inner type (e.g. a container or compatible tuple) receives the + // sequence elements directly. + return detail::parse_sequence(parser, first, last, ctx, alloy::get<0>(attr)); + } else { + using partition = partition_attribute< + typename Parser::left_type, + typename Parser::right_type, + Attr + >; + + auto&& l_attr = partition::left(attr); + auto&& r_attr = partition::right(attr); + + auto&& l_attr_appender = x4::make_container_appender(l_attr); + auto&& r_attr_appender = x4::make_container_appender(r_attr); + + It local_it = first; + if (parser.left.parse(local_it, last, ctx, l_attr_appender) && + parser.right.parse(local_it, last, ctx, r_attr_appender) + ) { + first = std::move(local_it); + return true; + } + + return false; } - return false; } +// Whether the appender path is needed for parsing a sequence into a container. +// The primary template returns false for non-container Attr (always use the +// default path). The container specialization checks whether the container's +// value_type is broad enough to represent every possible result the sequence +// parser produces. When it is NOT, each sub-parser must decide individually +// whether to append (e.g. an optional sub-parser skips appending on no-match). +// +// e.g. sequence_attr = char, container_value = char → false (same type, direct path OK) +// e.g. sequence_attr = optional, container_value = char → true (optional may be empty; needs appender) +// e.g. sequence_attr = optional, container_value = optional → false (same type, direct path OK) +template +struct sequence_needs_appender : std::false_type {}; + +template +struct sequence_needs_appender + : std::negation::type, + typename Sequence::attribute_type + >> +{}; + template struct parse_into_container_impl> { @@ -287,18 +232,9 @@ struct parse_into_container_impl> Context const& ctx, Attr& attr ) // never noexcept (requires container insertion) { - 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); - } + if constexpr (sequence_needs_appender, Attr>::value) { + auto&& appender = x4::make_container_appender(x4::assume_container(attr)); + return detail::parse_sequence(parser, first, last, ctx, appender); } else { 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 index 3d9c6e21f..0334e2548 100644 --- a/include/iris/x4/core/list_like_parser.hpp +++ b/include/iris/x4/core/list_like_parser.hpp @@ -14,11 +14,8 @@ #include #include -#include #include -#include - namespace iris::x4 { namespace list_like_parser { @@ -29,7 +26,7 @@ template // non-variant `ExposedAttr` struct unwrap_container_candidate { - using type = traits::synthesized_value< + using type = traits::unwrap_if_single_element_tuple_like< unwrap_recursive_type< typename unwrap_container_appender::type > @@ -52,32 +49,6 @@ struct chunk_buffer_impl 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 @@ -88,10 +59,10 @@ 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< + using unwrapped_attr_type = traits::unwrap_single_element_plain< unwrap_recursive_type >::type; - auto& unwrapped_attr = detail::unwrap_single_element(iris::unwrap_recursive(attr)); + auto& unwrapped_attr = traits::do_unwrap_if_single_element_tuple_like(iris::unwrap_recursive(attr)); if constexpr (traits::is_variant_v) { using container_alternative = traits::variant_find_holdable_type< diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index 24612e6c1..2f93ceb3c 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -116,38 +117,47 @@ constexpr void move_to(It const&, Se const&, unused_type const&&) = delete; // t // Category specific -------------------------------------- template Dest> - requires traits::is_size_one_sequence_v + requires traits::is_single_element_tuple_like>::value 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"); - // TODO: preliminarily invoke static_assert to check if the assignment is valid + static_assert( + detail::is_assignable_without_lossy_conversion(alloy::get<0>(std::forward(src))))>::value, + "Lossy conversion detected in move_to (single-element tuple-like to plain)" + ); dest = std::forward_like(alloy::get<0>(std::forward(src))); } template Dest> - requires (!traits::is_size_one_sequence_v) + requires (!traits::is_single_element_tuple_like>::value) 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); + static_assert( + detail::is_assignable_without_lossy_conversion::value, + "Lossy conversion detected in move_to (source to plain)" + ); dest = std::forward(src); } template Dest> requires - traits::is_same_size_sequence_v && - (!traits::is_size_one_sequence_v) + traits::is_same_size_tuple_like>::value && + (!traits::is_single_element_tuple_like::value) constexpr void move_to(Source&& src, Dest& dest) noexcept(noexcept(alloy::tuple_assign(std::forward(src), dest))) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); - // TODO: preliminarily invoke static_assert to check if the assignment is valid + static_assert( + detail::is_tuple_assignable_without_lossy_conversion>::value, + "Lossy conversion detected in move_to (tuple element-wise assignment)" + ); alloy::tuple_assign(std::forward(src), dest); } @@ -166,7 +176,7 @@ move_to(Source&& src, Dest& dest) } template Dest> - requires (!std::is_assignable_v) && traits::is_size_one_sequence_v + requires (!std::is_assignable_v) && traits::is_single_element_tuple_like>::value constexpr void move_to(Source&& src, Dest& dest) noexcept(noexcept(dest = std::forward_like(alloy::get<0>(std::forward(src))))) @@ -191,6 +201,10 @@ move_to(Source&& src, Dest& dest) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); static_assert(std::is_assignable_v); + static_assert( + detail::is_assignable_without_lossy_conversion::value, + "Lossy conversion detected in move_to (source to optional)" + ); dest = std::forward(src); } @@ -207,6 +221,14 @@ move_to(It first, Se last, Dest& dest) static_assert(!std::same_as, unused_type>); static_assert(!std::same_as, unused_container_type>); + static_assert( + detail::is_assignable_without_lossy_conversion< + typename traits::container_value::type&, + std::iter_reference_t + >::value, + "Lossy conversion detected in move_to (container element-wise)" + ); + if constexpr (!is_ttp_specialization_of_v) { if (!traits::is_empty(dest)) { traits::clear(dest); @@ -220,15 +242,6 @@ move_to(It first, Se last, Dest& dest) traits::append(dest, first, last); // try to reuse underlying memory buffer } -template Se, traits::CategorizedAttr Dest> - requires traits::is_size_one_sequence_v -constexpr void -move_to(It first, Se last, Dest& dest) - noexcept(noexcept(x4::move_to(first, last, alloy::get<0>(dest)))) -{ - x4::move_to(first, last, alloy::get<0>(dest)); -} - // Move non-container `src` into container `dest`. // e.g. Source=std::string_view, Dest=std::string (used in `attr_parser`) template Dest> @@ -266,12 +279,44 @@ move_to(Source&& src, Dest& dest) } } +namespace detail { + +template +struct is_single_element_move_to_nothrow + : std::false_type +{}; + +template + requires + (!traits::is_single_element_tuple_like::value) && + requires { x4::move_to(std::declval(), std::declval()); } +struct is_single_element_move_to_nothrow + : std::bool_constant< + noexcept(x4::move_to(std::declval(), std::declval())) + > +{}; + +template + requires traits::is_single_element_tuple_like::value +struct is_single_element_move_to_nothrow + : std::bool_constant< + noexcept(alloy::get<0>(std::declval())) && + is_single_element_move_to_nothrow< + Source, + alloy::tuple_element_t<0, Dest> + >::value + > +{}; + +} // detail + + // Size-one tuple-like forwarding template Dest> - requires traits::is_size_one_sequence_v + requires traits::is_single_element_tuple_like::value constexpr void move_to(Source&& src, Dest& dest) - noexcept(noexcept(x4::move_to(std::forward(src), alloy::get<0>(dest)))) + noexcept(detail::is_single_element_move_to_nothrow::value) { static_assert(!std::same_as, Dest>, "[BUG] This call should instead resolve to the overload handling identical types"); diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index 344d8ca61..6cbaaf464 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -43,6 +44,15 @@ struct rule; namespace detail { +template +struct rule_should_unwrap : std::false_type {}; + +template + requires traits::is_single_element_tuple_like::value +struct rule_should_unwrap + : traits::can_hold> +{}; + template struct rule_id { @@ -179,6 +189,14 @@ struct rule_impl It start = first; // backup + auto&& unwrapped_attr = [&]() -> decltype(auto) { + if constexpr (rule_should_unwrap::attribute_type, RHSAttr>::value) { + return alloy::get<0>(rhs_attr); + } else { + return rhs_attr; + } + }(); + // // NOTE: The branches below are intentionally written verbosely to make sure // we have the minimal call stack. DON'T extract these procedures into a @@ -188,7 +206,7 @@ struct rule_impl bool ok; if constexpr (SkipDefinitionInjection || !is_default_parse_rule) { - ok = rhs.parse(first, last, rcontext, rhs_attr); + ok = rhs.parse(first, last, rcontext, unwrapped_attr); } else { // If there is no `IRIS_X4_DEFINE` for this rule, @@ -196,7 +214,7 @@ struct rule_impl // so we can extract the rule later on in the default // `parse_rule` overload. auto const rule_id_context = x4::make_context(rhs, rcontext); - ok = rhs.parse(first, last, rule_id_context, rhs_attr); + ok = rhs.parse(first, last, rule_id_context, unwrapped_attr); } if constexpr (need_on_success) { @@ -338,31 +356,18 @@ struct rule_definition : parser -struct narrowing_checker -{ - using Dest = Exposed[]; - - // emulate `Exposed x[] = {std::forward(t)};` - template - static void operator()(T&&) - requires requires(T&& t) { { Dest{std::forward(t)} }; }; -}; - - template -concept RuleAttrConvertible = +concept RuleAttrAssignable = X4Attribute && std::is_assignable_v>&, RuleAttr>; template -concept RuleAttrConvertibleWithoutNarrowing = - RuleAttrConvertible && - requires { - narrowing_checker< - unwrap_container_appender_t> - >::operator()(std::declval()); - }; +concept RuleAttrAssignableWithoutLoss = + RuleAttrAssignable && + is_assignable_without_lossy_conversion< + unwrap_container_appender_t>&, + RuleAttr + >::value; // Resolves "The Spirit X3 rule problem" in Boost.Parser's documentation // https://www.boost.org/doc/libs/1_89_0/doc/html/boost_parser/this_library_s_relationship_to_boost_spirit.html#boost_parser.this_library_s_relationship_to_boost_spirit.the_spirit_x3_rule_problem @@ -372,8 +377,8 @@ concept RuleAttrTransformable = X4Attribute> && X4Attribute && std::default_initializable && - RuleAttrConvertible && - RuleAttrConvertibleWithoutNarrowing< + RuleAttrAssignable && + RuleAttrAssignableWithoutLoss< unwrap_container_appender_t>, RuleAttr >; @@ -391,6 +396,7 @@ struct rule : parser> static_assert(X4Attribute); static_assert(X4UnusedAttribute || !std::is_const_v, "Rule attribute cannot be const qualified"); static_assert(!std::is_same_v, unused_container_type>, "`rule` with `unused_container_type` is not supported"); + static_assert(!is_ttp_specialization_of::value, "alloy::tuple is intended for internal use only"); using id = RuleID; using attribute_type = RuleAttr; @@ -454,7 +460,6 @@ struct rule : parser> std::make_move_iterator(traits::end(rule_attr)) ); } else { - static_assert(std::is_assignable_v); exposed_attr = std::move(rule_attr); } return true; @@ -465,10 +470,10 @@ struct rule : parser> requires (!std::same_as, unused_type>) && (!detail::RuleAttrCompatible) && - detail::RuleAttrConvertible && - (!detail::RuleAttrConvertibleWithoutNarrowing) + detail::RuleAttrAssignable && + (!detail::RuleAttrAssignableWithoutLoss) [[nodiscard]] constexpr bool - parse(It&, Se const&, Context const&, Exposed&) const = delete; // Rule attribute needs narrowing conversion + parse(It&, Se const&, Context const&, Exposed&) const = delete; // Rule attribute needs lossy conversion template Se, class Context> diff --git a/include/iris/x4/string/detail/string_parse.hpp b/include/iris/x4/string/detail/string_parse.hpp index 945a6a732..76cc030d0 100644 --- a/include/iris/x4/string/detail/string_parse.hpp +++ b/include/iris/x4/string/detail/string_parse.hpp @@ -30,24 +30,27 @@ string_parse( Attr& attr, CaseCompareFunc const& compare ) noexcept(std::same_as, unused_container_type>) { - using synthesized_value_type = traits::synthesized_value_t; - static_assert(std::same_as, traits::container_attr>); - using value_type = traits::container_value::type; - static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); - - It it = first; - auto stri = str.begin(); - auto str_last = str.end(); - - for (; stri != str_last; ++stri, ++it) { - if (it == last || compare(*stri, *it) != 0) { - return false; + if constexpr (traits::is_single_element_tuple_like::value) { + return detail::string_parse(str, first, last, traits::do_unwrap_if_single_element_tuple_like(attr), compare); + } else { + static_assert(std::same_as::type, traits::container_attr>); + using value_type = traits::container_value::type; + static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); + + It it = first; + auto stri = str.begin(); + auto str_last = str.end(); + + for (; stri != str_last; ++stri, ++it) { + if (it == last || compare(*stri, *it) != 0) { + return false; + } } - } - x4::move_to(first, it, attr); - first = it; - return true; + x4::move_to(first, it, attr); + first = it; + return true; + } } template Se, class CaseCompareFunc> @@ -77,24 +80,27 @@ string_parse( It& first, Se const& last, Attr& attr ) noexcept(std::same_as, unused_container_type>) { - using synthesized_value_type = traits::synthesized_value_t; - static_assert(std::same_as, traits::container_attr>); - using value_type = traits::container_value::type; - static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); - - auto uc_it = ucstr.begin(); - auto uc_last = ucstr.end(); - auto lc_it = lcstr.begin(); - It it = first; - - for (; uc_it != uc_last; ++uc_it, ++lc_it, ++it) { - if (it == last || (*uc_it != *it && *lc_it != *it)) { - return false; + if constexpr (traits::is_single_element_tuple_like::value) { + return detail::string_parse(ucstr, lcstr, first, last, traits::do_unwrap_if_single_element_tuple_like(attr)); + } else { + static_assert(std::same_as, traits::container_attr>); + using value_type = traits::container_value::type; + static_assert(!traits::CharLike || std::same_as, "Mixing incompatible char types is not allowed"); + + auto uc_it = ucstr.begin(); + auto uc_last = ucstr.end(); + auto lc_it = lcstr.begin(); + It it = first; + + for (; uc_it != uc_last; ++uc_it, ++lc_it, ++it) { + if (it == last || (*uc_it != *it && *lc_it != *it)) { + return false; + } } + x4::move_to(first, it, attr); + first = it; + return true; } - x4::move_to(first, it, attr); - first = it; - return true; } template Se, X4Attribute Attr> diff --git a/include/iris/x4/traits/can_hold.hpp b/include/iris/x4/traits/can_hold.hpp index 469046c7f..e5ca76649 100644 --- a/include/iris/x4/traits/can_hold.hpp +++ b/include/iris/x4/traits/can_hold.hpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -31,34 +32,29 @@ struct is_variant; namespace detail { template>> -struct is_all_substitute_for_tuple_impl {}; +struct is_all_substitute_for_tuple {}; 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 {}; +struct is_all_substitute_for_tuple> + : std::conjunction>, std::remove_reference_t>>...> {}; 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 +// This "implementation" exists for short-circuiting `can_hold` for certain trivial combinations. +// Note: lossy conversions are NOT rejected here. Instead, the actual conversion +// sites (e.g. move_to overloads) static_assert against lossy conversions, so the +// user gets a clear error message at the point of use. template -struct can_hold_impl : std::false_type {}; +struct can_hold_impl : std::is_assignable {}; template requires - alloy::is_tuple_like_v && - alloy::is_tuple_like_v + is_same_size_tuple_like::value struct can_hold_impl - : detail::is_all_substitute_for_tuple + : is_all_substitute_for_tuple {}; template @@ -66,7 +62,7 @@ template is_container_v && is_container_v struct can_hold_impl - : detail::value_type_can_hold + : value_type_can_hold {}; template diff --git a/include/iris/x4/traits/lossy_conversion.hpp b/include/iris/x4/traits/lossy_conversion.hpp new file mode 100644 index 000000000..b03e1bf31 --- /dev/null +++ b/include/iris/x4/traits/lossy_conversion.hpp @@ -0,0 +1,67 @@ +#ifndef IRIS_ZZ_X4_TRAITS_LOSSY_CONVERSION_HPP +#define IRIS_ZZ_X4_TRAITS_LOSSY_CONVERSION_HPP + +/*============================================================================= + Copyright (c) 2026 The Iris Project Contributors + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +=============================================================================*/ + +#include + +#include + +#include +#include +#include + +namespace iris::x4::detail { + +// is_lossy_assignment +// +// Customization point: determines whether `Dest = Source` is a lossy assignment. +// The default implementation detects arithmetic narrowing conversions ([dcl.init.list]). +// Users can specialize this trait for their own types (e.g. big integer libraries) +// to flag additional lossy assignments. +// +// Dest and Source are unqualified types (no references, no cv). +template +struct is_lossy_assignment : std::false_type {}; + +template + requires std::is_arithmetic_v +struct is_lossy_assignment + : std::bool_constant< + !iris::is_convertible_without_narrowing_v + > +{}; + +// is_assignable_without_lossy_conversion +// +// True when `Dest = Source` is valid AND does not involve a lossy conversion +// as determined by `is_lossy_assignment`. +template +struct is_assignable_without_lossy_conversion + : std::bool_constant< + std::is_assignable_v && + !is_lossy_assignment, std::remove_cvref_t>::value + > +{}; + +template>> +struct is_tuple_assignable_without_lossy_conversion; + +template +struct is_tuple_assignable_without_lossy_conversion> + : std::conjunction< + is_assignable_without_lossy_conversion< + alloy::tuple_element_t&, + decltype(alloy::get(std::declval())) + >... + > +{}; + +} // iris::x4::detail + +#endif diff --git a/include/iris/x4/traits/tuple_traits.hpp b/include/iris/x4/traits/tuple_traits.hpp index 0771aa886..48da60d9f 100644 --- a/include/iris/x4/traits/tuple_traits.hpp +++ b/include/iris/x4/traits/tuple_traits.hpp @@ -8,7 +8,7 @@ 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 @@ -17,75 +17,68 @@ namespace iris::x4::traits { template -struct has_same_size - : std::bool_constant< - alloy::tuple_size_v> == - alloy::tuple_size_v> - > +struct is_same_size_tuple_like + : std::false_type {}; template -constexpr bool has_same_size_v = has_same_size::value; - -template -struct has_size - : std::bool_constant> == N> -{}; - -template -constexpr bool has_size_v = has_size::value; - -template -struct is_same_size_sequence - : std::bool_constant>, - alloy::is_tuple_like>, - has_same_size - >> + requires + alloy::is_tuple_like_v && + alloy::is_tuple_like_v +struct is_same_size_tuple_like + : std::bool_constant == alloy::tuple_size_v> {}; -template -constexpr bool is_same_size_sequence_v = is_same_size_sequence::value; - -template -struct is_size_one_sequence - : std::bool_constant>, - has_size - >> +template +struct is_single_element_tuple_like + : std::false_type {}; -template -constexpr bool is_size_one_sequence_v = is_size_one_sequence::value; - -template -struct is_size_one_view - : std::bool_constant>, - has_size - >> +template + requires alloy::is_tuple_like_v +struct is_single_element_tuple_like + : std::bool_constant == 1> {}; -template -constexpr bool is_size_one_view_v = is_size_one_view::value; - - template -struct synthesized_value +struct unwrap_if_single_element_tuple_like { using type = T; }; template -using synthesized_value_t = typename synthesized_value::type; + requires is_single_element_tuple_like::value +struct unwrap_if_single_element_tuple_like +{ + using type = alloy::tuple_element_t<0, T>; +}; + +template +struct unwrap_single_element_plain +{ + using type = std::remove_cvref_t; +}; template - requires is_size_one_sequence_v> -struct synthesized_value + requires traits::is_single_element_tuple_like>::value +struct unwrap_single_element_plain { using type = std::remove_cvref_t>; }; +template +[[nodiscard]] constexpr auto&& do_unwrap_if_single_element_tuple_like(T&& value) noexcept +{ + return std::forward(value); +} + +template + requires traits::is_single_element_tuple_like>::value +[[nodiscard]] constexpr auto&& do_unwrap_if_single_element_tuple_like(T&& value) noexcept(noexcept(alloy::get<0>(std::declval()))) +{ + return alloy::get<0>(std::forward(value)); +} + } // iris::x4::traits #endif diff --git a/modules/iris b/modules/iris index 8104bf726..daab5d19c 160000 --- a/modules/iris +++ b/modules/iris @@ -1 +1 @@ -Subproject commit 8104bf726122384bea8e18feac2d956ffa26bf7f +Subproject commit daab5d19ccf743e35093df3f1930f5d8ab937593 diff --git a/test/x4/CMakeLists.txt b/test/x4/CMakeLists.txt index 0ea63393f..7cc80e7dc 100644 --- a/test/x4/CMakeLists.txt +++ b/test/x4/CMakeLists.txt @@ -67,6 +67,7 @@ x4_define_tests( lit matches move_to + lossy_conversion not_predicate no_case no_skip @@ -86,6 +87,7 @@ x4_define_tests( rule4 seek sequence + single_element_tuple_like skip substitution symbols1 diff --git a/test/x4/alternative.cpp b/test/x4/alternative.cpp index 6c1ea50a5..9759d952f 100644 --- a/test/x4/alternative.cpp +++ b/test/x4/alternative.cpp @@ -27,13 +27,15 @@ #include +#include #include #include +#include #include -#include +#include #include -#include +#include struct di_ignore { @@ -135,7 +137,7 @@ TEST_CASE("alternative") // test if alternatives with all components having unused // attributes have an unused attribute - alloy::tuple v; + std::tuple v; REQUIRE((parse("abc", char_ >> (omit[char_] | omit[char_]) >> char_, v))); CHECK((alloy::get<0>(v) == 'a')); CHECK((alloy::get<1>(v) == 'c')); @@ -256,12 +258,12 @@ TEST_CASE("alternative") // single element tuple-like case { - alloy::tuple> fv; + std::tuple> fv; REQUIRE(parse("12345", int_ | +char_, fv)); CHECK(iris::get(alloy::get<0>(fv)) == 12345); } { - alloy::tuple> fvi; + std::tuple> fvi; REQUIRE(parse("12345", int_ | int_, fvi)); CHECK(iris::get(alloy::get<0>(fvi)) == 12345); } @@ -273,7 +275,7 @@ TEST_CASE("alternative") constexpr auto keys = key1 | key2; constexpr auto pair = keys >> lit("=") >> +char_; - alloy::tuple, std::string> attr_; + std::tuple, std::string> attr_; REQUIRE(parse("long=ABC", pair, attr_)); CHECK(iris::get_if(&alloy::get<0>(attr_)) != nullptr); @@ -357,7 +359,7 @@ TEST_CASE("alternative of same attributes (a | a)") >); STATIC_CHECK(x4::parser_traits::sequence_size == 2); - alloy::tuple var; + std::tuple var; auto const res = parse("42true", int_bool, var); REQUIRE(res.completed()); CHECK(var == decltype(var){42, true}); @@ -373,7 +375,7 @@ TEST_CASE("alternative of same attributes (a | a)") >); STATIC_CHECK(x4::parser_traits::sequence_size == 3); - alloy::tuple var; + std::tuple var; auto const res = parse("foo42true", foo_int_bool, var); REQUIRE(res.completed()); CHECK(var == decltype(var){"foo", 42, true}); @@ -406,7 +408,7 @@ TEST_CASE("alternative of same attributes (a | a)") >); STATIC_CHECK(x4::parser_traits::sequence_size == 2); - alloy::tuple> var; + std::tuple> var; auto const res = parse("footruefalse", foo_bools, var); REQUIRE(res.completed()); CHECK(var == decltype(var){"foo", {true, false}}); diff --git a/test/x4/attribute_type_check.cpp b/test/x4/attribute_type_check.cpp index d70d218e6..47b221c67 100644 --- a/test/x4/attribute_type_check.cpp +++ b/test/x4/attribute_type_check.cpp @@ -13,12 +13,14 @@ #include #include +#include #include #include #include #include #include +#include #include namespace { @@ -97,7 +99,7 @@ void gen_tests(Values const&... values) { gen_single_item_tests(values...); - alloy::tuple attribute(values...); + std::tuple attribute(values...); gen_sequence_tests(attribute, values...); } @@ -109,13 +111,13 @@ void make_test(Attributes const&... attrs) gen_tests(attrs...); gen_tests< std::optional..., - alloy::tuple... + std::tuple... >(attrs..., attrs...); gen_tests< - std::optional>..., - alloy::tuple>... - >(alloy::tuple(attrs)..., attrs...); + std::optional>..., + std::tuple>... + >(std::tuple(attrs)..., attrs...); } } // anonymous diff --git a/test/x4/debug.cpp b/test/x4/debug.cpp index 244d930cc..bb7fb11a7 100644 --- a/test/x4/debug.cpp +++ b/test/x4/debug.cpp @@ -20,13 +20,16 @@ #include #include +#include #include +#include #include -#include #include +#include +#include + #include -#include namespace { @@ -112,7 +115,7 @@ TEST_CASE("debug") { // std::container attributes - using tpl = alloy::tuple; + using tpl = std::tuple; rule> start("start"); auto start_def = start = *(int_ >> alpha); diff --git a/test/x4/expect.cpp b/test/x4/expect.cpp index 5da40afd1..d63eca11b 100644 --- a/test/x4/expect.cpp +++ b/test/x4/expect.cpp @@ -47,14 +47,16 @@ #include #include +#include #include #include -#include #include #include -#include +#include #include +#include +#include // NOLINTBEGIN(bugprone-chained-comparison) @@ -352,7 +354,7 @@ TEST_CASE("expect") // Test that attributes with > (sequences) work just like >> (sequences) { - alloy::tuple attr; + std::tuple attr; X4_TEST_ATTR_SUCCESS_PASS(" a\n b\n c", char_ > char_ > char_, space, attr); CHECK((alloy::get<0>(attr) == 'a')); CHECK((alloy::get<1>(attr) == 'b')); @@ -360,7 +362,7 @@ TEST_CASE("expect") } { - alloy::tuple attr; + std::tuple attr; X4_TEST_ATTR_SUCCESS_PASS(" a\n b\n c", char_ > char_ >> char_, space, attr); CHECK((alloy::get<0>(attr) == 'a')); CHECK((alloy::get<1>(attr) == 'b')); @@ -368,7 +370,7 @@ TEST_CASE("expect") } { - alloy::tuple attr; + std::tuple attr; X4_TEST_ATTR_SUCCESS_PASS(" a, b, c", char_ >> ',' >> expect[char_] >> ',' >> expect[char_], space, attr); CHECK((alloy::get<0>(attr) == 'a')); CHECK((alloy::get<1>(attr) == 'b')); diff --git a/test/x4/int.cpp b/test/x4/int.cpp index 7a1cc7394..e63d73e9b 100644 --- a/test/x4/int.cpp +++ b/test/x4/int.cpp @@ -14,9 +14,11 @@ #include #include +#include #include #include +#include #include #include @@ -233,7 +235,7 @@ TEST_CASE("int") // single-element tuple tests { - alloy::tuple i{}; + std::tuple i{}; REQUIRE(parse("-123456", int_, i)); CHECK(alloy::get<0>(i) == -123456); diff --git a/test/x4/iris_x4_test.hpp b/test/x4/iris_x4_test.hpp index cc54d29a5..4f6b224c6 100644 --- a/test/x4/iris_x4_test.hpp +++ b/test/x4/iris_x4_test.hpp @@ -269,8 +269,22 @@ struct custom_container void insert(T*, It, Se) {} }; +template +struct single_element_struct +{ + T value; + + bool operator==(single_element_struct const&) const = default; +}; + } // x4_test +template +struct iris::alloy::adaptor> +{ + using getters_list = make_getters_list<&x4_test::single_element_struct::value>; +}; + using x4_test::parse; using x4_test::parse_debug; diff --git a/test/x4/lit.cpp b/test/x4/lit.cpp index ba370320a..03109374e 100644 --- a/test/x4/lit.cpp +++ b/test/x4/lit.cpp @@ -16,9 +16,11 @@ #include #include +#include #include #include +#include TEST_CASE("lit") { @@ -148,7 +150,7 @@ TEST_CASE("lit") { // single-element tuple tests - alloy::tuple s; + std::tuple s; REQUIRE(parse("kimpo", x4::standard::string("kimpo"), s)); CHECK(alloy::get<0>(s) == "kimpo"); } diff --git a/test/x4/lossy_conversion.cpp b/test/x4/lossy_conversion.cpp new file mode 100644 index 000000000..43847253d --- /dev/null +++ b/test/x4/lossy_conversion.cpp @@ -0,0 +1,344 @@ +#include "iris_x4_test.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +template +using SES = x4_test::single_element_struct; + + +// ======================================================================= +// Mock "small int" type for is_lossy_assignment customization point test +// ======================================================================= + +struct small_int { + std::int8_t value{}; + + small_int() = default; + explicit small_int(std::int8_t v) : value(v) {} + + small_int& operator=(int v) { value = static_cast(v); return *this; } // convenient but may narrow +}; + +// Specialization: assigning int to small_int is lossy because +// small_int can only hold 8-bit values. +template<> +struct iris::x4::detail::is_lossy_assignment : std::true_type {}; + +struct convertible_to_int { + operator int() const { return 42; } +}; + +struct convertible_to_long_long { + operator long long() const { return 42; } +}; + + +// ======================================================================= +// Rule definitions for lossy conversion tests +// ======================================================================= + +using IntRule = x4::rule; +using LongLongRule = x4::rule; +using ShortRule = x4::rule; +using FloatRule = x4::rule; +using DoubleRule = x4::rule; + +IRIS_X4_DECLARE_CONSTEXPR(IntRule) +IRIS_X4_DECLARE_CONSTEXPR(LongLongRule) +IRIS_X4_DECLARE_CONSTEXPR(ShortRule) +IRIS_X4_DECLARE_CONSTEXPR(FloatRule) +IRIS_X4_DECLARE_CONSTEXPR(DoubleRule) + +constexpr IntRule int_rule; +constexpr LongLongRule long_long_rule; +constexpr ShortRule short_rule; +constexpr FloatRule float_rule; +constexpr DoubleRule double_rule; + +constexpr auto int_rule_def = x4::int_; +constexpr auto long_long_rule_def = x4::long_long; +constexpr auto short_rule_def = x4::short_; +constexpr auto float_rule_def = x4::float_; +constexpr auto double_rule_def = x4::double_; + +IRIS_X4_DEFINE_CONSTEXPR(int_rule) +IRIS_X4_DEFINE_CONSTEXPR(long_long_rule) +IRIS_X4_DEFINE_CONSTEXPR(short_rule) +IRIS_X4_DEFINE_CONSTEXPR(float_rule) +IRIS_X4_DEFINE_CONSTEXPR(double_rule) + + +// ======================================================================= +// Trait-level static checks +// ======================================================================= + +TEST_CASE("lossy: is_lossy_assignment trait") +{ + using x4::detail::is_lossy_assignment; + + // Default: arithmetic narrowing + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + + // Non-arithmetic Dest: not checked by default + STATIC_CHECK(!is_lossy_assignment::value); + + // Non-arithmetic Source with conversion operator to arithmetic Dest + STATIC_CHECK(!is_lossy_assignment::value); + STATIC_CHECK(is_lossy_assignment::value); + STATIC_CHECK(!is_lossy_assignment::value); + + // Customization point: small_int = int is flagged as lossy (user-specialized) + STATIC_CHECK(is_lossy_assignment::value); +} + +TEST_CASE("lossy: is_assignable_without_lossy_conversion trait") +{ + using x4::detail::is_assignable_without_lossy_conversion; + + // Non-lossy: same type + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + + // Non-lossy: widening + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + + // Lossy conversions + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + + // Signed/unsigned mismatch + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); + + // Non-arithmetic dest: lossy conversion not checked + STATIC_CHECK(is_assignable_without_lossy_conversion::value); + + // Customization point: small_int::operator=(int) exists, but the specialization rejects it + STATIC_CHECK(!is_assignable_without_lossy_conversion::value); +} + +TEST_CASE("lossy: is_tuple_assignable_without_lossy_conversion trait") +{ + using x4::detail::is_tuple_assignable_without_lossy_conversion; + + STATIC_CHECK((is_tuple_assignable_without_lossy_conversion< + std::tuple, + std::tuple + >::value)); + + STATIC_CHECK((!is_tuple_assignable_without_lossy_conversion< + std::tuple, + std::tuple + >::value)); + + STATIC_CHECK((!is_tuple_assignable_without_lossy_conversion< + std::tuple, + std::tuple + >::value)); +} + + +// ======================================================================= +// Parse-level tests (non-lossy conversions that must compile & run) +// ======================================================================= + +TEST_CASE("lossy: parse int to long long (widening)") +{ + long long a{}; + REQUIRE(parse("42", x4::int_, a)); + CHECK(a == 42); +} + +TEST_CASE("lossy: parse short to int (widening)") +{ + int a{}; + REQUIRE(parse("7", x4::short_, a)); + CHECK(a == 7); +} + +TEST_CASE("lossy: parse float to double (widening)") +{ + double a{}; + REQUIRE(parse("3.14", x4::float_, a)); + CHECK(a > 3.13); + CHECK(a < 3.15); +} + +TEST_CASE("lossy: parse int to long long via SES (single-element tuple-like)") +{ + SES a{}; + REQUIRE(parse("99", x4::int_, a)); + CHECK(a.value == 99); +} + +TEST_CASE("lossy: parse int to optional (widening)") +{ + std::optional a; + REQUIRE(parse("55", x4::int_, a)); + REQUIRE(a.has_value()); + CHECK(*a == 55); +} + +TEST_CASE("lossy: parse tuple element-wise (all widening)") +{ + std::tuple a; + REQUIRE(parse("1,3.0", x4::int_ >> ',' >> x4::float_, a)); + CHECK(std::get<0>(a) == 1); + CHECK(std::get<1>(a) > 2.99); +} + +TEST_CASE("lossy: parse same type (int to int)") +{ + int a{}; + REQUIRE(parse("123", x4::int_, a)); + CHECK(a == 123); +} + +TEST_CASE("lossy: parse same type (char to char)") +{ + char a{}; + REQUIRE(parse("x", x4::char_, a)); + CHECK(a == 'x'); +} + + +// ======================================================================= +// Rule-level tests +// +// When the exposed attribute type differs from the rule's attribute type, +// the rule materializes its own attribute internally and then assigns it +// to the exposed attribute. The lossy conversion check applies to that +// assignment. +// ======================================================================= + +TEST_CASE("lossy: RuleAttrAssignableWithoutLoss concept") +{ + using x4::detail::RuleAttrAssignableWithoutLoss; + + // Same type: always OK + STATIC_CHECK(RuleAttrAssignableWithoutLoss); + STATIC_CHECK(RuleAttrAssignableWithoutLoss); + + // Widening: OK + STATIC_CHECK(RuleAttrAssignableWithoutLoss); + STATIC_CHECK(RuleAttrAssignableWithoutLoss); + STATIC_CHECK(RuleAttrAssignableWithoutLoss); + + // Lossy: rejected + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); + + // Signed/unsigned mismatch + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); + STATIC_CHECK(!RuleAttrAssignableWithoutLoss); +} + +TEST_CASE("lossy: rule with same exposed type (int rule to int)") +{ + int a{}; + REQUIRE(parse("42", int_rule, a)); + CHECK(a == 42); +} + +TEST_CASE("lossy: rule widening (int rule to long long)") +{ + long long a{}; + REQUIRE(parse("42", int_rule, a)); + CHECK(a == 42); +} + +TEST_CASE("lossy: rule widening (short rule to int)") +{ + int a{}; + REQUIRE(parse("7", short_rule, a)); + CHECK(a == 7); +} + +TEST_CASE("lossy: rule widening (float rule to double)") +{ + double a{}; + REQUIRE(parse("3.14", float_rule, a)); + CHECK(a > 3.13); + CHECK(a < 3.15); +} + +TEST_CASE("lossy: immediate rule widening (int to long long)") +{ + long long a{}; + REQUIRE(parse("42", x4::rule{} = x4::int_, a)); + CHECK(a == 42); +} + + +// ======================================================================= +// Lossy conversion static_assert tests +// +// The following test cases are commented out because they are expected to +// produce static_assert failures. Uncomment any single case to verify the +// assertion fires. +// ======================================================================= + +// --- move_to: source to plain (lossy) --- +// TEST_CASE("lossy: int to char") { char a{}; REQUIRE(parse("42", x4::int_, a)); } +// TEST_CASE("lossy: int to short") { short a{}; REQUIRE(parse("42", x4::int_, a)); } +// TEST_CASE("lossy: long long to int") { int a{}; REQUIRE(parse("42", x4::long_long, a)); } +// TEST_CASE("lossy: double to float") { float a{}; REQUIRE(parse("3.14", x4::double_, a)); } + +// --- move_to: single-element tuple-like to plain (lossy) --- +// TEST_CASE("lossy: SES int to char") { SES a{}; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: source to optional (lossy) --- +// TEST_CASE("lossy: int to optional") { std::optional a; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: tuple element-wise (lossy) --- +// TEST_CASE("lossy: tuple char,int") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } +// TEST_CASE("lossy: tuple int,char") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } + +// --- move_to: source to non-arithmetic dest (lossy via customization point) --- +// TEST_CASE("lossy: int to small_int") { small_int a; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: single-element tuple-like with non-arithmetic dest (lossy) --- +// TEST_CASE("lossy: SES small_int from int") { SES a{}; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: optional with non-arithmetic dest (lossy) --- +// TEST_CASE("lossy: int to optional") { std::optional a; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: tuple with non-arithmetic dest (lossy) --- +// TEST_CASE("lossy: tuple small_int,int") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } +// TEST_CASE("lossy: tuple int,small_int") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } + +// --- rule: rule attribute to exposed attribute (lossy) --- +// TEST_CASE("lossy: int rule to char") { char a{}; REQUIRE(parse("42", int_rule, a)); } +// TEST_CASE("lossy: int rule to short") { short a{}; REQUIRE(parse("42", int_rule, a)); } +// TEST_CASE("lossy: long long rule to int") { int a{}; REQUIRE(parse("42", long_long_rule, a)); } +// TEST_CASE("lossy: double rule to float") { float a{}; REQUIRE(parse("3.14", double_rule, a)); } +// TEST_CASE("lossy: immediate rule lossy") { char a{}; REQUIRE(parse("42", x4::rule{} = x4::int_, a)); } +// TEST_CASE("lossy: int rule to small_int") { small_int a; REQUIRE(parse("42", int_rule, a)); } diff --git a/test/x4/move_to.cpp b/test/x4/move_to.cpp index b7f168bde..fab0c21f3 100644 --- a/test/x4/move_to.cpp +++ b/test/x4/move_to.cpp @@ -7,6 +7,9 @@ #include #include +#include + +#include #include struct X {}; @@ -38,21 +41,21 @@ TEST_CASE("move_to") // tuple contains reference { int n = 0; - alloy::tuple ref_tuple{ n }; + std::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 }; + std::tuple ref_tuple{ n }; + std::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; + std::tuple ref_tuple{ mo }; + std::tuple dest; x4::move_to(std::move(ref_tuple), dest); // move "far" ownership } } diff --git a/test/x4/omit.cpp b/test/x4/omit.cpp index bc24f4a18..6fd9c275e 100644 --- a/test/x4/omit.cpp +++ b/test/x4/omit.cpp @@ -17,9 +17,11 @@ #include #include +#include #include #include +#include namespace { @@ -65,7 +67,7 @@ TEST_CASE("omit") { // omit[] means we don't receive the attribute - alloy::tuple<> attr; + std::tuple<> attr; CHECK(parse("abc", omit[char_] >> omit['b'] >> omit[char_], attr)); } @@ -81,7 +83,7 @@ TEST_CASE("omit") // omit[] means we don't receive the attribute, if all elements of a // sequence have unused attributes, the whole sequence has an unused // attribute as well - alloy::tuple attr; + std::tuple attr; REQUIRE(parse("abcde", char_ >> (omit[char_] >> omit['c'] >> omit[char_]) >> char_, attr)); CHECK(alloy::get<0>(attr) == 'a'); CHECK(alloy::get<1>(attr) == 'e'); @@ -89,7 +91,7 @@ TEST_CASE("omit") { // "hello" has an unused_type. unused attrubutes are not part of the sequence - alloy::tuple attr; + std::tuple attr; REQUIRE(parse("a hello c", char_ >> "hello" >> char_, space, attr)); CHECK(alloy::get<0>(attr) == 'a'); CHECK(alloy::get<1>(attr) == 'c'); diff --git a/test/x4/optional.cpp b/test/x4/optional.cpp index d45152329..6c6e4a175 100644 --- a/test/x4/optional.cpp +++ b/test/x4/optional.cpp @@ -18,11 +18,13 @@ #include #include +#include #include #include #include #include +#include #include #ifdef _MSC_VER @@ -105,7 +107,7 @@ TEST_CASE("optional") static_assert(!x4::parser_traits>::has_attribute); static_assert(std::same_as>::attribute_type, unused_type>); - alloy::tuple v; + std::tuple v; REQUIRE(parse("a1234c", char_ >> -omit[int_] >> char_, v)); CHECK(alloy::get<0>(v) == 'a'); CHECK(alloy::get<1>(v) == 'c'); @@ -132,7 +134,7 @@ TEST_CASE("optional") } } { - alloy::tuple v; + std::tuple v; REQUIRE(parse("a1234c", char_ >> omit[-int_] >> char_, v)); CHECK(alloy::get<0>(v) == 'a'); CHECK(alloy::get<1>(v) == 'c'); diff --git a/test/x4/plus.cpp b/test/x4/plus.cpp index 55ece085f..164d91ca8 100644 --- a/test/x4/plus.cpp +++ b/test/x4/plus.cpp @@ -18,9 +18,11 @@ #include #include +#include #include #include +#include #include TEST_CASE("plus") @@ -114,7 +116,7 @@ TEST_CASE("plus") // single-element tuple tests { - alloy::tuple fs; + std::tuple fs; REQUIRE(parse("12345", +char_, fs)); CHECK(alloy::get<0>(fs) == "12345"); } diff --git a/test/x4/real.hpp b/test/x4/real.hpp index cf0cc7128..b669182da 100644 --- a/test/x4/real.hpp +++ b/test/x4/real.hpp @@ -60,8 +60,10 @@ struct ts_real_policies : x4::ureal_policies constexpr uint_parser uint3; constexpr uint_parser uint3_3; - if (auto res = parse(first, last, uint3, result); res.ok) { - Accumulator n; + unsigned u; + if (auto res = parse(first, last, uint3, u); res.ok) { + result = u; + unsigned n; It iter = res.remainder.begin(); first = iter; diff --git a/test/x4/rule4.cpp b/test/x4/rule4.cpp index 4824331fa..de65ad8db 100644 --- a/test/x4/rule4.cpp +++ b/test/x4/rule4.cpp @@ -22,10 +22,11 @@ #include +#include #include #include + #include -#include namespace { @@ -172,9 +173,10 @@ TEST_CASE("rule4") // test handling of single element tuple { - auto r = rule>{} = int_; + using attr_type = x4_test::single_element_struct; + auto r = rule{} = int_; - alloy::tuple v(0); + attr_type v(0); REQUIRE(parse("1", r, v)); CHECK(alloy::get<0>(v) == 1); } diff --git a/test/x4/sequence.cpp b/test/x4/sequence.cpp index b92d6cb0a..96c01d003 100644 --- a/test/x4/sequence.cpp +++ b/test/x4/sequence.cpp @@ -29,11 +29,13 @@ #include +#include #include #include #include #include +#include TEST_CASE("sequence") { @@ -71,14 +73,14 @@ TEST_CASE("sequence") CHECK(parse(" Hello, World", lit("Hello") >> ',' >> "World", space)); { - alloy::tuple vec; + std::tuple vec; REQUIRE(parse("ab", char_ >> char_, vec)); CHECK(alloy::get<0>(vec) == 'a'); CHECK(alloy::get<1>(vec) == 'b'); } { - alloy::tuple vec; + std::tuple vec; REQUIRE(parse(" a\n b\n c", char_ >> char_ >> char_, space, vec)); CHECK(alloy::get<0>(vec) == 'a'); CHECK(alloy::get<1>(vec) == 'b'); @@ -87,7 +89,7 @@ TEST_CASE("sequence") { // 'b' has an unused_type. unused attributes are not part of the sequence - alloy::tuple vec; + std::tuple vec; REQUIRE(parse("abc", char_ >> 'b' >> char_, vec)); CHECK(alloy::get<0>(vec) == 'a'); CHECK(alloy::get<1>(vec) == 'c'); @@ -95,7 +97,7 @@ TEST_CASE("sequence") { // 'b' has an unused_type. unused attributes are not part of the sequence - alloy::tuple vec; + std::tuple vec; REQUIRE(parse("acb", char_ >> char_ >> 'b', vec)); CHECK(alloy::get<0>(vec) == 'a'); CHECK(alloy::get<1>(vec) == 'c'); @@ -103,7 +105,7 @@ TEST_CASE("sequence") { // "hello" has an unused_type. unused attributes are not part of the sequence - alloy::tuple vec; + std::tuple vec; REQUIRE(parse("a hello c", char_ >> "hello" >> char_, space, vec)); CHECK(alloy::get<0>(vec) == 'a'); CHECK(alloy::get<1>(vec) == 'c'); @@ -118,7 +120,7 @@ TEST_CASE("sequence") { // a single element tuple - alloy::tuple vec; + std::tuple vec; REQUIRE(parse("ab", char_ >> 'b', vec)); CHECK(alloy::get<0>(vec) == 'a'); } @@ -133,7 +135,7 @@ TEST_CASE("sequence") // rule version { - using attr_type = alloy::tuple; + using attr_type = std::tuple; attr_type tpl; auto r = rule{} = char_ >> ',' >> int_; @@ -144,7 +146,7 @@ TEST_CASE("sequence") // as version { - using attr_type = alloy::tuple; + using attr_type = std::tuple; attr_type tpl; auto r = as(char_ >> ',' >> int_); @@ -161,18 +163,18 @@ TEST_CASE("sequence") // rule version { - using attr_type = alloy::tuple; - attr_type tpl; + using attr_type = x4_test::single_element_struct; + attr_type ses; auto r = rule{} = int_; - REQUIRE(parse("test:1", "test:" >> r, tpl)); - CHECK((tpl == attr_type(1))); + REQUIRE(parse("test:1", "test:" >> r, ses)); + CHECK((ses == attr_type(1))); } // as version { - using attr_type = alloy::tuple; + using attr_type = x4_test::single_element_struct; attr_type tpl; auto r = as(int_); @@ -396,7 +398,7 @@ TEST_CASE("sequence") // Test from spirit mailing list // "Error with container within sequence" { - using attr_type = alloy::tuple; + using attr_type = std::tuple; attr_type vec; constexpr auto r = *alnum; @@ -405,7 +407,7 @@ TEST_CASE("sequence") CHECK(alloy::get<0>(vec) == "abcdef"); } { - using attr_type = alloy::tuple>; + using attr_type = std::tuple>; attr_type vec; constexpr auto r = *int_; @@ -418,7 +420,7 @@ TEST_CASE("sequence") { // Non-flat optional - alloy::tuple>> v; + std::tuple>> v; constexpr auto p = int_ >> -(':' >> int_ >> '-' >> int_); REQUIRE(parse("1:2-3", p, v)); REQUIRE(alloy::get<1>(v).has_value()); @@ -430,12 +432,12 @@ TEST_CASE("sequence") constexpr auto p = char_ >> -(':' >> +char_); { - alloy::tuple> v; + std::tuple> v; REQUIRE(parse("x", p, v)); CHECK(!alloy::get<1>(v).has_value()); } { - alloy::tuple> v; + std::tuple> v; REQUIRE(parse("x:abc", p, v)); REQUIRE(alloy::get<1>(v).has_value()); CHECK(*alloy::get<1>(v) == "abc"); @@ -536,3 +538,22 @@ TEST_CASE("sequence") CHECK(v.size() == 4); } } + +// commented-out test case that verifies whether static_assert will correctly fire +// TEST_CASE("wrong_sequence_size") +// { +// using x4::char_; +// using x4::int_; +// // actual < expected +// { +// constexpr auto parser = char_ >> int_; +// std::tuple attr; +// REQUIRE(!parse("a123", parser, attr)); +// } +// // actual > expected +// { +// constexpr auto parser = char_ >> int_; +// std::tuple attr; +// REQUIRE(!parse("a123", parser, attr)); +// } +// } diff --git a/test/x4/single_element_tuple_like.cpp b/test/x4/single_element_tuple_like.cpp new file mode 100644 index 000000000..b33a1e239 --- /dev/null +++ b/test/x4/single_element_tuple_like.cpp @@ -0,0 +1,637 @@ +#include "iris_x4_test.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + + +struct Ident +{ + std::string value; + + bool operator==(const Ident&) const = default; +}; + +struct Var +{ + Ident ident; +}; + +struct TwoInts +{ + int a; + int b; +}; + +struct NamePath +{ + bool is_rooted = false; + std::vector segments; +}; + +struct AnyID +{ + NamePath path; +}; + +IRIS_ALLOY_ADAPT_STRUCT(Ident, value) +IRIS_ALLOY_ADAPT_STRUCT(Var, ident) +IRIS_ALLOY_ADAPT_STRUCT(TwoInts, a, b) +IRIS_ALLOY_ADAPT_STRUCT(NamePath, is_rooted, segments) +IRIS_ALLOY_ADAPT_STRUCT(AnyID, path) + +template +using SES = x4_test::single_element_struct; + +using IdentRule = x4::rule; +using VarRule = x4::rule; +using NamePathRule = x4::rule; +using AnyIDRule = x4::rule; + +IRIS_X4_DECLARE_CONSTEXPR(IdentRule) +IRIS_X4_DECLARE_CONSTEXPR(VarRule) +IRIS_X4_DECLARE_CONSTEXPR(NamePathRule) +IRIS_X4_DECLARE_CONSTEXPR(AnyIDRule) + +constexpr IdentRule ident; +constexpr VarRule var; +constexpr NamePathRule name_path; +constexpr AnyIDRule any_id; + +constexpr auto ident_def = x4::alpha >> *x4::alnum; +constexpr auto var_def = '$' >> ident; +constexpr auto name_path_def = x4::matches["::"] >> (ident % "::"); +constexpr auto any_id_def = name_path; + +IRIS_X4_DEFINE_CONSTEXPR(ident) +IRIS_X4_DEFINE_CONSTEXPR(var) +IRIS_X4_DEFINE_CONSTEXPR(name_path) +IRIS_X4_DEFINE_CONSTEXPR(any_id) + +TEST_CASE("single_element_tuple_like") +{ + using x4::as; + using x4::alnum; + using x4::alpha; + using x4::eps; + using x4::int_; + using x4::lexeme; + using x4::string; + + // ident + { + constexpr auto parser = string("abc"); + Ident attr; + STATIC_CHECK(std::same_as::attribute_type, std::string>); + REQUIRE(parse("abc", parser, attr)); + CHECK(attr.value == "abc"); + } + { + constexpr auto parser = ident; + Ident attr; + STATIC_CHECK(std::same_as::attribute_type, Ident>); + REQUIRE(parse("abc", parser, attr)); + CHECK(attr.value == "abc"); + } + { + constexpr auto parser = ident >> eps; + Ident attr; + STATIC_CHECK(std::same_as::attribute_type, Ident>); + REQUIRE(parse("abc", parser, attr)); + CHECK(attr.value == "abc"); + } + + // var + { + constexpr auto parser = '$' >> string("abc"); + Var attr; + STATIC_CHECK(std::same_as::attribute_type, std::string>); + REQUIRE(parse("$abc", parser, attr)); + CHECK(attr.ident.value == "abc"); + } + { + constexpr auto parser = '$' >> as(string("abc")); + Var attr; + STATIC_CHECK(std::same_as::attribute_type, Ident>); + REQUIRE(parse("$abc", parser, attr)); + CHECK(attr.ident.value == "abc"); + } + { + constexpr auto parser = var; + Var attr; + STATIC_CHECK(std::same_as::attribute_type, Var>); + REQUIRE(parse("$abc", parser, attr)); + CHECK(attr.ident.value == "abc"); + } + { + constexpr auto parser = var >> eps; + Var attr; + STATIC_CHECK(std::same_as::attribute_type, Var>); + REQUIRE(parse("$abc", parser, attr)); + CHECK(attr.ident.value == "abc"); + } + + // name_path + { + constexpr auto parser = name_path; + NamePath attr; + REQUIRE(parse("::foo::bar", parser, attr)); + CHECK(attr.is_rooted); + CHECK(attr.segments == std::vector{Ident{"foo"}, Ident{"bar"}}); + } + + // any_id + { + constexpr auto parser = any_id; + AnyID attr; + REQUIRE(parse("::foo::bar", parser, attr)); + CHECK(attr.path.is_rooted); + CHECK(attr.path.segments == std::vector{Ident{"foo"}, Ident{"bar"}}); + } + + // see https://github.com/boostorg/spirit_x4/issues/27 + { + constexpr auto parser = alpha >> *alnum; + std::string attr; + REQUIRE(parse("abc", parser, attr)); + CHECK(attr == "abc"); + } + { + constexpr auto parser = lexeme[alpha >> *alnum]; + std::string attr; + REQUIRE(parse("abc", parser, attr)); + CHECK(attr == "abc"); + } + { + constexpr auto identifier = alpha >> *alnum; + constexpr auto func_call = identifier >> '(' >> int_ >> ')'; + REQUIRE(parse("abc(42)", func_call)); + } + { + constexpr auto identifier = lexeme[alpha >> *alnum]; + constexpr auto func_call = identifier >> '(' >> int_ >> ')'; + REQUIRE(parse("abc(42)", func_call)); + } +} + + +// +// AUTOGENERATED CONTENT; DO NOT EDIT UNLESS YOU KNOW WHAT YOU ARE DOING. +// +// The test cases below are AI-generated content that was explicitly approved +// by @saki7, the current maintainer of X4. They were produced during a pair- +// programming session led by @yaito3014, a major contributor and the author +// of the `alloy` tuple library. The session was screen-shared over a Discord +// voice chat, and we spent more than six hours in deep discussion to ensure +// that the generated content was semantically meaningful. +// +// The problem these test cases were designed to solve essentially requires +// covering the full set of parent/child parser attribute combinations. The +// only practical way to express this in the test suite was to generate the +// Cartesian product of those attribute combinations, resulting in well over +// one hundred cases in total. In other words, this task was inherently well +// suited to AI-assisted generation, and we considered it an ideal place to +// explore how AI could support X4 development. +// +// As a result, these tests identified a single point of failure in +// `parse_sequence` and directly contributed to fixing the bug. +// +// For that reason, the code below was produced under very careful supervision +// by the maintainer and should be treated as an exceptional case of accepting +// AI-generated content into our codebase. +// +// Any future contributor who attempts to use the existence of this code as +// justification for submitting vibe-coded slop will be considered harmful to +// the codebase and will receive a permanent ban. +// + +// Tests attribute compatibility across the matrix of: +// attribute_category: plain, container, single_element_tuple (SES), multi_element_tuple (MET), variant +// parser_form: identity, single_element_tuple, variant +// +// Invalid combos: (container, SES) and (MET, SES) — no valid move_to +// Valid combos per participant: 10 +// +// Notation: SES = x4_test::single_element_struct +// TwoInts = struct { int a, b; } +// +// Tests are organized in three groups: +// 1. Standalone: each combo parsed independently +// 2. In sequence: unused >> parser → dest (attribute flow through sequences) +// 3. Composition: two attributed parsers → combined dest (partition and interaction) + +// TODO: add container appender case (parent needs to be inside the list like parser) + +// =================================================================== +// 1. Standalone: parse(input, parser, dest) +// =================================================================== + +TEST_CASE("SES standalone") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; + + // plain x identity + { int a{}; REQUIRE(parse("42", int_, a)); CHECK(a == 42); } + // plain x SES + { int a{}; REQUIRE(parse("", x4::attr(SES{42}), a)); CHECK(a == 42); } + // plain x variant + { int a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a == 42); } + // container x identity + { std::string a; REQUIRE(parse("abc", string("abc"), a)); CHECK(a == "abc"); } + // container x variant + { std::string a; REQUIRE(parse("a", alpha | char_, a)); CHECK(a == "a"); } + // SES x identity + { SES a{}; REQUIRE(parse("42", int_, a)); CHECK(a.value == 42); } + // SES x SES + { SES a{}; REQUIRE(parse("", x4::attr(SES{42}), a)); CHECK(a.value == 42); } + // SES x variant + { SES a{}; REQUIRE(parse("42", int_ | char_, a)); CHECK(a.value == 42); } + // MET x identity + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + // MET x variant + { TwoInts a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + // variant_attr x identity + { iris::rvariant a; REQUIRE(parse("42", int_, a)); CHECK(iris::get(a) == 42); } + // variant_attr x SES + { iris::rvariant a; REQUIRE(parse("", x4::attr(SES{42}), a)); CHECK(iris::get(a) == 42); } + // variant_attr x variant + { iris::rvariant a; REQUIRE(parse("42", int_ | char_, a)); CHECK(iris::get(a) == 42); } + // SES x identity + { SES> a; REQUIRE(parse("42", int_, a)); CHECK(iris::get(a.value) == 42); } + // SES x SES + { SES> a; REQUIRE(parse("", x4::attr(SES{42}), a)); CHECK(iris::get(a.value) == 42); } + // SES x variant + { SES> a; REQUIRE(parse("42", int_ | char_, a)); CHECK(iris::get(a.value) == 42); } +} + +// =================================================================== +// 2. In sequence: parse(input, unused >> parser, dest) +// Tests partition_attribute with one unused side +// =================================================================== + +TEST_CASE("SES in sequence") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; + + // plain x identity + { int a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a == 42); } + // plain x SES + { int a{}; REQUIRE(parse("+", '+' >> x4::attr(SES{42}), a)); CHECK(a == 42); } + // plain x variant + { int a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a == 42); } + // container x identity + { std::string a; REQUIRE(parse("+a", '+' >> alpha, a)); CHECK(a == "a"); } + // container x variant + { std::string a; REQUIRE(parse("+a", '+' >> (alpha | char_), a)); CHECK(a == "a"); } + // SES x identity + { SES a{}; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(a.value == 42); } + // SES x SES + { SES a{}; REQUIRE(parse("+", '+' >> x4::attr(SES{42}), a)); CHECK(a.value == 42); } + // SES x variant + { SES a{}; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(a.value == 42); } + // MET x identity + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> (int_ >> ',' >> int_), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + // MET x variant + { TwoInts a{}; REQUIRE(parse("+1,2", '+' >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(a.a == 1); CHECK(a.b == 2); } + // variant_attr x identity + { iris::rvariant a; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(iris::get(a) == 42); } + // variant_attr x SES + { iris::rvariant a; REQUIRE(parse("+", '+' >> x4::attr(SES{42}), a)); CHECK(iris::get(a) == 42); } + // variant_attr x variant + { iris::rvariant a; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(iris::get(a) == 42); } + // SES x identity + { SES> a; REQUIRE(parse("+42", '+' >> int_, a)); CHECK(iris::get(a.value) == 42); } + // SES x SES + { SES> a; REQUIRE(parse("+", '+' >> x4::attr(SES{42}), a)); CHECK(iris::get(a.value) == 42); } + // SES x variant + { SES> a; REQUIRE(parse("+42", '+' >> (int_ | char_), a)); CHECK(iris::get(a.value) == 42); } +} + +// =================================================================== +// 3. Composition: left_parser >> right_parser → combined dest +// Tests partition_attribute splitting + per-slot attribute handling +// +// Parser forms (sequence_size=1): +// P_int: int_ → int +// P_set: attr(SES{V}) → SES +// P_var: int_ | char_ → rvariant +// P_chr: alpha → char +// P_slit: string("abc") → std::string +// P_str: +alpha → std::string +// +// Parser forms (sequence_size=2): +// P_met: int_ >> ',' >> int_ +// P_metv: (int_ >> ',' >> int_) | (char_ >> ',' >> char_) +// +// Slot types tested: int, char, std::string, SES, SES, +// SES, TwoInts +// =================================================================== + +TEST_CASE("SES composition: left int_") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; + + // int_ x int_ → (int, int) + { std::tuple a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ x int_ → MET struct + { TwoInts a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.a == 1); CHECK(a.b == 2); } + // int_ x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ x int_ → (int, SES) + { std::tuple> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 2); } + // int_ x int_ → (SES, SES) + { std::tuple, SES> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a).value == 2); } + // int_ x attr(SES) + { std::tuple a; REQUIRE(parse("42", int_ >> x4::attr(SES{99}), a)); CHECK(alloy::get<0>(a) == 42); CHECK(alloy::get<1>(a) == 99); } + // int_ x variant + { std::tuple a; REQUIRE(parse("1,2", int_ >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ x alpha + { std::tuple a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } + // int_ x alpha → (int, SES) + { std::tuple> a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 'a'); } + // int_ x alpha → (SES, SES) + { std::tuple, SES> a; REQUIRE(parse("1a", int_ >> alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a).value == 'a'); } + // int_ x string("abc") + { std::tuple a; REQUIRE(parse("1abc", int_ >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + // int_ x +alpha + { std::tuple a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + // int_ x +alpha → (int, SES) + { std::tuple> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == "abc"); } + // int_ x +alpha → (SES, string) + { std::tuple, std::string> a; REQUIRE(parse("1abc", int_ >> +alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == "abc"); } +} + +TEST_CASE("SES composition: left attr(SES)") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; + + // attr(SES) x int_ + { std::tuple a; REQUIRE(parse("42", x4::attr(SES{1}) >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 42); } + // attr(SES) x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("42", x4::attr(SES{1}) >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 42); } + // attr(SES) x attr(SES) + { std::tuple a; REQUIRE(parse("", x4::attr(SES{1}) >> x4::attr(SES{2}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // attr(SES) x variant + { std::tuple a; REQUIRE(parse("2", x4::attr(SES{1}) >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // attr(SES) x alpha + { std::tuple a; REQUIRE(parse("a", x4::attr(SES{1}) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } + // attr(SES) x alpha → (SES, char) + { std::tuple, char> a; REQUIRE(parse("a", x4::attr(SES{1}) >> alpha, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 'a'); } + // attr(SES) x string("abc") + { std::tuple a; REQUIRE(parse("abc", x4::attr(SES{1}) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + // attr(SES) x +alpha + { std::tuple a; REQUIRE(parse("abc", x4::attr(SES{1}) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } +} + +TEST_CASE("SES composition: left variant") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; + + // variant x int_ + { std::tuple a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // variant x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(alloy::get<0>(a).value == 1); CHECK(alloy::get<1>(a) == 2); } + // variant x attr(SES) + { std::tuple a; REQUIRE(parse("1", (int_ | char_) >> x4::attr(SES{2}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // variant x variant + { std::tuple a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); } + // variant x variant → (int, SES) + { std::tuple> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a).value == 2); } + // variant x alpha + { std::tuple a; REQUIRE(parse("1a", (int_ | char_) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 'a'); } + // variant x string("abc") + { std::tuple a; REQUIRE(parse("1abc", (int_ | char_) >> string("abc"), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } + // variant x +alpha + { std::tuple a; REQUIRE(parse("1abc", (int_ | char_) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == "abc"); } +} + +TEST_CASE("SES composition: left alpha") +{ + using x4::int_; + using x4::alpha; + using x4::alnum; + using x4::string; + using x4::standard::char_; + + // alpha x int_ + { std::tuple a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 42); } + // alpha x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a).value == 'a'); CHECK(alloy::get<1>(a) == 42); } + // alpha x int_ → (char, SES) + { std::tuple> a; REQUIRE(parse("a42", alpha >> int_, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).value == 42); } + // alpha x attr(SES) + { std::tuple a; REQUIRE(parse("a", alpha >> x4::attr(SES{2}), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 2); } + // alpha x variant + { std::tuple a; REQUIRE(parse("a1", alpha >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 1); } + // alpha x alpha + { std::tuple a; REQUIRE(parse("ab", alpha >> alpha, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 'b'); } + // alpha x alpha → (SES, SES) + { std::tuple, SES> a; REQUIRE(parse("ab", alpha >> alpha, a)); CHECK(alloy::get<0>(a).value == 'a'); CHECK(alloy::get<1>(a).value == 'b'); } + // alpha x string("abc") + { std::tuple a; REQUIRE(parse("xabc", alpha >> string("abc"), a)); CHECK(alloy::get<0>(a) == 'x'); CHECK(alloy::get<1>(a) == "abc"); } + // alpha x string("abc") → (char, SES) + { std::tuple> a; REQUIRE(parse("xabc", alpha >> string("abc"), a)); CHECK(alloy::get<0>(a) == 'x'); CHECK(alloy::get<1>(a).value == "abc"); } + // alpha x +alpha + { std::tuple a; REQUIRE(parse("ab", alpha >> +alpha, a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == "b"); } +} + +TEST_CASE("SES composition: left string") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; + + // string("abc") x int_ + { std::tuple a; REQUIRE(parse("abc42", string("abc") >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } + // string("abc") x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("abc42", string("abc") >> int_, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a) == 42); } + // string("abc") x attr(SES) + { std::tuple a; REQUIRE(parse("abc", string("abc") >> x4::attr(SES{2}), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 2); } + // string("abc") x variant + { std::tuple a; REQUIRE(parse("abc1", string("abc") >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); } + // string("abc") x alpha + { std::tuple a; REQUIRE(parse("abcd", string("abc") >> alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 'd'); } + // string("abc") x string("def") + { std::tuple a; REQUIRE(parse("abcdef", string("abc") >> string("def"), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } + // string("abc") x string("def") → (SES, SES) + { std::tuple, SES> a; REQUIRE(parse("abcdef", string("abc") >> string("def"), a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } + // string("abc") x +alpha + { std::tuple a; REQUIRE(parse("abcdef", string("abc") >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } +} + +TEST_CASE("SES composition: left +alpha") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; + + // +alpha x int_ + { std::tuple a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 42); } + // +alpha x int_ → (SES, int) + { std::tuple, int> a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a) == 42); } + // +alpha x int_ → (string, SES) + { std::tuple> a; REQUIRE(parse("abc42", +alpha >> int_, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a).value == 42); } + // +alpha x attr(SES) + { std::tuple a; REQUIRE(parse("abc", +alpha >> x4::attr(SES{2}), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 2); } + // +alpha x variant + { std::tuple a; REQUIRE(parse("abc1", +alpha >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); } + // +alpha x ',' >> alpha + { std::tuple a; REQUIRE(parse("abc,d", +alpha >> ',' >> alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 'd'); } + // +alpha x ',' >> +alpha + { std::tuple a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == "def"); } + // +alpha x ',' >> +alpha → (SES, SES) + { std::tuple, SES> a; REQUIRE(parse("abc,def", +alpha >> ',' >> +alpha, a)); CHECK(alloy::get<0>(a).value == "abc"); CHECK(alloy::get<1>(a).value == "def"); } +} + +TEST_CASE("SES composition: SES dest wrapping") +{ + using x4::int_; + using x4::alpha; + using x4::alnum; + using x4::string; + using x4::standard::char_; + + // --- SES dest wrapping sequence result (core fix) --- + // Exercises the parse_sequence unwrap path: + // is_single_element_tuple_like && has_attribute_v && has_attribute_v + + // SES: alpha >> *alnum → SES (= Ident pattern) + { SES a; REQUIRE(parse("abc", alpha >> *alnum, a)); CHECK(a.value == "abc"); } + // SES: int_ >> ',' >> int_ → SES + { SES a{}; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(a.value.a == 1); CHECK(a.value.b == 2); } + // SES: int_ >> ',' >> int_ → SES> + { SES> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a.value) == 1); CHECK(alloy::get<1>(a.value) == 2); } + // SES with variant parser + { SES a{}; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(a.value.a == 1); CHECK(a.value.b == 2); } + // SES: int_ >> ',' >> int_ → SES> (variant dest wrapping) + { SES> a; REQUIRE(parse("1,2", (int_ >> ',' >> int_) | (char_ >> ',' >> char_), a)); CHECK(iris::get(a.value).a == 1); CHECK(iris::get(a.value).b == 2); } +} + +TEST_CASE("SES composition: variant_attr dest") +{ + using x4::int_; + using x4::alpha; + using x4::string; + using x4::standard::char_; + + // int_ x int_ → (rvariant, int) + { std::tuple, int> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(iris::get(alloy::get<0>(a)) == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ x int_ → (int, rvariant) + { std::tuple> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(iris::get(alloy::get<1>(a)) == 2); } + // int_ x int_ → (SES>, int) + { std::tuple>, int> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(iris::get(alloy::get<0>(a).value) == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ x int_ → (int, SES>) + { std::tuple>> a; REQUIRE(parse("1,2", int_ >> ',' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(iris::get(alloy::get<1>(a).value) == 2); } + // variant x int_ → (rvariant, int) + { std::tuple, int> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> int_, a)); CHECK(iris::get(alloy::get<0>(a)) == 1); CHECK(alloy::get<1>(a) == 2); } + // int_ x variant → (int, rvariant) + { std::tuple> a; REQUIRE(parse("1,2", int_ >> ',' >> (int_ | char_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(iris::get(alloy::get<1>(a)) == 2); } + // variant x variant → (rvariant, rvariant) + { std::tuple, iris::rvariant> a; REQUIRE(parse("1,2", (int_ | char_) >> ',' >> (int_ | char_), a)); CHECK(iris::get(alloy::get<0>(a)) == 1); CHECK(iris::get(alloy::get<1>(a)) == 2); } + // alpha x +alpha → (rvariant, rvariant) + { std::tuple, iris::rvariant> a; REQUIRE(parse("ab", alpha >> +alpha, a)); CHECK(iris::get(alloy::get<0>(a)) == 'a'); CHECK(iris::get(alloy::get<1>(a)) == "b"); } +} + +TEST_CASE("SES composition: MET parsers") +{ + using x4::int_; + using x4::alpha; + using x4::alnum; + using x4::string; + using x4::standard::char_; + + // MET x int_ → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2:3", (int_ >> ',' >> int_) >> ':' >> int_, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); } + // MET x alpha → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2a", (int_ >> ',' >> int_) >> alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 'a'); } + // MET x +alpha → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2abc", (int_ >> ',' >> int_) >> +alpha, a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == "abc"); } + // MET x attr(SES) → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2", (int_ >> ',' >> int_) >> x4::attr(SES{99}), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 99); } + // int_ x MET → 3-slot tuple + { std::tuple a; REQUIRE(parse("1:2,3", int_ >> ':' >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); } + // alpha x MET → 3-slot tuple + { std::tuple a; REQUIRE(parse("a1,2", alpha >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } + // +alpha x MET → 3-slot tuple + { std::tuple a; REQUIRE(parse("abc1,2", +alpha >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == "abc"); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } + // attr(SES) x MET → 3-slot tuple + { std::tuple a; REQUIRE(parse("1,2", x4::attr(SES{99}) >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 99); CHECK(alloy::get<1>(a) == 1); CHECK(alloy::get<2>(a) == 2); } + // MET x MET → 4-slot tuple + { std::tuple a; REQUIRE(parse("1,2:3,4", (int_ >> ',' >> int_) >> ':' >> (int_ >> ',' >> int_), a)); CHECK(alloy::get<0>(a) == 1); CHECK(alloy::get<1>(a) == 2); CHECK(alloy::get<2>(a) == 3); CHECK(alloy::get<3>(a) == 4); } + // MET_variant x alpha → 2-slot tuple (alternative has sequence_size=1) + { std::tuple a; REQUIRE(parse("1,2a", ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)) >> alpha, a)); CHECK(alloy::get<0>(a).a == 1); CHECK(alloy::get<0>(a).b == 2); CHECK(alloy::get<1>(a) == 'a'); } + // alpha x MET_variant → 2-slot tuple + { std::tuple a; REQUIRE(parse("a1,2", alpha >> ((int_ >> ',' >> int_) | (char_ >> ',' >> char_)), a)); CHECK(alloy::get<0>(a) == 'a'); CHECK(alloy::get<1>(a).a == 1); CHECK(alloy::get<1>(a).b == 2); } + + // SES>: double unwrap through move_to + { + SES> a; + REQUIRE(parse("42", int_, a)); + CHECK(a.value.value == 42); + } + // SES>: double unwrap through parse_sequence (= Var pattern) + { + SES> a; + REQUIRE(parse("abc", alpha >> *alnum, a)); + CHECK(a.value.value == "abc"); + } +} + +// =================================================================== +// Narrowing conversion static_assert tests +// +// The following test cases are commented out because they are expected +// to produce static_assert failures. Uncomment any single case to +// verify the assertion fires. +// =================================================================== + +// --- move_to: source to plain (narrowing) --- +// TEST_CASE("narrowing: source to plain (char)") { char a{}; REQUIRE(parse("42", x4::int_, a)); } +// TEST_CASE("narrowing: source to plain (short)") { short a{}; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: single-element tuple-like to plain (narrowing) --- +// TEST_CASE("narrowing: single-element to plain") { SES a{}; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: source to optional (narrowing) --- +// TEST_CASE("narrowing: source to optional") { std::optional a; REQUIRE(parse("42", x4::int_, a)); } + +// --- move_to: tuple element-wise (narrowing) --- +// TEST_CASE("narrowing: tuple element-wise 1") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } +// TEST_CASE("narrowing: tuple element-wise 2") { std::tuple a; REQUIRE(parse("1,2", x4::int_ >> ',' >> x4::int_, a)); } diff --git a/test/x4/substitution.cpp b/test/x4/substitution.cpp index 5d8d24aac..28d16b0f7 100644 --- a/test/x4/substitution.cpp +++ b/test/x4/substitution.cpp @@ -7,6 +7,10 @@ #include +#include + +#include + template inline constexpr bool can_hold_v = x4::traits::can_hold::value; @@ -15,7 +19,7 @@ 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, std::tuple>); STATIC_CHECK(can_hold_v, iris::rvariant>); // `iris::rvariant` is "broader" than `int` @@ -26,5 +30,5 @@ TEST_CASE("can_hold") STATIC_CHECK(can_hold_v>, std::vector>); // tuple-like types - STATIC_CHECK(can_hold_v>, alloy::tuple>); + STATIC_CHECK(can_hold_v>, std::tuple>); } diff --git a/test/x4/uint.cpp b/test/x4/uint.cpp index d255fad24..330c9a03f 100644 --- a/test/x4/uint.cpp +++ b/test/x4/uint.cpp @@ -237,33 +237,33 @@ TEST_CASE("uint") } { x4::uint_parser u_int8_; - std::uint8_t u8 = 0; + std::int8_t i8 = 0; - CHECK(!parse("999", u_int8_, u8)); - CHECK(!parse("-1", u_int8_, u8)); - CHECK(!parse("128", u_int8_, u8)); - CHECK(parse("127", u_int8_, u8)); - CHECK(parse("0", u_int8_, u8)); + CHECK(!parse("999", u_int8_, i8)); + CHECK(!parse("-1", u_int8_, i8)); + CHECK(!parse("128", u_int8_, i8)); + CHECK(parse("127", u_int8_, i8)); + CHECK(parse("0", u_int8_, i8)); } { x4::uint_parser u_int16_; - std::uint16_t u16 = 0; + std::int16_t i16 = 0; - CHECK(!parse("99999", u_int16_, u16)); - CHECK(!parse("-1", u_int16_, u16)); - CHECK(!parse("32768", u_int16_, u16)); - CHECK(parse("32767", u_int16_, u16)); - CHECK(parse("0", u_int16_, u16)); + CHECK(!parse("99999", u_int16_, i16)); + CHECK(!parse("-1", u_int16_, i16)); + CHECK(!parse("32768", u_int16_, i16)); + CHECK(parse("32767", u_int16_, i16)); + CHECK(parse("0", u_int16_, i16)); } { x4::uint_parser u_int32_; - std::uint32_t u32 = 0; + std::int32_t i32 = 0; - CHECK(!parse("9999999999", u_int32_, u32)); - CHECK(!parse("-1", u_int32_, u32)); - CHECK(!parse("2147483648", u_int32_, u32)); - CHECK(parse("2147483647", u_int32_, u32)); - CHECK(parse("0", u_int32_, u32)); + CHECK(!parse("9999999999", u_int32_, i32)); + CHECK(!parse("-1", u_int32_, i32)); + CHECK(!parse("2147483648", u_int32_, i32)); + CHECK(parse("2147483647", u_int32_, i32)); + CHECK(parse("0", u_int32_, i32)); } // custom uint tests