From 82fdbace4f114aebb10dad99cb0035d2f1e9864a Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 3 Aug 2026 14:49:26 +0900 Subject: [PATCH 01/11] Remove `seek` directive --- include/iris/x4/directive.hpp | 1 - include/iris/x4/directive/seek.hpp | 74 --------------------------- test/x4/CMakeLists.txt | 1 - test/x4/expect.cpp | 13 ----- test/x4/iterator.cpp | 25 --------- test/x4/seek.cpp | 81 ------------------------------ 6 files changed, 195 deletions(-) delete mode 100644 include/iris/x4/directive/seek.hpp delete mode 100644 test/x4/seek.cpp diff --git a/include/iris/x4/directive.hpp b/include/iris/x4/directive.hpp index 898383ef1..fed0c8442 100644 --- a/include/iris/x4/directive.hpp +++ b/include/iris/x4/directive.hpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include diff --git a/include/iris/x4/directive/seek.hpp b/include/iris/x4/directive/seek.hpp deleted file mode 100644 index 7d98ce4ff..000000000 --- a/include/iris/x4/directive/seek.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef IRIS_ZZ_X4_DIRECTIVE_SEEK_HPP -#define IRIS_ZZ_X4_DIRECTIVE_SEEK_HPP - -/*============================================================================= - Copyright (c) 2011 Jamboree - Copyright (c) 2014 Lee Clagett - Copyright (c) 2017 wanghan02 - Copyright (c) 2024-2025 Nana Sakisaka - Copyright (c) 2026 The Iris Project Contributors - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -==============================================================================*/ - -#include -#include - -#include -#include -#include - -namespace iris::x4 { - -template -struct seek_directive : proxy_parser> -{ - template Se, class Context, X4Attribute Attr> - [[nodiscard]] constexpr bool - parse(It& first, Se const& last, Context const& ctx, Attr& attr) const - { - for (It current(first); ; ++current) { - if (this->subject.parse(current, last, ctx, attr)) { - first = current; - return true; - } - - if constexpr (has_context_v) { - if (x4::has_expectation_failure(ctx)) { - return false; - } - } - - // fail only after subject fails & no input - if (current == last) return false; - } - } -}; - -namespace detail { - -struct seek_gen -{ - template - [[nodiscard]] constexpr seek_directive> - operator[](Subject&& subject) const // TODO: MSVC does not support static operator[] - noexcept(is_parser_nothrow_constructible_v>, Subject>) - { - return {as_parser(std::forward(subject))}; - } -}; - -} // detail - -namespace parsers::directive { - -[[maybe_unused]] inline constexpr detail::seek_gen seek{}; - -} // parsers::directive - -using parsers::directive::seek; - -} // iris::x4 - -#endif diff --git a/test/x4/CMakeLists.txt b/test/x4/CMakeLists.txt index bf0d0e4e5..5584568a0 100644 --- a/test/x4/CMakeLists.txt +++ b/test/x4/CMakeLists.txt @@ -84,7 +84,6 @@ x4_define_tests( rule2 rule3 rule4 - seek sequence skip smart_ptr diff --git a/test/x4/expect.cpp b/test/x4/expect.cpp index 5da40afd1..4a5f35754 100644 --- a/test/x4/expect.cpp +++ b/test/x4/expect.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -152,7 +151,6 @@ TEST_CASE("expectation_failure_context_uninstantiated_in_expect_less_parse") using x4::no_skip; using x4::omit; using x4::repeat; - using x4::seek; using x4::skip; using x4::with; @@ -215,7 +213,6 @@ TEST_CASE("expectation_failure_context_uninstantiated_in_expect_less_parse") (void)omit[eps].parse(first, last, unused, unused); (void)repeat(1)[eps].parse(first, last, unused, unused); - (void)seek[eps].parse(first, last, unused, unused); (void)skip(space)[eps].parse(first, last, unused, unused); (void)with(input)[eps].parse(first, last, unused, unused); @@ -290,7 +287,6 @@ TEST_CASE("expect") using x4::no_skip; using x4::omit; using x4::skip; - using x4::seek; using x4::repeat; using x4::matches; using x4::eps; @@ -653,15 +649,6 @@ TEST_CASE("expect") X4_TEST_SUCCESS_PASS("bcab", repeat(2, 3)[lit('a') > 'b'] | +alpha); } - // seek - { - X4_TEST_SUCCESS_PASS("a1b1c1", seek[lit('c') > '1']); - X4_TEST_FAILURE("a1b1c2c1", seek[lit('c') > '1'], { - CHECK(which == "'1'"sv); - CHECK(where == "2c1"sv); - }); - } - // alternative { X4_TEST_SUCCESS_PASS("ac", lit('a') >> 'b' | "ac"); diff --git a/test/x4/iterator.cpp b/test/x4/iterator.cpp index 14f9dc8d6..404ecb1af 100644 --- a/test/x4/iterator.cpp +++ b/test/x4/iterator.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -290,7 +289,6 @@ TEST_CASE("rollback on failed parse (directive)") using x4::no_skip; using x4::omit; using x4::repeat; - using x4::seek; using x4::skip; using x4::with; @@ -494,29 +492,6 @@ TEST_CASE("rollback on failed parse (directive)") CHECK(dummy_bools == std::vector{}); } - { - 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(); diff --git a/test/x4/seek.cpp b/test/x4/seek.cpp deleted file mode 100644 index 561b0e0f9..000000000 --- a/test/x4/seek.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/*============================================================================= - Copyright (c) 2011 Jamboree - Copyright (c) 2014 Lee Clagett - Copyright (c) 2025 Nana Sakisaka - Copyright (c) 2026 The Iris Project Contributors - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -=============================================================================*/ - -#include "iris_x4_test.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -TEST_CASE("seek") -{ - IRIS_X4_ASSERT_CONSTEXPR_CTORS(x4::seek['x']); - - // test eoi - CHECK(parse("", x4::seek[x4::eoi])); - CHECK(parse(" ", x4::seek[x4::eoi], x4::space)); - CHECK(parse("a", x4::seek[x4::eoi])); - CHECK(parse(" a", x4::seek[x4::eoi], x4::space)); - - // test literal finding - { - int i = 0; - REQUIRE(parse("!@#$%^&*KEY:123", x4::seek["KEY:"] >> x4::int_, i)); - CHECK(i == 123); - } - // test sequence finding - { - int i = 0; - REQUIRE(parse("!@#$%^&* KEY : 123", x4::seek[x4::lit("KEY") >> ':'] >> x4::int_, x4::space, i)); - CHECK(i == 123); - } - - // test attr finding - { - std::vector v; - REQUIRE(parse("a06b78c3d", +x4::seek[x4::int_], v).is_partial_match()); - REQUIRE(v.size() == 3); - CHECK(v[0] == 6); - CHECK(v[1] == 78); - CHECK(v[2] == 3); - } - - // test action - { - - bool b = false; - auto const action = [&b] { b = true; }; - REQUIRE(parse("abcdefg", x4::seek["def"][action]).is_partial_match()); - CHECK(b == true); - } - - // test container - { - std::vector v; - REQUIRE(parse("abcInt:100Int:95Int:44", x4::seek[+("Int:" >> x4::int_)], v)); - REQUIRE(v.size() == 3); - CHECK(v[0] == 100); - CHECK(v[1] == 95); - CHECK(v[2] == 44); - } - - // test failure rollback - CHECK(!parse("abcdefg", x4::seek[x4::int_])); - - // past the end regression GH#658 - CHECK(!parse(" ", x4::seek['x'], x4::space)); -} From 3ce571eccb967c45afde58f2cba5cfd734140a04 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 3 Aug 2026 14:52:09 +0900 Subject: [PATCH 02/11] Rename X4's alloy test case --- test/x4/CMakeLists.txt | 2 +- ...st_case.cpp => alloy_wrong_substitute.cpp} | 40 ++++++------------- 2 files changed, 14 insertions(+), 28 deletions(-) rename test/x4/{alloy_wrong_substitute_test_case.cpp => alloy_wrong_substitute.cpp} (54%) diff --git a/test/x4/CMakeLists.txt b/test/x4/CMakeLists.txt index 5584568a0..fca6f6b00 100644 --- a/test/x4/CMakeLists.txt +++ b/test/x4/CMakeLists.txt @@ -40,6 +40,7 @@ endfunction() x4_define_tests( actions + alloy_wrong_substitute alternative and_predicate as @@ -99,7 +100,6 @@ x4_define_tests( with_local without x3_rule_problem - alloy_wrong_substitute_test_case ) x4_define_test(rule_separate_tu rule_separate_tu.cpp rule_separate_tu_grammar.cpp) diff --git a/test/x4/alloy_wrong_substitute_test_case.cpp b/test/x4/alloy_wrong_substitute.cpp similarity index 54% rename from test/x4/alloy_wrong_substitute_test_case.cpp rename to test/x4/alloy_wrong_substitute.cpp index d92e9dad3..79770c077 100644 --- a/test/x4/alloy_wrong_substitute_test_case.cpp +++ b/test/x4/alloy_wrong_substitute.cpp @@ -9,8 +9,6 @@ #include -namespace alloy_wrong_substitute_test_case { - struct A { int foo; @@ -23,47 +21,37 @@ struct B std::string fuga; }; -} // alloy_wrong_substitute_test_case - namespace iris::alloy { // If enabled, `B` can be wrongly treated as substitutable to `A` template<> -struct adaptor +struct adaptor { using getters_list = make_getters_list< - &alloy_wrong_substitute_test_case::A::foo, - &alloy_wrong_substitute_test_case::A::bar + &A::foo, + &A::bar >; }; template<> -struct adaptor +struct adaptor { using getters_list = make_getters_list< - &alloy_wrong_substitute_test_case::B::hoge, - &alloy_wrong_substitute_test_case::B::fuga + &B::hoge, + &B::fuga >; }; } // iris::alloy -namespace ast { - -using A = alloy_wrong_substitute_test_case::A; -using B = alloy_wrong_substitute_test_case::B; using AorB = iris::rvariant< - alloy_wrong_substitute_test_case::A, - alloy_wrong_substitute_test_case::B + A, + B >; -} // ast - -namespace alloy_wrong_substitute_test_case { - -using ARule = x4::rule; -using BRule = x4::rule; -using AorBRule = x4::rule; +using ARule = x4::rule; +using BRule = x4::rule; +using AorBRule = x4::rule; constexpr ARule a; constexpr BRule b; @@ -85,11 +73,9 @@ IRIS_X4_DEFINE(a_or_b); IRIS_X4_INSTANTIATE(AorBRule, const char*, x4::unused_type); -TEST_CASE("alloy_wrong_substitute_test_case") +TEST_CASE("alloy_wrong_substitute") { const char* ptr = nullptr; - ast::AorB result; + AorB result; (void)a_or_b.parse(ptr, nullptr, x4::unused, result); } - -} // alloy_wrong_substitute_test_case From 3e766ac28b2f32b85cafc040232831493f1d41ea Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 3 Aug 2026 14:55:49 +0900 Subject: [PATCH 03/11] Merge without.hpp into with.hpp --- include/iris/x4/directive.hpp | 1 - include/iris/x4/directive/with.hpp | 56 ++++++++++++++++++++ include/iris/x4/directive/without.hpp | 73 --------------------------- test/x4/without.cpp | 1 - 4 files changed, 56 insertions(+), 75 deletions(-) delete mode 100644 include/iris/x4/directive/without.hpp diff --git a/include/iris/x4/directive.hpp b/include/iris/x4/directive.hpp index fed0c8442..b0b9ea188 100644 --- a/include/iris/x4/directive.hpp +++ b/include/iris/x4/directive.hpp @@ -22,6 +22,5 @@ #include #include #include -#include #endif diff --git a/include/iris/x4/directive/with.hpp b/include/iris/x4/directive/with.hpp index b0ca2b5ac..236b24436 100644 --- a/include/iris/x4/directive/with.hpp +++ b/include/iris/x4/directive/with.hpp @@ -224,6 +224,62 @@ template using parsers::directive::with; + +// -------------------------------------------------------- +// -------------------------------------------------------- +// -------------------------------------------------------- + + +template +struct without_directive : proxy_parser> +{ + template Se, class Context, X4Attribute Attr> + [[nodiscard]] constexpr bool + parse(It& first, Se const& last, Context const& ctx, Attr& attr) const + noexcept( + x4::is_nothrow_parsable_v< + Subject, It, Se, + std::remove_cvref_t(ctx))>, + Attr + > + ) + { + return this->subject.parse(first, last, x4::remove_all_contexts(ctx), attr); + } + + [[nodiscard]] constexpr std::string get_x4_info() const + { + return std::format("without<...>[{}]", get_info{}(this->subject)); + } +}; + +namespace detail { + +template +struct without_gen +{ + template + [[nodiscard]] constexpr without_directive, IDs...> + operator[](Subject&& subject) const // TODO: MSVC 2022 does not properly handle static operator[] + noexcept(std::is_nothrow_constructible_v, IDs...>, Subject>) + { + return without_directive, IDs...>{ + std::forward(subject) + }; + } +}; + +} // detail + +namespace parsers::directive { + +template +[[maybe_unused]] inline constexpr detail::without_gen without{}; + +} // parsers::directive + +using parsers::directive::without; + } // iris::x4 #endif diff --git a/include/iris/x4/directive/without.hpp b/include/iris/x4/directive/without.hpp deleted file mode 100644 index 123f414b1..000000000 --- a/include/iris/x4/directive/without.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef IRIS_ZZ_X4_DIRECTIVE_WITHOUT_HPP -#define IRIS_ZZ_X4_DIRECTIVE_WITHOUT_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 -#include - -namespace iris::x4 { - -template -struct without_directive : proxy_parser> -{ - template Se, class Context, X4Attribute Attr> - [[nodiscard]] constexpr bool - parse(It& first, Se const& last, Context const& ctx, Attr& attr) const - noexcept( - x4::is_nothrow_parsable_v< - Subject, It, Se, - std::remove_cvref_t(ctx))>, - Attr - > - ) - { - return this->subject.parse(first, last, x4::remove_all_contexts(ctx), attr); - } - - [[nodiscard]] constexpr std::string get_x4_info() const - { - return std::format("without<...>[{}]", get_info{}(this->subject)); - } -}; - -namespace detail { - -template -struct without_gen -{ - template - [[nodiscard]] constexpr without_directive, IDs...> - operator[](Subject&& subject) const // TODO: MSVC 2022 does not properly handle static operator[] - noexcept(std::is_nothrow_constructible_v, IDs...>, Subject>) - { - return without_directive, IDs...>{ - std::forward(subject) - }; - } -}; - -} // detail - -namespace parsers::directive { - -template -[[maybe_unused]] inline constexpr detail::without_gen without{}; - -} // parsers::directive - -using parsers::directive::without; - -} // iris::x4 - -#endif diff --git a/test/x4/without.cpp b/test/x4/without.cpp index 3e09178e3..28829d35b 100644 --- a/test/x4/without.cpp +++ b/test/x4/without.cpp @@ -8,7 +8,6 @@ #include "iris_x4_test.hpp" #include -#include #include #include From 0adef04b23a86ab9672baaa3e189fcb99c86fcb9 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 3 Aug 2026 14:59:53 +0900 Subject: [PATCH 04/11] Add debug related includes to the global header --- include/iris/x4.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/iris/x4.hpp b/include/iris/x4.hpp index 27d6bc77f..fad88751a 100644 --- a/include/iris/x4.hpp +++ b/include/iris/x4.hpp @@ -11,6 +11,7 @@ ==============================================================================*/ #include + #include #include #include @@ -20,4 +21,7 @@ #include #include +#include +#include + #endif From e9cfe779d8224eb24b322049b86e6f68756a57d6 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:00:41 +0900 Subject: [PATCH 05/11] Rename `attr` to `fixed_value`, `init_attr` to `reset_value` --- include/iris/x4.hpp | 1 + include/iris/x4/attribute.hpp | 15 ++++ .../attr.hpp => attribute/value.hpp} | 69 ++++++++------- include/iris/x4/core/move_to.hpp | 2 +- test/x4/CMakeLists.txt | 2 +- test/x4/alternative.cpp | 14 ++-- test/x4/as.cpp | 36 ++++---- test/x4/attribute_type_check.cpp | 12 +-- test/x4/expect.cpp | 9 +- test/x4/iterator.cpp | 6 +- test/x4/partial_success.cpp | 26 +++--- test/x4/sequence.cpp | 6 +- test/x4/{attr.cpp => value.cpp} | 84 +++++++++---------- 13 files changed, 154 insertions(+), 128 deletions(-) create mode 100644 include/iris/x4/attribute.hpp rename include/iris/x4/{auxiliary/attr.hpp => attribute/value.hpp} (64%) rename test/x4/{attr.cpp => value.cpp} (60%) diff --git a/include/iris/x4.hpp b/include/iris/x4.hpp index fad88751a..36fcb7dd0 100644 --- a/include/iris/x4.hpp +++ b/include/iris/x4.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include diff --git a/include/iris/x4/attribute.hpp b/include/iris/x4/attribute.hpp new file mode 100644 index 000000000..0d4954f0e --- /dev/null +++ b/include/iris/x4/attribute.hpp @@ -0,0 +1,15 @@ +#ifndef IRIS_ZZ_X4_ATTRIBUTE_HPP +#define IRIS_ZZ_X4_ATTRIBUTE_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 // excluded +#include + +#endif diff --git a/include/iris/x4/auxiliary/attr.hpp b/include/iris/x4/attribute/value.hpp similarity index 64% rename from include/iris/x4/auxiliary/attr.hpp rename to include/iris/x4/attribute/value.hpp index 5e1b31fea..30f296595 100644 --- a/include/iris/x4/auxiliary/attr.hpp +++ b/include/iris/x4/attribute/value.hpp @@ -1,5 +1,5 @@ -#ifndef IRIS_ZZ_X4_AUXILIARY_ATTR_HPP -#define IRIS_ZZ_X4_AUXILIARY_ATTR_HPP +#ifndef IRIS_ZZ_X4_ATTRIBUTE_VALUE_HPP +#define IRIS_ZZ_X4_ATTRIBUTE_VALUE_HPP /*============================================================================= Copyright (c) 2001-2011 Hartmut Kaiser @@ -26,16 +26,17 @@ namespace iris::x4 { +// `fixed_value(...)` template -struct attr_parser : parser> +struct fixed_value_parser : parser> { static_assert(X4Attribute); - static_assert(!X4UnusedAttribute, "attr_parser with `unused_type` is meaningless"); + static_assert(!X4UnusedAttribute, "fixed_value_parser with `unused_type` is meaningless"); // `HeldValueT` is almost always equal to `T`. // - // The most notable situation where they differ is when `attr_parser` is initialized - // by `char const (&)[N]`. In such case, `attr_parser` must hold the value by + // The most notable situation where they differ is when `fixed_value_parser` is initialized + // by `char const (&)[N]`. In such case, `fixed_value_parser` must hold the value by // `std::string_view`, instead of `std::string`, to be constexpr. static_assert(X4Movable); @@ -45,9 +46,9 @@ struct attr_parser : parser> template requires - (!std::is_same_v, attr_parser>) && + (!std::is_same_v, fixed_value_parser>) && std::is_constructible_v - constexpr explicit attr_parser(U&& value) + constexpr explicit fixed_value_parser(U&& value) noexcept(std::is_nothrow_constructible_v) : held_value_(std::forward(value)) {} @@ -66,12 +67,12 @@ struct attr_parser : parser> HeldValueT held_value_; }; -// `init_attr` +// `reset_value` template -struct attr_parser : parser> +struct fixed_value_parser : parser> { static_assert(X4Attribute); - static_assert(!X4UnusedAttribute, "attr_parser with `unused_type` is meaningless"); + static_assert(!X4UnusedAttribute, "fixed_value_parser with `unused_type` is meaningless"); using attribute_type = T; @@ -105,7 +106,7 @@ struct attr_parser : parser> namespace detail { template -using string_array_attr_parser_t = attr_parser< +using string_array_attr_parser_t = fixed_value_parser< std::basic_string>>, std::basic_string_view>> >; @@ -113,40 +114,44 @@ using string_array_attr_parser_t = attr_parser< } // detail template -attr_parser(R const&) -> attr_parser< +fixed_value_parser(R const&) -> fixed_value_parser< std::basic_string>>, std::basic_string_view>> >; template -struct get_info> +struct get_info> { using result_type = std::string; [[nodiscard]] constexpr std::string - operator()(attr_parser const&) const + operator()(fixed_value_parser const&) const { - return "attr"; + if constexpr (std::is_void_v) { + return "reset_value"; + } else { + return "fixed_value(...)"; + } } }; namespace detail { -struct attr_gen +struct fixed_value_gen { template - [[nodiscard]] static constexpr attr_parser> + [[nodiscard]] static constexpr fixed_value_parser> operator()(T&& value) - noexcept(std::is_nothrow_constructible_v>, T>) + noexcept(std::is_nothrow_constructible_v>, T>) { - return attr_parser>{std::forward(value)}; + return fixed_value_parser>{std::forward(value)}; } - template - [[nodiscard]] static constexpr string_array_attr_parser_t - operator()(R&& value) - noexcept(std::is_nothrow_constructible_v, R>) + template + [[nodiscard]] static constexpr string_array_attr_parser_t + operator()(CharArrayT&& char_array) + noexcept(std::is_nothrow_constructible_v, CharArrayT>) { - return string_array_attr_parser_t{std::forward(value)}; + return string_array_attr_parser_t{std::forward(char_array)}; } }; @@ -156,21 +161,21 @@ namespace parsers { // An always-succeeding parser that has the `attribute_type` equivalent // to the given parameter. Copies the held instance on each invocation. -[[maybe_unused]] inline constexpr detail::attr_gen attr{}; +[[maybe_unused]] inline constexpr detail::fixed_value_gen fixed_value{}; -// A special `attr` parser that resets the variable and always succeeds. +// A special `fixed_value` parser that resets the variable and always succeeds. // // This can be used for constructing `constexpr` instance of a parser // even when `T` has dynamically allocated storage. -// For example, normal `attr(std::vector{})` cannot be assigned -// to a `constexpr` instance, but `init_attr>` can. +// For example, normal `fixed_value(std::vector{})` cannot be assigned +// to a `constexpr` instance, but `reset_value>` can. template -[[maybe_unused]] inline constexpr attr_parser init_attr{}; +[[maybe_unused]] inline constexpr fixed_value_parser reset_value{}; } // parsers -using parsers::attr; -using parsers::init_attr; +using parsers::fixed_value; +using parsers::reset_value; } // iris::x4 diff --git a/include/iris/x4/core/move_to.hpp b/include/iris/x4/core/move_to.hpp index bb1c7589a..025c2da7f 100644 --- a/include/iris/x4/core/move_to.hpp +++ b/include/iris/x4/core/move_to.hpp @@ -240,7 +240,7 @@ move_to(It first, Se last, Dest& dest) } // Move non-container `src` into container `dest`. -// e.g. Source=std::string_view, Dest=std::string (used in `attr_parser`) +// e.g. Source=std::string_view, Dest=std::string (used in `fixed_value_parser`) template Dest> requires (!traits::X4Container) && diff --git a/test/x4/CMakeLists.txt b/test/x4/CMakeLists.txt index fca6f6b00..90fc25e63 100644 --- a/test/x4/CMakeLists.txt +++ b/test/x4/CMakeLists.txt @@ -44,7 +44,6 @@ x4_define_tests( alternative and_predicate as - attr attribute_type_check bool char @@ -96,6 +95,7 @@ x4_define_tests( uint uint_radix unused + value with with_local without diff --git a/test/x4/alternative.cpp b/test/x4/alternative.cpp index 6c1ea50a5..bd698a6f1 100644 --- a/test/x4/alternative.cpp +++ b/test/x4/alternative.cpp @@ -11,7 +11,7 @@ #include "iris_x4_test.hpp" #include -#include +#include #include #include #include @@ -61,7 +61,7 @@ TEST_CASE("alternative") { using x4::standard::char_; using x4::standard::lit; - using x4::attr; + using x4::fixed_value; using x4::int_; using x4::unused; using x4::omit; @@ -268,8 +268,8 @@ TEST_CASE("alternative") // alternative over single element tuple as part of another tuple { - constexpr auto key1 = lit("long") >> attr(long()); - constexpr auto key2 = lit("char") >> attr(char()); + constexpr auto key1 = lit("long") >> fixed_value(long{}); + constexpr auto key2 = lit("char") >> fixed_value(char{}); constexpr auto keys = key1 | key2; constexpr auto pair = keys >> lit("=") >> +char_; @@ -305,7 +305,7 @@ TEST_CASE("alternative") // regressing test for #603 struct X {}; std::vector> v; - REQUIRE(parse("xx42x9y", *(int_ | +char_('x') | 'y' >> attr(X{})), v)); + REQUIRE(parse("xx42x9y", *(int_ | +char_('x') | 'y' >> fixed_value(X{})), v)); CHECK(v.size() == 5); } @@ -324,7 +324,7 @@ TEST_CASE("alternative") iris::rvariant v; iris::rvariant x{X{}}; v = x; // iris::rvariant supports that convertion - auto const p = 'x' >> attr(x) | 'z' >> attr(Z{}); + auto const p = 'x' >> fixed_value(x) | 'z' >> fixed_value(Z{}); REQUIRE(parse("z", p, v)); CHECK(iris::get_if(&v) != nullptr); REQUIRE(parse("x", p, v)); @@ -337,7 +337,7 @@ TEST_CASE("alternative") using Foo = std::vector>; using Bar = std::vector>; Bar x; - CHECK(parse("abaabb", +('a' >> attr(Foo{}) | 'b' >> attr(int{})), x)); + CHECK(parse("abaabb", +('a' >> fixed_value(Foo{}) | 'b' >> fixed_value(int{})), x)); } } diff --git a/test/x4/as.cpp b/test/x4/as.cpp index 9b0335c32..6ae6aa4f6 100644 --- a/test/x4/as.cpp +++ b/test/x4/as.cpp @@ -10,7 +10,7 @@ #include "iris_x4_test.hpp" -#include +#include #include #include #include @@ -53,6 +53,7 @@ TEST_CASE("as(p)") { using x4::_as_var; using x4::_attr; + using x4::fixed_value; // result = int or long long // T = int @@ -61,7 +62,7 @@ TEST_CASE("as(p)") // with semantic action { { - constexpr auto p = x4::as(x4::attr(3))[([](auto&& ctx) { + constexpr auto p = x4::as(fixed_value(3))[([](auto&& ctx) { static_assert(std::same_as, unused_type>); static_assert(std::same_as, int>); _attr(ctx) += 5; @@ -80,13 +81,13 @@ TEST_CASE("as(p)") // do nothing in semantic action { - constexpr auto p = x4::attr(3); + constexpr auto p = fixed_value(3); int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); CHECK(result == 3); } { - constexpr auto p = x4::attr(3)[do_nothing]; + constexpr auto p = fixed_value(3)[do_nothing]; int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); CHECK(result == 3); @@ -94,7 +95,7 @@ TEST_CASE("as(p)") { constexpr auto p = x4::as( - x4::attr(3) + fixed_value(3) ); int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); @@ -102,7 +103,7 @@ TEST_CASE("as(p)") } { constexpr auto p = x4::as( - x4::attr(3)[do_nothing] + fixed_value(3)[do_nothing] ); int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); @@ -113,7 +114,7 @@ TEST_CASE("as(p)") // the intermediate value always propagates up. { constexpr auto p = x4::as( - x4::attr(3) + fixed_value(3) )[do_nothing]; int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); @@ -121,7 +122,7 @@ TEST_CASE("as(p)") } { constexpr auto p = x4::as( - x4::attr(3)[do_nothing] + fixed_value(3)[do_nothing] )[do_nothing]; int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); @@ -132,7 +133,7 @@ TEST_CASE("as(p)") // ------------------------------------------- // without semantic action { - constexpr auto p = x4::as(x4::attr(3)); + constexpr auto p = x4::as(fixed_value(3)); int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); CHECK(result == 3); @@ -151,6 +152,7 @@ TEST_CASE("as(as(p))") { using x4::_as_var; using x4::_attr; + using x4::fixed_value; // result = int or long long // T = int @@ -160,7 +162,7 @@ TEST_CASE("as(as(p))") // with semantic action { constexpr auto p = x4::as( - x4::as(x4::attr(3))[([](auto&& ctx) { + x4::as(fixed_value(3))[([](auto&& ctx) { static_assert(std::same_as, int>); static_assert(std::same_as, int>); CHECK(std::addressof(_as_var(ctx)) != std::addressof(_attr(ctx))); @@ -182,7 +184,7 @@ TEST_CASE("as(as(p))") // do nothing in semantic action { constexpr auto p = x4::as( - x4::as(x4::attr(3))[do_nothing] + x4::as(fixed_value(3))[do_nothing] ); { @@ -201,7 +203,7 @@ TEST_CASE("as(as(p))") // without semantic action { constexpr auto p = x4::as( - x4::as(x4::attr(3)) + x4::as(fixed_value(3)) ); { @@ -221,6 +223,7 @@ TEST_CASE("as(as(p))") { using x4::_as_var; using x4::_attr; + using x4::fixed_value; // result = int or long long // T = int @@ -230,7 +233,7 @@ TEST_CASE("as(as(p))") // with semantic action { constexpr auto p = x4::as( - x4::as(x4::attr(short(3)))[([](auto&& ctx) { + x4::as(fixed_value(short(3)))[([](auto&& ctx) { static_assert(std::same_as, int>); static_assert(std::same_as, short>); _as_var(ctx) = _attr(ctx) + 5; @@ -251,7 +254,7 @@ TEST_CASE("as(as(p))") // do nothing in semantic action { constexpr auto p = x4::as( - x4::as(x4::attr(short(3)))[do_nothing] + x4::as(fixed_value(short(3)))[do_nothing] ); { @@ -270,7 +273,7 @@ TEST_CASE("as(as(p))") // without semantic action { constexpr auto p = x4::as( - x4::as(x4::attr(short(3))) + x4::as(fixed_value(short(3))) ); { @@ -440,6 +443,7 @@ TEST_CASE("_as_var") using x4::_attr; using x4::_rule_var; using x4::_as_var; + using x4::fixed_value; // `_as_var(ctx)` (with auto attribute propagation) { @@ -509,7 +513,7 @@ TEST_CASE("_as_var") std::string result; /*constexpr*/ auto unused_rule = x4::as( - x4::attr("default") >> + fixed_value("default") >> eps[([]([[maybe_unused]] auto&& ctx) { static_assert(std::same_as, std::string>); diff --git a/test/x4/attribute_type_check.cpp b/test/x4/attribute_type_check.cpp index d70d218e6..40bcfc35d 100644 --- a/test/x4/attribute_type_check.cpp +++ b/test/x4/attribute_type_check.cpp @@ -9,7 +9,7 @@ #include "iris_x4_test.hpp" -#include +#include #include #include @@ -25,12 +25,12 @@ namespace { // just an `attr` with added type checker template -struct checked_attr_parser : x4::attr_parser +struct checked_fixed_value_parser : x4::fixed_value_parser { - using base_type = x4::attr_parser; + using base_type = x4::fixed_value_parser; - checked_attr_parser(Value const& value) : base_type(value) {} - checked_attr_parser(Value&& value) : base_type(std::move(value)) {} + checked_fixed_value_parser(Value const& value) : base_type(value) {} + checked_fixed_value_parser(Value&& value) : base_type(std::move(value)) {} template Se, class Context, class Attr> [[nodiscard]] constexpr bool @@ -42,7 +42,7 @@ struct checked_attr_parser : x4::attr_parser }; template -checked_attr_parser, Expected> +checked_fixed_value_parser, Expected> checked_attr(Value&& value) { return { std::forward(value) }; } // instantiate our type checker diff --git a/test/x4/expect.cpp b/test/x4/expect.cpp index 4a5f35754..034f19a4f 100644 --- a/test/x4/expect.cpp +++ b/test/x4/expect.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -138,7 +138,7 @@ TEST_CASE("expectation_failure_context_uninstantiated_in_expect_less_parse") using x4::eoi; using x4::eol; using x4::eps; - using x4::attr; + using x4::fixed_value; using x4::lit; using x4::string; using x4::char_; @@ -171,7 +171,8 @@ TEST_CASE("expectation_failure_context_uninstantiated_in_expect_less_parse") (void)int_[([]{})].parse(first, last, unused, dummy_int); // action (void)(int_ >> int_)[([]{})].parse(first, last, unused, dummy_ints); // action - (void)attr(42).parse(first, last, unused, unused); + (void)fixed_value(42).parse(first, last, unused, unused); + (void)fixed_value("foo").parse(first, last, unused, unused); (void)eoi.parse(first, last, unused, unused); (void)eol.parse(first, last, unused, unused); (void)eps.parse(first, last, unused, unused); @@ -491,7 +492,7 @@ TEST_CASE("expect") }); int n = 0; - X4_TEST_ATTR_SUCCESS_PASS("abc", lit("abc") > x4::attr(12) > eoi, n); + X4_TEST_ATTR_SUCCESS_PASS("abc", lit("abc") > x4::fixed_value(12) > eoi, n); CHECK(n == 12); } diff --git a/test/x4/iterator.cpp b/test/x4/iterator.cpp index 404ecb1af..331a6ed9b 100644 --- a/test/x4/iterator.cpp +++ b/test/x4/iterator.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include @@ -235,7 +235,7 @@ TEST_CASE("rollback on failed parse (action)") TEST_CASE("rollback on failed parse (auxiliary)") { - using x4::attr; + using x4::fixed_value; using x4::eps; using x4::eoi; using x4::eol; @@ -244,7 +244,7 @@ TEST_CASE("rollback on failed parse (auxiliary)") 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)); + REQUIRE_FALSE((fixed_value(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 } diff --git a/test/x4/partial_success.cpp b/test/x4/partial_success.cpp index 9219b6284..d5f63cb43 100644 --- a/test/x4/partial_success.cpp +++ b/test/x4/partial_success.cpp @@ -8,7 +8,7 @@ #include "iris_x4_test.hpp" -#include +#include #include #include #include @@ -81,7 +81,7 @@ struct strong_int TEST_CASE("partial success (alternative)") { - using x4::attr; + using x4::fixed_value; using x4::eps; using x4::omit; using x4::int_; @@ -94,12 +94,12 @@ TEST_CASE("partial success (alternative)") // Sanity checks { int i = -1; - REQUIRE(parse("", attr(42), i)); + REQUIRE(parse("", fixed_value(42), i)); CHECK(i == 42); } { std::string str; - REQUIRE(parse("", attr("foo"), str)); + REQUIRE(parse("", fixed_value("foo"), str)); CHECK(str == "foo"); } { @@ -117,12 +117,12 @@ TEST_CASE("partial success (alternative)") { std::vector ints; - REQUIRE(parse("1 2", eps(false) | attr(98) >> attr(99), ints).is_partial_match()); + REQUIRE(parse("1 2", eps(false) | fixed_value(98) >> fixed_value(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()); + REQUIRE(parse("1 2", int_ >> int_ >> eps(false) | fixed_value(98) >> fixed_value(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}); @@ -130,13 +130,13 @@ TEST_CASE("partial success (alternative)") // 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)); + REQUIRE(!parse("1 2", int_ >> int_ >> eps(false) | fixed_value(98) >> fixed_value(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()); + REQUIRE(parse("1 2", fixed_value(std::vector{3, 4}) >> eps(false) | fixed_value(98) >> fixed_value(99), space, ints).is_partial_match()); // Wrong implementation yields {3, 4, 98, 99} CHECK(ints == std::vector{98, 99}); } @@ -173,14 +173,14 @@ TEST_CASE("partial success (alternative)") } { std::string str; - REQUIRE(parse("foodie", attr("bookworm") >> eps(false) | string("foodie"), str)); + REQUIRE(parse("foodie", fixed_value("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)); + REQUIRE(!parse("foodie", fixed_value("bookworm") >> eps(false) | string("foodie") >> eps(false), str)); // Wrong implementation yields "bookwormfoodie" or "foodie" CHECK(str == ""); } @@ -201,7 +201,7 @@ TEST_CASE("partial success (alternative)") { strong_int si; - REQUIRE(parse("1", int_ | attr(strong_int{9}), si)); + REQUIRE(parse("1", int_ | fixed_value(strong_int{9}), si)); CHECK(si == strong_int{1}); CHECK(si.assigned_count == 1); } @@ -224,13 +224,13 @@ TEST_CASE("partial success (alternative)") { pair_int pi; - REQUIRE(parse("1 2", int_ >> int_ | attr(pair_int{98, 99}), space, pi)); + REQUIRE(parse("1 2", int_ >> int_ | fixed_value(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_], + int_ >> int_ >> eps(false) | fixed_value(pair_int{98, 99}) >> omit[int_ >> int_], space, pi )); CHECK(pi == pair_int{98, 99}); diff --git a/test/x4/sequence.cpp b/test/x4/sequence.cpp index b92d6cb0a..b027ff54c 100644 --- a/test/x4/sequence.cpp +++ b/test/x4/sequence.cpp @@ -10,7 +10,7 @@ #include "iris_x4_test.hpp" #include -#include +#include #include #include #include @@ -44,7 +44,7 @@ TEST_CASE("sequence") using x4::standard::string; using x4::standard::lit; using x4::standard::alnum; - using x4::attr; + using x4::fixed_value; using x4::omit; using x4::unused; using x4::int_; @@ -460,7 +460,7 @@ TEST_CASE("sequence") // Test that sequence with only one parser producing attribute makes it unwrapped STATIC_CHECK(std::same_as< - x4::parser_traits> attr(long()))>::attribute_type, + x4::parser_traits> fixed_value(long{}))>::attribute_type, long >); diff --git a/test/x4/attr.cpp b/test/x4/value.cpp similarity index 60% rename from test/x4/attr.cpp rename to test/x4/value.cpp index e85d21a7c..d6ac989a5 100644 --- a/test/x4/attr.cpp +++ b/test/x4/value.cpp @@ -9,7 +9,7 @@ #include "iris_x4_test.hpp" -#include +#include #include #include #include @@ -27,23 +27,23 @@ TEST_CASE("attr") using namespace std::string_literals; using namespace std::string_view_literals; - using x4::attr; + using x4::fixed_value; using x4::int_; { - [[maybe_unused]] constexpr auto attr_p = attr(1); - STATIC_CHECK(std::same_as, x4::attr_parser>); + [[maybe_unused]] constexpr auto attr_p = fixed_value(1); + STATIC_CHECK(std::same_as, x4::fixed_value_parser>); } { - [[maybe_unused]] constexpr auto attr_p = attr(3.14); - STATIC_CHECK(std::same_as, x4::attr_parser>); + [[maybe_unused]] constexpr auto attr_p = fixed_value(3.14); + STATIC_CHECK(std::same_as, x4::fixed_value_parser>); } { - constexpr auto attr_p = attr("foo"); - STATIC_REQUIRE(std::same_as, x4::attr_parser, std::basic_string_view>>); + constexpr auto attr_p = fixed_value("foo"); + STATIC_REQUIRE(std::same_as, x4::fixed_value_parser, std::basic_string_view>>); - // Make sure `attr(std::string_view)` is parsable into std::string + // Make sure `fixed_value(std::string_view)` is parsable into std::string { constexpr auto result = [&](std::string_view expected_str) consteval { std::string str; @@ -66,71 +66,71 @@ TEST_CASE("attr") } } { - [[maybe_unused]] /*constexpr*/ auto attr_p = attr("foo"s); - STATIC_CHECK(std::same_as, x4::attr_parser>>); + [[maybe_unused]] /*constexpr*/ auto attr_p = fixed_value("foo"s); + STATIC_CHECK(std::same_as, x4::fixed_value_parser>>); } { - [[maybe_unused]] constexpr auto attr_p = attr("foo"sv); - STATIC_CHECK(std::same_as, x4::attr_parser>>); + [[maybe_unused]] constexpr auto attr_p = fixed_value("foo"sv); + STATIC_CHECK(std::same_as, x4::fixed_value_parser>>); } { - [[maybe_unused]] constexpr auto attr_p = attr(U"foo"); - STATIC_CHECK(std::same_as, x4::attr_parser, std::basic_string_view>>); + [[maybe_unused]] constexpr auto attr_p = fixed_value(U"foo"); + STATIC_CHECK(std::same_as, x4::fixed_value_parser, std::basic_string_view>>); } { - [[maybe_unused]] /*constexpr*/ auto attr_p = attr(U"foo"s); - STATIC_CHECK(std::same_as, x4::attr_parser>>); + [[maybe_unused]] /*constexpr*/ auto attr_p = fixed_value(U"foo"s); + STATIC_CHECK(std::same_as, x4::fixed_value_parser>>); } { - [[maybe_unused]] constexpr auto attr_p = attr(U"foo"sv); - STATIC_CHECK(std::same_as, x4::attr_parser>>); + [[maybe_unused]] constexpr auto attr_p = fixed_value(U"foo"sv); + STATIC_CHECK(std::same_as, x4::fixed_value_parser>>); } - IRIS_X4_ASSERT_CONSTEXPR_CTORS(attr(1)); - IRIS_X4_ASSERT_CONSTEXPR_CTORS(attr("asd")); + IRIS_X4_ASSERT_CONSTEXPR_CTORS(fixed_value(1)); + IRIS_X4_ASSERT_CONSTEXPR_CTORS(fixed_value("asd")); { constexpr char s[] = "asd"; - IRIS_X4_ASSERT_CONSTEXPR_CTORS(attr(s)); + IRIS_X4_ASSERT_CONSTEXPR_CTORS(fixed_value(s)); } { int d = 0; - REQUIRE(parse("", attr(1), d)); + REQUIRE(parse("", fixed_value(1), d)); CHECK(d == 1); } { int d = 0; int d1 = 1; - REQUIRE(parse("", attr(d1), d)); + REQUIRE(parse("", fixed_value(d1), d)); CHECK(d == 1); } { std::pair p; - REQUIRE(parse("1", int_ >> attr(2), p)); + REQUIRE(parse("1", int_ >> fixed_value(2), p)); CHECK(p.first == 1); CHECK(p.second == 2); } { char c = '\0'; - REQUIRE(parse("", attr('a'), c)); + REQUIRE(parse("", fixed_value('a'), c)); CHECK(c == 'a'); } { std::string str; - REQUIRE(parse("", attr("test"), str)); + REQUIRE(parse("", fixed_value("test"), str)); CHECK(str == "test"); } { std::string str; - REQUIRE(parse("", attr(std::string("test")), str)); + REQUIRE(parse("", fixed_value(std::string("test")), str)); CHECK(str == "test"); } { std::vector array = {0, 1, 2}; std::vector vec; - REQUIRE(parse("", attr(array), vec)); + REQUIRE(parse("", fixed_value(array), vec)); REQUIRE(vec.size() == 3); CHECK(vec[0] == 0); CHECK(vec[1] == 1); @@ -139,7 +139,7 @@ TEST_CASE("attr") { std::string s; - REQUIRE(parse("s", "s" >> attr(std::string("123")), s)); + REQUIRE(parse("s", "s" >> fixed_value(std::string("123")), s)); CHECK(s == "123"); } @@ -154,7 +154,7 @@ TEST_CASE("attr") } { std::vector> vecs; - REQUIRE(parse("", attr(std::vector{1, 2, 3}) >> attr(std::vector{4, 5, 6}), vecs)); + REQUIRE(parse("", fixed_value(std::vector{1, 2, 3}) >> fixed_value(std::vector{4, 5, 6}), vecs)); CHECK(vecs == std::vector{std::vector{1, 2, 3}, std::vector{4, 5, 6}}); } @@ -167,41 +167,41 @@ TEST_CASE("attr") } { std::vector strs; - REQUIRE(parse("", attr(std::string("123")) >> attr(std::string("456")), strs)); + REQUIRE(parse("", fixed_value(std::string("123")) >> fixed_value(std::string("456")), strs)); CHECK(strs == std::vector{"123", "456"}); } { std::string s; - REQUIRE(parse("", attr(std::string("123")) >> attr(std::string("456")), s)); + REQUIRE(parse("", fixed_value(std::string("123")) >> fixed_value(std::string("456")), s)); CHECK(s == "123456"); } { std::vector ints; - REQUIRE(parse("", attr(std::vector{1, 2, 3}) >> attr(std::vector{4, 5, 6}), ints)); + REQUIRE(parse("", fixed_value(std::vector{1, 2, 3}) >> fixed_value(std::vector{4, 5, 6}), ints)); CHECK(ints == std::vector{1, 2, 3, 4, 5, 6}); } { std::vector ints; REQUIRE(parse("", - (attr(std::vector{1, 2, 3}) >> attr(std::vector{4, 5, 6})) >> - (attr(std::vector{7, 8, 9}) >> attr(std::vector{0, 1, 2})), + (fixed_value(std::vector{1, 2, 3}) >> fixed_value(std::vector{4, 5, 6})) >> + (fixed_value(std::vector{7, 8, 9}) >> fixed_value(std::vector{0, 1, 2})), ints )); CHECK(ints == std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}); } } -TEST_CASE("init_attr") +TEST_CASE("reset_value") { - using x4::init_attr; + using x4::reset_value; { int val = 42; - STATIC_CHECK(std::same_as)>::attribute_type, int>); - REQUIRE(parse("", init_attr, val)); + STATIC_CHECK(std::same_as)>::attribute_type, int>); + REQUIRE(parse("", reset_value, val)); CHECK(val == 0); } { @@ -210,8 +210,8 @@ TEST_CASE("init_attr") val.emplace_back(42); auto const prev_capacity = val.capacity(); - STATIC_CHECK(std::same_as>)>::attribute_type, std::vector>); - REQUIRE(parse("", init_attr>, val)); + STATIC_CHECK(std::same_as>)>::attribute_type, std::vector>); + REQUIRE(parse("", reset_value>, val)); CHECK(val.empty()); CHECK(val.capacity() == prev_capacity); // should preserve capacity as per `.clear()` } From f2f39b31110b64ebf57a7cac1cffa254ad41ca31 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:06:44 +0900 Subject: [PATCH 06/11] Rename `as_directive` to `as_type_parser` --- include/iris/x4/attribute.hpp | 1 + .../iris/x4/{directive => attribute}/as.hpp | 36 ++++++++++--------- include/iris/x4/directive.hpp | 1 - include/iris/x4/rule.hpp | 2 +- test/x4/as.cpp | 2 +- test/x4/container_support.cpp | 2 +- test/x4/sequence.cpp | 2 +- test/x4/with_local.cpp | 2 +- 8 files changed, 25 insertions(+), 23 deletions(-) rename include/iris/x4/{directive => attribute}/as.hpp (81%) diff --git a/include/iris/x4/attribute.hpp b/include/iris/x4/attribute.hpp index 0d4954f0e..0ffbb29f9 100644 --- a/include/iris/x4/attribute.hpp +++ b/include/iris/x4/attribute.hpp @@ -9,6 +9,7 @@ ==============================================================================*/ #include +#include // #include // excluded #include diff --git a/include/iris/x4/directive/as.hpp b/include/iris/x4/attribute/as.hpp similarity index 81% rename from include/iris/x4/directive/as.hpp rename to include/iris/x4/attribute/as.hpp index 6f4c1d47c..d64a9f4ed 100644 --- a/include/iris/x4/directive/as.hpp +++ b/include/iris/x4/attribute/as.hpp @@ -1,5 +1,5 @@ -#ifndef IRIS_ZZ_X4_DIRECTIVE_AS_HPP -#define IRIS_ZZ_X4_DIRECTIVE_AS_HPP +#ifndef IRIS_ZZ_X4_ATTRIBUTE_AS_HPP +#define IRIS_ZZ_X4_ATTRIBUTE_AS_HPP /*============================================================================= Copyright (c) 2025 Nana Sakisaka @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include @@ -23,12 +25,12 @@ namespace iris::x4 { namespace detail { template -struct as_directive_ctx_impl // false +struct as_type_parser_ctx_impl // false { using type = Context; }; template -struct as_directive_ctx_impl +struct as_type_parser_ctx_impl { using type = std::remove_cvref_t( std::declval(), @@ -38,11 +40,11 @@ struct as_directive_ctx_impl } // detail -// `as_directive` forces the attribute of subject parser +// `as_type_parser` forces the attribute of subject parser // to be `T`. When `T` is `unused_type`, this is equivalent to // `omit_directive`. template -struct as_directive : unary_parser> +struct as_type_parser : unary_parser> { static_assert(!std::is_const_v); // Forbid const `unused_type` static_assert(!std::same_as); // Unknown use case, not supported for now @@ -55,9 +57,9 @@ struct as_directive : unary_parser> static constexpr bool has_action = false; // Explicitly re-enable attribute detection in `x4::rule` static constexpr bool requires_exact_attribute_type = true; - // `as_directive` should NOT inherit underlying parser's `handles_container` - // because `as_directive` is an atomic parser. The default implementation of - // `parser_traits>::handles_container` must transparently + // `as_type_parser` should NOT inherit underlying parser's `handles_container` + // because `as_type_parser` is an atomic parser. The default implementation of + // `parser_traits>::handles_container` must transparently // handle this case. private: @@ -72,7 +74,7 @@ struct as_directive : unary_parser> requires std::same_as, T> [[nodiscard]] constexpr bool parse(It& first, Se const& last, Context const& ctx, OuterAttr& outer_attr) const - noexcept(is_nothrow_parsable_v::type, exposed_attr_for_child_t>) + noexcept(is_nothrow_parsable_v::type, exposed_attr_for_child_t>) { if constexpr (Subject::has_action) { return this->subject.parse(first, last, x4::replace_first_context(ctx, outer_attr), unused); @@ -87,7 +89,7 @@ struct as_directive : unary_parser> (!std::same_as, T>) [[nodiscard]] constexpr bool parse(It& first, Se const& last, Context const& ctx, OuterAttr&) const - noexcept(is_nothrow_parsable_v::type, unused_type>) + noexcept(is_nothrow_parsable_v::type, unused_type>) { if constexpr (Subject::has_action) { return this->subject.parse(first, last, x4::replace_first_context(ctx, unused), unused); @@ -104,7 +106,7 @@ struct as_directive : unary_parser> [[nodiscard]] constexpr bool parse(It& first, Se const& last, Context const& ctx, OuterAttr& outer_attr) const noexcept( - is_nothrow_parsable_v::type, exposed_attr_for_child_t> && + is_nothrow_parsable_v::type, exposed_attr_for_child_t> && noexcept(x4::move_to(std::declval(), outer_attr)) ) { @@ -134,9 +136,9 @@ template struct as_fn { template - [[nodiscard]] static constexpr as_directive> + [[nodiscard]] static constexpr as_type_parser> operator()(Subject&& subject) - noexcept(is_parser_nothrow_constructible_v>, Subject>) + noexcept(is_parser_nothrow_constructible_v>, Subject>) { return {std::forward(subject)}; } @@ -144,14 +146,14 @@ struct as_fn } // detail -namespace parsers::directive { +namespace parsers { template [[maybe_unused]] inline constexpr detail::as_fn as{}; -} // parsers::directive +} // parsers -using parsers::directive::as; +using parsers::as; } // iris::x4 diff --git a/include/iris/x4/directive.hpp b/include/iris/x4/directive.hpp index b0b9ea188..33c3bcc76 100644 --- a/include/iris/x4/directive.hpp +++ b/include/iris/x4/directive.hpp @@ -11,7 +11,6 @@ ==============================================================================*/ #include -#include #include #include #include diff --git a/include/iris/x4/rule.hpp b/include/iris/x4/rule.hpp index 344d8ca61..de7c06650 100644 --- a/include/iris/x4/rule.hpp +++ b/include/iris/x4/rule.hpp @@ -268,7 +268,7 @@ struct rule_impl // // Note: `x4::as(...)` explicitly unsets `has_action` even if the underlying subject // has semantic action, so it will be dispatched to the latter branch (unless the - // `as_directive` itself has semantic action). + // `as_type_parser` itself has semantic action). if constexpr (RHS::has_action) { if constexpr (ForceAttr) { parse_ok = rule_impl::parse_rhs( diff --git a/test/x4/as.cpp b/test/x4/as.cpp index 6ae6aa4f6..dc47ae4ba 100644 --- a/test/x4/as.cpp +++ b/test/x4/as.cpp @@ -10,9 +10,9 @@ #include "iris_x4_test.hpp" +#include #include #include -#include #include #include #include diff --git a/test/x4/container_support.cpp b/test/x4/container_support.cpp index e7efada1a..54490f5ec 100644 --- a/test/x4/container_support.cpp +++ b/test/x4/container_support.cpp @@ -11,9 +11,9 @@ #include "iris_x4_test.hpp" #include +#include #include #include -#include #include #include #include diff --git a/test/x4/sequence.cpp b/test/x4/sequence.cpp index b027ff54c..4fbff615c 100644 --- a/test/x4/sequence.cpp +++ b/test/x4/sequence.cpp @@ -10,12 +10,12 @@ #include "iris_x4_test.hpp" #include +#include #include #include #include #include #include -#include #include #include #include diff --git a/test/x4/with_local.cpp b/test/x4/with_local.cpp index d3f3c9882..cb78d81ce 100644 --- a/test/x4/with_local.cpp +++ b/test/x4/with_local.cpp @@ -8,8 +8,8 @@ #include "iris_x4_test.hpp" +#include #include -#include #include #include #include From 754e5cac178eb7dc4fd207ae66f59d2ab696b3cd Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:02:20 +0900 Subject: [PATCH 07/11] Rename "auxiliary" to "primitive" --- include/iris/x4.hpp | 2 +- .../iris/x4/{auxiliary.hpp => primitive.hpp} | 11 ++++----- .../iris/x4/{auxiliary => primitive}/eoi.hpp | 4 ++-- .../iris/x4/{auxiliary => primitive}/eol.hpp | 4 ++-- .../iris/x4/{auxiliary => primitive}/eps.hpp | 4 ++-- test/x4/alloy_wrong_substitute.cpp | 2 +- test/x4/alternative.cpp | 8 +++++-- test/x4/as.cpp | 4 +++- test/x4/attribute_type_check.cpp | 2 +- test/x4/eoi.cpp | 2 +- test/x4/eol.cpp | 2 +- test/x4/eps.cpp | 2 +- test/x4/error_handler.cpp | 6 +++-- test/x4/expect.cpp | 24 ++++++++++++------- test/x4/iterator.cpp | 22 ++++++++++------- test/x4/partial_success.cpp | 2 +- test/x4/rule3.cpp | 2 +- test/x4/sequence.cpp | 9 ++++--- test/x4/smart_ptr.cpp | 2 +- test/x4/with_local.cpp | 4 ++-- test/x4/without.cpp | 4 ++-- 21 files changed, 72 insertions(+), 50 deletions(-) rename include/iris/x4/{auxiliary.hpp => primitive.hpp} (71%) rename include/iris/x4/{auxiliary => primitive}/eoi.hpp (95%) rename include/iris/x4/{auxiliary => primitive}/eol.hpp (95%) rename include/iris/x4/{auxiliary => primitive}/eps.hpp (98%) diff --git a/include/iris/x4.hpp b/include/iris/x4.hpp index 36fcb7dd0..55dd2fd0d 100644 --- a/include/iris/x4.hpp +++ b/include/iris/x4.hpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include #include diff --git a/include/iris/x4/auxiliary.hpp b/include/iris/x4/primitive.hpp similarity index 71% rename from include/iris/x4/auxiliary.hpp rename to include/iris/x4/primitive.hpp index 6f9558d2d..87dd63852 100644 --- a/include/iris/x4/auxiliary.hpp +++ b/include/iris/x4/primitive.hpp @@ -1,5 +1,5 @@ -#ifndef IRIS_ZZ_X4_AUXILIARY_HPP -#define IRIS_ZZ_X4_AUXILIARY_HPP +#ifndef IRIS_ZZ_X4_PRIMITIVE_HPP +#define IRIS_ZZ_X4_PRIMITIVE_HPP /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman @@ -12,9 +12,8 @@ ==============================================================================*/ #include -#include -#include -#include -#include +#include +#include +#include #endif diff --git a/include/iris/x4/auxiliary/eoi.hpp b/include/iris/x4/primitive/eoi.hpp similarity index 95% rename from include/iris/x4/auxiliary/eoi.hpp rename to include/iris/x4/primitive/eoi.hpp index 426919743..d276da8ec 100644 --- a/include/iris/x4/auxiliary/eoi.hpp +++ b/include/iris/x4/primitive/eoi.hpp @@ -1,5 +1,5 @@ -#ifndef IRIS_ZZ_X4_AUXILIARY_EOI_HPP -#define IRIS_ZZ_X4_AUXILIARY_EOI_HPP +#ifndef IRIS_ZZ_X4_PRIMITIVE_EOI_HPP +#define IRIS_ZZ_X4_PRIMITIVE_EOI_HPP /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman diff --git a/include/iris/x4/auxiliary/eol.hpp b/include/iris/x4/primitive/eol.hpp similarity index 95% rename from include/iris/x4/auxiliary/eol.hpp rename to include/iris/x4/primitive/eol.hpp index b52529aa7..20bc70999 100644 --- a/include/iris/x4/auxiliary/eol.hpp +++ b/include/iris/x4/primitive/eol.hpp @@ -1,5 +1,5 @@ -#ifndef IRIS_ZZ_X4_AUXILIARY_EOL_HPP -#define IRIS_ZZ_X4_AUXILIARY_EOL_HPP +#ifndef IRIS_ZZ_X4_PRIMITIVE_EOL_HPP +#define IRIS_ZZ_X4_PRIMITIVE_EOL_HPP /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman diff --git a/include/iris/x4/auxiliary/eps.hpp b/include/iris/x4/primitive/eps.hpp similarity index 98% rename from include/iris/x4/auxiliary/eps.hpp rename to include/iris/x4/primitive/eps.hpp index 0c1f1165e..2e9f99b5a 100644 --- a/include/iris/x4/auxiliary/eps.hpp +++ b/include/iris/x4/primitive/eps.hpp @@ -1,5 +1,5 @@ -#ifndef IRIS_ZZ_X4_AUXILIARY_EPS_HPP -#define IRIS_ZZ_X4_AUXILIARY_EPS_HPP +#ifndef IRIS_ZZ_X4_PRIMITIVE_EPS_HPP +#define IRIS_ZZ_X4_PRIMITIVE_EPS_HPP /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman diff --git a/test/x4/alloy_wrong_substitute.cpp b/test/x4/alloy_wrong_substitute.cpp index 79770c077..4df41ed5e 100644 --- a/test/x4/alloy_wrong_substitute.cpp +++ b/test/x4/alloy_wrong_substitute.cpp @@ -1,6 +1,6 @@ #include "iris_x4_test.hpp" -#include +#include #include #include diff --git a/test/x4/alternative.cpp b/test/x4/alternative.cpp index bd698a6f1..d1aab440c 100644 --- a/test/x4/alternative.cpp +++ b/test/x4/alternative.cpp @@ -11,13 +11,17 @@ #include "iris_x4_test.hpp" #include + #include -#include +#include + #include #include -#include #include #include + +#include + #include #include #include diff --git a/test/x4/as.cpp b/test/x4/as.cpp index dc47ae4ba..5325c8284 100644 --- a/test/x4/as.cpp +++ b/test/x4/as.cpp @@ -12,11 +12,13 @@ #include #include -#include +#include + #include #include #include #include + #include #include #include diff --git a/test/x4/attribute_type_check.cpp b/test/x4/attribute_type_check.cpp index 40bcfc35d..1c05f3ace 100644 --- a/test/x4/attribute_type_check.cpp +++ b/test/x4/attribute_type_check.cpp @@ -10,7 +10,7 @@ #include "iris_x4_test.hpp" #include -#include +#include #include #include diff --git a/test/x4/eoi.cpp b/test/x4/eoi.cpp index 9ac6880d8..8d8490b02 100644 --- a/test/x4/eoi.cpp +++ b/test/x4/eoi.cpp @@ -9,7 +9,7 @@ #include "iris_x4_test.hpp" -#include +#include TEST_CASE("eoi") { diff --git a/test/x4/eol.cpp b/test/x4/eol.cpp index 792c6cd0a..240fe08e9 100644 --- a/test/x4/eol.cpp +++ b/test/x4/eol.cpp @@ -9,7 +9,7 @@ #include "iris_x4_test.hpp" -#include +#include TEST_CASE("eol") { diff --git a/test/x4/eps.cpp b/test/x4/eps.cpp index 6d05a0c3f..12863c6f4 100644 --- a/test/x4/eps.cpp +++ b/test/x4/eps.cpp @@ -9,7 +9,7 @@ #include "iris_x4_test.hpp" -#include +#include #include TEST_CASE("eps") diff --git a/test/x4/error_handler.cpp b/test/x4/error_handler.cpp index 1403c14df..7fd764045 100644 --- a/test/x4/error_handler.cpp +++ b/test/x4/error_handler.cpp @@ -10,11 +10,13 @@ #include "iris_x4_test.hpp" #include -#include + +#include #include +#include + #include #include -#include #include #include diff --git a/test/x4/expect.cpp b/test/x4/expect.cpp index 034f19a4f..3b4fa4f79 100644 --- a/test/x4/expect.cpp +++ b/test/x4/expect.cpp @@ -15,18 +15,24 @@ #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 #include #include #include @@ -35,7 +41,7 @@ #include #include #include -#include + #include #include #include @@ -475,7 +481,7 @@ TEST_CASE("expect") }); } - // auxilary parsers + // primitive parsers { X4_TEST_SUCCESS_PASS("a12", lit('a') > eps > +digit); X4_TEST_SUCCESS_PASS("a12", lit('a') > +digit > eoi); diff --git a/test/x4/iterator.cpp b/test/x4/iterator.cpp index 331a6ed9b..94c020884 100644 --- a/test/x4/iterator.cpp +++ b/test/x4/iterator.cpp @@ -12,18 +12,20 @@ #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 #include @@ -32,10 +34,12 @@ #include #include #include + #include #include #include #include + #include #include #include @@ -46,6 +50,8 @@ #include #include +#include + #include #include #include @@ -233,7 +239,7 @@ TEST_CASE("rollback on failed parse (action)") } } -TEST_CASE("rollback on failed parse (auxiliary)") +TEST_CASE("rollback on failed parse (primitive)") { using x4::fixed_value; using x4::eps; diff --git a/test/x4/partial_success.cpp b/test/x4/partial_success.cpp index d5f63cb43..ab6586ad9 100644 --- a/test/x4/partial_success.cpp +++ b/test/x4/partial_success.cpp @@ -9,11 +9,11 @@ #include "iris_x4_test.hpp" #include -#include #include #include #include #include +#include #include #include #include diff --git a/test/x4/rule3.cpp b/test/x4/rule3.cpp index 128b2d2ea..5300ad15e 100644 --- a/test/x4/rule3.cpp +++ b/test/x4/rule3.cpp @@ -10,10 +10,10 @@ #include "iris_x4_test.hpp" #include -#include #include #include #include +#include #include #include #include diff --git a/test/x4/sequence.cpp b/test/x4/sequence.cpp index 4fbff615c..47df56e52 100644 --- a/test/x4/sequence.cpp +++ b/test/x4/sequence.cpp @@ -10,17 +10,20 @@ #include "iris_x4_test.hpp" #include + #include #include -#include +#include #include #include #include -#include -#include #include #include #include + +#include +#include + #include #include #include diff --git a/test/x4/smart_ptr.cpp b/test/x4/smart_ptr.cpp index 069b9ade7..b6224ba4e 100644 --- a/test/x4/smart_ptr.cpp +++ b/test/x4/smart_ptr.cpp @@ -8,7 +8,7 @@ #include "iris_x4_test.hpp" #include -#include +#include #include #include diff --git a/test/x4/with_local.cpp b/test/x4/with_local.cpp index cb78d81ce..d5ca52de4 100644 --- a/test/x4/with_local.cpp +++ b/test/x4/with_local.cpp @@ -9,9 +9,9 @@ #include "iris_x4_test.hpp" #include -#include #include -#include +#include +#include #include #include diff --git a/test/x4/without.cpp b/test/x4/without.cpp index 28829d35b..fd1be30bb 100644 --- a/test/x4/without.cpp +++ b/test/x4/without.cpp @@ -7,9 +7,9 @@ #include "iris_x4_test.hpp" -#include -#include #include +#include +#include #include #include From 7f3b6ab5b21e9c8078bde160333e14d314bace9e Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:11:41 +0900 Subject: [PATCH 08/11] Rename `list` to `delimited_list` --- include/iris/x4/operator.hpp | 2 +- .../iris/x4/operator/{list.hpp => delimited_list.hpp} | 8 ++++---- test/x4/CMakeLists.txt | 2 +- test/x4/alternative.cpp | 2 +- test/x4/container_support.cpp | 2 +- test/x4/{list.cpp => delimited_list.cpp} | 2 +- test/x4/expect.cpp | 4 ++-- test/x4/iterator.cpp | 4 ++-- test/x4/partial_success.cpp | 10 +++++----- test/x4/recursive.cpp | 2 +- test/x4/rule3.cpp | 2 +- test/x4/with.cpp | 2 +- 12 files changed, 21 insertions(+), 21 deletions(-) rename include/iris/x4/operator/{list.hpp => delimited_list.hpp} (93%) rename test/x4/{list.cpp => delimited_list.cpp} (98%) diff --git a/include/iris/x4/operator.hpp b/include/iris/x4/operator.hpp index dfcc0ef58..96224a9a6 100644 --- a/include/iris/x4/operator.hpp +++ b/include/iris/x4/operator.hpp @@ -13,8 +13,8 @@ #include #include #include +#include #include -#include #include #include #include diff --git a/include/iris/x4/operator/list.hpp b/include/iris/x4/operator/delimited_list.hpp similarity index 93% rename from include/iris/x4/operator/list.hpp rename to include/iris/x4/operator/delimited_list.hpp index 18258b92a..d9df58eaa 100644 --- a/include/iris/x4/operator/list.hpp +++ b/include/iris/x4/operator/delimited_list.hpp @@ -24,7 +24,7 @@ namespace iris::x4 { template -struct list : binary_parser> +struct delimited_list : binary_parser> { using attribute_type = traits::default_container::attribute_type>::type; @@ -34,7 +34,7 @@ struct list : binary_parser> traits::can_hold::attribute_type, typename traits::container_value::type> >; - using binary_parser::binary_parser; + using binary_parser::binary_parser; template Se, class Context, X4NonUnusedAttribute Attr> [[nodiscard]] constexpr bool @@ -108,13 +108,13 @@ struct list : binary_parser> }; template -[[nodiscard]] constexpr list, as_parser_plain_t> +[[nodiscard]] constexpr delimited_list, as_parser_plain_t> operator%(Left&& left, Right&& right) noexcept( is_parser_nothrow_castable_v && is_parser_nothrow_castable_v && std::is_nothrow_constructible_v< - list, as_parser_plain_t>, + delimited_list, as_parser_plain_t>, as_parser_t, as_parser_t > diff --git a/test/x4/CMakeLists.txt b/test/x4/CMakeLists.txt index 90fc25e63..1fbf60563 100644 --- a/test/x4/CMakeLists.txt +++ b/test/x4/CMakeLists.txt @@ -51,6 +51,7 @@ x4_define_tests( container_support context debug + delimited_list difference eoi eol @@ -63,7 +64,6 @@ x4_define_tests( iterator kleene lexeme - list lit matches move_to diff --git a/test/x4/alternative.cpp b/test/x4/alternative.cpp index d1aab440c..870b15a57 100644 --- a/test/x4/alternative.cpp +++ b/test/x4/alternative.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include diff --git a/test/x4/container_support.cpp b/test/x4/container_support.cpp index 54490f5ec..9a1df2063 100644 --- a/test/x4/container_support.cpp +++ b/test/x4/container_support.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include diff --git a/test/x4/list.cpp b/test/x4/delimited_list.cpp similarity index 98% rename from test/x4/list.cpp rename to test/x4/delimited_list.cpp index a19c20fb1..464e0774c 100644 --- a/test/x4/list.cpp +++ b/test/x4/delimited_list.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/test/x4/expect.cpp b/test/x4/expect.cpp index 3b4fa4f79..5015a94d8 100644 --- a/test/x4/expect.cpp +++ b/test/x4/expect.cpp @@ -45,7 +45,7 @@ #include #include #include -#include +#include #include #include #include @@ -700,7 +700,7 @@ TEST_CASE("expect") }); } - // list + // delimited_list { X4_TEST_SUCCESS_PASS("ab::ab::ac", (lit('a') >> 'b') % (lit(':') >> ':') >> "::ac"); X4_TEST_SUCCESS_PASS("ab::ab:ac", (lit('a') > 'b') % (lit(':') >> ':') >> ":ac"); diff --git a/test/x4/iterator.cpp b/test/x4/iterator.cpp index 94c020884..39c68008d 100644 --- a/test/x4/iterator.cpp +++ b/test/x4/iterator.cpp @@ -43,7 +43,7 @@ #include #include #include -#include +#include #include #include #include @@ -738,7 +738,7 @@ TEST_CASE("rollback on failed parse (operator)") 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 + CHECK(dummy_bools == std::vector{true});// `delimited_list` parser (within sequence) exposes the side effects } { diff --git a/test/x4/partial_success.cpp b/test/x4/partial_success.cpp index ab6586ad9..c04fca916 100644 --- a/test/x4/partial_success.cpp +++ b/test/x4/partial_success.cpp @@ -23,7 +23,7 @@ // list-like #include #include -#include +#include #include #include @@ -273,7 +273,7 @@ TEST_CASE("partial success (list-like)") STATIC_CHECK(x4::parser_traits::template handles_container); STATIC_CHECK(x4::parser_traits>::template handles_container); STATIC_CHECK(x4::parser_traits>::template handles_container); - STATIC_CHECK(x4::parser_traits>>::template handles_container); + STATIC_CHECK(x4::parser_traits>>::template handles_container); } // kleene @@ -300,7 +300,7 @@ TEST_CASE("partial success (list-like)") CHECK(abcs == "abcabc"sv); // wrong implementation yields "abcabcab" } - // list + // delimited_list { std::string abcs; REQUIRE(parse("abc,abx", abc % ',' >> ",abx", abcs)); @@ -367,7 +367,7 @@ TEST_CASE("partial success (list-like)") STATIC_CHECK(x4::parser_traits::template handles_container); STATIC_CHECK(x4::parser_traits>::template handles_container); STATIC_CHECK(x4::parser_traits>::template handles_container); - STATIC_CHECK(x4::parser_traits>>::template handles_container); + STATIC_CHECK(x4::parser_traits>>::template handles_container); } // kleene @@ -394,7 +394,7 @@ TEST_CASE("partial success (list-like)") CHECK(aOOcs == "aOOcaOOc"sv); // wrong implementation yields "aOOcaOOcab" } - // list + // delimited_list { std::string aOOcs; REQUIRE(parse("aOOc,aOOx", aOOc % ',' >> ",aOOx", aOOcs)); diff --git a/test/x4/recursive.cpp b/test/x4/recursive.cpp index 49b05d123..fae38e9ac 100644 --- a/test/x4/recursive.cpp +++ b/test/x4/recursive.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include diff --git a/test/x4/rule3.cpp b/test/x4/rule3.cpp index 5300ad15e..2c5a4d1df 100644 --- a/test/x4/rule3.cpp +++ b/test/x4/rule3.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/test/x4/with.cpp b/test/x4/with.cpp index f876a4e19..0826dfb34 100644 --- a/test/x4/with.cpp +++ b/test/x4/with.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include From 7dbabd1e023377681a712a01b3632894614fabd3 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 3 Aug 2026 18:02:47 +0900 Subject: [PATCH 09/11] Replace semantic action's `operator[](...)` with `p.on_match(...)` --- include/iris/x4/core/parser.hpp | 23 ++++++++- test/x4/actions.cpp | 8 ++-- test/x4/alternative.cpp | 4 +- test/x4/as.cpp | 82 ++++++++++++++++----------------- test/x4/char_class.cpp | 4 +- test/x4/delimited_list.cpp | 2 +- test/x4/difference.cpp | 4 +- test/x4/expect.cpp | 6 +-- test/x4/int.cpp | 6 +-- test/x4/iterator.cpp | 6 +-- test/x4/kleene.cpp | 4 +- test/x4/omit.cpp | 4 +- test/x4/optional.cpp | 6 +-- test/x4/plus.cpp | 4 +- test/x4/rule2.cpp | 16 +++---- test/x4/rule3.cpp | 12 ++--- test/x4/rule4.cpp | 4 +- test/x4/rule_separate_tu.cpp | 8 ++-- test/x4/sequence.cpp | 4 +- test/x4/symbols1.cpp | 16 +++---- test/x4/uint.cpp | 4 +- test/x4/with.cpp | 18 ++++---- test/x4/with_local.cpp | 76 +++++++++++++++--------------- 23 files changed, 170 insertions(+), 151 deletions(-) diff --git a/include/iris/x4/core/parser.hpp b/include/iris/x4/core/parser.hpp index 24ad68ba5..08c1f6a7b 100644 --- a/include/iris/x4/core/parser.hpp +++ b/include/iris/x4/core/parser.hpp @@ -76,8 +76,9 @@ struct parser : private detail::parser_base decltype(std::declval().derived()), Action > - [[nodiscard]] constexpr action> - operator[](this Self&& self, Action&& f) + [[nodiscard]] + constexpr action> + on_match(this Self&& self, Action&& f) noexcept(std::is_nothrow_constructible_v< action>, decltype(std::forward(self).derived()), @@ -86,6 +87,24 @@ struct parser : private detail::parser_base { return {std::forward(self).derived(), std::forward(f)}; } + + template + requires std::is_constructible_v< + action>, + decltype(std::declval().derived()), + Action + > + [[nodiscard, deprecated("Use `p.on_match(...)` instead. The legacy `operator[]` syntax will be removed because it frequently conflicts with lambda syntax.")]] + constexpr action> + operator[](this Self&& self, Action&& f) + noexcept(std::is_nothrow_constructible_v< + action>, + decltype(std::forward(self).derived()), + Action + >) + { + return std::forward(self).on_match(std::forward(f)); + } }; template diff --git a/test/x4/actions.cpp b/test/x4/actions.cpp index dd4aa10e6..96fb0fdc1 100644 --- a/test/x4/actions.cpp +++ b/test/x4/actions.cpp @@ -19,12 +19,12 @@ TEST_CASE("action") { using x4::int_; - IRIS_X4_ASSERT_CONSTEXPR_CTORS(x4::int_[std::true_type{}]); + IRIS_X4_ASSERT_CONSTEXPR_CTORS(x4::int_.on_match(std::true_type{})); { int x = 0; auto const fun_action = [&](auto&& ctx) { x += x4::_attr(ctx); }; - CHECK(parse("{42}", '{' >> int_[fun_action] >> '}')); + CHECK(parse("{42}", '{' >> int_.on_match(fun_action) >> '}')); } { auto const fail = [](auto&&) { return false; }; @@ -35,7 +35,7 @@ TEST_CASE("action") next = x4::_attr(ctx); }; - REQUIRE(parse(input, x4::int_[fail] | x4::digit[setnext], x4::space).is_partial_match()); + REQUIRE(parse(input, x4::int_.on_match(fail) | x4::digit.on_match(setnext), x4::space).is_partial_match()); CHECK(next == '1'); } @@ -46,7 +46,7 @@ TEST_CASE("action") x4_test::stationary st { 0 }; static_assert(x4::X4Attribute); - REQUIRE(parse("{42}", p[([]{})], st)); + REQUIRE(parse("{42}", p.on_match([]{}), st)); CHECK(st.val == 42); } } diff --git a/test/x4/alternative.cpp b/test/x4/alternative.cpp index 870b15a57..3b758cfd4 100644 --- a/test/x4/alternative.cpp +++ b/test/x4/alternative.cpp @@ -167,8 +167,8 @@ TEST_CASE("alternative") constexpr auto f = [&](auto& ctx){ _rule_var(ctx) = _attr(ctx); }; - (void)(r3 = (eps >> r1)[f]); - (void)(r3 = (r1 | r2)[f]); + (void)(r3 = (eps >> r1).on_match(f)); + (void)(r3 = (r1 | r2).on_match(f)); (void)(r3 = eps >> r1 | r2); (void)r3; } diff --git a/test/x4/as.cpp b/test/x4/as.cpp index 5325c8284..232dc962c 100644 --- a/test/x4/as.cpp +++ b/test/x4/as.cpp @@ -45,7 +45,7 @@ using Se = It; using Context = unused_type; constexpr auto do_nothing = [](auto&&) {}; -constexpr auto disable_attr = eps[([](auto&&) {})]; +constexpr auto disable_attr = eps.on_match([](auto&&) {}); constexpr auto quoted_string = '\'' >> *~x4::char_('\'') >> '\''; char const* empty_input_first = nullptr; @@ -64,11 +64,11 @@ TEST_CASE("as(p)") // with semantic action { { - constexpr auto p = x4::as(fixed_value(3))[([](auto&& ctx) { + constexpr auto p = x4::as(fixed_value(3)).on_match([](auto&& ctx) { static_assert(std::same_as, unused_type>); static_assert(std::same_as, int>); _attr(ctx) += 5; - })]; + }); { int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); @@ -89,7 +89,7 @@ TEST_CASE("as(p)") CHECK(result == 3); } { - constexpr auto p = fixed_value(3)[do_nothing]; + constexpr auto p = fixed_value(3).on_match(do_nothing); int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); CHECK(result == 3); @@ -105,7 +105,7 @@ TEST_CASE("as(p)") } { constexpr auto p = x4::as( - fixed_value(3)[do_nothing] + fixed_value(3).on_match(do_nothing) ); int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); @@ -117,15 +117,15 @@ TEST_CASE("as(p)") { constexpr auto p = x4::as( fixed_value(3) - )[do_nothing]; + ).on_match(do_nothing); int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); CHECK(result == 3); } { constexpr auto p = x4::as( - fixed_value(3)[do_nothing] - )[do_nothing]; + fixed_value(3).on_match(do_nothing) + ).on_match(do_nothing); int result = 42; REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); CHECK(result == 42); @@ -164,12 +164,12 @@ TEST_CASE("as(as(p))") // with semantic action { constexpr auto p = x4::as( - x4::as(fixed_value(3))[([](auto&& ctx) { + x4::as(fixed_value(3)).on_match([](auto&& ctx) { static_assert(std::same_as, int>); static_assert(std::same_as, int>); CHECK(std::addressof(_as_var(ctx)) != std::addressof(_attr(ctx))); _as_var(ctx) = _attr(ctx) + 5; - })] + }) ); { int result = 42; @@ -186,7 +186,7 @@ TEST_CASE("as(as(p))") // do nothing in semantic action { constexpr auto p = x4::as( - x4::as(fixed_value(3))[do_nothing] + x4::as(fixed_value(3)).on_match(do_nothing) ); { @@ -235,11 +235,11 @@ TEST_CASE("as(as(p))") // with semantic action { constexpr auto p = x4::as( - x4::as(fixed_value(short(3)))[([](auto&& ctx) { + x4::as(fixed_value(short(3))).on_match([](auto&& ctx) { static_assert(std::same_as, int>); static_assert(std::same_as, short>); _as_var(ctx) = _attr(ctx) + 5; - })] + }) ); { int result = 42; @@ -256,7 +256,7 @@ TEST_CASE("as(as(p))") // do nothing in semantic action { constexpr auto p = x4::as( - x4::as(fixed_value(short(3)))[do_nothing] + x4::as(fixed_value(short(3))).on_match(do_nothing) ); { @@ -295,11 +295,11 @@ TEST_CASE("as(as(p))") { constexpr auto p = x4::as( - x4::as(+x4::unicode::char_)[([](auto&& ctx) { + x4::as(+x4::unicode::char_).on_match([](auto&& ctx) { static_assert(std::same_as, std::string>); static_assert(std::same_as, std::u32string>); _as_var(ctx) = iris::unicode::transcode(_attr(ctx)); - })] + }) ); std::u32string_view input = U"テスト"; @@ -453,13 +453,13 @@ TEST_CASE("_as_var") constexpr auto string_rule = x4::rule{""} = x4::as( - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { _rule_var(ctx) = "default"; - })] >> + }) >> - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { _as_var(ctx) = "foo"; - })] + }) ); std::string_view const input; @@ -475,13 +475,13 @@ TEST_CASE("_as_var") constexpr auto string_rule = x4::rule{""} = x4::as( - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { _rule_var(ctx) = "default"; - })] >> + }) >> - eps[([]([[maybe_unused]] auto&& ctx) { + eps.on_match([]([[maybe_unused]] auto&& ctx) { static_assert(std::same_as, unused_type>); - })] + }) ) >> disable_attr; // <---------- std::string_view const input; @@ -497,9 +497,9 @@ TEST_CASE("_as_var") constexpr auto unused_rule = x4::as( x4::as( - eps[([]([[maybe_unused]] auto&& ctx) { + eps.on_match([]([[maybe_unused]] auto&& ctx) { static_assert(std::same_as, unused_type>); - })] + }) ) ); @@ -517,14 +517,14 @@ TEST_CASE("_as_var") /*constexpr*/ auto unused_rule = x4::as( fixed_value("default") >> - eps[([]([[maybe_unused]] auto&& ctx) { + eps.on_match([]([[maybe_unused]] auto&& ctx) { static_assert(std::same_as, std::string>); - })] >> + }) >> x4::as( - eps[([]([[maybe_unused]] auto&& ctx) { + eps.on_match([]([[maybe_unused]] auto&& ctx) { static_assert(std::same_as, unused_type>); - })] + }) ) ); @@ -548,15 +548,15 @@ TEST_CASE("_as_var") { constexpr auto string_literal = x4::rule{"StringLiteral"} = - eps[([](auto& ctx) { _rule_var(ctx).is_quoted = false; })] >> + eps.on_match([](auto& ctx) { _rule_var(ctx).is_quoted = false; }) >> x4::as( - x4::lit('"')[([](auto&& ctx) { + x4::lit('"').on_match([](auto&& ctx) { StringLiteral& rule_var = _rule_var(ctx); rule_var.is_quoted = true; - })] >> - *(~x4::char_('"'))[([](auto&& ctx) { _as_var(ctx).push_back(_attr(ctx)); })] >> + }) >> + *(~x4::char_('"')).on_match([](auto&& ctx) { _as_var(ctx).push_back(_attr(ctx)); }) >> '"' - )[([](auto&& ctx) { _rule_var(ctx).text = std::move(_attr(ctx)); })]; + ).on_match([](auto&& ctx) { _rule_var(ctx).text = std::move(_attr(ctx)); }); It first = input.begin(); Se const last = input.end(); @@ -568,15 +568,15 @@ TEST_CASE("_as_var") } { constexpr auto string_literal = x4::rule{"StringLiteral"} = - eps[([](auto& ctx) { _rule_var(ctx).is_quoted = false; })] >> + eps.on_match([](auto& ctx) { _rule_var(ctx).is_quoted = false; }) >> x4::as( - x4::lit('"')[([](auto&& ctx) { + x4::lit('"').on_match([](auto&& ctx) { StringLiteral& rule_var = _rule_var(ctx); rule_var.is_quoted = true; - })] >> + }) >> *~x4::char_('"') >> // <----------------- attribute ignored '"' - )[([](auto&& ctx) { _rule_var(ctx).text = std::move(_attr(ctx)); })]; + ).on_match([](auto&& ctx) { _rule_var(ctx).text = std::move(_attr(ctx)); }); It first = input.begin(); Se const last = input.end(); @@ -588,12 +588,12 @@ TEST_CASE("_as_var") } { constexpr auto string_literal = x4::rule{"StringLiteral"} = - eps[([](auto& ctx) { _rule_var(ctx).is_quoted = false; })] >> + eps.on_match([](auto& ctx) { _rule_var(ctx).is_quoted = false; }) >> x4::as( x4::lit('"') >> // <----------------- no semantic action *~x4::char_('"') >> // <----------------- attribute NOT ignored '"' - )[([](auto&& ctx) { _rule_var(ctx).text = std::move(_attr(ctx)); })]; + ).on_match([](auto&& ctx) { _rule_var(ctx).text = std::move(_attr(ctx)); }); It first = input.begin(); Se const last = input.end(); diff --git a/test/x4/char_class.cpp b/test/x4/char_class.cpp index 4f1269078..775268879 100644 --- a/test/x4/char_class.cpp +++ b/test/x4/char_class.cpp @@ -199,9 +199,9 @@ TEST_CASE("char_class") char ch = '\0'; auto f = [&](auto&& ctx){ ch = _attr(ctx); }; - REQUIRE(parse("x", alnum[f])); + REQUIRE(parse("x", alnum.on_match(f))); CHECK(ch == 'x'); - REQUIRE(parse(" A", alnum[f], space)); + REQUIRE(parse(" A", alnum.on_match(f), space)); CHECK(ch == 'A'); } } diff --git a/test/x4/delimited_list.cpp b/test/x4/delimited_list.cpp index 464e0774c..75b3c3c14 100644 --- a/test/x4/delimited_list.cpp +++ b/test/x4/delimited_list.cpp @@ -114,7 +114,7 @@ TEST_CASE("list") std::string s; auto f = [&](auto&& ctx){ s = std::string(_attr(ctx).begin(), _attr(ctx).end()); }; - REQUIRE(parse("a,b,c,d,e,f,g,h", (char_ % ',')[f])); + REQUIRE(parse("a,b,c,d,e,f,g,h", (char_ % ',').on_match(f))); CHECK(s == "abcdefgh"); } diff --git a/test/x4/difference.cpp b/test/x4/difference.cpp index d561f60d5..c4dd601f1 100644 --- a/test/x4/difference.cpp +++ b/test/x4/difference.cpp @@ -57,7 +57,7 @@ TEST_CASE("difference") std::string s; REQUIRE(parse( "/*abcdefghijk*/", - "/*" >> *(char_ - "*/")[([&](auto&& ctx){ s += _attr(ctx); })] >> "*/" + "/*" >> *(char_ - "*/").on_match([&](auto&& ctx){ s += _attr(ctx); }) >> "*/" )); CHECK(s == "abcdefghijk"); } @@ -65,7 +65,7 @@ TEST_CASE("difference") std::string s; REQUIRE(parse( " /*abcdefghijk*/", - "/*" >> *(char_ - "*/")[([&](auto&& ctx){ s += _attr(ctx); })] >> "*/", + "/*" >> *(char_ - "*/").on_match([&](auto&& ctx){ s += _attr(ctx); }) >> "*/", space )); CHECK(s == "abcdefghijk"); diff --git a/test/x4/expect.cpp b/test/x4/expect.cpp index 5015a94d8..6f13d3805 100644 --- a/test/x4/expect.cpp +++ b/test/x4/expect.cpp @@ -173,9 +173,9 @@ TEST_CASE("expectation_failure_context_uninstantiated_in_expect_less_parse") bool dummy_bool = false; - (void)eps[([]{})].parse(first, last, unused, unused); // action - (void)int_[([]{})].parse(first, last, unused, dummy_int); // action - (void)(int_ >> int_)[([]{})].parse(first, last, unused, dummy_ints); // action + (void)eps.on_match([]{}).parse(first, last, unused, unused); // action + (void)int_.on_match([]{}).parse(first, last, unused, dummy_int); // action + (void)(int_ >> int_).on_match([]{}).parse(first, last, unused, dummy_ints); // action (void)fixed_value(42).parse(first, last, unused, unused); (void)fixed_value("foo").parse(first, last, unused, unused); diff --git a/test/x4/int.cpp b/test/x4/int.cpp index 7a1cc7394..369f45efb 100644 --- a/test/x4/int.cpp +++ b/test/x4/int.cpp @@ -213,12 +213,12 @@ TEST_CASE("int") auto f = [&](auto&& ctx){ n = _attr(ctx); }; - REQUIRE(parse("123", int_[f])); + REQUIRE(parse("123", int_.on_match(f))); CHECK(n == 123); - REQUIRE(parse("789", int_[f], m)); + REQUIRE(parse("789", int_.on_match(f), m)); REQUIRE(n == 789); CHECK(m == 789); - REQUIRE(parse(" 456", int_[f], space)); + REQUIRE(parse(" 456", int_.on_match(f), space)); CHECK(n == 456); } diff --git a/test/x4/iterator.cpp b/test/x4/iterator.cpp index 39c68008d..f346acdbf 100644 --- a/test/x4/iterator.cpp +++ b/test/x4/iterator.cpp @@ -218,14 +218,14 @@ TEST_CASE("rollback on failed parse (action)") { constexpr auto input = "foo"sv; auto first = input.begin(); - REQUIRE_FALSE(eps[([](auto&&) { return false; })].parse(first, input.end(), unused, unused)); + REQUIRE_FALSE(eps.on_match([](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)); + REQUIRE_FALSE(int_.on_match([](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 } @@ -233,7 +233,7 @@ TEST_CASE("rollback on failed parse (action)") 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)); + REQUIRE_FALSE((int_ >> ',' >> int_).on_match([](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 } diff --git a/test/x4/kleene.cpp b/test/x4/kleene.cpp index 693868dad..a86e37f0b 100644 --- a/test/x4/kleene.cpp +++ b/test/x4/kleene.cpp @@ -87,7 +87,7 @@ TEST_CASE("kleene") std::string v; auto f = [&](auto&& ctx){ v = _attr(ctx); }; - REQUIRE(parse("bbbb", (*char_)[f])); + REQUIRE(parse("bbbb", (*char_).on_match(f))); REQUIRE(v.size() == 4); CHECK(v[0] == 'b'); CHECK(v[1] == 'b'); @@ -102,7 +102,7 @@ TEST_CASE("kleene") std::vector v; auto f = [&](auto&& ctx){ v = _attr(ctx); }; - REQUIRE(parse("123 456 789", (*int_)[f], space)); + REQUIRE(parse("123 456 789", (*int_).on_match(f), space)); CHECK(v.size() == 3); CHECK(v[0] == 123); CHECK(v[1] == 456); diff --git a/test/x4/omit.cpp b/test/x4/omit.cpp index bc24f4a18..822ebbd40 100644 --- a/test/x4/omit.cpp +++ b/test/x4/omit.cpp @@ -111,7 +111,7 @@ TEST_CASE("omit") char c = 0; auto f = [&](auto&& ctx){ c = _attr(ctx); }; - REQUIRE(parse("x123\"a string\"", (char_ >> omit[int_] >> "\"a string\"")[f])); + REQUIRE(parse("x123\"a string\"", (char_ >> omit[int_] >> "\"a string\"").on_match(f))); CHECK(c == 'x'); } @@ -120,7 +120,7 @@ TEST_CASE("omit") int n = 0; auto f = [&](auto&& ctx){ n = _attr(ctx); }; - REQUIRE(parse("x 123 \"a string\"", (omit[char_] >> int_ >> "\"a string\"")[f], space)); + REQUIRE(parse("x 123 \"a string\"", (omit[char_] >> int_ >> "\"a string\"").on_match(f), space)); CHECK(n == 123); } diff --git a/test/x4/optional.cpp b/test/x4/optional.cpp index d45152329..93c89346f 100644 --- a/test/x4/optional.cpp +++ b/test/x4/optional.cpp @@ -148,7 +148,7 @@ TEST_CASE("optional") { // test action std::optional n = 0; - REQUIRE(parse("1234", (-int_)[test_attribute_type()], n)); + REQUIRE(parse("1234", (-int_).on_match(test_attribute_type()), n)); CHECK(*n == 1234); } @@ -161,13 +161,13 @@ TEST_CASE("optional") { std::optional n; auto f = [&](auto&& ctx) { n = _attr(ctx); }; - CHECK(parse("abcd", (-int_)[f]).is_partial_match()); + CHECK(parse("abcd", (-int_).on_match(f)).is_partial_match()); CHECK(!n.has_value()); } { std::optional n = 0; auto f = [&](auto&& ctx){ n = _attr(ctx); }; - REQUIRE(parse("1234", (-int_)[f])); + REQUIRE(parse("1234", (-int_).on_match(f))); CHECK(*n == 1234); } diff --git a/test/x4/plus.cpp b/test/x4/plus.cpp index 55ece085f..36019941e 100644 --- a/test/x4/plus.cpp +++ b/test/x4/plus.cpp @@ -87,7 +87,7 @@ TEST_CASE("plus") std::string v; auto f = [&](auto&& ctx){ v = _attr(ctx); }; - REQUIRE(parse("bbb", (+char_)[f])); + REQUIRE(parse("bbb", (+char_).on_match(f))); REQUIRE(v.size() == 3); CHECK(v[0] == 'b'); CHECK(v[1] == 'b'); @@ -99,7 +99,7 @@ TEST_CASE("plus") std::vector v; auto f = [&](auto&& ctx){ v = _attr(ctx); }; - REQUIRE(parse("1 2 3", (+int_)[f], space)); + REQUIRE(parse("1 2 3", (+int_).on_match(f), space)); REQUIRE(v.size() == 3); CHECK(v[0] == 1); CHECK(v[1] == 2); diff --git a/test/x4/rule2.cpp b/test/x4/rule2.cpp index d38aff9e6..dea2d53af 100644 --- a/test/x4/rule2.cpp +++ b/test/x4/rule2.cpp @@ -37,21 +37,21 @@ TEST_CASE("rule2") char ch{}; // This semantic action requires the context auto f = [&](auto&& ctx){ ch = _attr(ctx); }; - REQUIRE(parse("x", a[f])); + REQUIRE(parse("x", a.on_match(f))); CHECK(ch == 'x'); } { char ch{}; // This semantic action requires the (unused) context auto f2 = [&](auto&&){ ch = 'y'; }; - REQUIRE(parse("x", a[f2])); + REQUIRE(parse("x", a.on_match(f2))); CHECK(ch == 'y'); } { char ch{}; // The semantic action may optionally not have any arguments at all auto f3 = [&]{ ch = 'z'; }; - REQUIRE(parse("x", a[f3])); + REQUIRE(parse("x", a.on_match(f3))); CHECK(ch == 'z'); } @@ -70,7 +70,7 @@ TEST_CASE("rule2") { char ch{}; auto f = [&](auto&& ctx){ ch = _attr(ctx); }; - REQUIRE(parse("x", a[f])); + REQUIRE(parse("x", a.on_match(f))); CHECK(ch == 'x'); } { @@ -81,7 +81,7 @@ TEST_CASE("rule2") { char ch{}; auto f = [&](auto&& ctx) { ch = _attr(ctx); }; - REQUIRE(parse("x", a[f])); + REQUIRE(parse("x", a.on_match(f))); CHECK(ch == 'x'); } { @@ -101,7 +101,7 @@ TEST_CASE("rule2") std::string s; auto f = [&](auto&& ctx) { s = _attr(ctx); }; - REQUIRE(parse("a,b,c,d,e,f", r[f])); + REQUIRE(parse("a,b,c,d,e,f", r.on_match(f))); CHECK(s == "abcdef"); } { @@ -109,7 +109,7 @@ TEST_CASE("rule2") std::string s; auto f = [&](auto&& ctx) { s = _attr(ctx); }; - REQUIRE(parse("a,b,c,d,e,f", r[f])); + REQUIRE(parse("a,b,c,d,e,f", r.on_match(f))); CHECK(s == "abcdef"); } { @@ -117,7 +117,7 @@ TEST_CASE("rule2") std::string s; auto f = [&](auto&& ctx) { s = _attr(ctx); }; - REQUIRE(parse("abcdef", r[f])); + REQUIRE(parse("abcdef", r.on_match(f))); CHECK(s == "abcdef"); } } diff --git a/test/x4/rule3.cpp b/test/x4/rule3.cpp index 2c5a4d1df..11f658cf6 100644 --- a/test/x4/rule3.cpp +++ b/test/x4/rule3.cpp @@ -117,9 +117,9 @@ TEST_CASE("rule3") std::string s; using rule_type = rule; - auto rdef = rule_type{} = alpha[([](auto&& ctx) { + auto rdef = rule_type{} = alpha.on_match([](auto&& ctx) { x4::_rule_var(ctx) += x4::_attr(ctx); - })]; + }); REQUIRE(parse("abcdef", +rdef, s)); CHECK(s == "abcdef"); @@ -131,21 +131,21 @@ TEST_CASE("rule3") using rule_type = rule; auto rdef = rule_type() = - alpha[([](auto&& ctx) { + alpha.on_match([](auto&& ctx) { _rule_var(ctx) += _attr(ctx); - })]; + }); REQUIRE(parse("abcdef", +rdef, s)); CHECK(s == "abcdef"); } { - auto r = rule{} = eps[([] ([[maybe_unused]] auto&& ctx) { + auto r = rule{} = eps.on_match([] ([[maybe_unused]] auto&& ctx) { static_assert( std::is_same_v, unused_type>, "Attr must not be synthesized" ); - })]; + }); CHECK(parse("", r)); } diff --git a/test/x4/rule4.cpp b/test/x4/rule4.cpp index 4824331fa..018c60317 100644 --- a/test/x4/rule4.cpp +++ b/test/x4/rule4.cpp @@ -105,8 +105,8 @@ TEST_CASE("rule4") rule rb; (void)rb; auto f = [](auto&&) {}; - auto ra_def = ra %= int_[f]; - auto ra_def2 = (rb = (ra %= int_[f])); + auto ra_def = ra %= int_.on_match(f); + auto ra_def2 = (rb = (ra %= int_.on_match(f))); { int attr = 0; diff --git a/test/x4/rule_separate_tu.cpp b/test/x4/rule_separate_tu.cpp index 8f849d768..df713c4a5 100644 --- a/test/x4/rule_separate_tu.cpp +++ b/test/x4/rule_separate_tu.cpp @@ -20,19 +20,19 @@ namespace sem_act { constexpr auto nop = [](auto const&) {}; constexpr x4::rule used_attr1 = "used_attr1"; -constexpr auto used_attr1_def = used_attr::grammar[nop]; +constexpr auto used_attr1_def = used_attr::grammar.on_match(nop); IRIS_X4_DEFINE(used_attr1); constexpr x4::rule used_attr2 = "used_attr2"; -constexpr auto used_attr2_def = unused_attr::grammar[nop]; +constexpr auto used_attr2_def = unused_attr::grammar.on_match(nop); IRIS_X4_DEFINE(used_attr2); constexpr x4::rule unused_attr1 = "unused_attr1"; -constexpr auto unused_attr1_def = used_attr::grammar[nop]; +constexpr auto unused_attr1_def = used_attr::grammar.on_match(nop); IRIS_X4_DEFINE(unused_attr1); constexpr x4::rule unused_attr2 = "unused_attr2"; -constexpr auto unused_attr2_def = unused_attr::grammar[nop]; +constexpr auto unused_attr2_def = unused_attr::grammar.on_match(nop); IRIS_X4_DEFINE(unused_attr2); } // sem_act diff --git a/test/x4/sequence.cpp b/test/x4/sequence.cpp index 47df56e52..55ac84c6c 100644 --- a/test/x4/sequence.cpp +++ b/test/x4/sequence.cpp @@ -477,7 +477,7 @@ TEST_CASE("sequence") n = alloy::get<1>(_attr(ctx)); }; - REQUIRE(parse("x123\"a string\"", (char_ >> int_ >> "\"a string\"")[f])); + REQUIRE(parse("x123\"a string\"", (char_ >> int_ >> "\"a string\"").on_match(f))); CHECK(c == 'x'); CHECK(n == 123); } @@ -491,7 +491,7 @@ TEST_CASE("sequence") n = alloy::get<1>(_attr(ctx)); }; - REQUIRE(parse("x 123 \"a string\"", (char_ >> int_ >> "\"a string\"")[f], space)); + REQUIRE(parse("x 123 \"a string\"", (char_ >> int_ >> "\"a string\"").on_match(f), space)); CHECK(c == 'x'); CHECK(n == 123); } diff --git a/test/x4/symbols1.cpp b/test/x4/symbols1.cpp index d2bd863c4..4fe0f11fb 100644 --- a/test/x4/symbols1.cpp +++ b/test/x4/symbols1.cpp @@ -159,21 +159,21 @@ TEST_CASE("symbols1") int i = 0; auto f = [&](auto&& ctx){ i = _attr(ctx); }; // lambda with capture (important for subsequent checks) - using Parser = std::remove_cvref_t; + using Parser = std::remove_cvref_t; STATIC_CHECK(x4::X4ExplicitSubject); - CHECK(parse("Joel", sym[f])); + CHECK(parse("Joel", sym.on_match(f))); CHECK(i == 1); - CHECK(parse("Ruby", sym[f])); + CHECK(parse("Ruby", sym.on_match(f))); CHECK(i == 2); - CHECK(parse("Tenji", sym[f])); + CHECK(parse("Tenji", sym.on_match(f))); CHECK(i == 3); - CHECK(parse("Tutit", sym[f])); + CHECK(parse("Tutit", sym.on_match(f))); CHECK(i == 4); - CHECK(parse("Kim", sym[f])); + CHECK(parse("Kim", sym.on_match(f))); CHECK(i == 5); - CHECK(parse("Joey", sym[f])); + CHECK(parse("Joey", sym.on_match(f))); CHECK(i == 6); - CHECK(!parse("XXX", sym[f])); + CHECK(!parse("XXX", sym.on_match(f))); } } diff --git a/test/x4/uint.cpp b/test/x4/uint.cpp index d255fad24..729ce1cac 100644 --- a/test/x4/uint.cpp +++ b/test/x4/uint.cpp @@ -204,9 +204,9 @@ TEST_CASE("uint") int n = 0; auto f = [&](auto&& ctx){ n = _attr(ctx); }; - REQUIRE(parse("123", uint_[f])); + REQUIRE(parse("123", uint_.on_match(f))); CHECK(n == 123); - REQUIRE(parse(" 456", uint_[f], space)); + REQUIRE(parse(" 456", uint_.on_match(f), space)); CHECK(n == 456); } diff --git a/test/x4/with.cpp b/test/x4/with.cpp index 0826dfb34..bfc9f3e5f 100644 --- a/test/x4/with.cpp +++ b/test/x4/with.cpp @@ -50,11 +50,11 @@ using x4::with; using x4::_attr; template -constexpr auto value_equals = int_[([](auto&& ctx) { +constexpr auto value_equals = int_.on_match([](auto&& ctx) { auto&& with_val = x4::get(ctx); static_assert(std::same_as); return with_val == _attr(ctx); -})]; +}); } // anonymous @@ -148,22 +148,22 @@ TEST_CASE("with") { // injecting non-const lvalue into the context int val = 0; - auto const r = int_[([](auto&& ctx){ + auto const r = int_.on_match([](auto&& ctx){ x4::get(ctx) += x4::_attr(ctx); - })]; + }); REQUIRE(parse("123,456", with(val)[r % ','])); CHECK(val == 579); } { // injecting rvalue into the context - auto const r1 = int_[([](auto&& ctx){ + auto const r1 = int_.on_match([](auto&& ctx){ x4::get(ctx) += x4::_attr(ctx); - })]; + }); auto const r2 = rule() = - x4::lit('(') >> (r1 % ',') >> x4::lit(')')[([](auto&& ctx){ + x4::lit('(') >> (r1 % ',') >> x4::lit(')').on_match([](auto&& ctx){ x4::_rule_var(ctx) = x4::get(ctx); - })]; + }); int attr = 0; REQUIRE(parse("(1,2,3)", with(100)[r2], attr)); CHECK(attr == 106); @@ -186,7 +186,7 @@ TEST_CASE("with") auto f = [](auto&& ctx){ x4::_rule_var(ctx) = x4::_attr(ctx) + functor()(x4::get(ctx)); }; - auto const r = rule() = int_[f]; + auto const r = rule() = int_.on_match(f); { int attr = 0; diff --git a/test/x4/with_local.cpp b/test/x4/with_local.cpp index d5ca52de4..e547976d4 100644 --- a/test/x4/with_local.cpp +++ b/test/x4/with_local.cpp @@ -34,13 +34,13 @@ TEST_CASE("with_local") { constexpr auto p = with_local[ as( - int_[([](auto&& ctx) { + int_.on_match([](auto&& ctx) { CHECK(_local_var(ctx) == 0); _local_var(ctx) = _attr(ctx) * 100; - })] >> - eps[([](auto&& ctx) { + }) >> + eps.on_match([](auto&& ctx) { _as_var(ctx) = _local_var(ctx); - })] + }) ) ]; constexpr std::string_view input = "42"; @@ -55,13 +55,13 @@ TEST_CASE("with_local") int i = -1; auto const p = with_local[ - int_[([](auto&& ctx) { + int_.on_match([](auto&& ctx) { CHECK(_local_var(ctx) == 0); _local_var(ctx) = _attr(ctx) * 100; - })] >> - eps[([&](auto&& ctx) { + }) >> + eps.on_match([&](auto&& ctx) { i = _local_var(ctx); - })] + }) ]; constexpr std::string_view input = "42"; auto first = input.begin(); @@ -74,25 +74,25 @@ TEST_CASE("with_local") int i = -1; auto const p = with_local[ - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { CHECK(_local_var(ctx) == 0); - })] >> + }) >> with_local[ - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { CHECK(_local_var(ctx) == 0); - })] >> - int_[([](auto&& ctx) { + }) >> + int_.on_match([](auto&& ctx) { _local_var(ctx) = _attr(ctx); - })] >> - eps[([&](auto&& ctx) { + }) >> + eps.on_match([&](auto&& ctx) { i = _local_var(ctx) * 100; - })] + }) ] >> - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { CHECK(_local_var(ctx) == 0); - })] + }) ]; constexpr std::string_view input = "42"; auto first = input.begin(); @@ -105,27 +105,27 @@ TEST_CASE("with_local") double d = -1.0; auto const p = with_local[ - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { static_assert(std::same_as); CHECK(_local_var(ctx) == 0); - })] >> + }) >> with_local[ - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { static_assert(std::same_as); CHECK(_local_var(ctx) == 0.0); - })] >> - int_[([](auto&& ctx) { + }) >> + int_.on_match([](auto&& ctx) { _local_var(ctx) = _attr(ctx); - })] >> - eps[([&](auto&& ctx) { + }) >> + eps.on_match([&](auto&& ctx) { d = std::ceil(_local_var(ctx) / 10); - })] + }) ] >> - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { CHECK(_local_var(ctx) == 0); - })] + }) ]; constexpr std::string_view input = "42"; auto first = input.begin(); @@ -141,26 +141,26 @@ TEST_CASE("with_local") struct B_ID {}; auto const p = with_local[ - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { CHECK(x4::get(ctx) == 0); - })] >> + }) >> with_local[ - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { CHECK(x4::get(ctx) == 0.0); - })] >> - int_[([](auto&& ctx) { + }) >> + int_.on_match([](auto&& ctx) { x4::get(ctx) = _attr(ctx) * 10; x4::get(ctx) = _attr(ctx) * 100; - })] >> - eps[([&](auto&& ctx) { + }) >> + eps.on_match([&](auto&& ctx) { res = std::make_tuple(x4::get(ctx), x4::get(ctx)); - })] + }) ] >> - eps[([](auto&& ctx) { + eps.on_match([](auto&& ctx) { CHECK(x4::get(ctx) == 420); - })] + }) ]; constexpr std::string_view input = "42"; auto first = input.begin(); From b3a8a56236e095a94f6325c2bbc4f0b7ab7fb29a Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 3 Aug 2026 18:10:47 +0900 Subject: [PATCH 10/11] Remove old `operator/` --- include/iris/x4/core/action.hpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/include/iris/x4/core/action.hpp b/include/iris/x4/core/action.hpp index d0c02ee18..b3b70234b 100644 --- a/include/iris/x4/core/action.hpp +++ b/include/iris/x4/core/action.hpp @@ -249,27 +249,6 @@ struct action : proxy_parser> } }; -template -[[nodiscard, deprecated( - "Use `operator[]` instead. The symbol `/` normally means \"ordered choice\" " - "in PEG, and is irrelevant to semantic actions. Furthermore, using C++'s " - "`operator/` for this purpose may introduce surprising behavior when it's " - "mixed with ordinary PEG operators, for instance, the unary `operator+`, " - "due to precedence." -)]] -constexpr action, std::remove_cvref_t> -operator/(Subject&& p, Action&& f) - noexcept( - is_parser_nothrow_castable_v && - std::is_nothrow_constructible_v< - action, std::remove_cvref_t>, - as_parser_t, Action - > - ) -{ - return {as_parser(std::forward(p)), std::forward(f)}; -} - } // iris::x4 #endif From caa6efd389a51cf4699f37ea6461d44b713ea95c Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 3 Aug 2026 20:28:35 +0900 Subject: [PATCH 11/11] Add basic documentation about the entire library --- README.md | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 191 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 62f7a1700..5be9de9b8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ X4 scales from quick prototypes to production parsers for DSLs, data formats, an - C++23 and C++26 - GCC 14 - Clang 21 -- MSVC 2022 and 2026 +- MSVC 2026 ## How to use X4 @@ -28,3 +28,193 @@ Edit your `CMakeLists.txt`: add_subdirectory(modules/x4) target_link_libraries(my_app PUBLIC Iris::X4) ``` + + +## Terminology + +### "attribute" + +An *attribute* is the value produced by a successful parse. It represents the semantic result of a parser after it consumes input, and is propagated through combinators, directives, and rules according to their transformation rules. Attributes may be primitive values, containers, or user-defined types, and can be constructed, transformed, or suppressed depending on the parser expression. + +Phrases like "the attribute type of a parser" usually refer to the value type of the attribute produced by that parser class. + +### "semantic action" + +A *semantic action* is a user-provided invocable object (usually a lambda) that is executed when a parser successfully matches its input. It is used to inspect, transform, or validate the parsed result, and may optionally influence control flow by accepting or rejecting the match. Semantic actions operate on the current parsing context and the attribute produced by the parser, allowing fine-grained post-processing of successful parses. + +The primary syntax for attaching a semantic action is `p.on_match(f)`, where `f` is invoked after `p` matches. The callable may observe the matched attribute, access contextual information, and optionally return a boolean to either accept the result or force the parser to treat the match as a failure, enabling backtracking when appropriate. + +The signature of a semantic action is `[](auto&& ctx) { /* ... */ }`. + +> While highly flexible and convenient, **it is generally discouraged to introduce semantic actions prematurely as part of a language's syntax definition,** since most grammars can be expressed purely through combinations of concrete parsers (typically `x4::rule` parsers) when they are properly structured. +> +> The primary intended use of semantic actions is to handle cases that require ad hoc transformation, such as constructing a binary operator object through a more complex algorithm like precedence climbing. + + +## Directory Structure + +Each parser header is organized into subdirectories as needed, but there are several special directories that contain very specific kinds of components. These are described below. + +#### `core/` + +The core components of X4. In contrast to the facilities in the `traits/` directory, the core components are not intended to be user-customizable. + +> **Note for contributors:** every non-detail header in this directory must start with `#include `. + +#### `traits/` + +Customizable type traits that allow user-defined types to participate in parser logic or attribute processing. A trait may alter parser semantics, type classification, compatibility checks, attribute propagation, storage, conversion, or transformation. + +#### `operator/` + +Directives or combinators that use overloaded C++ operators as their composition syntax. They form the fundamental grammar-composition vocabulary, including sequence, alternative, repetition, optionality, predicates, difference, and list composition. + +#### `directive/` + +Parser adapters or combinators that operate on one or more subject parsers and modify parsing behavior, context, control flow, repetition, or interpretation. They do not use special C++ operators for composition, but normally use subscript syntax such as `directive[p]` to express the combination. Facilities whose primary purpose is value production or attribute representation may instead belong to `attribute/`. + +#### Other Subdirectories + +Remaining facilities that do not fit the categories described above are organized into additional subdirectories when a clear semantic grouping warrants it. + + +## Quick Reference + +The descriptions below focus on recognition behavior and omit many details concerning attribute propagation and customization. + +### Operators + +| Syntax | Meaning | +| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `a \| b` | Try `a`. If it fails normally, restore the input position and try `b`. An expectation failure prevents the fallback to `b`. | +| `a >> b` | Parse `a`, followed by `b`. If either parser fails normally, restore the input position to the beginning of the sequence. | +| `a > b` | Parse `a`, followed by an expected `b`. Equivalent to `a >> expect[b]`. Failure of `b` records an expectation failure and prevents ordinary backtracking. | +| `*p` | Parse zero or more occurrences of `p`. | +| `+p` | Parse one or more occurrences of `p`. | +| `p % d` | Parse one or more occurrences of `p`, separated by `d`. | +| `a - b` | Parse `a` only when `b` does not match at the same input position. The test of `b` does not consume input. | +| `-p` | Parse zero or one occurrence of `p`. | +| `&p` | Succeed when `p` matches, without consuming input or producing its attribute. | +| `!p` | Succeed when `p` does not match, without consuming input or producing an attribute. | + +### Major Directives + +These facilities commonly appear in production language grammars. + +| Syntax | Meaning | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `p.on_match(f)` | After `p` matches, invoke the semantic action `f`. The action may inspect or modify the parsing context and may reject the match. | +| `expect[p]` | Parse `p`. If it fails, record an expectation failure that prevents ordinary backtracking and alternative recovery. Usually invoked indirectly via the `a > b` syntax. | +| `lexeme[p]` | Perform the normal pre-skip, then parse `p` with automatic skipping disabled inside it. | +| `with(value)[p]` | Bind `value` to the context id `ID` while parsing `p`.
The instance be fetched via `x4::get(ctx)` in semantic action. | +| `with_local[p]` | Create a value-initialized local value of type `T` for each invocation of `p` and bind it to the context id `ID`. If `ID` is omitted, the default local-variable context id (`x4::contexts::local_var`) is used.
The instance be fetched via `x4::get(ctx)` or `x4::_local_var(ctx)`, respectively, in semantic action. | + +### Minor Directives + +These facilities are used less frequently in ordinary language grammars. + +| Syntax | Meaning | +| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `omit[p]` | Parse `p`, but suppress its attribute. | +| `skip(s)[p]` | Parse `p` using `s` as its active skipper, replacing the previously active skipper within the directive. | +| `no_skip[p]` | Parse `p` without performing a pre-skip and with automatic skipping disabled inside it. Most ordinary contiguous-token parsing can instead use `lexeme[p]`. | +| `no_case[p]` | Apply case-insensitive character and string comparison while parsing `p`. | +| `matches[p]` | Attempt to parse `p` and expose the result as `bool`: `true` when `p` matches and `false` when it fails normally. | +| `repeat(n)[p]` | Parse exactly `n` occurrences of `p`. | +| `repeat(min, max)[p]` | Parse between `min` and `max` occurrences of `p`, inclusive. | +| `repeat(min, x4::repeat_inf)[p]` | Parse at least `min` occurrences of `p`, with no upper limit. | +| `without[p]` | Remove every context entry whose key is one of `IDs...` while parsing `p`.
Useful for sanitizing the context type correlated with the `x4::rule` type required by `IRIS_X4_INSTANTIATE`. | + +### Attribute Facilities + +| Syntax | Meaning | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `as(p)` | Force `p` to expose `T` as its attribute type. When the outer destination has another compatible type, parse into a temporary `T` and move the result into the destination. | +| `fixed_value(value)` | Always succeed without consuming input and copy the stored `value` into the exposed attribute. | +| `reset_value` | Always succeed without consuming input and reset the exposed attribute. Containers are cleared; other values are assigned a value-initialized instance. | +| `unique_ptr(p)` | Expose a `std::unique_ptr` attribute and parse `p` into its pointee. Deduce the pointee type from the attribute of `p`. | +| `unique_ptr(p)` | Expose a `std::unique_ptr` attribute and parse `p` into its pointee. An optional deleter type `D` may also be specified. | +| `shared_ptr(p)` | Expose a `std::shared_ptr` attribute and parse `p` into its pointee. Deduce the pointee type from the attribute of `p`. | +| `shared_ptr(p)` | Expose a `std::shared_ptr` attribute and parse `p` into its pointee. An optional deleter type `D` may also be specified. | + +### Primitives + +| Syntax | Meaning | +| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `eol` | Match and consume an end-of-line sequence: `"\n"`, `"\r"`, or `"\r\n"`. | +| `eoi` | Succeed only when the input has been exhausted. | +| `eps` | Always succeed without consuming input. | +| `eps(cond)` | Succeed without consuming input when `cond` is `true`. | +| `eps(f)` | Lazily invoke `f` and succeed without consuming input when it returns `true`. The callable may accept the parsing context or no arguments. | + +### Character and String Parsers + +| Syntax | Meaning | +| ----------------- | -------------------------------------------------------------------------------------------- | +| `lit('a')` | Match the character `'a'` without producing an attribute. | +| `lit("str")` | Match the exact string `"str"` without producing an attribute. | +| `char_` | Match any valid character and expose the matched character. | +| `char_('a')` | Match the character `'a'` and expose it. | +| `char_("abc")` | Match one character from the set `{'a', 'b', 'c'}` and expose it. | +| `char_("a-zA-Z")` | Match and expose one character from the specified character set. A hyphen defines an inclusive range, so this example matches an alphabet. | +| `char_('a', 'z')` | Match one character in the inclusive range `'a'` through `'z'` and expose it. | +| `~char_(...)` | Match and expose one character not accepted by the enclosed character parser, set, or range. | +| `string("str")` | Match the exact string `"str"` and expose the matched string. | + +### Numeric Parsers + +#### Boolean Parsers + +| Syntax | Meaning | +| -------- | ---------------------------------------------------------------------- | +| `bool_` | Match `"true"` or `"false"` and expose the corresponding `bool` value. | +| `true_` | Match `"true"` and expose `true`. | +| `false_` | Match `"false"` and expose `false`. | + +#### Signed Integer Parsers + +| Syntax | Meaning | +| ----------- | --------------------------------------------------------------- | +| `short_` | Parse a signed integer and expose the result as `short`. | +| `int_` | Parse a signed integer and expose the result as `int`. | +| `long_` | Parse a signed integer and expose the result as `long`. | +| `long_long` | Parse a signed integer and expose the result as `long long`. | +| `int8` | Parse a signed integer and expose the result as `std::int8_t`. | +| `int16` | Parse a signed integer and expose the result as `std::int16_t`. | +| `int32` | Parse a signed integer and expose the result as `std::int32_t`. | +| `int64` | Parse a signed integer and expose the result as `std::int64_t`. | + +#### Unsigned Integer Parsers + +| Syntax | Meaning | +| ------------ | ----------------------------------------------------------------------- | +| `ushort_` | Parse an unsigned integer and expose the result as `unsigned short`. | +| `uint_` | Parse an unsigned integer and expose the result as `unsigned int`. | +| `ulong_` | Parse an unsigned integer and expose the result as `unsigned long`. | +| `ulong_long` | Parse an unsigned integer and expose the result as `unsigned long long`. | +| `uint8` | Parse an unsigned integer and expose the result as `std::uint8_t`. | +| `uint16` | Parse an unsigned integer and expose the result as `std::uint16_t`. | +| `uint32` | Parse an unsigned integer and expose the result as `std::uint32_t`. | +| `uint64` | Parse an unsigned integer and expose the result as `std::uint64_t`. | +| `bin` | Parse a base-2 unsigned integer and expose the result as `unsigned int`. | +| `oct` | Parse a base-8 unsigned integer and expose the result as `unsigned int`. | +| `hex` | Parse a base-16 unsigned integer and expose the result as `unsigned int`. | + +#### Real Number Parsers + +| Syntax | Meaning | +| ------------- | ---------------------------------------------------------- | +| `float_` | Parse a signed real number and expose the result as `float`. | +| `double_` | Parse a signed real number and expose the result as `double`. | +| `long_double` | Parse a signed real number and expose the result as `long double`. | + +### Context Fetchers + +The following function objects retrieve commonly used values from the parsing context (usually via a semantic action). Each fetcher returns the context entry itself, normally by reference, and is available only when the corresponding entry exists in the current context. + +| Syntax | Meaning | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `_attr(ctx)` | Return the attribute associated with the current semantic action. This is the attribute instance produced by the parser `p` on the expression `p.on_match(f)`. It is available only when that parser exposes a non-`unused_type` attribute. Equivalent to `x4::get(ctx)`. | +| `_rule_var(ctx)` | Return the attribute variable of the innermost active `x4::rule` invocation. In a recursive rule, this always refers to the current recursive invocation rather than an outer invocation. Equivalent to `x4::get(ctx)`. | +| `_local_var(ctx)` | Return the innermost local variable created by `with_local[p]` using the default `x4::contexts::local_var` context id. Equivalent to `x4::get(ctx)`.
*Note:* when `with_local[p]` uses a custom id, retrieve the value with `x4::get(ctx)` instead. | +| `_as_var(ctx)` | Return the attribute variable managed by the innermost active `as(p)` facility. Semantic actions inside `p` can use this fetcher to access the value being constructed for the enclosing `as` parser. Equivalent to `x4::get(ctx)`. |