2
0
mirror of https://github.com/boostorg/spirit.git synced 2026-01-19 04:42:11 +00:00

Revert "Modernize x3::action (#815)"

This reverts commit 3df9ca3788.
This commit is contained in:
Nana Sakisaka
2025-09-13 08:57:05 +09:00
parent c41c9aec95
commit 569c52220e
3 changed files with 125 additions and 237 deletions

View File

@@ -1,6 +1,5 @@
/*============================================================================= /*=============================================================================
Copyright (c) 2001-2014 Joel de Guzman Copyright (c) 2001-2014 Joel de Guzman
Copyright (c) 2025 Nana Sakisaka
Distributed under the Boost Software License, Version 1.0. (See accompanying 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) file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,243 +9,102 @@
#include <boost/spirit/home/x3/support/context.hpp> #include <boost/spirit/home/x3/support/context.hpp>
#include <boost/spirit/home/x3/support/traits/attribute_of.hpp> #include <boost/spirit/home/x3/support/traits/attribute_of.hpp>
#include <boost/spirit/home/x3/nonterminal/detail/transform_attribute.hpp>
#include <boost/spirit/home/x3/core/call.hpp> #include <boost/spirit/home/x3/core/call.hpp>
#include <boost/spirit/home/x3/nonterminal/detail/transform_attribute.hpp>
#include <boost/range/iterator_range_core.hpp>
#ifndef BOOST_SPIRIT_X3_NO_BOOST_ITERATOR_RANGE namespace boost { namespace spirit { namespace x3
# pragma message("Use of `boost::iterator_range` is deprecated in X3. #define BOOST_SPIRIT_X3_NO_BOOST_ITERATOR_RANGE")
# include <boost/range/iterator_range_core.hpp>
#endif
#include <ranges>
#include <iterator>
#include <concepts>
#include <type_traits>
#include <utility>
namespace boost::spirit::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 template <typename Context>
// like `X3ActionFunctor<F>`, but we technically can't. inline bool& _pass(Context const& context)
// {
// In order to check `std::invocable`, we need to know the actual context return x3::get<parse_pass_context_tag>(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 <typename Subject, typename Action> template <typename Subject, typename Action>
struct action : unary_parser<Subject, action<Subject, Action>> struct action : unary_parser<Subject, action<Subject, Action>>
{ {
static_assert( typedef unary_parser<Subject, action<Subject, Action>> base_type;
!std::is_reference_v<Action>, static bool const is_pass_through_unary = true;
"Reference type is disallowed for semantic action functor to prevent dangling reference" static bool const has_action = true;
);
using base_type = unary_parser<Subject, action<Subject, Action>>; constexpr action(Subject const& subject, Action f)
static constexpr bool is_pass_through_unary = true; : base_type(subject), f(f) {}
static constexpr bool has_action = true;
Action f; template <typename Iterator, typename Context, typename RuleContext, typename Attribute>
bool call_action(
template <typename SubjectT, typename ActionT> Iterator& first, Iterator const& last
requires std::is_constructible_v<base_type, SubjectT> && std::is_constructible_v<Action, ActionT> , Context const& context, RuleContext& rcontext, Attribute& attr) const
constexpr action(SubjectT&& subject, ActionT&& f)
noexcept(std::is_nothrow_constructible_v<base_type, SubjectT> && std::is_nothrow_constructible_v<Action, ActionT>)
: base_type(std::forward<SubjectT>(subject))
, f(std::forward<ActionT>(f))
{ {
}
// attr==unused, action wants attribute
template <std::forward_iterator It, std::sentinel_for<It> 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<traits::attribute_of_t<action<Subject, Action>, Context>> &&
noexcept(this->parse_main(first, last, context, rcontext, std::declval<traits::attribute_of_t<action<Subject, Action>, Context>&>()))
)
{
using attribute_type = traits::attribute_of_t<action<Subject, Action>, 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 <std::forward_iterator It, std::sentinel_for<It> 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 <std::forward_iterator It, std::sentinel_for<It> 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<It, Se> const,
#else
boost::iterator_range<It> const,
#endif
x3::context<
rule_val_context_tag,
RContext,
x3::context<
parse_pass_context_tag,
bool,
Context
>
>
>
>;
template <std::forward_iterator It, std::sentinel_for<It> Se, typename Context, typename RContext, typename Attribute>
requires std::invocable<Action const&, composed_context_t<It, Se, Context, RContext, Attribute> 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<It, Se>;
#else
using where_range_t = boost::iterator_range<It>;
#endif
static_assert(
std::is_void_v<std::invoke_result_t<Action const&, composed_context_t<It, Se, Context, RContext, Attribute> const&>>,
"Semantic action should not return value. Check your function signature."
);
bool pass = true; bool pass = true;
auto const pass_context = x3::make_context<parse_pass_context_tag>(pass, context); auto action_context = make_context<parse_pass_context_tag>(pass, context);
call(f, first, last, action_context, rcontext, attr);
auto const val_context = x3::make_context<rule_val_context_tag>(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_context_tag>(where_rng, val_context);
auto const attr_context = x3::make_context<attr_context_tag>(attr, where_context);
// Sanity check (internal check to detect implementation divergence)
static_assert(std::same_as<
std::remove_cvref_t<decltype(attr_context)>,
composed_context_t<It, Se, Context, RContext, Attribute>
>);
this->f(attr_context);
return pass; return pass;
} }
template <std::forward_iterator It, std::sentinel_for<It> Se, typename Context, typename RContext, typename Attribute> template <typename Iterator, typename Context
requires (!std::invocable<Action const&, composed_context_t<It, Se, Context, RContext, Attribute> const&>) , typename RuleContext, typename Attribute>
[[nodiscard]] constexpr bool bool parse_main(Iterator& first, Iterator const& last
call_action( , Context const& context, RuleContext& rcontext, Attribute& attr) const
It&, Se const&,
Context const&, RContext&, Attribute&
) const noexcept(std::is_nothrow_invocable_v<Action const&>)
{ {
// Explicitly make this hard error instead of emitting "no matching overload". Iterator save = first;
// This provides much more human-friendly errors.
static_assert(
std::invocable<Action const&>,
"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<std::invoke_result_t<Action const&>>,
"Semantic action should not return value. Check your function signature."
);
this->f();
return true;
}
template <std::forward_iterator It, std::sentinel_for<It> 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<It> &&
is_nothrow_parsable_v<Subject, It, Se, Context, RContext, Attribute> &&
noexcept(this->call_action(first, last, context, rcontext, attr))
)
{
It const saved_first = first;
if (this->subject.parse(first, last, context, rcontext, attr)) 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; return true;
}
// reset iterators if semantic action failed the match // reset iterators if semantic action failed the match
// retrospectively // retrospectively
first = saved_first; first = save;
} }
return false; return false;
} }
// attr==raw_attribute_type, action wants iterator_range (see raw.hpp) // attr==raw_attribute_type, action wants iterator_range (see raw.hpp)
template <std::forward_iterator It, std::sentinel_for<It> Se, typename Context, typename RContext> template <typename Iterator, typename Context, typename RuleContext>
[[nodiscard]] constexpr bool bool parse_main(Iterator& first, Iterator const& last
parse_main( , Context const& context, RuleContext& rcontext, raw_attribute_type&) const
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
{ {
#ifdef BOOST_SPIRIT_X3_NO_BOOST_ITERATOR_RANGE boost::iterator_range<Iterator> rng;
std::ranges::subrange<It, Se> rng;
#else
boost::iterator_range<It> rng;
#endif
// synthesize the attribute since one is not supplied // 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 <typename Iterator, typename Context, typename RuleContext>
bool parse(Iterator& first, Iterator const& last
, Context const& context, RuleContext& rcontext, unused_type) const
{
typedef typename
traits::attribute_of<action<Subject, Action>, 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 <typename Iterator, typename Context
, typename RuleContext, typename Attribute>
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 <X3Subject Subject, typename Action> template <typename P, typename Action>
[[nodiscard, deprecated( constexpr action<typename extension::as_parser<P>::value_type, Action>
"Use `operator[]` instead. The symbol `/` normally means \"ordered choice\" " operator/(P const& p, Action f)
"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<as_parser_plain_t<Subject>, std::remove_cvref_t<Action>>
operator/(Subject&& p, Action&& f)
noexcept(
is_parser_nothrow_castable_v<Subject> &&
std::is_nothrow_constructible_v<
action<as_parser_plain_t<Subject>, std::remove_cvref_t<Action>>,
as_parser_t<Subject>, Action
>
)
{ {
return { as_parser(std::forward<Subject>(p)), std::forward<Action>(f) }; return { as_parser(p), f };
} }
} // boost::spirit::x3 }}}
#endif #endif

View File

@@ -1,6 +1,5 @@
/*============================================================================= /*=============================================================================
Copyright (c) 2001-2014 Joel de Guzman Copyright (c) 2001-2014 Joel de Guzman
Copyright (c) 2025 Nana Sakisaka
Distributed under the Boost Software License, Version 1.0. (See accompanying 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) 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) #if !defined(BOOST_SPIRIT_X3_CALL_CONTEXT_MAY_26_2014_0234PM)
#define BOOST_SPIRIT_X3_CALL_CONTEXT_MAY_26_2014_0234PM #define BOOST_SPIRIT_X3_CALL_CONTEXT_MAY_26_2014_0234PM
#include <type_traits>
#include <boost/spirit/home/x3/support/context.hpp> #include <boost/spirit/home/x3/support/context.hpp>
#include <boost/spirit/home/x3/support/utility/is_callable.hpp>
#include <boost/range/iterator_range_core.hpp>
namespace boost::spirit::x3 namespace boost { namespace spirit { namespace x3
{ {
struct parse_pass_context_tag; // _pass ////////////////////////////////////////////////////////////////////////////
struct rule_val_context_tag; // _val struct rule_val_context_tag;
struct where_context_tag; // _where
struct attr_context_tag; // _attr
template <typename Context> template <typename Context>
[[nodiscard]] constexpr bool& _pass(Context const& context) noexcept inline decltype(auto) _val(Context const& context)
{
return x3::get<parse_pass_context_tag>(context);
}
template <typename Context>
[[nodiscard]] constexpr auto&& _val(Context const& context) noexcept
{ {
return x3::get<rule_val_context_tag>(context); return x3::get<rule_val_context_tag>(context);
} }
////////////////////////////////////////////////////////////////////////////
struct where_context_tag;
template <typename Context> template <typename Context>
[[nodiscard]] constexpr auto&& _where(Context const& context) noexcept inline decltype(auto) _where(Context const& context)
{ {
return x3::get<where_context_tag>(context); return x3::get<where_context_tag>(context);
} }
////////////////////////////////////////////////////////////////////////////
struct attr_context_tag;
template <typename Context> template <typename Context>
[[nodiscard]] constexpr auto&& _attr(Context const& context) noexcept inline decltype(auto) _attr(Context const& context)
{ {
return x3::get<attr_context_tag>(context); return x3::get<attr_context_tag>(context);
} }
} // boost::spirit::x3
////////////////////////////////////////////////////////////////////////////
namespace detail
{
template <typename F, typename Context>
auto call(F f, Context const& context, mpl::true_)
{
return f(context);
}
template <typename F, typename Context>
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<Iterator> rng(first, last);
auto val_context = make_context<rule_val_context_tag>(rcontext, context);
auto where_context = make_context<where_context_tag>(rng, val_context);
auto attr_context = make_context<attr_context_tag>(attr, where_context);
return detail::call(f, attr_context, is_callable<F(decltype(attr_context) const&)>());
}
}}}
#endif #endif

View File

@@ -13,7 +13,6 @@
#include <boost/fusion/include/std_pair.hpp> #include <boost/fusion/include/std_pair.hpp>
#include <boost/variant.hpp> #include <boost/variant.hpp>
#include <string> #include <string>
#include <vector> #include <vector>
#include <cstring> #include <cstring>
@@ -109,14 +108,15 @@ int main()
using spirit_test::test_attr; using spirit_test::test_attr;
using spirit_test::test; 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::rule;
using boost::spirit::x3::lit; using boost::spirit::x3::lit;
using boost::spirit::x3::eps; using boost::spirit::x3::eps;
using boost::spirit::x3::unused_type; using boost::spirit::x3::unused_type;
// synth attribute value-init
{ { // synth attribute value-init
std::string s; std::string s;
typedef rule<class r, std::string> rule_type; typedef rule<class r, std::string> rule_type;
@@ -128,15 +128,17 @@ int main()
BOOST_TEST(s == "abcdef"); BOOST_TEST(s == "abcdef");
} }
// synth attribute value-init { // synth attribute value-init
{
std::string s; std::string s;
typedef rule<class r, std::string> rule_type; typedef rule<class r, std::string> rule_type;
auto rdef = rule_type() = auto rdef = rule_type() =
alpha[([](auto& ctx) { alpha /
_val(ctx) += _attr(ctx); [](auto& ctx)
})] {
_val(ctx) += _attr(ctx);
}
; ;
BOOST_TEST(test_attr("abcdef", +rdef, s)); BOOST_TEST(test_attr("abcdef", +rdef, s));
@@ -146,16 +148,13 @@ int main()
{ {
auto r = rule<class r_id, int>{} = eps[([] (auto& ctx) { auto r = rule<class r_id, int>{} = eps[([] (auto& ctx) {
using boost::spirit::x3::_val; using boost::spirit::x3::_val;
static_assert( static_assert(std::is_same<std::decay_t<decltype(_val(ctx))>, unused_type>::value,
std::is_same_v<std::decay_t<decltype(_val(ctx))>, unused_type>, "Attribute must not be synthesized");
"Attribute must not be synthesized"
);
})]; })];
BOOST_TEST(test("", r)); BOOST_TEST(test("", r));
} }
// ensure no unneeded synthesization, copying and moving occurred { // ensure no unneeded synthesization, copying and moving occurred
{
stationary st { 0 }; stationary st { 0 };
BOOST_TEST(test_attr("{42}", check_stationary::b, st)); BOOST_TEST(test_attr("{42}", check_stationary::b, st));
BOOST_TEST_EQ(st.val, 42); BOOST_TEST_EQ(st.val, 42);