/*============================================================================= Copyright (c) 2001-2017 Joel de Guzman Copyright (c) 2017 think-cell GmbH 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) =============================================================================*/ #define BOOST_SPIRIT_X4_UNICODE #include "test.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::string_view_literals; // On failed parse, // - The `first` iterator shall point to the previous position, and // - The attribute shall not be modified. // - Best-effort; there are many exceptions as of now. In other words, // rollbacks may or may not occur depending on the situation. // - In practice, the current implementation only "holds" the attribute // only when it is semantically possible to preliminarily check the // parse success on the right hand side of the underlying parser. // NOLINTBEGIN(readability-container-size-empty) TEST_CASE("rollback on failed parse (numeric)") { using x4::int_; using x4::uint_; using x4::double_; { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -99; REQUIRE_FALSE(int_.parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -99); } { constexpr auto input = "-"sv; auto first = input.begin(); int dummy_int = -99; REQUIRE_FALSE(int_.parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -99); } { constexpr auto input = "-9999999999999999999999999999999999999"sv; // overflow auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(int_.parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "9999999999999999999999999999999999999"sv; // overflow auto first = input.begin(); unsigned dummy_uint = static_cast(-1); REQUIRE_FALSE(uint_.parse(first, input.end(), unused, dummy_uint)); CHECK(first == input.begin()); CHECK(dummy_uint == static_cast(-1)); } // TODO: https://github.com/boostorg/spirit_x4/issues/63 //{ // //constexpr double aaa = 9.9e999999999999999999999; // error // constexpr auto input = "9.9e999999999999999999999"sv; // overflow // auto first = input.begin(); // double dummy_double = 3.14; // REQUIRE_FALSE(double_.parse(first, input.end(), unused, dummy_double)); // CHECK(first == input.begin()); // CHECK(dummy_double == static_cast(3.14)); //} } TEST_CASE("rollback on failed parse (char)") { using x4::char_; using x4::lit; { constexpr auto input = U"\x00110000"sv; // not a valid Unicode code point auto first = input.begin(); char32_t dummy_char = U'd'; REQUIRE_FALSE(x4::unicode::char_.parse(first, input.end(), unused, dummy_char)); // NOLINT(readability-static-accessed-through-instance) CHECK(first == input.begin()); CHECK(dummy_char == U'd'); } { constexpr auto input = "x"sv; auto first = input.begin(); char dummy_char = 'd'; REQUIRE_FALSE(char_('a').parse(first, input.end(), unused, dummy_char)); CHECK(first == input.begin()); CHECK(dummy_char == 'd'); } { constexpr auto input = "1"sv; auto first = input.begin(); char dummy_char = 'd'; REQUIRE_FALSE(char_('a', 'z').parse(first, input.end(), unused, dummy_char)); CHECK(first == input.begin()); CHECK(dummy_char == 'd'); } { constexpr auto input = "1"sv; auto first = input.begin(); char dummy_char = 'd'; REQUIRE_FALSE(char_("a-z").parse(first, input.end(), unused, dummy_char)); CHECK(first == input.begin()); CHECK(dummy_char == 'd'); } { constexpr auto input = "1"sv; auto first = input.begin(); char dummy_char = 'd'; REQUIRE_FALSE((~char_).parse(first, input.end(), unused, dummy_char)); CHECK(first == input.begin()); CHECK(dummy_char == 'd'); } { constexpr auto input = "1"sv; auto first = input.begin(); char dummy_char = 'd'; REQUIRE_FALSE(lit('a').parse(first, input.end(), unused, dummy_char)); CHECK(first == input.begin()); CHECK(dummy_char == 'd'); } } TEST_CASE("rollback on failed parse (string)") { using x4::string; using x4::lit; using x4::unique_symbols; { constexpr auto input = "x"sv; auto first = input.begin(); REQUIRE_FALSE(lit("foo").parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "x"sv; auto first = input.begin(); std::string dummy_string = "dummy"; REQUIRE_FALSE(string("foo").parse(first, input.end(), unused, dummy_string)); CHECK(first == input.begin()); CHECK(dummy_string == "dummy"); } { unique_symbols syms{{"foo", 0}, {"bar", 1}}; constexpr auto input = "baz"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(syms.parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } } TEST_CASE("rollback on failed parse (action)") { using x4::eps; using x4::int_; using x4::_pass; { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(eps[([](auto&&) { return false; })].parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "42"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(int_[([](auto&&) { return false; })].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == 42); // sequence parser itself succeeds; always results in side effect } { constexpr auto input = "42,43"sv; auto first = input.begin(); std::vector dummy_ints; REQUIRE_FALSE((int_ >> ',' >> int_)[([](auto&&) { return false; })].parse(first, input.end(), unused, dummy_ints)); CHECK(first == input.begin()); CHECK(dummy_ints == std::vector{42, 43}); // sequence parser itself succeeds; always results in side effect } } TEST_CASE("rollback on failed parse (auxiliary)") { using x4::attr; using x4::eps; using x4::eoi; using x4::eol; { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE((attr(42) >> eps(false)).parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == 42); // sequence parser has side effect because attribute is not a container } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(eoi.parse(first, input.end(), unused, unused)); } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(eol.parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(eps(false).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(eps([]{ return false; }).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } } TEST_CASE("rollback on failed parse (directive)") { using It = std::string_view::const_iterator; using x4::int_; using x4::true_; using x4::eps; using x4::standard::space; using x4::expect; using x4::lexeme; using x4::matches; using x4::no_case; using x4::no_skip; using x4::omit; using x4::raw; using x4::repeat; using x4::seek; using x4::skip; using x4::reskip; using x4::with; { constexpr auto input = "foo"sv; auto first = input.begin(); x4::expectation_failure failure; auto const ctx = x4::make_context(failure); REQUIRE_FALSE(expect[eps(false)].parse(first, input.end(), ctx, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); x4::expectation_failure failure; auto const ctx = x4::make_context(failure); int dummy_int = -1; REQUIRE_FALSE(expect[int_].parse(first, input.end(), ctx, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "42!"sv; auto first = input.begin(); x4::expectation_failure failure; auto const ctx = x4::make_context(failure); int dummy_int = -1; REQUIRE_FALSE((int_ >> expect['i']).parse(first, input.end(), ctx, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == 42); // sequence parser has side effect because attribute is not a container } { constexpr auto input = "42"sv; auto first = input.begin(); x4::expectation_failure failure; auto const ctx = x4::make_context(failure); std::vector dummy_ints; REQUIRE_FALSE((int_ >> expect[','] >> int_).parse(first, input.end(), ctx, dummy_ints)); CHECK(first == input.begin()); CHECK(dummy_ints == std::vector{}); // sequence parser has NO side effect because attribute is a container } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(lexeme[eps(false)].parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "42"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(lexeme[int_ >> eps(false)].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == 42); // sequence parser has side effect because attribute is not a container } { constexpr auto input = "42"sv; auto first = input.begin(); std::vector dummy_ints; REQUIRE_FALSE(lexeme[int_ >> ',' >> int_].parse(first, input.end(), unused, dummy_ints)); CHECK(first == input.begin()); CHECK(dummy_ints == std::vector{}); // sequence parser has NO side effect because attribute is a container } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE(matches[eps(false)].parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "42"sv; auto first = input.begin(); bool dummy_bool = false; REQUIRE(matches[int_].parse(first, input.end(), unused, dummy_bool)); CHECK(first == input.end()); CHECK(dummy_bool == true); } { constexpr auto input = "foo"sv; auto first = input.begin(); bool dummy_bool = true; REQUIRE(matches[int_].parse(first, input.end(), unused, dummy_bool)); CHECK(first == input.begin()); CHECK(dummy_bool == false); } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(no_case[eps(false)].parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(no_case[int_].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "42"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(no_case[int_ >> eps(false)].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == 42); // sequence parser has side effect because attribute is not a container } { constexpr auto input = "42"sv; auto first = input.begin(); std::vector dummy_ints; REQUIRE_FALSE(no_case[int_ >> ',' >> int_].parse(first, input.end(), unused, dummy_ints)); CHECK(first == input.begin()); CHECK(dummy_ints == std::vector{}); // sequence parser has NO side effect because attribute is a container } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(no_skip[eps(false)].parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(no_skip[int_].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "42"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(no_skip[int_ >> eps(false)].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == 42); // sequence parser has side effect because attribute is not a container } { constexpr auto input = "42"sv; auto first = input.begin(); std::vector dummy_ints; REQUIRE_FALSE(no_skip[int_ >> ',' >> int_].parse(first, input.end(), unused, dummy_ints)); CHECK(first == input.begin()); // sequence parser has NO side effect because attribute is not a container } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(omit[eps(false)].parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(omit[int_].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "42"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(omit[int_ >> eps(false)].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); // `omit` never yields an attribute } { constexpr auto input = "foo"sv; auto first = input.begin(); std::ranges::subrange dummy_subrange{input.end(), input.end()}; REQUIRE_FALSE(raw[eps(false)].parse(first, input.end(), unused, dummy_subrange)); CHECK(first == input.begin()); CHECK(dummy_subrange.begin() == input.end()); CHECK(dummy_subrange.end() == input.end()); } { constexpr auto input = "foo"sv; auto first = input.begin(); std::ranges::subrange dummy_subrange{input.end(), input.end()}; REQUIRE_FALSE(raw[int_].parse(first, input.end(), unused, dummy_subrange)); CHECK(first == input.begin()); CHECK(dummy_subrange.begin() == input.end()); CHECK(dummy_subrange.end() == input.end()); } { constexpr auto input = "42"sv; auto first = input.begin(); std::ranges::subrange dummy_subrange{input.end(), input.end()}; REQUIRE_FALSE(raw[int_ >> eps(false)].parse(first, input.end(), unused, dummy_subrange)); CHECK(first == input.begin()); CHECK(dummy_subrange.begin() == input.end()); CHECK(dummy_subrange.end() == input.end()); } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(repeat(1)[eps(false)].parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "42"sv; auto first = input.begin(); std::vector dummy_ints; REQUIRE_FALSE(repeat(1)[int_ >> eps(false)].parse(first, input.end(), unused, dummy_ints)); CHECK(first == input.begin()); CHECK(dummy_ints == std::vector{}); } { constexpr auto input = "true123"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE(repeat(1)[true_ >> true_].parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); CHECK(dummy_bools == std::vector{true}); // sequence parser has side effect } { constexpr auto input = "true123"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE(repeat(2)[true_].parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); CHECK(dummy_bools == std::vector{true}); // sequence parser has side effect } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(seek[eps(false)].parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(seek[int_].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "42"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(seek[int_ >> eps(false)].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == 2); // `seek` has side effect } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(skip(space)[eps(false)].parse(first, input.end(), x4::make_context(space), unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(skip(space)[int_].parse(first, input.end(), x4::make_context(space), dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(skip(space)[int_ >> eps(false)].parse(first, input.end(), x4::make_context(space), dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "42 foo"sv; auto first = input.begin(); std::vector dummy_ints; REQUIRE_FALSE(skip(space)[int_ >> int_].parse(first, input.end(), x4::make_context(space), dummy_ints)); CHECK(first == input.begin()); CHECK(dummy_ints == std::vector{42}); // sequence parser has side effect } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(reskip[eps(false)].parse(first, input.end(), x4::make_context(space), unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(reskip[int_].parse(first, input.end(), x4::make_context(space), dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(reskip[int_ >> eps(false)].parse(first, input.end(), x4::make_context(space), dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "42 foo"sv; auto first = input.begin(); std::vector dummy_ints; REQUIRE_FALSE(reskip[int_ >> int_].parse(first, input.end(), x4::make_context(space), dummy_ints)); CHECK(first == input.begin()); CHECK(dummy_ints == std::vector{42}); // sequence parser has side effect } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE(with(input)[eps(false)].parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(with(input)[int_].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "42"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE(with(input)[int_ >> eps(false)].parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == 42); // sequence parser has side effect } } TEST_CASE("rollback on failed parse (operator)") { using x4::int_; using x4::true_; using x4::false_; using x4::eps; // `sequence` should be tested first because it is the requirement for subsequent test cases { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE((eps >> eps(false)).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "truefalse"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE((true_ >> true_).parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); CHECK(dummy_bools == std::vector{true}); // `sequence` parser exposes the side effects } // ----------------------------------------------- { constexpr auto input = "42"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE((int_ >> eps(false)).parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == 42); // sequence parser has side effect because attribute is not a container } { constexpr auto input = "42,43"sv; auto first = input.begin(); std::vector dummy_ints; REQUIRE_FALSE((int_ >> eps(false) >> int_).parse(first, input.end(), unused, dummy_ints)); CHECK(first == input.begin()); CHECK(dummy_ints == std::vector{}); // sequence parser has NO side effect because LHS is `sequence`, which does not move the result until `eps(false)` is evaluated } { constexpr auto input = "42,43"sv; auto first = input.begin(); std::vector dummy_ints; REQUIRE_FALSE((int_ >> (eps(false) >> int_)).parse(first, input.end(), unused, dummy_ints)); CHECK(first == input.begin()); CHECK(dummy_ints == std::vector{42}); // sequence parser has side effect, in contrast to above } // NOLINTBEGIN(misc-redundant-expression) { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE((eps(false) | eps(false)).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE((eps(false) | int_).parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "42"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE((eps(false) | int_ >> eps(false)).parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); // `alternative` parser shall not expose the side effects on the failed branch } { constexpr auto input = "true"sv; auto first = input.begin(); bool dummy_bool = true; REQUIRE_FALSE((false_ | false_).parse(first, input.end(), unused, dummy_bool)); CHECK(first == input.begin()); CHECK(dummy_bool == true); } { constexpr auto input = "true"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE((true_ >> false_ | true_ >> false_).parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); CHECK(dummy_bools == std::vector{}); } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE((eps - eps).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE((int_ - eps).parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); } { constexpr auto input = "42"sv; auto first = input.begin(); int dummy_int = -1; REQUIRE_FALSE((int_ - eps).parse(first, input.end(), unused, dummy_int)); CHECK(first == input.begin()); CHECK(dummy_int == -1); // `difference` parser shall not expose the side effects } { constexpr auto input = "truefalse"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE(((true_ >> true_) - eps).parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); CHECK(dummy_bools == std::vector{}); // `difference` parser shall not expose the side effects } { constexpr auto input = "truetrue"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE(((true_ >> true_) - eps).parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); CHECK(dummy_bools == std::vector{}); // `difference` parser shall not expose the side effects } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE((*eps(false) >> eps(false)).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "truefalse"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE((*true_ >> eps(false)).parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); CHECK(dummy_bools == std::vector{true}); // `kleene` parser (within sequence) exposes the side effects } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE((+eps(false)).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "truefalse"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE((+true_ >> eps(false)).parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); CHECK(dummy_bools == std::vector{true}); // `plus` parser (within sequence) exposes the side effects } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE((eps(false) % eps(false)).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "false"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE((true_ % eps(false)).parse(first, input.end(), unused, dummy_bools)); // initial unmatch shall result in total parse failure CHECK(first == input.begin()); } { constexpr auto input = "false"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE((true_ % eps(true)).parse(first, input.end(), unused, dummy_bools)); // initial unmatch shall result in total parse failure (even if the delimiter parse succeeds) CHECK(first == input.begin()); } { constexpr auto input = "truefalse"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE((true_ % eps(false) >> eps(false)).parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); CHECK(dummy_bools == std::vector{true});// `list` parser (within sequence) exposes the side effects } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE((&eps(false)).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "true"sv; auto first = input.begin(); REQUIRE_FALSE((&false_).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "truefalse"sv; auto first = input.begin(); REQUIRE_FALSE((&(true_ >> true_)).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE((!eps).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "true"sv; auto first = input.begin(); REQUIRE_FALSE((!true_).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "truetrue"sv; auto first = input.begin(); REQUIRE_FALSE((!(true_ >> true_)).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "foo"sv; auto first = input.begin(); REQUIRE_FALSE((-eps >> eps(false)).parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "false"sv; auto first = input.begin(); bool dummy_bool = false; REQUIRE_FALSE((-true_ >> eps(false)).parse(first, input.end(), unused, dummy_bool)); CHECK(first == input.begin()); CHECK(dummy_bool == false); } { constexpr auto input = "true"sv; auto first = input.begin(); bool dummy_bool = false; REQUIRE_FALSE((-true_ >> eps(false)).parse(first, input.end(), unused, dummy_bool)); CHECK(first == input.begin()); CHECK(dummy_bool == true); } { constexpr auto input = "false"sv; auto first = input.begin(); std::optional dummy_optional_bool = false; REQUIRE_FALSE((-true_ >> eps(false)).parse(first, input.end(), unused, dummy_optional_bool)); CHECK(first == input.begin()); REQUIRE(dummy_optional_bool.has_value()); CHECK(*dummy_optional_bool == false); } { constexpr auto input = "true"sv; auto first = input.begin(); std::optional dummy_optional_bool = false; REQUIRE_FALSE((-true_ >> eps(false)).parse(first, input.end(), unused, dummy_optional_bool)); CHECK(first == input.begin()); REQUIRE(dummy_optional_bool.has_value()); CHECK(*dummy_optional_bool == true); } { constexpr auto input = "truefalse"sv; auto first = input.begin(); std::vector dummy_bools; REQUIRE_FALSE((-(true_ >> true_) >> eps(false)).parse(first, input.end(), unused, dummy_bools)); CHECK(first == input.begin()); CHECK(dummy_bools == std::vector{true}); } { constexpr auto input = "truefalse"sv; auto first = input.begin(); std::optional> dummy_optional_bools; REQUIRE_FALSE((-(true_ >> true_) >> eps(false)).parse(first, input.end(), unused, dummy_optional_bools)); CHECK(first == input.begin()); CHECK(dummy_optional_bools.has_value() == false); // `optional` parser for `optional` shall not expose the side effects } // NOLINTEND(misc-redundant-expression) } TEST_CASE("rollback on failed parse (rule)") { using x4::lit; { constexpr auto input = "ab"sv; auto first = input.begin(); constexpr x4::rule r("r"); constexpr auto p = r = lit('a') >> r; REQUIRE_FALSE(p.parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } { constexpr auto input = "ab"sv; auto first = input.begin(); constexpr x4::rule r("r"); constexpr auto p = r %= lit('a') >> r; REQUIRE_FALSE(p.parse(first, input.end(), unused, unused)); CHECK(first == input.begin()); } } TEST_CASE("transform iterator") { using x4::raw; using x4::eps; using x4::eoi; using x4::standard::upper; using x4::repeat; std::string input = "abcde"; auto const rng = input | std::views::transform([](char c) { return c < 'a' || 'z' < c ? c : static_cast(c - 'a' + 'A'); }); using range_type = decltype(rng); static_assert(std::ranges::forward_range); { std::string str; REQUIRE(parse(std::ranges::begin(rng), std::ranges::end(rng), +upper >> eoi, str)); CHECK("ABCDE" == str); } { std::ranges::subrange> str; REQUIRE(parse(std::ranges::begin(rng), std::ranges::end(rng), raw[+upper >> eoi], str)); CHECK(std::ranges::equal(std::string("ABCDE"), str)); } CHECK(parse(std::ranges::begin(rng), std::ranges::end(rng), (repeat(6)[upper] | repeat(5)[upper]) >> eoi)); } // NOLINTEND(readability-container-size-empty)