From 569c52220e95a8b3bdfcbddb458f7bba0e3ccdd7 Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Sat, 13 Sep 2025 08:57:05 +0900 Subject: [PATCH] Revert "Modernize `x3::action` (#815)" This reverts commit 3df9ca3788dfec5f54730284b5d2cbb2d40472a3. --- include/boost/spirit/home/x3/core/action.hpp | 270 +++++-------------- include/boost/spirit/home/x3/core/call.hpp | 63 +++-- test/x3/rule3.cpp | 29 +- 3 files changed, 125 insertions(+), 237 deletions(-) diff --git a/include/boost/spirit/home/x3/core/action.hpp b/include/boost/spirit/home/x3/core/action.hpp index 4640ac00c..0de52c93b 100644 --- a/include/boost/spirit/home/x3/core/action.hpp +++ b/include/boost/spirit/home/x3/core/action.hpp @@ -1,6 +1,5 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman - Copyright (c) 2025 Nana Sakisaka Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -10,243 +9,102 @@ #include #include -#include #include +#include +#include -#ifndef BOOST_SPIRIT_X3_NO_BOOST_ITERATOR_RANGE -# pragma message("Use of `boost::iterator_range` is deprecated in X3. #define BOOST_SPIRIT_X3_NO_BOOST_ITERATOR_RANGE") -# include -#endif - -#include -#include -#include -#include -#include - -namespace boost::spirit::x3 +namespace boost { namespace spirit { namespace x3 { - struct raw_attribute_type; // TODO: move this to detail + struct raw_attribute_type; + struct parse_pass_context_tag; - // Ideally we should have a context-agnostic concept that can be used - // like `X3ActionFunctor`, but we technically can't. - // - // In order to check `std::invocable`, we need to know the actual context - // type passed to the `.parse(...)` function but it is unknown until - // runtime. - // - // Even if we make up the most trivial context type (i.e. `unused_type`), - // such concept will be useless because a user-provided functor always - // operates on user-specific precondition that assumes the context - // holds exact specific type provided to the entry point (`x3::parse`). + template + inline bool& _pass(Context const& context) + { + return x3::get(context); + } template struct action : unary_parser> { - static_assert( - !std::is_reference_v, - "Reference type is disallowed for semantic action functor to prevent dangling reference" - ); + typedef unary_parser> base_type; + static bool const is_pass_through_unary = true; + static bool const has_action = true; - using base_type = unary_parser>; - static constexpr bool is_pass_through_unary = true; - static constexpr bool has_action = true; + constexpr action(Subject const& subject, Action f) + : base_type(subject), f(f) {} - Action f; - - template - requires std::is_constructible_v && std::is_constructible_v - constexpr action(SubjectT&& subject, ActionT&& f) - noexcept(std::is_nothrow_constructible_v && std::is_nothrow_constructible_v) - : base_type(std::forward(subject)) - , f(std::forward(f)) + template + bool call_action( + Iterator& first, Iterator const& last + , Context const& context, RuleContext& rcontext, Attribute& attr) const { - } - - // attr==unused, action wants attribute - template Se, typename Context, typename RContext> - [[nodiscard]] constexpr bool - parse( - It& first, Se const& last, Context const& context, RContext& rcontext, unused_type - ) const noexcept( - std::is_nothrow_default_constructible_v, Context>> && - noexcept(this->parse_main(first, last, context, rcontext, std::declval, Context>&>())) - ) - { - using attribute_type = traits::attribute_of_t, Context>; - - // Synthesize the attribute since one is not supplied - attribute_type attribute; // default-initialize - return this->parse_main(first, last, context, rcontext, attribute); - } - - // Catch-all overload for non-unused_type attribute - template Se, typename Context, typename RContext, typename Attribute> - [[nodiscard]] constexpr bool - parse( - It& first, Se const& last, Context const& context, RContext& rcontext, Attribute& attr - ) const noexcept(noexcept(this->parse_main(first, last, context, rcontext, attr))) - { - return this->parse_main(first, last, context, rcontext, attr); - } - - private: - // Compose attr(where(val(pass(context)))) - template Se, typename Context, typename RContext, typename Attribute> - using composed_context_t = x3::context< - attr_context_tag, - Attribute, - x3::context< - where_context_tag, - #ifdef BOOST_SPIRIT_X3_NO_BOOST_ITERATOR_RANGE - std::ranges::subrange const, - #else - boost::iterator_range const, - #endif - x3::context< - rule_val_context_tag, - RContext, - x3::context< - parse_pass_context_tag, - bool, - Context - > - > - > - >; - - template Se, typename Context, typename RContext, typename Attribute> - requires std::invocable const&> - [[nodiscard]] constexpr bool - call_action( - It& first, Se const& last, - Context const& context, RContext& rcontext, Attribute& attr - ) const noexcept(false) // construction of `subrange` is never noexcept as per the standard - { - #ifdef BOOST_SPIRIT_X3_NO_BOOST_ITERATOR_RANGE - using where_range_t = std::ranges::subrange; - #else - using where_range_t = boost::iterator_range; - #endif - - static_assert( - std::is_void_v const&>>, - "Semantic action should not return value. Check your function signature." - ); - bool pass = true; - auto const pass_context = x3::make_context(pass, context); - - auto const val_context = x3::make_context(rcontext, pass_context); - - // TODO: Provide some trait to detect whether this is actually needed for - // each semantic actions. - // - // Although this can be assumed to be eliminated in optimized code, - // this still may introduce compile time overhead (and also runtime - // overhead, as constructing `subrange` is never noexcept). - where_range_t const where_rng(first, last); - auto const where_context = x3::make_context(where_rng, val_context); - - auto const attr_context = x3::make_context(attr, where_context); - - // Sanity check (internal check to detect implementation divergence) - static_assert(std::same_as< - std::remove_cvref_t, - composed_context_t - >); - - this->f(attr_context); + auto action_context = make_context(pass, context); + call(f, first, last, action_context, rcontext, attr); return pass; } - template Se, typename Context, typename RContext, typename Attribute> - requires (!std::invocable const&>) - [[nodiscard]] constexpr bool - call_action( - It&, Se const&, - Context const&, RContext&, Attribute& - ) const noexcept(std::is_nothrow_invocable_v) + template + bool parse_main(Iterator& first, Iterator const& last + , Context const& context, RuleContext& rcontext, Attribute& attr) const { - // Explicitly make this hard error instead of emitting "no matching overload". - // This provides much more human-friendly errors. - static_assert( - std::invocable, - "Neither `f(ctx)` nor `f()` is well-formed for your semantic action. " - "Check your function signature. Note that some functors might need " - "`const` qualifier to satisfy the constraints." - ); - - static_assert( - std::is_void_v>, - "Semantic action should not return value. Check your function signature." - ); - this->f(); - return true; - } - - template Se, typename Context, typename RContext, typename Attribute> - [[nodiscard]] constexpr bool - parse_main( - It& first, Se const& last, Context const& context, RContext& rcontext, Attribute& attr - ) const noexcept( - std::is_copy_assignable_v && - is_nothrow_parsable_v && - noexcept(this->call_action(first, last, context, rcontext, attr)) - ) - { - It const saved_first = first; + Iterator save = first; if (this->subject.parse(first, last, context, rcontext, attr)) { - if (this->call_action(first, last, context, rcontext, attr)) - { + if (call_action(first, last, context, rcontext, attr)) return true; - } // reset iterators if semantic action failed the match // retrospectively - first = saved_first; + first = save; } return false; } - + // attr==raw_attribute_type, action wants iterator_range (see raw.hpp) - template Se, typename Context, typename RContext> - [[nodiscard]] constexpr bool - parse_main( - It& first, Se const& last, Context const& context, RContext& rcontext, raw_attribute_type& - ) const noexcept(false) // construction of `subrange` is never noexcept as per the standard + template + bool parse_main(Iterator& first, Iterator const& last + , Context const& context, RuleContext& rcontext, raw_attribute_type&) const { - #ifdef BOOST_SPIRIT_X3_NO_BOOST_ITERATOR_RANGE - std::ranges::subrange rng; - #else - boost::iterator_range rng; - #endif + boost::iterator_range rng; // synthesize the attribute since one is not supplied - return this->parse_main(first, last, context, rcontext, rng); + return parse_main(first, last, context, rcontext, rng); } + + // attr==unused, action wants attribute + template + bool parse(Iterator& first, Iterator const& last + , Context const& context, RuleContext& rcontext, unused_type) const + { + typedef typename + traits::attribute_of, Context>::type + attribute_type; + + // synthesize the attribute since one is not supplied + attribute_type attribute{}; + return parse_main(first, last, context, rcontext, attribute); + } + + // main parse function + template + bool parse(Iterator& first, Iterator const& last + , Context const& context, RuleContext& rcontext, Attribute& attr) const + { + return parse_main(first, last, context, rcontext, attr); + } + + Action f; }; - 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 - > - ) + template + constexpr action::value_type, Action> + operator/(P const& p, Action f) { - return { as_parser(std::forward(p)), std::forward(f) }; + return { as_parser(p), f }; } -} // boost::spirit::x3 +}}} #endif diff --git a/include/boost/spirit/home/x3/core/call.hpp b/include/boost/spirit/home/x3/core/call.hpp index 74e7b46de..a344cf4bf 100644 --- a/include/boost/spirit/home/x3/core/call.hpp +++ b/include/boost/spirit/home/x3/core/call.hpp @@ -1,6 +1,5 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman - Copyright (c) 2025 Nana Sakisaka Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,38 +7,70 @@ #if !defined(BOOST_SPIRIT_X3_CALL_CONTEXT_MAY_26_2014_0234PM) #define BOOST_SPIRIT_X3_CALL_CONTEXT_MAY_26_2014_0234PM +#include + #include +#include +#include -namespace boost::spirit::x3 +namespace boost { namespace spirit { namespace x3 { - struct parse_pass_context_tag; // _pass - struct rule_val_context_tag; // _val - struct where_context_tag; // _where - struct attr_context_tag; // _attr + //////////////////////////////////////////////////////////////////////////// + struct rule_val_context_tag; template - [[nodiscard]] constexpr bool& _pass(Context const& context) noexcept - { - return x3::get(context); - } - - template - [[nodiscard]] constexpr auto&& _val(Context const& context) noexcept + inline decltype(auto) _val(Context const& context) { return x3::get(context); } + //////////////////////////////////////////////////////////////////////////// + struct where_context_tag; + template - [[nodiscard]] constexpr auto&& _where(Context const& context) noexcept + inline decltype(auto) _where(Context const& context) { return x3::get(context); } + //////////////////////////////////////////////////////////////////////////// + struct attr_context_tag; + template - [[nodiscard]] constexpr auto&& _attr(Context const& context) noexcept + inline decltype(auto) _attr(Context const& context) { return x3::get(context); } -} // boost::spirit::x3 + + //////////////////////////////////////////////////////////////////////////// + namespace detail + { + template + auto call(F f, Context const& context, mpl::true_) + { + return f(context); + } + + template + auto call(F f, Context const& /* context */, mpl::false_) + { + return f(); + } + } + + template < + typename F, typename Iterator + , typename Context, typename RuleContext, typename Attribute> + auto call( + F f, Iterator& first, Iterator const& last + , Context const& context, RuleContext& rcontext, Attribute& attr) + { + boost::iterator_range rng(first, last); + auto val_context = make_context(rcontext, context); + auto where_context = make_context(rng, val_context); + auto attr_context = make_context(attr, where_context); + return detail::call(f, attr_context, is_callable()); + } +}}} #endif diff --git a/test/x3/rule3.cpp b/test/x3/rule3.cpp index 1d42a480c..9e3a577c5 100644 --- a/test/x3/rule3.cpp +++ b/test/x3/rule3.cpp @@ -13,7 +13,6 @@ #include #include - #include #include #include @@ -109,14 +108,15 @@ int main() using spirit_test::test_attr; using spirit_test::test; - using namespace boost::spirit::x3::standard; + using namespace boost::spirit::x3::ascii; using boost::spirit::x3::rule; using boost::spirit::x3::lit; using boost::spirit::x3::eps; using boost::spirit::x3::unused_type; - // synth attribute value-init - { + + { // synth attribute value-init + std::string s; typedef rule rule_type; @@ -128,15 +128,17 @@ int main() BOOST_TEST(s == "abcdef"); } - // synth attribute value-init - { + { // synth attribute value-init + std::string s; typedef rule rule_type; auto rdef = rule_type() = - alpha[([](auto& ctx) { - _val(ctx) += _attr(ctx); - })] + alpha / + [](auto& ctx) + { + _val(ctx) += _attr(ctx); + } ; BOOST_TEST(test_attr("abcdef", +rdef, s)); @@ -146,16 +148,13 @@ int main() { auto r = rule{} = eps[([] (auto& ctx) { using boost::spirit::x3::_val; - static_assert( - std::is_same_v, unused_type>, - "Attribute must not be synthesized" - ); + static_assert(std::is_same, unused_type>::value, + "Attribute must not be synthesized"); })]; BOOST_TEST(test("", r)); } - // ensure no unneeded synthesization, copying and moving occurred - { + { // ensure no unneeded synthesization, copying and moving occurred stationary st { 0 }; BOOST_TEST(test_attr("{42}", check_stationary::b, st)); BOOST_TEST_EQ(st.val, 42);