/*============================================================================= Copyright (c) 2025 Nana Sakisaka 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 "test.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // NOLINTBEGIN(readability-container-size-empty) namespace { struct strong_int { int value = 0; int assigned_count = 0; strong_int() = default; strong_int(strong_int const&) = default; strong_int(strong_int&&) noexcept = default; explicit strong_int(int value) : value(value) {} strong_int& operator=(strong_int const& other) { value = other.value; ++assigned_count; return *this; } strong_int& operator=(strong_int&& other) noexcept { value = other.value; ++assigned_count; return *this; } strong_int& operator=(int new_value) { value = new_value; ++assigned_count; return *this; } bool operator==(strong_int const& other) const { return value == other.value; } friend std::ostream& operator<<(std::ostream& os, strong_int const& si) { return os << si.value; } }; } // anonymous TEST_CASE("attribute_alternative_hold") { using x4::attr; using x4::eps; using x4::omit; using x4::int_; using x4::string; using x4::lit; using x4::standard::char_; using x4::standard::space; // Sanity checks { int i = -1; REQUIRE(parse("", attr(42), i)); CHECK(i == 42); } { std::string str; REQUIRE(parse("", attr("foo"), str)); CHECK(str == "foo"); } { std::string str; REQUIRE(parse("foo", string("foo"), str)); CHECK(str == "foo"); } // Non-string container attribute // Related to: https://github.com/boostorg/spirit/issues/378 { static_assert(x4::traits::CategorizedAttr, x4::traits::container_attr>); static_assert(x4::traits::X4Container>); static_assert(x4::traits::is_container_v>); { std::vector ints; REQUIRE(parse("1 2", eps(false) | attr(98) >> attr(99), ints).is_partial_match()); CHECK(ints == std::vector{98, 99}); } { std::vector ints; REQUIRE(parse("1 2", int_ >> int_ >> eps(false) | attr(98) >> attr(99), space, ints).is_partial_match()); // If we don't properly "hold" the value on the failed branch of // `x4::alternative`, we would see {1, 2, 98, 99} here. CHECK(ints == std::vector{98, 99}); } // Failed parse should not modify the exposed attribute { std::vector ints; REQUIRE(!parse("1 2", int_ >> int_ >> eps(false) | attr(98) >> attr(99) >> eps(false), space, ints)); // Wrong implementation yields {1, 2, 98, 99} or {98, 99} CHECK(ints == std::vector{}); } { std::vector ints; REQUIRE(parse("1 2", attr(std::vector{3, 4}) >> eps(false) | attr(98) >> attr(99), space, ints).is_partial_match()); // Wrong implementation yields {3, 4, 98, 99} CHECK(ints == std::vector{98, 99}); } } // String container attribute // Intended for testing `detail::string_parse` { static_assert(x4::traits::CategorizedAttr); static_assert(x4::traits::X4Container); static_assert(x4::traits::is_container_v); { std::string str; REQUIRE(parse("foodie", "fox" | string("foodie"), str)); CHECK(str == "foodie"); } { constexpr auto fox = char_('f') >> char_('o') >> char_('x'); std::string str; REQUIRE(parse("foodie", fox | string("foodie"), str)); // If we don't properly "hold" the value on the failed branch of // `x4::alternative`, we would see "fofoodie" here. CHECK(str == "foodie"); } { constexpr auto foo = char_('f') >> char_('o') >> char_('o'); std::string str; REQUIRE(parse("foodie", foo >> eps(false) | string("foodie"), str)); // Wrong implementation yields "foofoodie" CHECK(str == "foodie"); } { std::string str; REQUIRE(parse("foodie", attr("bookworm") >> eps(false) | string("foodie"), str)); // Wrong implementation yields "bookwormfoodie" CHECK(str == "foodie"); } // Failed parse should not modify the exposed attribute { std::string str; REQUIRE(!parse("foodie", attr("bookworm") >> eps(false) | string("foodie") >> eps(false), str)); // Wrong implementation yields "bookwormfoodie" or "foodie" CHECK(str == ""); } { std::string str; REQUIRE(parse("foodie", string("food") >> "fan" | string("foodie"), str)); // Wrong implementation yields "foodfoodie" CHECK(str == "foodie"); } } // Plain attribute { static_assert(x4::traits::CategorizedAttr); static_assert(!x4::traits::X4Container); static_assert(!x4::traits::is_container_v); { strong_int si; REQUIRE(parse("1", int_ | attr(strong_int{9}), si)); CHECK(si == strong_int{1}); CHECK(si.assigned_count == 1); } { strong_int si; REQUIRE(parse("1", int_ >> eps(false) | int_, si)); CHECK(si == strong_int{1}); // Wrong implementation yields 2, because `x4::alternative` wrongly mutates the exposed variable CHECK(si.assigned_count == 1); } } // Tuple attribute { using pair_int = std::pair; static_assert(x4::traits::CategorizedAttr); static_assert(!x4::traits::X4Container); static_assert(!x4::traits::is_container_v); { pair_int pi; REQUIRE(parse("1 2", int_ >> int_ | attr(pair_int{98, 99}), space, pi)); CHECK(pi == pair_int{1, 2}); } { pair_int pi; REQUIRE(parse("1 2", int_ >> int_ >> eps(false) | attr(pair_int{98, 99}) >> omit[int_ >> int_], space, pi )); CHECK(pi == pair_int{98, 99}); } } } // NOLINTEND(readability-container-size-empty)