Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
a653da4
Rename and drop aliases
yaito3014 Mar 7, 2026
6b0c90f
Add test
yaito3014 Mar 7, 2026
f4430ee
Move single element tuple like traits
yaito3014 Mar 7, 2026
cfe7cb6
Unwrap single element tuple like in `string_parse`
yaito3014 Mar 7, 2026
6a17b00
Add test
yaito3014 Mar 7, 2026
a75a454
Fix test
yaito3014 Mar 7, 2026
2aa10ab
Unwrap single element tuple recursively
yaito3014 Mar 7, 2026
9e240ee
Add special handling for rule
yaito3014 Mar 7, 2026
bc08648
Fix
yaito3014 Mar 7, 2026
1461752
Revise single element tuple like type handling
yaito3014 Mar 8, 2026
765fe91
Add tests
yaito3014 Mar 8, 2026
d333ca9
Suppress MSVC warning
yaito3014 Mar 8, 2026
41e0f0c
Split test case
yaito3014 Mar 8, 2026
c022926
Reduce stack usage
yaito3014 Mar 8, 2026
f05a9e6
Add newline at end
yaito3014 Mar 8, 2026
d1bcdaf
Unwrap single element tuple like once in rule parser
yaito3014 Mar 8, 2026
114486d
Fix indentation
yaito3014 Mar 8, 2026
59c5661
Use remove_cvref_t consistently
yaito3014 Mar 8, 2026
7ff873e
Remove deprecated overload of `move_to`
yaito3014 Mar 8, 2026
acd02df
Add notes for AI generated content
saki7 Mar 9, 2026
fa0cb78
Remove unneeded branch from `pass_sequence_attribute`
yaito3014 Mar 10, 2026
38fb7fc
Add comment and adjust noexcept
yaito3014 Mar 10, 2026
f8401c3
Reorganize includes
yaito3014 Mar 11, 2026
0e7dc8c
Styling fix
yaito3014 Mar 11, 2026
e11c5a7
Remove unused forward declaration
yaito3014 Mar 11, 2026
14eea5f
Remove `pass` from `partition_attribute`
yaito3014 Mar 11, 2026
e05a682
Remove unneeded metafunctions
yaito3014 Mar 11, 2026
308f9d8
Use metafunction to simplify constexpr if
yaito3014 Mar 11, 2026
2a11631
Remove unused include
yaito3014 Mar 11, 2026
f15176e
Rename `SET` to `SES`
yaito3014 Mar 11, 2026
ebc71c2
Rename to clearer name and resolve noexcept issue
yaito3014 Mar 11, 2026
42627b0
Fix comment
yaito3014 Mar 11, 2026
5a3b28e
Add variant case
yaito3014 Mar 11, 2026
2b5d184
Use `is_convertible_without_narrowing`
yaito3014 Mar 11, 2026
bf81ed2
Resolve several issues
yaito3014 Mar 11, 2026
3f80fe5
Pass original attribute to `on_success`
yaito3014 Mar 11, 2026
ff1886a
Add static_assertion that checks narrowing assignment
yaito3014 Mar 11, 2026
15d3f80
Merge branch 'main' into fix-single-element-tuple-like-handling
yaito3014 Mar 12, 2026
ae16031
Refine static_assert of `partition_attribute`
yaito3014 Mar 12, 2026
34261db
Minor styling fix
yaito3014 Mar 12, 2026
ab7b0ef
Use more accurate type to detect narrowing
yaito3014 Mar 12, 2026
7567791
Add narrowing check test
yaito3014 Mar 12, 2026
2480d49
Change `can_hold` primary definition to `is_assignable`
yaito3014 Mar 12, 2026
483439d
Update iris
yaito3014 Mar 12, 2026
00c88bc
Update iris
yaito3014 Mar 12, 2026
80ce6f0
Move `is_assignable_without_narrowing` into submodule
yaito3014 Mar 12, 2026
a3dcecc
Revert "Move `is_assignable_without_narrowing` into submodule"
yaito3014 Mar 12, 2026
4b5c385
Update iris
yaito3014 Mar 12, 2026
6fc643f
Rename
yaito3014 Mar 12, 2026
511efa6
Introduce `is_lossy_assignment` customization point and rename narrow…
yaito3014 Mar 13, 2026
8d4c51d
Adjust comment styling
yaito3014 Mar 13, 2026
ba098db
Reorder and add tests
yaito3014 Mar 13, 2026
f70cb12
Remove redundant static_assert
yaito3014 Mar 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/iris/x4/core/detail/parse_alternative.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ struct pass_non_variant_attribute

// Unwrap single element sequences
template<class Parser, X4Attribute Attr>
requires traits::is_size_one_sequence_v<Attr>
requires traits::is_single_element_tuple_like<Attr>::value
struct pass_non_variant_attribute<Parser, Attr>
{
using attr_type = typename std::remove_reference_t<
Expand Down
2 changes: 1 addition & 1 deletion include/iris/x4/core/detail/parse_into_container.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ struct parse_into_container_impl_default
}

} else {
if constexpr (traits::is_size_one_sequence_v<unwrapped_attribute_type>) {
if constexpr (traits::is_single_element_tuple_like<unwrapped_attribute_type>::value) {
// attribute is single element tuple-like; unwrap and try again
return parse_into_container_impl_default<Parser>::call(parser, first, last, ctx, alloy::get<0>(unwrapped_attr));
} else {
Expand Down
226 changes: 81 additions & 145 deletions include/iris/x4/core/detail/parse_sequence.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <iris/x4/core/detail/parse_into_container.hpp>

#include <iris/x4/traits/attribute_category.hpp>
#include <iris/x4/traits/attribute_of_binary.hpp>
#include <iris/x4/traits/container_traits.hpp>
#include <iris/x4/traits/tuple_traits.hpp>
#include <iris/x4/traits/can_hold.hpp>
Expand All @@ -37,64 +38,12 @@ struct sequence;

namespace iris::x4::detail {

struct pass_sequence_attribute_unused
{
using type = unused_type;

template<class T>
[[nodiscard]] static constexpr unused_type
call(T&) noexcept
{
return unused_type{};
}
};

template<class Attr>
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<class Attr>
struct pass_through_sequence_attribute
{
using type = Attr&;

template<class Attr_>
[[nodiscard]] static constexpr Attr_&
call(Attr_& attribute) noexcept
{
return attribute;
}
};

template<class Parser, class Attr>
struct pass_sequence_attribute : std::conditional_t<
traits::is_size_one_view_v<Attr>,
pass_sequence_attribute_size_one_view<Attr>,
pass_through_sequence_attribute<Attr>
// Note: `ExpectedAttr` parameter exists for a better debugging experience
template<
class LParser, class RParser, traits::CategorizedAttr<traits::tuple_attr> ActualAttr,
class ExpectedAttr = typename traits::detail::attribute_of_sequence<LParser, RParser>::type
>
{};

template<class LParser, class RParser, class Attr>
struct pass_sequence_attribute<sequence<LParser, RParser>, Attr>
: pass_through_sequence_attribute<Attr>
{};

template<class Parser, class Attr>
requires requires {
typename Parser::proxy_backend_type;
}
struct pass_sequence_attribute<Parser, Attr>
: pass_sequence_attribute<typename Parser::proxy_backend_type, Attr>
{};
inline constexpr bool has_same_sequence_size_v = parser_traits<LParser>::sequence_size + parser_traits<RParser>::sequence_size == alloy::tuple_size_v<ActualAttr>;

template<class LParser, class RParser, class Attr>
struct partition_attribute {};
Expand All @@ -108,38 +57,26 @@ struct partition_attribute<LParser, RParser, Attr>
static constexpr std::size_t l_size = parser_traits<LParser>::sequence_size;
static constexpr std::size_t r_size = parser_traits<RParser>::sequence_size;

static constexpr std::size_t actual_size = alloy::tuple_size_v<Attr>;
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<LParser, RParser, Attr>, "sequence size mismatch");

using view = alloy::tuple_ref_t<Attr>;
using splitted = alloy::tuple_split_t<view, l_size, r_size>;
using l_part = alloy::tuple_element_t<0, splitted>;
using r_part = alloy::tuple_element_t<1, splitted>;
using l_pass = pass_sequence_attribute<LParser, l_part>;
using r_pass = pass_sequence_attribute<RParser, r_part>;
using split = alloy::tuple_split_t<view, l_size, r_size>;
using l_part = traits::unwrap_if_single_element_tuple_like<alloy::tuple_element_t<0, split>>::type;
using r_part = traits::unwrap_if_single_element_tuple_like<alloy::tuple_element_t<1, split>>::type;

[[nodiscard]] static constexpr l_part left(Attr& s)
// TODO: noexcept
{
return alloy::get<0>(alloy::tuple_split<l_size, r_size>(alloy::tuple_ref(s)));
return traits::do_unwrap_if_single_element_tuple_like(alloy::get<0>(alloy::tuple_split<l_size, r_size>(alloy::tuple_ref(s))));
}

[[nodiscard]] static constexpr r_part right(Attr& s)
// TODO: noexcept
{
return alloy::get<1>(alloy::tuple_split<l_size, r_size>(alloy::tuple_ref(s)));
return traits::do_unwrap_if_single_element_tuple_like(alloy::get<1>(alloy::tuple_split<l_size, r_size>(alloy::tuple_ref(s))));
}
};

Expand All @@ -149,9 +86,6 @@ template<class LParser, class RParser, class Attr>
has_attribute_v<RParser>
struct partition_attribute<LParser, RParser, Attr>
{
using l_pass = pass_sequence_attribute_unused;
using r_pass = pass_sequence_attribute<RParser, Attr>;

[[nodiscard]] static constexpr unused_type left(Attr&) noexcept
{
return unused;
Expand All @@ -169,9 +103,6 @@ template<class LParser, class RParser, class Attr>
(!has_attribute_v<RParser>)
struct partition_attribute<LParser, RParser, Attr>
{
using l_pass = pass_sequence_attribute<LParser, Attr>;
using r_pass = pass_sequence_attribute_unused;

[[nodiscard]] static constexpr Attr& left(Attr& s) noexcept
{
return s;
Expand All @@ -189,9 +120,6 @@ template<class LParser, class RParser, class Attr>
(!has_attribute_v<RParser>)
struct partition_attribute<LParser, RParser, Attr>
{
using l_pass = pass_sequence_attribute_unused;
using r_pass = pass_sequence_attribute_unused;

[[nodiscard]] static constexpr unused_type left(Attr&) noexcept
{
return unused;
Expand All @@ -203,39 +131,6 @@ struct partition_attribute<LParser, RParser, Attr>
}
};

// Default overload, no constraints on attribute category
template<class Parser, std::forward_iterator It, std::sentinel_for<It> 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<Attr>);

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<class Parser, std::forward_iterator It, std::sentinel_for<It> Se, class Context, X4Attribute Attr>
requires (parser_traits<Parser>::sequence_size > 1)
[[nodiscard]] constexpr bool
Expand All @@ -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<It> Se, class Context,
traits::CategorizedAttr<traits::container_attr> ContainerAttr
>
template<class Parser, std::forward_iterator It, std::sentinel_for<It> 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<It> &&
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<Attr>);

if constexpr (traits::is_container_v<Attr>) {
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<Attr>::value &&
has_attribute_v<typename Parser::left_type> &&
has_attribute_v<typename Parser::right_type>
) {
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<char>, container_value = char → true (optional may be empty; needs appender)
// e.g. sequence_attr = optional<char>, container_value = optional<char> → false (same type, direct path OK)
template<class Sequence, class Attr>
struct sequence_needs_appender : std::false_type {};

template<class Sequence, traits::X4Container Container>
struct sequence_needs_appender<Sequence, Container>
: std::negation<traits::can_hold<
typename traits::container_value<Container>::type,
typename Sequence::attribute_type
>>
{};

template<class Left, class Right>
struct parse_into_container_impl<sequence<Left, Right>>
{
Expand All @@ -287,18 +232,9 @@ struct parse_into_container_impl<sequence<Left, Right>>
Context const& ctx, Attr& attr
) // never noexcept (requires container insertion)
{
if constexpr (traits::is_container_v<Attr>) {
constexpr bool sequence_attribute_can_directly_hold_value_type = traits::can_hold<
typename sequence<Left, Right>::attribute_type,
typename traits::container_value<Attr>::type
>::value;
if constexpr (sequence_attribute_can_directly_hold_value_type) {
return parse_into_container_impl_default<sequence<Left, Right>>::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<sequence<Left, Right>, 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<sequence<Left, Right>>::call(parser, first, last, ctx, attr);
}
Expand Down
35 changes: 3 additions & 32 deletions include/iris/x4/core/list_like_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@
#include <iris/rvariant/rvariant.hpp>
#include <iris/rvariant/variant_helper.hpp>

#include <iris/alloy/tuple.hpp>
#include <iris/alloy/traits.hpp>

#include <type_traits>

namespace iris::x4 {

namespace list_like_parser {
Expand All @@ -29,7 +26,7 @@ template<X4NonUnusedAttribute ParserAttr, X4NonUnusedAttribute ExposedAttr>
// 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<ExposedAttr>::type
>
Expand All @@ -52,32 +49,6 @@ struct chunk_buffer_impl
static_assert(traits::X4Container<typename unwrap_container_candidate<ParserAttr, ExposedAttr>::type>);
};

template<class T>
[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept
{
return std::forward<T>(value);
}

template<class T>
requires traits::is_size_one_sequence_v<std::remove_cvref_t<T>>
[[nodiscard]] constexpr auto&& unwrap_single_element(T&& value) noexcept
{
return std::forward_like<T>(alloy::get<0>(std::forward<T>(value)));
}

template<class T>
struct unwrap_single_element_plain
{
using type = std::remove_cvref_t<T>;
};

template<class T>
requires traits::is_size_one_sequence_v<std::remove_cvref_t<T>>
struct unwrap_single_element_plain<T>
{
using type = std::remove_cvref_t<alloy::tuple_element_t<0, T>>;
};

} // detail


Expand All @@ -88,10 +59,10 @@ using chunk_buffer = detail::chunk_buffer_impl<ParserAttr, ExposedAttr>::type;
template<X4NonUnusedAttribute ParserAttr, X4NonUnusedAttribute ExposedAttr>
[[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<ExposedAttr>
>::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<unwrapped_attr_type>) {
using container_alternative = traits::variant_find_holdable_type<
Expand Down
Loading
Loading