diff --git a/doc/x3/tutorial/annotation.qbk b/doc/x3/tutorial/annotation.qbk index e02af1f29..a288c6651 100644 --- a/doc/x3/tutorial/annotation.qbk +++ b/doc/x3/tutorial/annotation.qbk @@ -1,5 +1,6 @@ [/============================================================================== Copyright (C) 2001-2018 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) @@ -128,9 +129,10 @@ our `on_success` handler: struct annotate_position { - template - inline void on_success(Iterator const& first, Iterator const& last - , T& ast, Context const& context) + template Se, typename T, typename Context> + void on_success( + It const& first, Se const& last, T& ast, Context const& context + ) const { auto& position_cache = x3::get(context).get(); position_cache.annotate(ast, first, last); diff --git a/doc/x3/tutorial/error_handling.qbk b/doc/x3/tutorial/error_handling.qbk index 9ad932884..3d8db5501 100644 --- a/doc/x3/tutorial/error_handling.qbk +++ b/doc/x3/tutorial/error_handling.qbk @@ -1,6 +1,6 @@ [/============================================================================== Copyright (C) 2001-2018 Joel de Guzman - Copyright (C) 2024 Nana Sakisaka + Copyright (C) 2024-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) @@ -129,10 +129,12 @@ Here's our `on_error` handler: struct error_handler { - template - x3::error_handler_result on_error( - Iterator& first, Iterator const& last - , Exception const& x, Context const& context) + template Se, typename Exception, typename Context> + [[nodiscard]] x3::error_handler_result + on_error( + It const& first, Se const& last, + Exception const& x, Context const& context + ) { auto& error_handler = x3::get(context).get(); std::string message = "Error! Expecting: " + x3::which(x) + " here:"; diff --git a/include/boost/spirit/home/x3/auxiliary/guard.hpp b/include/boost/spirit/home/x3/auxiliary/guard.hpp index 35ff6a0ee..339fc93d0 100644 --- a/include/boost/spirit/home/x3/auxiliary/guard.hpp +++ b/include/boost/spirit/home/x3/auxiliary/guard.hpp @@ -1,7 +1,7 @@ /*============================================================================= Copyright (c) 2001-2014 Joel de Guzman Copyright (c) 2017 wanghan02 - Copyright (c) 2024 Nana Sakisaka + Copyright (c) 2024-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) @@ -12,57 +12,79 @@ #include #include #include +#include -namespace boost { namespace spirit { namespace x3 +#include +#include +#include + +namespace boost::spirit::x3 { - enum class error_handler_result - { - fail - , retry - , accept - , rethrow // see BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE for alternative behaviors - }; - template struct guard : unary_parser> { - typedef unary_parser> base_type; - static bool const is_pass_through_unary = true; + static_assert( + !std::is_reference_v, + "Reference type is disallowed for `Handler` to prevent dangling reference" + ); - constexpr guard(Subject const& subject, Handler handler) - : base_type(subject), handler(handler) {} + using base_type = unary_parser>; + static constexpr bool is_pass_through_unary = true; - template - bool parse(Iterator& first, Iterator const& last - , Context const& context, RuleContext& rcontext, Attribute& attr) const + Handler handler; + + template + requires std::is_constructible_v && std::is_constructible_v + constexpr guard(SubjectT&& subject, HandlerT&& handler) + noexcept(std::is_nothrow_constructible_v && std::is_nothrow_constructible_v) + : base_type(std::forward(subject)) + , handler(std::forward(handler)) + {} + + 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 + // never noexcept; requires complex implementation details { - for (;;) + static_assert(Parsable); + + while (true) { - Iterator i = first; + It saved_it = first; #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE try #endif { - if (this->subject.parse(i, last, context, rcontext, attr)) + if (this->subject.parse(saved_it, last, context, rcontext, attr)) { - first = i; + first = saved_it; return true; } } #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE - catch (expectation_failure const& x) { + catch (expectation_failure const& x) { #else - if (has_expectation_failure(context)) { - auto& x = get_expectation_failure(context); + if (x3::has_expectation_failure(context)) { + auto const& x = x3::get_expectation_failure(context); #endif + static_assert( + std::is_invocable_r_v< + error_handler_result, + Handler const&, + It const&, Se const&, + std::remove_cvref_t const&, + Context const& + >, + "x3::guard: `Handler`'s signature is wrong" + ); + // X3 developer note: don't forget to sync this implementation with x3::detail::rule_parser - switch (handler(first, last, x, context)) + switch (handler(std::as_const(first), std::as_const(last), x, context)) { case error_handler_result::fail: - clear_expectation_failure(context); + x3::clear_expectation_failure(context); return false; case error_handler_result::retry: @@ -82,9 +104,7 @@ namespace boost { namespace spirit { namespace x3 return false; } } - - Handler handler; }; -}}} +} // boost::spirit::x3 #endif diff --git a/include/boost/spirit/home/x3/core/error_handler_types.hpp b/include/boost/spirit/home/x3/core/error_handler_types.hpp new file mode 100644 index 000000000..c6dd4fa02 --- /dev/null +++ b/include/boost/spirit/home/x3/core/error_handler_types.hpp @@ -0,0 +1,28 @@ +/*============================================================================= + Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2024-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) +=============================================================================*/ +#ifndef BOOST_SPIRIT_X3_CORE_ERROR_HANDLER_TYPES_HPP +#define BOOST_SPIRIT_X3_CORE_ERROR_HANDLER_TYPES_HPP + +namespace boost::spirit::x3 +{ + // Enum type used in `on_error`. + enum class error_handler_result + { + fail, + retry, + accept, + rethrow, // see BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE for alternative behaviors + }; + + // Note for X3 developers: + // You must at least sync the implementation of `rule_parser` and `guard` + // when you modify the semantics of error handlers. + +} // boost::spirit::x3 + +#endif diff --git a/include/boost/spirit/home/x3/nonterminal/detail/rule.hpp b/include/boost/spirit/home/x3/nonterminal/detail/rule.hpp deleted file mode 100644 index b44a92400..000000000 --- a/include/boost/spirit/home/x3/nonterminal/detail/rule.hpp +++ /dev/null @@ -1,370 +0,0 @@ -/*============================================================================= - Copyright (c) 2001-2014 Joel de Guzman - Copyright (c) 2017 wanghan02 - Copyright (c) 2024 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) -==============================================================================*/ -#if !defined(BOOST_SPIRIT_X3_DETAIL_RULE_JAN_08_2012_0326PM) -#define BOOST_SPIRIT_X3_DETAIL_RULE_JAN_08_2012_0326PM - -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(BOOST_SPIRIT_X3_DEBUG) -#include -#endif - -#include - -namespace boost { namespace spirit { namespace x3 -{ - template - struct identity; - - template - struct rule; - - struct parse_pass_context_tag; - - namespace detail - { - template - struct rule_id {}; - - // we use this so we can detect if the default parse_rule - // is the being called. - struct default_parse_rule_result - { - default_parse_rule_result(bool r) - : r(r) {} - operator bool() const { return r; } - bool r; - }; - } - - // default parse_rule implementation - template - inline detail::default_parse_rule_result - parse_rule( - detail::rule_id - , Iterator& first, Iterator const& last - , Context const& context, ActualAttribute& attr); -}}} - -namespace boost { namespace spirit { namespace x3 { namespace detail -{ -#if defined(BOOST_SPIRIT_X3_DEBUG) - template - struct context_debug - { - context_debug( - char const* rule_name - , Iterator const& first, Iterator const& last - , Attribute const& attr - , bool const& ok_parse //was parse successful? - ) - : ok_parse(ok_parse), rule_name(rule_name) - , first(first), last(last) - , attr(attr) - , f(detail::get_simple_trace()) - { - f(first, last, attr, pre_parse, rule_name); - } - - ~context_debug() - { - auto status = ok_parse ? successful_parse : failed_parse ; - f(first, last, attr, status, rule_name); - } - - bool const& ok_parse; - char const* rule_name; - Iterator const& first; - Iterator const& last; - Attribute const& attr; - detail::simple_trace_type& f; - }; -#endif - - template - struct has_on_error : mpl::false_ {}; - - template - struct has_on_error().on_error( - std::declval() - , std::declval() - , std::declval>() - , std::declval() - ) - )) - > - : mpl::true_ - {}; - - template - struct has_on_success : mpl::false_ {}; - - template - struct has_on_success().on_success( - std::declval() - , std::declval() - , std::declval() - , std::declval() - ) - )) - > - : mpl::true_ - {}; - - template - struct make_id - { - typedef identity type; - }; - - template - struct make_id> - { - typedef identity type; - }; - - template - Context const& - make_rule_context(RHS const& /* rhs */, Context const& context - , mpl::false_ /* is_default_parse_rule */) - { - return context; - } - - template - auto make_rule_context(RHS const& rhs, Context const& context - , mpl::true_ /* is_default_parse_rule */ ) - { - return make_unique_context(rhs, context); - } - - template - struct rule_parser - { - template - static bool call_on_success( - Iterator& /* before */, Iterator& /* after */ - , Context const& /* context */, ActualAttribute& /* attr */ - , mpl::false_ /* No on_success handler */ ) - { - return true; - } - - template - static bool call_on_success( - Iterator& before, Iterator& after - , Context const& context, ActualAttribute& attr - , mpl::true_ /* Has on_success handler */) - { - x3::skip_over(before, after, context); - bool pass = true; - ID().on_success( - before - , after - , attr - , make_context(pass, context) - ); - return pass; - } - - template - static bool parse_rhs_main( - RHS const& rhs - , Iterator& first, Iterator const& last - , Context const& context, RContext& rcontext, ActualAttribute& attr - , mpl::false_) - { - // see if the user has a BOOST_SPIRIT_DEFINE for this rule - typedef - decltype(parse_rule( - detail::rule_id{}, first, last - , make_unique_context(rhs, context), std::declval())) - parse_rule_result; - - // If there is no BOOST_SPIRIT_DEFINE for this rule, - // we'll make a context for this rule tagged by its ID - // so we can extract the rule later on in the default - // (generic) parse_rule function. - typedef - is_same - is_default_parse_rule; - - Iterator start = first; - bool r = rhs.parse( - first - , last - , make_rule_context(rhs, context, std::conditional_t()) - , rcontext - , attr - ); - - if (r) - { - r = call_on_success(start, first, context, attr - , has_on_success()); - } - - return r; - } - - template - static bool parse_rhs_main( - RHS const& rhs - , Iterator& first, Iterator const& last - , Context const& context, RContext& rcontext, ActualAttribute& attr - , mpl::true_ /* on_error is found */) - { - for (;;) - { - #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE - try - #endif - { - if (parse_rhs_main( - rhs, first, last, context, rcontext, attr, mpl::false_())) - { - return true; - } - } - - #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE - catch (expectation_failure const& x) { - #else - if (has_expectation_failure(context)) { - auto& x = get_expectation_failure(context); - #endif - // X3 developer note: don't forget to sync this implementation with x3::guard - switch (ID().on_error(first, last, x, context)) - { - case error_handler_result::fail: - clear_expectation_failure(context); - return false; - - case error_handler_result::retry: - continue; - - case error_handler_result::accept: - return true; - - case error_handler_result::rethrow: - #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE - throw; - #else - return false; // TODO: design decision required - #endif - } - } - return false; - } - } - - template - static bool parse_rhs_main( - RHS const& rhs - , Iterator& first, Iterator const& last - , Context const& context, RContext& rcontext, ActualAttribute& attr) - { - return parse_rhs_main( - rhs, first, last, context, rcontext, attr - , has_on_error() - ); - } - - template - static bool parse_rhs( - RHS const& rhs - , Iterator& first, Iterator const& last - , Context const& context, RContext& rcontext, ActualAttribute& attr - , mpl::false_) - { - return parse_rhs_main(rhs, first, last, context, rcontext, attr); - } - - template - static bool parse_rhs( - RHS const& rhs - , Iterator& first, Iterator const& last - , Context const& context, RContext& rcontext, ActualAttribute& /* attr */ - , mpl::true_) - { - return parse_rhs_main(rhs, first, last, context, rcontext, unused); - } - - template - static bool call_rule_definition( - RHS const& rhs - , char const* rule_name - , Iterator& first, Iterator const& last - , Context const& context, ActualAttribute& attr - , ExplicitAttrPropagation) - { - boost::ignore_unused(rule_name); - - // do down-stream transformation, provides attribute for - // rhs parser - typedef traits::transform_attribute< - ActualAttribute, Attribute, parser_id> - transform; - - typedef typename transform::type transform_attr; - transform_attr attr_ = transform::pre(attr); - - bool ok_parse - //Creates a place to hold the result of parse_rhs - //called inside the following scope. - ; - { - // Create a scope to cause the dbg variable below (within - // the #if...#endif) to call it's DTOR before any - // modifications are made to the attribute, attr_ passed - // to parse_rhs (such as might be done in - // transform::post when, for example, - // ActualAttribute is a recursive variant). -#if defined(BOOST_SPIRIT_X3_DEBUG) - context_debug - dbg(rule_name, first, last, attr_, ok_parse); -#endif - ok_parse = parse_rhs(rhs, first, last, context, attr_, attr_ - , mpl::bool_ - < ( RHS::has_action - && !ExplicitAttrPropagation::value - ) - >() - ); - } - if (ok_parse) - { - // do up-stream transformation, this integrates the results - // back into the original attribute value, if appropriate - transform::post(attr, std::forward(attr_)); - } - return ok_parse; - } - }; -}}}} - -#endif diff --git a/include/boost/spirit/home/x3/nonterminal/detail/rule_parser.hpp b/include/boost/spirit/home/x3/nonterminal/detail/rule_parser.hpp new file mode 100644 index 000000000..3424c5af8 --- /dev/null +++ b/include/boost/spirit/home/x3/nonterminal/detail/rule_parser.hpp @@ -0,0 +1,454 @@ +/*============================================================================= + Copyright (c) 2001-2014 Joel de Guzman + Copyright (c) 2017 wanghan02 + Copyright (c) 2024-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) +==============================================================================*/ +#if !defined(BOOST_SPIRIT_X3_DETAIL_RULE_JAN_08_2012_0326PM) +#define BOOST_SPIRIT_X3_DETAIL_RULE_JAN_08_2012_0326PM + +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_SPIRIT_X3_DEBUG) +#include +#endif + +#include +#include +#include +#include + +namespace boost::spirit::x3 +{ + template + struct rule; + + struct parse_pass_context_tag; + + namespace detail + { + template + struct rule_id {}; + + // Placeholder type to detect whether the default `parse_rule(...)` is called + enum struct default_parse_rule_result : bool {}; + + template + constexpr bool context_has_rule_id = !std::is_same_v< + std::remove_cvref_t(std::declval()))>, + unused_type + >; + } // detail + + // The default `parse_rule` definition. + // + // This overload will only be selected when there exists no user-defined + // definition for `parse_rule`. + // + // When a user invokes `BOOST_SPIRIT_X3_DEFINE_`, an unconstrained overload + // is generated at the user's namespace scope. It will never conflict with + // this (vvvvv) overload, as the generated one is never directly called with + // a context containing `ID`. + template Se, typename Context, typename Attribute> + requires detail::context_has_rule_id + [[nodiscard]] constexpr detail::default_parse_rule_result + parse_rule( + detail::rule_id, + It& first, Se const& last, + Context const& context, Attribute& attr + ) + { + auto&& rule_ = x3::get(context); + return static_cast( + rule_.parse(first, last, context, unused, attr) + ); + } + + // This overload is selected only when the user *declares* their `parse_rule` + // in the user's namespace scope AND the function definition is not found. + template Se, typename Context, typename Attribute> + requires (!detail::context_has_rule_id) + constexpr void + parse_rule( + detail::rule_id, + It&, Se const&, + Context const&, Attribute& + ) = delete; // BOOST_SPIRIT_X3_DEFINE undefined for this rule + +} // boost::spirit::x3 + +namespace boost::spirit::x3::detail +{ +#if defined(BOOST_SPIRIT_X3_DEBUG) + // TODO: This should be customizable by users + template Se, typename Attribute> + struct scoped_debug + { + scoped_debug( + char const* rule_name, + It const& first, Se const& last, + Attribute const& attr, + bool const& parse_ok + ) + : parse_ok(parse_ok) + , rule_name(rule_name) + , first(first) + , last(last) + , attr(attr) + , f(detail::get_simple_trace()) + { + f(first, last, attr, pre_parse, rule_name); + } + + ~scoped_debug() + { + f(first, last, attr, parse_ok ? successful_parse : failed_parse, rule_name); + } + + bool const& parse_ok; + char const* rule_name = nullptr; + It const& first; + Se const& last; + Attribute const& attr; + detail::simple_trace_type& f; + }; +#endif + + template + concept HasImmutableOnErrorOverload = + std::forward_iterator && + std::sentinel_for && + requires(ID& id) { // Note: `ID` should be non-const + id.on_error( + std::declval(), + std::declval(), + std::declval const&>(), + std::declval() + ); + }; + + template + concept HasMutableOnErrorOverload = + std::forward_iterator && + std::sentinel_for && + requires(ID& id) { // Note: `ID` should be non-const + id.on_error( + std::declval(), + std::declval(), + std::declval const&>(), + std::declval() + ); + }; + + template Se, typename Context> + struct has_on_error : std::false_type {}; + + template Se, typename Context> + requires HasImmutableOnErrorOverload + struct has_on_error : std::true_type + { + // We intentionally make this hard error to prevent error-prone definition + static_assert( + std::convertible_to< + decltype(std::declval().on_error( + std::declval(), + std::declval(), + std::declval const&>(), + std::declval() + )), + x3::error_handler_result + >, + "The return type of `on_error` should be convertible to `x3::error_handler_result`." + ); + }; + + template Se, typename Context> + requires + (!HasImmutableOnErrorOverload) && + HasMutableOnErrorOverload + struct has_on_error : std::false_type + { + // Historically, Spirit has passed mutable lvalue references of the iterators *as-is* + // to the `on_success`/`on_error` handlers. This behavior was simply a mistake, because: + // (1) `on_success`/`on_error` mechanism was designed to be grammar-agnostic, and + // (2) it does not make sense to modify the grammar-specific iterator on the + // grammar-agnostic callback. + // + // Furthermore, any modification to X3's internal iterator variables may invoke + // undefined behavior, since we never provide any kind of guarantee on how the + // intermediate iterator variables are processed in X3's implementation details. + static_assert( + false, + "The `on_error` callback should only accept const reference or passed-by-value iterators." + ); + }; + + template + concept HasImmutableOnSuccessOverload = + std::forward_iterator && + std::sentinel_for && + requires(ID& id) { // Note: `ID` should be non-const + id.on_success( + std::declval(), + std::declval(), + std::declval(), + std::declval() + ); + }; + + template + concept HasMutableOnSuccessOverload = + std::forward_iterator && + std::sentinel_for && + requires(ID& id) { // Note: `ID` should be non-const + id.on_success( + std::declval(), + std::declval(), + std::declval(), + std::declval() + ); + }; + + template Se, typename Attribute, typename Context> + struct has_on_success : std::false_type {}; + + template Se, typename Attribute, typename Context> + requires HasImmutableOnSuccessOverload + struct has_on_success : std::true_type + { + // We intentionally make this hard error to prevent error-prone definition + static_assert( + std::is_void_v< + decltype(std::declval().on_success( + std::declval(), + std::declval(), + std::declval(), + std::declval() + )) + >, + "The return type of `on_success` should be `void`." + ); + }; + + template Se, typename Attribute, typename Context> + requires + (!HasImmutableOnSuccessOverload) && + HasMutableOnSuccessOverload + struct has_on_success : std::false_type + { + // For details, see the comments on `has_on_error`. + static_assert( + false, + "The `on_success` callback should only accept const reference or passed-by-value iterators." + ); + }; + + template + struct rule_parser + { + template < + typename RHS, std::forward_iterator It, std::sentinel_for Se, + typename Context, typename RContext, typename ActualAttribute + > + [[nodiscard]] static constexpr bool + parse_rhs( + RHS const& rhs, It& first, Se const& last, + Context const& context, RContext& rcontext, ActualAttribute& attr + ) // never noexcept; requires complex handling + { + It start = first; + + // See if the user has a BOOST_SPIRIT_DEFINE for this rule + using parse_rule_result_type = decltype( + parse_rule( // ADL + std::declval>(), first, last, + std::declval(rhs, context))>(), + std::declval() + ) + ); + constexpr bool is_default_parse_rule = std::is_same_v< + parse_rule_result_type, default_parse_rule_result + >; + + bool ok; + if constexpr (SkipDefinitionInjection || !is_default_parse_rule) + { + ok = rhs.parse(first, last, context, rcontext, attr); + } + else + { + // If there is no BOOST_SPIRIT_DEFINE for this rule, + // we'll make a context for this rule tagged by its ID + // so we can extract the rule later on in the default + // parse_rule function. + auto rule_id_context = x3::make_unique_context(rhs, context); + ok = rhs.parse(first, last, rule_id_context, rcontext, attr); + } + + // Note: this check uses `It, It` because the value is actually iterator-iterator pair + if constexpr (has_on_success::value) + { + if (!ok) return false; + + x3::skip_over(start, first, context); + bool pass = true; + ID().on_success( + std::as_const(start), std::as_const(first), attr, + x3::make_context(pass, context) + ); + return pass; + } + else + { + return ok; + } + } + + template < + typename RHS, std::forward_iterator It, std::sentinel_for Se, + typename Context, typename RContext, typename ActualAttribute + > + [[nodiscard]] static constexpr bool + parse_rhs_with_on_error( + RHS const& rhs, It& first, Se const& last, + Context const& context, RContext& rcontext, ActualAttribute& attr + ) // never noexcept; requires complex handling + { + while (true) + { + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + try + #endif + { + if (rule_parser::parse_rhs(rhs, first, last, context, rcontext, attr)) + { + return true; + } + } + + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + catch (expectation_failure const& x) { + #else + if (x3::has_expectation_failure(context)) { + auto const& x = x3::get_expectation_failure(context); + #endif + // X3 developer note: don't forget to sync this implementation with x3::guard + switch (ID{}.on_error(std::as_const(first), std::as_const(last), x, context)) + { + case error_handler_result::fail: + x3::clear_expectation_failure(context); + return false; + + case error_handler_result::retry: + continue; + + case error_handler_result::accept: + return true; + + case error_handler_result::rethrow: + #if BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE + throw; + #else + return false; // TODO: design decision required + #endif + } + } + return false; + } + } + + template < + bool ForceAttribute, + typename RHS, std::forward_iterator It, std::sentinel_for Se, + typename Context, typename ActualAttribute + > + [[nodiscard]] static constexpr bool + call_rule_definition( + RHS const& rhs, char const* rule_name, + It& first, Se const& last, + Context const& context, ActualAttribute& attr + ) + { + // Do down-stream transformation, provides attribute for rhs parser + using transform = traits::transform_attribute; + using transform_attr = typename transform::type; + transform_attr attr_ = transform::pre(attr); + + // Creates a place to hold the result of parse_rhs + // called inside the following scope. + bool parse_ok; + { + #ifdef BOOST_SPIRIT_X3_DEBUG + // Create a scope to cause the dbg variable below (within + // the #if...#endif) to call it's DTOR before any + // modifications are made to the attribute, attr_ passed + // to parse_rhs (such as might be done in + // transform::post when, for example, + // ActualAttribute is a recursive variant). + scoped_debug + dbg(rule_name, first, last, attr_, parse_ok); + #else + (void)rule_name; + #endif + + // In theory, these calls can be overloaded using tag dispatches. + // However we should get over those legacy technique already, as they + // lead to enormous amount of call stack, generating unreadable + // compilation errors. Even when we have a single layer of tag dispatch, + // we should generally avoid it because the "true_type/false_type" + // argument placed at the very last param of the overload is virtually + // indistinguishable in messages and has serious QoL issue in debuggers. + constexpr bool rhs_has_on_error = has_on_error::value; + + // The existence of semantic action inhibits attribute materialization + // _unless_ it is explicitly required by the user (primarily via `%=`). + if constexpr (RHS::has_action && !ForceAttribute) + { + if constexpr (rhs_has_on_error) + { + parse_ok = rule_parser::parse_rhs_with_on_error( + rhs, first, last, context, attr_ /* rcontext */, unused + ); + } + else + { + parse_ok = rule_parser::parse_rhs( + rhs, first, last, context, attr_ /* rcontext */, unused + ); + } + } + else // attribute is required + { + if constexpr (rhs_has_on_error) + { + parse_ok = rule_parser::parse_rhs_with_on_error( + rhs, first, last, context, attr_ /* rcontext */, attr_ + ); + } + else + { + parse_ok = rule_parser::parse_rhs( + rhs, first, last, context, attr_ /* rcontext */, attr_ + ); + } + } + } + if (parse_ok) + { + // do up-stream transformation, this integrates the results + // back into the original attribute value, if appropriate + transform::post(attr, std::forward(attr_)); + } + return parse_ok; + } + }; +} // boost::spirit::x3::detail + +#endif diff --git a/include/boost/spirit/home/x3/nonterminal/rule.hpp b/include/boost/spirit/home/x3/nonterminal/rule.hpp index 8acb793bb..b2c0e1458 100644 --- a/include/boost/spirit/home/x3/nonterminal/rule.hpp +++ b/include/boost/spirit/home/x3/nonterminal/rule.hpp @@ -8,13 +8,16 @@ #if !defined(BOOST_SPIRIT_X3_RULE_JAN_08_2012_0326PM) #define BOOST_SPIRIT_X3_RULE_JAN_08_2012_0326PM -#include -#include +#include #include #include #include #include +#include + +#include #include +#include #if !defined(BOOST_SPIRIT_X3_NO_RTTI) #include @@ -22,240 +25,313 @@ namespace boost::spirit::x3 { - // default parse_rule implementation - template - inline detail::default_parse_rule_result - parse_rule( - detail::rule_id - , Iterator& first, Iterator const& last - , Context const& context, ActualAttribute& attr) + template + struct rule_definition : parser> { - static_assert(!is_same(context)), unused_type>::value, - "BOOST_SPIRIT_DEFINE undefined for this rule."); - return x3::get(context).parse(first, last, context, unused, attr); - } + using this_type = rule_definition; + using id = ID; + using rhs_type = RHS; + using lhs_type = rule; + using attribute_type = Attribute; - template - struct rule_definition : parser> - { - typedef rule_definition this_type; - typedef ID id; - typedef RHS rhs_type; - typedef rule lhs_type; - typedef Attribute attribute_type; + static constexpr bool has_attribute = !std::is_same_v; + static constexpr bool handles_container = traits::is_container::value; + static constexpr bool force_attribute = ForceAttribute; - static bool const has_attribute = - !is_same::value; - static bool const handles_container = - traits::is_container::value; - static bool const force_attribute = - force_attribute_; + template + requires std::is_constructible_v + constexpr rule_definition(RHS_T&& rhs, char const* name) + noexcept(std::is_nothrow_constructible_v) + : rhs(std::forward(rhs)) + , name(name) + {} - constexpr rule_definition(RHS const& rhs, char const* name) - : rhs(rhs), name(name) {} - - template - bool parse(Iterator& first, Iterator const& last - , Context const& context, unused_type, Attribute_& attr) const + template Se, typename Context, typename Attribute_> + [[nodiscard]] constexpr bool + parse(It& first, Se const& last, Context const& context, unused_type, Attribute_& attr) const + // never noexcept; requires very complex implementation details { - return detail::rule_parser - ::call_rule_definition( - rhs, name, first, last - , context - , attr - , mpl::bool_()); + return detail::rule_parser + ::template call_rule_definition( + rhs, name, first, last, context, attr + ); } RHS rhs; - char const* name; + char const* name = "unnamed"; }; - template - struct rule : parser> + template + struct rule : parser> { - static_assert(!std::is_reference::value, - "Reference qualifier on rule attribute type is meaningless"); + static_assert(!std::is_reference_v, "Reference qualifier on rule attribute type is meaningless"); - typedef ID id; - typedef Attribute attribute_type; - static bool const has_attribute = - !std::is_same, unused_type>::value; - static bool const handles_container = - traits::is_container::value; - static bool const force_attribute = force_attribute_; + using id = ID; + using attribute_type = Attribute; + static constexpr bool has_attribute = !std::is_same_v, unused_type>; + static constexpr bool handles_container = traits::is_container::value; + static constexpr bool force_attribute = ForceAttribute; #if !defined(BOOST_SPIRIT_X3_NO_RTTI) rule() : name(typeid(rule).name()) {} #else - constexpr rule() : name("unnamed") {} + constexpr rule() noexcept : name("unnamed") {} #endif - constexpr rule(char const* name) - : name(name) {} + constexpr rule(char const* name) noexcept + : name(name) + {} - constexpr rule(rule const& r) - : name(r.name) + constexpr rule(rule const& r) noexcept + : name(r.name) { // Assert that we are not copying an unitialized static rule. If // the static is in another TU, it may be initialized after we copy // it. If so, its name member will be nullptr. - BOOST_ASSERT_MSG(r.name, "uninitialized rule"); // static initialization order fiasco + BOOST_ASSERT_MSG(r.name != nullptr, "uninitialized rule"); // static initialization order fiasco } - template - constexpr rule_definition< - ID, typename extension::as_parser::value_type, Attribute, force_attribute_> - operator=(RHS const& rhs) const& + template + [[nodiscard]] constexpr rule_definition, Attribute, ForceAttribute> + operator=(RHS&& rhs) const& + noexcept( + is_parser_nothrow_castable_v && + std::is_nothrow_constructible_v< + rule_definition, Attribute, ForceAttribute>, + as_parser_t, char const* + > + ) { - return { as_parser(rhs), name }; + return { as_parser(std::forward(rhs)), name }; } - template - constexpr rule_definition< - ID, typename extension::as_parser::value_type, Attribute, true> - operator%=(RHS const& rhs) const& + template + [[nodiscard]] constexpr rule_definition, Attribute, true> + operator%=(RHS&& rhs) const& + noexcept( + is_parser_nothrow_castable_v && + std::is_nothrow_constructible_v< + rule_definition, Attribute, true>, + as_parser_t, char const* + > + ) { - return { as_parser(rhs), name }; + return { as_parser(std::forward(rhs)), name }; } // When a rule placeholder constructed and immediately consumed it cannot be used recursively, // that's why the rule definition injection into a parser context can be skipped. // This optimization has a huge impact on compile times because immediate rules are commonly // used to cast an attribute like `as`/`attr_cast` does in Qi. - template - constexpr rule_definition< - ID, typename extension::as_parser::value_type, Attribute, force_attribute_, true> - operator=(RHS const& rhs) const&& + template + [[nodiscard]] constexpr rule_definition, Attribute, ForceAttribute, true> + operator=(RHS&& rhs) const&& + noexcept( + is_parser_nothrow_castable_v && + std::is_nothrow_constructible_v< + rule_definition, Attribute, ForceAttribute, true>, + as_parser_t, char const* + > + ) { - return { as_parser(rhs), name }; + return { as_parser(std::forward(rhs)), name }; } - template - constexpr rule_definition< - ID, typename extension::as_parser::value_type, Attribute, true, true> - operator%=(RHS const& rhs) const&& + template + [[nodiscard]] constexpr rule_definition, Attribute, true, true> + operator%=(RHS&& rhs) const&& + noexcept( + is_parser_nothrow_castable_v && + std::is_nothrow_constructible_v< + rule_definition, Attribute, true, true>, + as_parser_t, char const* + > + ) { - return { as_parser(rhs), name }; + return { as_parser(std::forward(rhs)), name }; } - - template - bool parse(Iterator& first, Iterator const& last - , Context const& context, unused_type, Attribute_& attr) const + template Se, typename Context, typename Exposed> + requires (!std::is_same_v, unused_type>) + [[nodiscard]] constexpr bool + parse(It& first, Se const& last, Context const& context, unused_type, Exposed& exposed_attr) const + // never noexcept; requires very complex implementation details { - static_assert(has_attribute, - "The rule does not have an attribute. Check your parser."); + static_assert(has_attribute, "A rule must have an attribute. Check your rule definition."); - using transform = traits::transform_attribute< - Attribute_, attribute_type, detail::parser_id>; + static_assert( + traits::Transformable, + "Attribute type mismatch; the rule's attribute is not assignable to " + "the exposed attribute, and no eligible specialization of " + "`x3::traits::transform_attribute` was found." + ); - using transform_attr = typename transform::type; - transform_attr attr_ = transform::pre(attr); + using transform = traits::transform_attribute; + using transformed_type = typename transform::type; + transformed_type transformed_attr = transform::pre(exposed_attr); - if (parse_rule(detail::rule_id{}, first, last, context, attr_)) { - transform::post(attr, std::forward(attr_)); + // ADL + if (static_cast(parse_rule(detail::rule_id{}, first, last, context, transformed_attr))) { + transform::post(exposed_attr, std::forward(transformed_attr)); return true; } return false; } - template - bool parse(Iterator& first, Iterator const& last - , Context const& context, unused_type, unused_type) const + template Se, typename Context> + [[nodiscard]] constexpr bool + parse(It& first, Se const& last, Context const& context, unused_type, unused_type) const + // never noexcept; requires very complex implementation details { // make sure we pass exactly the rule attribute type - attribute_type no_attr{}; - return parse_rule(detail::rule_id{}, first, last, context, no_attr); + attribute_type no_attr; // default-initialize + + // ADL + return static_cast(parse_rule(detail::rule_id{}, first, last, context, no_attr)); } - char const* name; + char const* name = "unnamed"; }; namespace traits { template - struct is_rule : mpl::false_ {}; + struct is_rule : std::false_type {}; - template - struct is_rule> : mpl::true_ {}; + template + constexpr bool is_rule_v = is_rule::value; - template - struct is_rule> : mpl::true_ {}; + template + struct is_rule> : std::true_type {}; + + template + struct is_rule> : std::true_type {}; } template - struct get_info>::type> + requires traits::is_rule_v + struct get_info { - typedef std::string result_type; - std::string operator()(T const& r) const + using result_type = std::string; + [[nodiscard]] std::string operator()(T const& r) const { BOOST_ASSERT_MSG(r.name, "uninitialized rule"); // static initialization order fiasco return r.name? r.name : "uninitialized"; } }; -#define BOOST_SPIRIT_DECLARE_(r, data, rule_type) \ - template \ - bool parse_rule( \ - ::boost::spirit::x3::detail::rule_id \ - , Iterator& first, Iterator const& last \ - , Context const& context, rule_type::attribute_type& attr); \ - /***/ +// ------------------------------------------------------------- -#define BOOST_SPIRIT_DECLARE(...) BOOST_PP_SEQ_FOR_EACH( \ - BOOST_SPIRIT_DECLARE_, _, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \ - /***/ +#define BOOST_SPIRIT_X3_DEPRECATED_MACRO_WARN_I(x) _Pragma(#x) +#define BOOST_SPIRIT_X3_DEPRECATED_MACRO_WARN(msg) BOOST_SPIRIT_X3_DEPRECATED_MACRO_WARN_I(message(msg)) -#if BOOST_WORKAROUND(BOOST_MSVC, < 1910) -#define BOOST_SPIRIT_DEFINE_(r, data, rule_name) \ - using BOOST_PP_CAT(rule_name, _synonym) = decltype(rule_name); \ - template \ - inline bool parse_rule( \ - ::boost::spirit::x3::detail::rule_id \ - , Iterator& first, Iterator const& last \ - , Context const& context, BOOST_PP_CAT(rule_name, _synonym)::attribute_type& attr) \ - { \ - using rule_t = BOOST_JOIN(rule_name, _synonym); \ - return ::boost::spirit::x3::detail \ - ::rule_parser \ - ::call_rule_definition( \ - BOOST_JOIN(rule_name, _def), rule_name.name \ - , first, last, context, attr \ - , ::boost::mpl::bool_()); \ - } \ - /***/ -#else -#define BOOST_SPIRIT_DEFINE_(r, data, rule_name) \ - template \ - inline bool parse_rule( \ - ::boost::spirit::x3::detail::rule_id \ - , Iterator& first, Iterator const& last \ - , Context const& context, decltype(rule_name)::attribute_type& attr) \ - { \ - using rule_t = decltype(rule_name); \ - return ::boost::spirit::x3::detail \ - ::rule_parser \ - ::call_rule_definition( \ - BOOST_JOIN(rule_name, _def), rule_name.name \ - , first, last, context, attr \ - , ::boost::mpl::bool_()); \ - } \ - /***/ -#endif +#define BOOST_SPIRIT_X3_DECLARE_(r, constexpr_, rule_type) \ + template Se, typename Context> \ + [[nodiscard]] constexpr_ bool \ + parse_rule( \ + ::boost::spirit::x3::detail::rule_id::id>, \ + It& first, Se const& last, \ + Context const& context, typename std::remove_cvref_t::attribute_type& attr \ + ); -#define BOOST_SPIRIT_DEFINE(...) BOOST_PP_SEQ_FOR_EACH( \ - BOOST_SPIRIT_DEFINE_, _, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \ - /***/ +#define BOOST_SPIRIT_X3_DECLARE(rule_type) BOOST_SPIRIT_X3_DECLARE_(,, rule_type) +#define BOOST_SPIRIT_X3_DECLARE_CONSTEXPR(rule_type) BOOST_SPIRIT_X3_DECLARE_(, constexpr, rule_type) -#define BOOST_SPIRIT_INSTANTIATE(rule_type, Iterator, Context) \ - template bool parse_rule( \ - ::boost::spirit::x3::detail::rule_id \ - , Iterator& first, Iterator const& last \ - , Context const& context, rule_type::attribute_type&); \ - /***/ +// NB: This can't be `constexpr`, because a constexpr declaration +// cannot be used with explicit template instantiation. We simply +// can't drop (legit) use cases of `BOOST_SPIRIT_INSTANTIATE`, so +// this is a pure technical limitation. If you need `constexpr` +// support in your rule, use `BOOST_SPIRIT_X3_DECLARE_CONSTEXPR`. +#define BOOST_SPIRIT_DECLARE(...) \ + BOOST_SPIRIT_X3_DEPRECATED_MACRO_WARN( \ + "Use of variadic arguments with `BOOST_SPIRIT_DECLARE` is deprecated due to the heavy compile-time cost of " \ + "`BOOST_PP_SEQ_*`. Just apply `BOOST_SPIRIT_X3_DECLARE` for each of your rules." \ + ) \ + BOOST_PP_SEQ_FOR_EACH(BOOST_SPIRIT_X3_DECLARE_,, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) +#define BOOST_SPIRIT_X3_DEFINE_(r, constexpr_, rule_name) \ + template Se, typename Context> \ + [[nodiscard]] constexpr_ bool \ + parse_rule( \ + ::boost::spirit::x3::detail::rule_id::id>, \ + It& first, Se const& last, \ + Context const& context, \ + std::remove_cvref_t::attribute_type& attr \ + ) { \ + using rule_t = std::remove_cvref_t; \ + return ::boost::spirit::x3::detail::rule_parser< \ + typename rule_t::attribute_type, typename rule_t::id, true \ + >::call_rule_definition( \ + BOOST_JOIN(rule_name, _def), rule_name.name, \ + first, last, context, attr \ + ); \ + } + +#define BOOST_SPIRIT_X3_DEFINE(rule_type) BOOST_SPIRIT_X3_DEFINE_(,, rule_type) +#define BOOST_SPIRIT_X3_DEFINE_CONSTEXPR(rule_type) BOOST_SPIRIT_X3_DEFINE_(, constexpr, rule_type) + +// NB: This can't be `constexpr`, because a constexpr declaration +// cannot be used with explicit template instantiation. We simply +// can't drop (legit) use cases of `BOOST_SPIRIT_INSTANTIATE`, so +// this is a pure technical limitation. If you need `constexpr` +// support in your rule, use `BOOST_SPIRIT_X3_DEFINE_CONSTEXPR`. +#define BOOST_SPIRIT_DEFINE(...) \ + BOOST_SPIRIT_X3_DEPRECATED_MACRO_WARN( \ + "Use of variadic arguments with `BOOST_SPIRIT_DEFINE` is deprecated due to the heavy compile-time cost of " \ + "`BOOST_PP_SEQ_*`. Just apply `BOOST_SPIRIT_X3_DEFINE` for each of your rules." \ + ) \ + BOOST_PP_SEQ_FOR_EACH(BOOST_SPIRIT_X3_DEFINE_,, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) + + namespace detail + { + template + struct instantiate_macro_helper + { + using rule_type = RuleT; + using iterator_type = It; + + // Old API: + // A => Context + // B => void + + // New API: + // A => Se + // B => Context + + using sentinel_type = std::conditional_t, It, A>; + static_assert(std::sentinel_for); + + using context_type = std::conditional_t, A, B>; + }; + } // detail + +#define BOOST_SPIRIT_X3_INSTANTIATE_(rule_type, It, Se, Context) \ + template bool parse_rule( \ + ::boost::spirit::x3::detail::rule_id::id>, \ + It&, Se const&, Context const&, \ + typename std::remove_cvref_t::attribute_type& \ + ); + +#define BOOST_SPIRIT_X3_INSTANTIATE_WRAP(...) __VA_ARGS__ + +// NB: This can't be `constexpr`, because a constexpr declaration +// cannot be used with explicit template instantiation. +#define BOOST_SPIRIT_X3_INSTANTIATE(...) \ + BOOST_SPIRIT_X3_INSTANTIATE_( \ + BOOST_SPIRIT_X3_INSTANTIATE_WRAP(typename ::boost::spirit::x3::detail::instantiate_macro_helper<__VA_ARGS__>::rule_type), \ + BOOST_SPIRIT_X3_INSTANTIATE_WRAP(typename ::boost::spirit::x3::detail::instantiate_macro_helper<__VA_ARGS__>::iterator_type), \ + BOOST_SPIRIT_X3_INSTANTIATE_WRAP(typename ::boost::spirit::x3::detail::instantiate_macro_helper<__VA_ARGS__>::sentinel_type), \ + BOOST_SPIRIT_X3_INSTANTIATE_WRAP(typename ::boost::spirit::x3::detail::instantiate_macro_helper<__VA_ARGS__>::context_type) \ + ) + +#define BOOST_SPIRIT_INSTANTIATE(...) \ + BOOST_SPIRIT_X3_DEPRECATED_MACRO_WARN( \ + "Use `BOOST_SPIRIT_X3_INSTANTIATE`. `BOOST_SPIRIT_INSTANTIATE` is deprecated because " \ + "the name was not correctly prefixed with `X3`." \ + ) \ + BOOST_SPIRIT_X3_INSTANTIATE(__VA_ARGS__) + } // boost::spirit::x3 #endif diff --git a/include/boost/spirit/home/x3/support/utility/annotate_on_success.hpp b/include/boost/spirit/home/x3/support/utility/annotate_on_success.hpp index dcb80dfb2..972a8c695 100644 --- a/include/boost/spirit/home/x3/support/utility/annotate_on_success.hpp +++ b/include/boost/spirit/home/x3/support/utility/annotate_on_success.hpp @@ -1,5 +1,6 @@ /*============================================================================= Copyright (c) 2001-2015 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) @@ -7,15 +8,18 @@ #if !defined(BOOST_SPIRIT_X3__ANNOTATE_ON_SUCCESS_HPP) #define BOOST_SPIRIT_X3__ANNOTATE_ON_SUCCESS_HPP -#include #include -#include +#include +#include #include -#include -namespace boost { namespace spirit { namespace x3 +#include +#include + +namespace boost::spirit::x3 { - /////////////////////////////////////////////////////////////////////////// + struct error_handler_tag; + // The on_success handler tags the AST with the iterator position // for error handling. // @@ -25,13 +29,37 @@ namespace boost { namespace spirit { namespace x3 // x3/support/ast. // // We'll ask the X3's error_handler utility to do these. - /////////////////////////////////////////////////////////////////////////// struct annotate_on_success { - template - inline void on_success(Iterator const& first, Iterator const& last - , variant& ast, Context const& context) + // Catch-all default overload + template Se, typename T, typename Context> + constexpr void + on_success(It const& first, Se const& last, T& ast, Context const& context) + { + auto&& error_handler_ref = x3::get(context); + static_assert( + !std::is_same_v, unused_type>, + "This rule is derived from `x3::annotate_on_success`, but no reference was bound to " + "`x3::error_handler_tag`. You must provide a viable error handler via `x3::with`." + ); + + // unwrap `reference_wrapper` if neccessary + if constexpr (requires { + error_handler_ref.get().tag(ast, first, last); + }) + { + error_handler_ref.get().tag(ast, first, last); + } + else + { + error_handler_ref.tag(ast, first, last); + } + } + + template Se, typename... Types, typename Context> + constexpr void + on_success(It const& first, Se const& last, x3::variant& ast, Context const& context) { ast.apply_visitor(x3::make_lambda_visitor([&](auto& node) { @@ -39,21 +67,13 @@ namespace boost { namespace spirit { namespace x3 })); } - template - inline void on_success(Iterator const& first, Iterator const& last - , forward_ast& ast, Context const& context) + template Se, typename T, typename Context> + constexpr void + on_success(It const& first, Se const& last, forward_ast& ast, Context const& context) { this->on_success(first, last, ast.get(), context); } - - template - inline typename disable_if>::type on_success(Iterator const& first, Iterator const& last - , T& ast, Context const& context) - { - auto& error_handler = x3::get(context).get(); - error_handler.tag(ast, first, last); - } }; -}}} +} // boost::spirit::x3 #endif diff --git a/test/x3/container_support.cpp b/test/x3/container_support.cpp index ba80cb17f..658e5613d 100644 --- a/test/x3/container_support.cpp +++ b/test/x3/container_support.cpp @@ -1,6 +1,7 @@ /*============================================================================= Copyright (c) 2001-2015 Joel de Guzman Copyright (c) 2001-2011 Hartmut Kaiser + 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) @@ -58,7 +59,8 @@ x3::rule const string_rule("string"); auto const pair_rule_def = string_rule > x3::lit('=') > string_rule; auto const string_rule_def = x3::lexeme[*x3::alnum]; -BOOST_SPIRIT_DEFINE(pair_rule, string_rule) +BOOST_SPIRIT_X3_DEFINE(pair_rule) +BOOST_SPIRIT_X3_DEFINE(string_rule) template void test_map_support() diff --git a/test/x3/debug.cpp b/test/x3/debug.cpp index de0508de0..12dfa16ef 100644 --- a/test/x3/debug.cpp +++ b/test/x3/debug.cpp @@ -1,26 +1,29 @@ /*============================================================================= Copyright (c) 2001-2015 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) =============================================================================*/ #define BOOST_SPIRIT_X3_DEBUG +#include "test.hpp" + #include #include #include +#include #include #include #include #include -#include "test.hpp" struct my_error_handler { - template + template Se, typename Exception, typename Context> boost::spirit::x3::error_handler_result - operator()(Iterator&, Iterator const& last, Exception const& x, Context const&) const + operator()(It const&, Se const& last, Exception const& x, Context const&) const { std::cout << "Error! Expecting: " @@ -46,24 +49,22 @@ struct my_attribute alive = false; } - friend std::ostream & operator << (std::ostream & os, my_attribute const & attr) + friend std::ostream& operator<<(std::ostream & os, my_attribute const & attr) { attr.access(); return os << "my_attribute"; } }; -int -main() +int main() { using spirit_test::test_attr; using spirit_test::test; - using namespace boost::spirit::x3::ascii; + using namespace boost::spirit::x3::standard; using boost::spirit::x3::rule; using boost::spirit::x3::symbols; using boost::spirit::x3::int_; - using boost::spirit::x3::alpha; { // basic tests diff --git a/test/x3/error_handler.cpp b/test/x3/error_handler.cpp index 00d152efb..de9b4623c 100644 --- a/test/x3/error_handler.cpp +++ b/test/x3/error_handler.cpp @@ -1,5 +1,6 @@ /*============================================================================= Copyright (c) 2001-2015 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) @@ -7,8 +8,11 @@ #include #include +#include #include + +#include #include #include @@ -16,10 +20,11 @@ namespace x3 = boost::spirit::x3; struct error_handler_base { - template + template Se, typename Exception, typename Context> x3::error_handler_result on_error( - Iterator&, Iterator const& - , Exception const& x, Context const& context) const + It const&, Se const&, + Exception const& x, Context const& context + ) const { std::string message = "Error! Expecting: " + x.which() + " here:"; auto& error_handler = x3::get(context).get(); @@ -36,35 +41,39 @@ x3::rule const test_rule; auto const test_inner_rule_def = x3::lit("bar"); auto const test_rule_def = x3::lit("foo") > test_inner_rule > x3::lit("git"); -BOOST_SPIRIT_DEFINE(test_inner_rule, test_rule) +BOOST_SPIRIT_X3_DEFINE(test_inner_rule) +BOOST_SPIRIT_X3_DEFINE(test_rule) -void test(std::string const& line_break) { +void test(std::string const& line_break) +{ std::string const input("foo" + line_break + " foo" + line_break + "git"); auto const begin = std::begin(input); auto const end = std::end(input); + { + std::stringstream stream; + x3::error_handler error_handler{begin, end, stream}; + + auto const parser = x3::with(std::ref(error_handler))[test_rule]; + (void)x3::phrase_parse(begin, end, parser, x3::standard::space); + + BOOST_TEST_EQ(stream.str(), "In line 2:\nError! Expecting: \"bar\" here:\n foo\n__^_\n"); + } + + { + // TODO: cleanup when error_handler is reenterable + std::stringstream stream; + x3::error_handler error_handler{ begin, end, stream }; + + auto const parser = x3::with(std::ref(error_handler))[test_rule]; + (void)x3::parse(begin, end, parser); + + BOOST_TEST_CSTR_EQ(stream.str().c_str(), "In line 1:\nError! Expecting: \"bar\" here:\nfoo\n___^_\n"); + } +} + +void test_line_break_first(std::string const& line_break) { - std::stringstream stream; - x3::error_handler error_handler{begin, end, stream}; - - auto const parser = x3::with(std::ref(error_handler))[test_rule]; - x3::phrase_parse(begin, end, parser, x3::space); - - BOOST_TEST_EQ(stream.str(), "In line 2:\nError! Expecting: \"bar\" here:\n foo\n__^_\n"); -} - -{ // TODO: cleanup when error_handler is reenterable - std::stringstream stream; - x3::error_handler error_handler{ begin, end, stream }; - - auto const parser = x3::with(std::ref(error_handler))[test_rule]; - x3::parse(begin, end, parser); - - BOOST_TEST_CSTR_EQ(stream.str().c_str(), "In line 1:\nError! Expecting: \"bar\" here:\nfoo\n___^_\n"); -} -} - -void test_line_break_first(std::string const& line_break) { std::string const input(line_break + "foo foo" + line_break + "git"); auto const begin = std::begin(input); auto const end = std::end(input); @@ -73,12 +82,13 @@ void test_line_break_first(std::string const& line_break) { x3::error_handler error_handler{begin, end, stream}; auto const parser = x3::with(std::ref(error_handler))[test_rule]; - x3::phrase_parse(begin, end, parser, x3::space); + (void)x3::phrase_parse(begin, end, parser, x3::space); BOOST_TEST_EQ(stream.str(), "In line 2:\nError! Expecting: \"bar\" here:\nfoo foo\n_____^_\n"); } -int main() { +int main() +{ test("\n"); test("\r"); test("\r\n"); diff --git a/test/x3/grammar.cpp b/test/x3/grammar.cpp index 519e15cc2..1de22ee8d 100644 --- a/test/x3/grammar.cpp +++ b/test/x3/grammar.cpp @@ -1,6 +1,16 @@ +/*============================================================================= + Copyright (c) 2019 Joel de Guzman + Copyright (c) 2019 Nikita Kniazev + Copyright (c) 2025 Nana Sakisaka + + Use, modification and distribution is subject to 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 "grammar.hpp" auto const grammar_def = x3::int_; -BOOST_SPIRIT_DEFINE(grammar) -BOOST_SPIRIT_INSTANTIATE(grammar_type, char const*, x3::unused_type) +BOOST_SPIRIT_X3_DEFINE(grammar) +BOOST_SPIRIT_X3_INSTANTIATE(grammar_type, char const*, x3::unused_type) diff --git a/test/x3/grammar.hpp b/test/x3/grammar.hpp index 14b9402a5..84a7250ef 100644 --- a/test/x3/grammar.hpp +++ b/test/x3/grammar.hpp @@ -1,7 +1,17 @@ +/*============================================================================= + Copyright (c) 2019 Joel de Guzman + Copyright (c) 2019 Nikita Kniazev + Copyright (c) 2025 Nana Sakisaka + + Use, modification and distribution is subject to 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 namespace x3 = boost::spirit::x3; x3::rule const grammar; using grammar_type = decltype(grammar); -BOOST_SPIRIT_DECLARE(grammar_type) +BOOST_SPIRIT_X3_DECLARE(grammar_type) diff --git a/test/x3/omit.cpp b/test/x3/omit.cpp index e8b733e9f..13b2aa950 100644 --- a/test/x3/omit.cpp +++ b/test/x3/omit.cpp @@ -1,5 +1,6 @@ /*============================================================================= Copyright (c) 2001-2015 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) @@ -20,10 +21,10 @@ rule indirect_rule = "indirect_rule"; auto const direct_rule_def = boost::spirit::x3::int_; auto const indirect_rule_def = direct_rule; -BOOST_SPIRIT_DEFINE(direct_rule, indirect_rule) +BOOST_SPIRIT_X3_DEFINE(direct_rule) +BOOST_SPIRIT_X3_DEFINE(indirect_rule) -int -main() +int main() { using namespace boost::spirit::x3::ascii; using boost::spirit::x3::omit; diff --git a/test/x3/raw.cpp b/test/x3/raw.cpp index 59b651b4b..eed193f9e 100644 --- a/test/x3/raw.cpp +++ b/test/x3/raw.cpp @@ -22,7 +22,8 @@ rule indirect_rule = "indirect_rule"; auto const direct_rule_def = boost::spirit::x3::int_; auto const indirect_rule_def = direct_rule; -BOOST_SPIRIT_DEFINE(direct_rule, indirect_rule) +BOOST_SPIRIT_X3_DEFINE(direct_rule) +BOOST_SPIRIT_X3_DEFINE(indirect_rule) int main() { @@ -83,7 +84,7 @@ int main() boost::variant attr; std::string str("test"); - parse(str.begin(), str.end(), (int_ | raw[*char_]), attr); + (void)parse(str.begin(), str.end(), (int_ | raw[*char_]), attr); auto rng = boost::get(attr); BOOST_TEST(std::string(rng.begin(), rng.end()) == "test"); @@ -92,7 +93,7 @@ int main() { std::vector> attr; std::string str("123abcd"); - parse(str.begin(), str.end() + (void)parse(str.begin(), str.end() , (raw[int_] >> raw[*char_]) , attr ); @@ -104,7 +105,7 @@ int main() { std::pair> attr; std::string str("123abcd"); - parse(str.begin(), str.end() + (void)parse(str.begin(), str.end() , (int_ >> raw[*char_]) , attr ); diff --git a/test/x3/rule3.cpp b/test/x3/rule3.cpp index 7490422fb..9e3a577c5 100644 --- a/test/x3/rule3.cpp +++ b/test/x3/rule3.cpp @@ -54,7 +54,8 @@ boost::spirit::x3::rule const b; auto const a_def = '{' >> boost::spirit::x3::int_ >> '}'; auto const b_def = a; -BOOST_SPIRIT_DEFINE(a, b) +BOOST_SPIRIT_X3_DEFINE(a) +BOOST_SPIRIT_X3_DEFINE(b) } @@ -69,7 +70,7 @@ boost::spirit::x3::rule const grammar; auto const grammar_def = '[' >> grammar % ',' >> ']' | boost::spirit::x3::int_; -BOOST_SPIRIT_DEFINE(grammar) +BOOST_SPIRIT_X3_DEFINE(grammar) } @@ -95,9 +96,9 @@ namespace check_recursive_tuple { x3::rule const grammar; auto const grammar_def = x3::int_ >> ('{' >> grammar % ',' >> '}' | x3::eps); -BOOST_SPIRIT_DEFINE(grammar) +BOOST_SPIRIT_X3_DEFINE(grammar) -BOOST_SPIRIT_INSTANTIATE(decltype(grammar), char const*, x3::unused_type) +BOOST_SPIRIT_X3_INSTANTIATE(decltype(grammar), char const*, x3::unused_type) } diff --git a/test/x3/rule4.cpp b/test/x3/rule4.cpp index b9ece72b1..26852d499 100644 --- a/test/x3/rule4.cpp +++ b/test/x3/rule4.cpp @@ -13,19 +13,25 @@ #include #include +#include #include #include #include namespace x3 = boost::spirit::x3; +namespace { int got_it = 0; +} struct my_rule_class { - template + //template Se, typename Exception, typename Context> + //x3::error_handler_result + //on_error(It const&, Se const& last, Exception const& x, Context const&) + template Se, typename Exception, typename Context> x3::error_handler_result - on_error(Iterator&, Iterator const& last, Exception const& x, Context const&) + on_error(It const&, Se const& last, Exception const& x, Context const&) { std::cout << "Error! Expecting: " @@ -38,9 +44,9 @@ struct my_rule_class return x3::error_handler_result::fail; } - template - inline void - on_success(Iterator const&, Iterator const&, Attribute&, Context const&) + template Se, typename Attribute, typename Context> + void + on_success(It const&, Se const&, Attribute&, Context const&) { ++got_it; } @@ -50,51 +56,28 @@ struct on_success_gets_preskipped_iterator { static bool ok; - template - void on_success(Iterator before, Iterator& after, Attribute&, Context const&) + template Se, typename Attribute, typename Context> + void on_success(It before, Se const& after, Attribute&, Context const&) { - ok = ('b' == *before) && (++before == after); + bool const before_was_b = 'b' == *before; + ok = before_was_b && (++before == after); } }; bool on_success_gets_preskipped_iterator::ok = false; -struct on_success_advance_iterator -{ - template - void on_success(Iterator const&, Iterator& after, Attribute&, Context const&) - { - ++after; - } -}; -struct on_success_advance_iterator_mutref -{ - template - void on_success(Iterator&, Iterator& after, Attribute&, Context const&) - { - ++after; - } -}; -struct on_success_advance_iterator_byval -{ - template - void on_success(Iterator, Iterator& after, Attribute&, Context const&) - { - ++after; - } -}; -int -main() +int main() { using spirit_test::test_attr; using spirit_test::test; - using namespace boost::spirit::x3::ascii; + using namespace boost::spirit::x3::standard; using boost::spirit::x3::rule; using boost::spirit::x3::int_; using boost::spirit::x3::lit; - { // show that ra = rb and ra %= rb works as expected + // show that ra = rb and ra %= rb works as expected + { rule ra; rule rb; int attr; @@ -112,7 +95,8 @@ main() BOOST_TEST(attr == 123); } - { // show that ra %= rb works as expected with semantic actions + // show that ra %= rb works as expected with semantic actions + { rule ra; rule rb; int attr; @@ -128,8 +112,8 @@ main() } - { // std::string as container attribute with auto rules - + // std::string as container attribute with auto rules + { std::string attr; // test deduced auto rule behavior @@ -142,8 +126,8 @@ main() BOOST_TEST(attr == "x"); } - { // error handling - + // error handling + { auto r = rule() = '(' > int_ > ',' > int_ > ')'; @@ -156,34 +140,23 @@ main() BOOST_TEST(got_it == 1); } - { // on_success gets pre-skipped iterator + // on_success gets pre-skipped iterator + { auto r = rule() = lit("b"); BOOST_TEST(test("a b", 'a' >> r, lit(' '))); BOOST_TEST(on_success_gets_preskipped_iterator::ok); } - { // on_success handler mutable 'after' iterator - auto r1 = rule() - = lit("ab"); - BOOST_TEST(test("abc", r1)); - auto r2 = rule() - = lit("ab"); - BOOST_TEST(test("abc", r2)); - auto r3 = rule() - = lit("ab"); - BOOST_TEST(test("abc", r3)); - } - { - typedef boost::variant v_type; + using v_type = boost::variant; auto r1 = rule() = int_; v_type v; BOOST_TEST(test_attr("1", r1, v) && v.which() == 1 && boost::get(v) == 1); - typedef boost::optional ov_type; + using ov_type = boost::optional; auto r2 = rule() = int_; ov_type ov; @@ -201,21 +174,19 @@ main() BOOST_TEST(test_attr("1", r, v) && at_c<0>(v) == 1); } - { // attribute compatibility test - using boost::spirit::x3::rule; - using boost::spirit::x3::int_; + // attribute compatibility test + { + constexpr auto expr = int_; - auto const expr = int_; + long long i = 0; + BOOST_TEST(test_attr("1", expr, i) && i == 1); - long long i; - BOOST_TEST(test_attr("1", expr, i) && i == 1); // ok + constexpr rule int_rule("int_rule"); + constexpr auto int_rule_def = int_; + constexpr auto start = int_rule = int_rule_def; - const rule< class int_rule, int > int_rule( "int_rule" ); - auto const int_rule_def = int_; - auto const start = int_rule = int_rule_def; - - long long j; - BOOST_TEST(test_attr("1", start, j) && j == 1); // error + long long j = 0; + BOOST_TEST(test_attr("1", start, j) && j == 1); } return boost::report_errors(); diff --git a/test/x3/rule_separate_tu.cpp b/test/x3/rule_separate_tu.cpp index 48e9e61a2..aad2baabc 100644 --- a/test/x3/rule_separate_tu.cpp +++ b/test/x3/rule_separate_tu.cpp @@ -1,5 +1,6 @@ /*============================================================================= Copyright (c) 2019 Nikita Kniazev + Copyright (c) 2025 Nana Sakisaka Use, modification and distribution is subject to the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at @@ -19,19 +20,19 @@ auto nop = [](auto const&){}; x3::rule used_attr1; auto const used_attr1_def = used_attr::grammar[nop]; -BOOST_SPIRIT_DEFINE(used_attr1); +BOOST_SPIRIT_X3_DEFINE(used_attr1); x3::rule used_attr2; auto const used_attr2_def = unused_attr::grammar[nop]; -BOOST_SPIRIT_DEFINE(used_attr2); +BOOST_SPIRIT_X3_DEFINE(used_attr2); x3::rule unused_attr1; auto const unused_attr1_def = used_attr::grammar[nop]; -BOOST_SPIRIT_DEFINE(unused_attr1); +BOOST_SPIRIT_X3_DEFINE(unused_attr1); x3::rule unused_attr2; auto const unused_attr2_def = unused_attr::grammar[nop]; -BOOST_SPIRIT_DEFINE(unused_attr2); +BOOST_SPIRIT_X3_DEFINE(unused_attr2); } diff --git a/test/x3/rule_separate_tu_grammar.cpp b/test/x3/rule_separate_tu_grammar.cpp index e63bce946..8a6db5323 100644 --- a/test/x3/rule_separate_tu_grammar.cpp +++ b/test/x3/rule_separate_tu_grammar.cpp @@ -14,36 +14,36 @@ namespace unused_attr { auto const skipper_def = x3::standard::lit('*'); -BOOST_SPIRIT_DEFINE(skipper) -BOOST_SPIRIT_INSTANTIATE(skipper_type, char const*, x3::unused_type) +BOOST_SPIRIT_X3_DEFINE(skipper) +BOOST_SPIRIT_X3_INSTANTIATE(skipper_type, char const*, x3::unused_type) auto const skipper2_def = x3::standard::lit('#'); -BOOST_SPIRIT_DEFINE(skipper2) -BOOST_SPIRIT_INSTANTIATE(skipper2_type, char const*, x3::unused_type) +BOOST_SPIRIT_X3_DEFINE(skipper2) +BOOST_SPIRIT_X3_INSTANTIATE(skipper2_type, char const*, x3::unused_type) auto const grammar_def = *x3::standard::lit('='); -BOOST_SPIRIT_DEFINE(grammar) -BOOST_SPIRIT_INSTANTIATE(grammar_type, char const*, x3::unused_type) +BOOST_SPIRIT_X3_DEFINE(grammar) +BOOST_SPIRIT_X3_INSTANTIATE(grammar_type, char const*, x3::unused_type) using skipper_context_type = typename x3::phrase_parse_context::type; -BOOST_SPIRIT_INSTANTIATE(grammar_type, char const*, skipper_context_type) +BOOST_SPIRIT_X3_INSTANTIATE(grammar_type, char const*, skipper_context_type) using skipper2_context_type = typename x3::phrase_parse_context::type; -BOOST_SPIRIT_INSTANTIATE(grammar_type, char const*, skipper2_context_type) +BOOST_SPIRIT_X3_INSTANTIATE(grammar_type, char const*, skipper2_context_type) } namespace used_attr { auto const skipper_def = x3::standard::space; -BOOST_SPIRIT_DEFINE(skipper) -BOOST_SPIRIT_INSTANTIATE(skipper_type, char const*, x3::unused_type) +BOOST_SPIRIT_X3_DEFINE(skipper) +BOOST_SPIRIT_X3_INSTANTIATE(skipper_type, char const*, x3::unused_type) auto const grammar_def = x3::int_; -BOOST_SPIRIT_DEFINE(grammar) -BOOST_SPIRIT_INSTANTIATE(grammar_type, char const*, x3::unused_type) +BOOST_SPIRIT_X3_DEFINE(grammar) +BOOST_SPIRIT_X3_INSTANTIATE(grammar_type, char const*, x3::unused_type) using skipper_context_type = typename x3::phrase_parse_context::type; -BOOST_SPIRIT_INSTANTIATE(grammar_type, char const*, skipper_context_type) +BOOST_SPIRIT_X3_INSTANTIATE(grammar_type, char const*, skipper_context_type) } diff --git a/test/x3/rule_separate_tu_grammar.hpp b/test/x3/rule_separate_tu_grammar.hpp index 4bd671697..0f3e07ebe 100644 --- a/test/x3/rule_separate_tu_grammar.hpp +++ b/test/x3/rule_separate_tu_grammar.hpp @@ -1,5 +1,6 @@ /*============================================================================= Copyright (c) 2019 Nikita Kniazev + Copyright (c) 2025 Nana Sakisaka Use, modification and distribution is subject to the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at @@ -8,7 +9,7 @@ #include -// Check that `BOOST_SPIRIT_INSTANTIATE` instantiates `parse_rule` with proper +// Check that `BOOST_SPIRIT_X3_INSTANTIATE` instantiates `parse_rule` with proper // types when a rule has no attribute. namespace unused_attr { @@ -18,17 +19,17 @@ namespace x3 = boost::spirit::x3; // skipper must has no attribute, checks `parse` and `skip_over` using skipper_type = x3::rule; const skipper_type skipper; -BOOST_SPIRIT_DECLARE(skipper_type) +BOOST_SPIRIT_X3_DECLARE(skipper_type) // the `unused_type const` must have the same effect as no attribute using skipper2_type = x3::rule; const skipper2_type skipper2; -BOOST_SPIRIT_DECLARE(skipper2_type) +BOOST_SPIRIT_X3_DECLARE(skipper2_type) // grammar must has no attribute, checks `parse` and `phrase_parse` using grammar_type = x3::rule; const grammar_type grammar; -BOOST_SPIRIT_DECLARE(grammar_type) +BOOST_SPIRIT_X3_DECLARE(grammar_type) } @@ -40,10 +41,10 @@ namespace x3 = boost::spirit::x3; using skipper_type = x3::rule; const skipper_type skipper; -BOOST_SPIRIT_DECLARE(skipper_type) +BOOST_SPIRIT_X3_DECLARE(skipper_type) using grammar_type = x3::rule; const grammar_type grammar; -BOOST_SPIRIT_DECLARE(grammar_type) +BOOST_SPIRIT_X3_DECLARE(grammar_type) } diff --git a/test/x3/with.cpp b/test/x3/with.cpp index ca6eb7e8b..74247f555 100644 --- a/test/x3/with.cpp +++ b/test/x3/with.cpp @@ -1,36 +1,39 @@ /*============================================================================= Copyright (c) 2015 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) =============================================================================*/ -#include #include "test.hpp" +#include + +#include + namespace x3 = boost::spirit::x3; struct my_tag; struct my_rule_class { - template - x3::error_handler_result - on_error(Iterator&, Iterator const&, Exception const&, Context const& context) + template Se, typename Exception, typename Context> + [[nodiscard]] x3::error_handler_result + on_error(It const&, Se const&, Exception const&, Context const& context) { - x3::get(context)++; + ++x3::get(context); return x3::error_handler_result::fail; } - template - inline void - on_success(Iterator const&, Iterator const&, Attribute&, Context const& context) + template Se, typename Attribute, typename Context> + void + on_success(It const&, Se const&, Attribute&, Context const& context) { - x3::get(context)++; + ++x3::get(context); } }; -int -main() +int main() { using spirit_test::test_attr; using spirit_test::test;