diff --git a/include/iris/x4/core/action.hpp b/include/iris/x4/core/action.hpp index e3ad26b34..d0c02ee18 100644 --- a/include/iris/x4/core/action.hpp +++ b/include/iris/x4/core/action.hpp @@ -17,7 +17,6 @@ #include #include -#include // subrange #include #include #include @@ -71,6 +70,7 @@ struct action : proxy_parser> static constexpr bool has_action = true; static constexpr bool need_rcontext = true; + static constexpr bool requires_exact_attribute_type = false; // reset ActionF f; @@ -83,22 +83,44 @@ struct action : proxy_parser> { } - // attr==unused, action wants attribute - template Se, class Context> + // When the exposed attribute is `unused_type`. + // Since we can assume that the subject unconditionally requires `_attr`, + // we must create a temporary variable to pass it to the subject. + template Se, class Context, X4UnusedAttribute UnusedAttr> [[nodiscard]] constexpr bool - parse(It& first, Se const& last, Context const& ctx, unused_type) const + parse(It& first, Se const& last, Context const& ctx, UnusedAttr&) const noexcept( std::is_nothrow_default_constructible_v && noexcept(this->parse_main(first, last, ctx, std::declval())) ) { - // Synthesize the attribute since one is not supplied - typename base_type::attribute_type attribute; // default-initialize - return this->parse_main(first, last, ctx, attribute); + typename base_type::attribute_type attr_temp; // default-initialize + return this->parse_main(first, last, ctx, attr_temp); } - // Catch-all overload for non-unused_type attribute - template Se, class Context, X4Attribute Attr> +private: + // We need to handle the general case when `Attr` and the subject's attribute + // type are different. + // + // We can't unconditionally create `attr_temp` of the exact matching type + // because there exists some cases where the temporary variable is truly + // unnecessary. + // + // For instance, when the exposed attribute is `std::vector` and the + // underlying parser is `int_ >> int_` (attr is `alloy::tuple`), + // we must just pass the exposed vector variable directly. + // + // Conversely, the only reliable method to determine whether the underlying + // parser really needs the exact matching type is by checking the dedicated + // trait flag like below. + template + static constexpr bool can_pass_exposed_attr = + std::same_as || !Subject::requires_exact_attribute_type; + +public: + // When the exposed attribute is NOT `unused_type`. + template Se, class Context, X4NonUnusedAttribute Attr> + requires can_pass_exposed_attr [[nodiscard]] constexpr bool parse(It& first, Se const& last, Context const& ctx, Attr& attr) const noexcept(noexcept(this->parse_main(first, last, ctx, attr))) @@ -106,6 +128,20 @@ struct action : proxy_parser> return this->parse_main(first, last, ctx, attr); } + // When the exposed attribute is NOT `unused_type`. + template Se, class Context, X4NonUnusedAttribute Attr> + requires (!can_pass_exposed_attr) + [[nodiscard]] constexpr bool + parse(It& first, Se const& last, Context const& ctx, Attr& /* attr is discarded */) const + noexcept( + std::is_nothrow_default_constructible_v && + noexcept(this->parse_main(first, last, ctx, std::declval())) + ) + { + typename base_type::attribute_type attr_temp; // default-initialize + return this->parse_main(first, last, ctx, attr_temp); + } + constexpr void operator[](auto const&) const = delete; // You can't add semantic action for semantic action [[nodiscard]] constexpr std::string get_x4_info() const diff --git a/include/iris/x4/core/parser.hpp b/include/iris/x4/core/parser.hpp index efdcbf68c..988e0f555 100644 --- a/include/iris/x4/core/parser.hpp +++ b/include/iris/x4/core/parser.hpp @@ -48,6 +48,7 @@ struct parser : private detail::parser_base static constexpr bool has_action = false; static constexpr bool need_rcontext = false; + static constexpr bool requires_exact_attribute_type = false; [[nodiscard]] constexpr Derived& derived() & noexcept { diff --git a/include/iris/x4/directive/as.hpp b/include/iris/x4/directive/as.hpp index c4f2c4a6c..6f4c1d47c 100644 --- a/include/iris/x4/directive/as.hpp +++ b/include/iris/x4/directive/as.hpp @@ -22,17 +22,17 @@ namespace iris::x4 { namespace detail { -template +template struct as_directive_ctx_impl // false { using type = Context; }; -template -struct as_directive_ctx_impl +template +struct as_directive_ctx_impl { using type = std::remove_cvref_t( std::declval(), - std::declval() + std::declval() ))>; }; @@ -53,6 +53,7 @@ struct as_directive : unary_parser> static constexpr bool has_attribute = !std::same_as; 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 @@ -60,39 +61,42 @@ struct as_directive : unary_parser> // handle this case. private: - static constexpr bool need_as_var = Subject::has_action; + template + using exposed_attr_for_child_t = std::conditional_t< + Subject::has_action, unused_type, Attr + >; public: - // `as(as(subject))` forwards the outer `T&` for `subject` + // `outer_parser(as(subject))` forwards the outer `T&` (exposed attribute) for the subject template Se, class Context, X4Attribute OuterAttr> 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, OuterAttr>) + noexcept(is_nothrow_parsable_v::type, exposed_attr_for_child_t>) { - if constexpr (need_as_var) { - return this->subject.parse(first, last, x4::replace_first_context(ctx, outer_attr), outer_attr); + if constexpr (Subject::has_action) { + return this->subject.parse(first, last, x4::replace_first_context(ctx, outer_attr), unused); } else { return this->subject.parse(first, last, ctx, outer_attr); } } - // `as(as(subject))` gives `unused_type` for `subject` + // `outer_parser(as(subject))` forwards `unused` for the subject template Se, class Context, X4UnusedAttribute OuterAttr> requires (!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 (need_as_var) { + if constexpr (Subject::has_action) { return this->subject.parse(first, last, x4::replace_first_context(ctx, unused), unused); } else { return this->subject.parse(first, last, ctx, unused); } } - // `as(as(subject))` gives `T` for `subject`, then move `T&&` to `U&` + // `outer_parser(as(subject))` forwards temporary `T` local variable for the subject, then move the variable to `U&` template Se, class Context, X4NonUnusedAttribute OuterAttr> requires (!std::same_as, T>) && @@ -100,7 +104,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, T> && + is_nothrow_parsable_v::type, exposed_attr_for_child_t> && noexcept(x4::move_to(std::declval(), outer_attr)) ) { @@ -111,11 +115,10 @@ struct as_directive : unary_parser> // Note that this behavior is our implementation details. The underlying parser should // not rely on this behavior; they should never assume the given attribute is defaulted // to some arbitrary initial value. - T attr_{}; // value-initialize - if constexpr (need_as_var) { - if (!this->subject.parse(first, last, x4::replace_first_context(ctx, attr_), attr_)) return false; + if constexpr (Subject::has_action) { + if (!this->subject.parse(first, last, x4::replace_first_context(ctx, attr_), unused)) return false; } else { if (!this->subject.parse(first, last, ctx, attr_)) return false; } @@ -123,13 +126,6 @@ struct as_directive : unary_parser> x4::move_to(std::move(attr_), outer_attr); return true; } - - //template Se, class Context, X4NonUnusedAttribute OuterAttr> - // requires - // (!std::same_as, T>) && - // (!X4Movable) - //constexpr void - //parse(It&, Se const&, Context const&, OuterAttr&) const = delete; // `T` is not movable to the exposed attribute }; namespace detail { diff --git a/test/x4/as.cpp b/test/x4/as.cpp index 1b7c2a2ea..9b0335c32 100644 --- a/test/x4/as.cpp +++ b/test/x4/as.cpp @@ -6,21 +6,26 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) =============================================================================*/ +#define IRIS_X4_UNICODE + #include "iris_x4_test.hpp" #include #include #include #include -#include +#include #include #include #include #include #include -#include +#include #include +#include + +#include #include #include #include @@ -28,19 +33,285 @@ // NOLINTBEGIN(readability-container-size-empty) -TEST_CASE("as") +using namespace std::string_view_literals; + +using x4::eps; +using x4::string; + +using It = std::string_view::const_iterator; +using Se = It; +using Context = unused_type; + +constexpr auto do_nothing = [](auto&&) {}; +constexpr auto disable_attr = eps[([](auto&&) {})]; +constexpr auto quoted_string = '\'' >> *~x4::char_('\'') >> '\''; + +char const* empty_input_first = nullptr; +char const* const empty_input_last = nullptr; + +TEST_CASE("as(p)") { - using namespace std::string_view_literals; + using x4::_as_var; + using x4::_attr; + + // result = int or long long + // T = int + + // ------------------------------------------- + // with semantic action + { + { + constexpr auto p = x4::as(x4::attr(3))[([](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)); + CHECK(result == 8); + } + { + long long result = 42ll; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 42ll); // the semantic action's content is discarded + } + } + + // do nothing in semantic action + { + constexpr auto p = x4::attr(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]; + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 3); + } + + { + constexpr auto p = x4::as( + x4::attr(3) + ); + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 3); + } + { + constexpr auto p = x4::as( + x4::attr(3)[do_nothing] + ); + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 42); + } + + // The toplevel semantic action is always treated differently; + // the intermediate value always propagates up. + { + constexpr auto p = x4::as( + x4::attr(3) + )[do_nothing]; + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 3); + } + { + constexpr auto p = x4::as( + x4::attr(3)[do_nothing] + )[do_nothing]; + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 42); + } + } + + // ------------------------------------------- + // without semantic action + { + constexpr auto p = x4::as(x4::attr(3)); + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 3); + } + + // `unused` + { + constexpr auto p = x4::as(eps); + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 42); + } +} - using x4::eps; - using x4::string; +TEST_CASE("as(as(p))") +{ + using x4::_as_var; using x4::_attr; - using x4::_rule_var; + + // result = int or long long + // T = int + // U = int + + // ------------------------------------------- + // with semantic action + { + constexpr auto p = x4::as( + x4::as(x4::attr(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))); + _as_var(ctx) = _attr(ctx) + 5; + })] + ); + { + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 8); + } + { + long long result = 42ll; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 8ll); + } + } + + // do nothing in semantic action + { + constexpr auto p = x4::as( + x4::as(x4::attr(3))[do_nothing] + ); + + { + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 42); + } + { + long long result = 42ll; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 0); // discarded + } + } + + // ------------------------------------------- + // without semantic action + { + constexpr auto p = x4::as( + x4::as(x4::attr(3)) + ); + + { + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 3); + } + { + long long result = 42ll; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 3ll); + } + } +} + +TEST_CASE("as(as(p))") +{ using x4::_as_var; + using x4::_attr; + + // result = int or long long + // T = int + // U = short + + // ------------------------------------------- + // with semantic action + { + constexpr auto p = x4::as( + x4::as(x4::attr(short(3)))[([](auto&& ctx) { + static_assert(std::same_as, int>); + static_assert(std::same_as, short>); + _as_var(ctx) = _attr(ctx) + 5; + })] + ); + { + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 8); + } + { + long long result = 42ll; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 8ll); + } + } + + // do nothing in semantic action + { + constexpr auto p = x4::as( + x4::as(x4::attr(short(3)))[do_nothing] + ); + + { + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 42); + } + { + long long result = 42ll; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 0); // discarded + } + } + + // ------------------------------------------- + // without semantic action + { + constexpr auto p = x4::as( + x4::as(x4::attr(short(3))) + ); + + { + int result = 42; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 3); + } + { + long long result = 42ll; + REQUIRE(p.parse(empty_input_first, empty_input_last, unused, result)); + CHECK(result == 3ll); + } + } + + // -------------------------------------------------------- + // string + + { + constexpr auto p = x4::as( + x4::as(+x4::unicode::char_)[([](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"テスト"; + std::u32string_view::const_iterator first = input.begin(); + std::u32string_view::const_iterator const last = input.end(); + + std::string result; + REQUIRE(p.parse(first, last, unused, result)); + CHECK(result == "テスト"sv); + } +} - using It = std::string_view::const_iterator; - using Se = It; - using Context = unused_type; +TEST_CASE("as (single type)") +{ + using x4::_attr; + using x4::_rule_var; + using x4::_as_var; // as { @@ -86,9 +357,6 @@ TEST_CASE("as") (void)p.parse(first, last, unused, attr); } - constexpr auto disable_attr = eps[([](auto&) {})]; - constexpr auto quoted_string = '\'' >> *~x4::char_('\'') >> '\''; - // `as` only { std::string attr; @@ -111,8 +379,14 @@ TEST_CASE("as") REQUIRE(parse("'fo", x4::as(quoted_string) | x4::string("'fo"), attr)); CHECK(attr == "'fo"sv); } +} + +TEST_CASE("as + rule") +{ + using x4::_attr; + using x4::_rule_var; + using x4::_as_var; - // `as` + `rule` { constexpr x4::rule rule_maker{"rule_maker"}; @@ -132,11 +406,11 @@ TEST_CASE("as") CHECK(str == ""sv); // Disabled attribute should yield default-constructed attribute } { - // Attribute is forced because `as_directive` sets `::has_action` to `false` + // Attribute is disabled because the sub parser has semantic action constexpr auto rule_without_attr = rule_maker = x4::as(quoted_string >> disable_attr); std::string str; REQUIRE(parse("'foo'", rule_without_attr, str)); - CHECK(str == "foo"sv); // Forced attribute should hold the parsed value + CHECK(str == ""sv); // Disabled attribute should yield default-constructed attribute } // Forced attribute, `operator%=` @@ -156,9 +430,16 @@ TEST_CASE("as") constexpr auto rule_with_forced_attr = rule_maker %= x4::as(quoted_string >> disable_attr); std::string str; REQUIRE(parse("'foo'", rule_with_forced_attr, str)); - CHECK(str == "foo"sv); // `as` should not create a temporary; it should directly parse into the exposed variable + CHECK(str == ""sv); // Disabled attribute should yield default-constructed attribute } } +} + +TEST_CASE("_as_var") +{ + using x4::_attr; + using x4::_rule_var; + using x4::_as_var; // `_as_var(ctx)` (with auto attribute propagation) { @@ -246,7 +527,7 @@ TEST_CASE("as") Se const last = input.end(); REQUIRE(unused_rule.parse(first, last, unused, result)); - CHECK(result == "default"sv); + CHECK(result == ""sv); } // Use `_rule_var(ctx)` inside `as(...)` @@ -257,26 +538,65 @@ TEST_CASE("as") std::string text; }; - StringLiteral result; - - constexpr auto string_literal = x4::rule{"StringLiteral"} = - eps[([](auto& ctx) { _rule_var(ctx).is_quoted = false; })] >> - x4::as( - x4::lit('"')[([](auto&& ctx) { - StringLiteral& rule_var = _rule_var(ctx); - rule_var.is_quoted = true; - })] >> - *~x4::char_('"') >> - '"' - )[([](auto&& ctx) { _rule_var(ctx).text = std::move(_attr(ctx)); })]; - - std::string_view input = R"("foo")"; - It first = input.begin(); - Se const last = input.end(); + std::string_view const input = R"("foo")"; - REQUIRE(string_literal.parse(first, last, unused, result)); - CHECK(result.is_quoted == true); - CHECK(result.text == "foo"sv); + { + constexpr auto string_literal = x4::rule{"StringLiteral"} = + eps[([](auto& ctx) { _rule_var(ctx).is_quoted = false; })] >> + x4::as( + x4::lit('"')[([](auto&& ctx) { + StringLiteral& rule_var = _rule_var(ctx); + rule_var.is_quoted = true; + })] >> + *(~x4::char_('"'))[([](auto&& ctx) { _as_var(ctx).push_back(_attr(ctx)); })] >> + '"' + )[([](auto&& ctx) { _rule_var(ctx).text = std::move(_attr(ctx)); })]; + + It first = input.begin(); + Se const last = input.end(); + + StringLiteral result; + REQUIRE(string_literal.parse(first, last, unused, result)); + CHECK(result.is_quoted == true); + CHECK(result.text == "foo"sv); + } + { + constexpr auto string_literal = x4::rule{"StringLiteral"} = + eps[([](auto& ctx) { _rule_var(ctx).is_quoted = false; })] >> + x4::as( + x4::lit('"')[([](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)); })]; + + It first = input.begin(); + Se const last = input.end(); + + StringLiteral result; + REQUIRE(string_literal.parse(first, last, unused, result)); + CHECK(result.is_quoted == true); + CHECK(result.text == ""sv); + } + { + constexpr auto string_literal = x4::rule{"StringLiteral"} = + eps[([](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)); })]; + + It first = input.begin(); + Se const last = input.end(); + + StringLiteral result; + REQUIRE(string_literal.parse(first, last, unused, result)); + CHECK(result.is_quoted == false); + CHECK(result.text == "foo"sv); + } } }