Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

boost/parser/parser.hpp

// Copyright (C) 2024 T. Zachary Laine
//
// 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_PARSER_PARSER_HPP
#define BOOST_PARSER_PARSER_HPP

#include <boost/parser/parser_fwd.hpp>
#include <boost/parser/concepts.hpp>
#include <boost/parser/error_handling.hpp>
#include <boost/parser/tuple.hpp>
#include <boost/parser/detail/hl.hpp>
#include <boost/parser/detail/numeric.hpp>
#include <boost/parser/detail/case_fold.hpp>
#include <boost/parser/detail/unicode_char_sets.hpp>
#include <boost/parser/detail/pp_for_each.hpp>
#include <boost/parser/detail/printing.hpp>

#include <boost/parser/detail/text/algorithm.hpp>
#include <boost/parser/detail/text/trie_map.hpp>
#include <boost/parser/detail/text/unpack.hpp>

#include <type_traits>
#include <variant>
#include <vector>


namespace boost { namespace parser {

    /** A placeholder type used to represent the absence of information,
        value, etc., inside semantic actions.  For instance, calling
        `_locals(ctx)` in a semantic action associated with a parser that has
        no locals will yield a `none`. */
    struct none;

#if defined(BOOST_PARSER_NO_RUNTIME_ASSERTIONS)
    struct none
    {};
#else
    struct none
    {
        none() = default;

        // Constructible from, assignable from, and implicitly convertible to,
        // anything.
        template<typename T>
        none(T const &)
        {
            fail();
        }
        template<typename T>
        none & operator=(T const &)
        {
            fail();
            return *this;
        }
        template<typename T>
        operator T() const
        {
            fail();
            return T{};
        }

        // unary operators
        none operator+() const
        {
            fail();
            return none{};
        }
        none operator-() const
        {
            fail();
            return none{};
        }
        none operator*() const
        {
            fail();
            return none{};
        }
        none operator~() const
        {
            fail();
            return none{};
        }
        none operator&() const
        {
            fail();
            return none{};
        }
        none operator!() const
        {
            fail();
            return none{};
        }
        none operator++()
        {
            fail();
            return none{};
        }
        none & operator++(int)
        {
            fail();
            return *this;
        }
        none operator--()
        {
            fail();
            return none{};
        }
        none operator--(int)
        {
            fail();
            return *this;
        }

        // binary operators
        template<typename T>
        none operator<<(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator>>(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator*(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator/(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator%(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator+(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator-(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator<(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator>(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator<=(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator>=(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator==(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator!=(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator||(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator&&(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator&(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator|(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator^(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator,(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator->*(T const &) const
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator<<=(T const &)
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator>>=(T const &)
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator*=(T const &)
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator/=(T const &)
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator%=(T const &)
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator+=(T const &)
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator-=(T const &)
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator&=(T const &)
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator|=(T const &)
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator^=(T const &)
        {
            fail();
            return none{};
        }
        template<typename T>
        none operator[](T const &) const
        {
            fail();
            return none{};
        }

        // n-ary operators
        template<typename... Args>
        none operator()(Args const &...) const
        {
            fail();
            return none{};
        }

        void fail() const
        {
            // If you're seeing this, you've probably gotten a `none` out of
            // the parse context, and are trying to use it because you think
            // it's something else.  For instance, if your parser produces an
            // int attribute, the semantic ation `[](auto & ctx) { _attr(ctx)
            // = 0; }` may be fine.  If you attach that same semantic action
            // to `eps`, you end up here, because `eps` has no attribute, and
            // so `_attr(ctx)` produces a `none`.
            BOOST_PARSER_DEBUG_ASSERT(false);
        }
    };
#endif

    namespace detail {
        // Follows boost/mpl/print.hpp.
#if defined(_MSC_VER)
#pragma warning(push, 3)
#pragma warning(disable : 4307)
#endif
#if defined(__EDG_VERSION__)
        namespace print_aux {
            template<typename T>
            struct dependent_unsigned
            {
                static const unsigned int value = 1;
            };
        }
#endif
        template<typename T>
        struct identity_t
        {
            using type = T;
        };
        template<typename T>
        struct print_t : identity_t<T>
        {
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc++11-extensions"
            const int x_ = 1 / (sizeof(T) - sizeof(T));
#pragma clang diagnostic pop
#elif defined(_MSC_VER)
            enum { n = sizeof(T) + -1 };
#elif defined(__MWERKS__)
            void f(int);
#else
            enum {
                n =
#if defined(__EDG_VERSION__)
                    print_aux::dependent_unsigned<T>::value > -1
#else
                    sizeof(T) > -1
#endif
            };
#endif
        };
#if defined(_MSC_VER)
#pragma warning(pop)
#endif

        template<typename T>
        using print_type = typename print_t<T>::type;

        template<typename R, typename Parser>
        struct attribute_impl;

        // Utility types.

        struct nope
        {
            template<typename T>
            constexpr nope & operator=(T const &)
            {
                return *this;
            }

            operator std::nullopt_t() const noexcept { return std::nullopt; }

            template<typename Context>
            constexpr bool operator()(Context const &) const noexcept
            {
                return true;
            }

            constexpr nope operator*() const noexcept { return nope{}; }

            friend constexpr bool operator==(nope, nope) { return true; }
            friend constexpr bool operator!=(nope, nope) { return false; }

            template<typename T>
            friend constexpr bool operator==(T, nope)
            {
                return false;
            }
            template<typename T>
            friend constexpr bool operator!=(T, nope)
            {
                return false;
            }
        };

        inline nope global_nope;

        template<typename T>
        using parser_interface_tag_expr =
            typename T::parser_interface_derivation_tag;
        template<typename T>
        constexpr bool derived_from_parser_interface_v =
            is_detected_v<parser_interface_tag_expr, T>;

        template<typename T, bool AlwaysConst = false>
        using nope_or_pointer_t = std::conditional_t<
            std::is_same_v<std::remove_const_t<T>, nope>,
            nope,
            std::conditional_t<AlwaysConst, T const *, T *>>;

        template<
            bool DoTrace,
            bool UseCallbacks,
            typename I,
            typename S,
            typename ErrorHandler,
            typename GlobalState = nope,
            typename Callbacks = nope,
            typename Attr = nope,
            typename Val = nope,
            typename RuleTag = void,
            typename RuleLocals = nope,
            typename RuleParams = nope,
            typename Where = nope>
        struct parse_context
        {
            parse_context() = default;
            parse_context(parse_context const &) = default;
            parse_context & operator=(parse_context const &) = default;

            using rule_tag = RuleTag;

            static constexpr bool do_trace = DoTrace;
            static constexpr bool use_callbacks = UseCallbacks;

            I first_;
            S last_;
            bool * pass_ = nullptr;
            int * trace_indent_ = nullptr;
            symbol_table_tries_t * symbol_table_tries_ = nullptr;
            pending_symbol_table_operations_t *
                pending_symbol_table_operations_ = nullptr;
            ErrorHandler const * error_handler_ = nullptr;
            nope_or_pointer_t<GlobalState> globals_{};
            nope_or_pointer_t<Callbacks, true> callbacks_{};
            nope_or_pointer_t<Attr> attr_{};
            nope_or_pointer_t<Val> val_{};
            nope_or_pointer_t<RuleLocals> locals_{};
            nope_or_pointer_t<RuleParams, true> params_{};
            nope_or_pointer_t<Where, true> where_{};
            int no_case_depth_ = 0;

            template<typename T>
            static auto nope_or_address(T & x)
            {
                if constexpr (std::is_same_v<std::remove_const_t<T>, nope>)
                    return nope{};
                else
                    return std::addressof(x);
            }

            template<typename T, typename U>
            static auto other_or_address(T other, U & x)
            {
                if constexpr (std::is_same_v<std::remove_const_t<U>, nope>)
                    return other;
                else
                    return std::addressof(x);
            }

            parse_context(
                std::bool_constant<DoTrace>,
                std::bool_constant<UseCallbacks>,
                I & first,
                S last,
                bool & success,
                int & indent,
                ErrorHandler const & error_handler,
                GlobalState & globals,
                symbol_table_tries_t & symbol_table_tries,
                pending_symbol_table_operations_t &
                    pending_symbol_table_operations) :
                first_(first),
                last_(last),
                pass_(std::addressof(success)),
                trace_indent_(std::addressof(indent)),
                symbol_table_tries_(std::addressof(symbol_table_tries)),
                pending_symbol_table_operations_(
                    std::addressof(pending_symbol_table_operations)),
                error_handler_(std::addressof(error_handler)),
                globals_(nope_or_address(globals))
            {}

            // With callbacks.
            parse_context(
                std::bool_constant<DoTrace>,
                std::bool_constant<UseCallbacks>,
                I & first,
                S last,
                bool & success,
                int & indent,
                ErrorHandler const & error_handler,
                Callbacks const & callbacks,
                GlobalState & globals,
                symbol_table_tries_t & symbol_table_tries,
                pending_symbol_table_operations_t &
                    pending_symbol_table_operations) :
                first_(first),
                last_(last),
                pass_(std::addressof(success)),
                trace_indent_(std::addressof(indent)),
                symbol_table_tries_(std::addressof(symbol_table_tries)),
                pending_symbol_table_operations_(
                    std::addressof(pending_symbol_table_operations)),
                error_handler_(std::addressof(error_handler)),
                globals_(nope_or_address(globals)),
                callbacks_(std::addressof(callbacks))
            {}

            // For making rule contexts.
            template<
                typename OldVal,
                typename OldRuleTag,
                typename OldRuleLocals,
                typename OldRuleParams,
                typename NewVal,
                typename NewRuleTag,
                typename NewRuleLocals,
                typename NewRuleParams>
            parse_context(
                parse_context<
                    DoTrace,
                    UseCallbacks,
                    I,
                    S,
                    ErrorHandler,
                    GlobalState,
                    Callbacks,
                    Attr,
                    OldVal,
                    OldRuleTag,
                    OldRuleLocals,
                    OldRuleParams> const & other,
                NewRuleTag * tag_ptr,
                NewVal & value,
                NewRuleLocals & locals,
                NewRuleParams const & params) :
                first_(other.first_),
                last_(other.last_),
                pass_(other.pass_),
                trace_indent_(other.trace_indent_),
                symbol_table_tries_(other.symbol_table_tries_),
                pending_symbol_table_operations_(
                    other.pending_symbol_table_operations_),
                error_handler_(other.error_handler_),
                globals_(other.globals_),
                callbacks_(other.callbacks_),
                attr_(other.attr_),
                no_case_depth_(other.no_case_depth_)
            {
                if constexpr (
                    std::is_same_v<OldRuleTag, NewRuleTag> &&
                    !std::is_same_v<OldRuleTag, void>) {
                    val_ = other.val_;
                    locals_ = other.locals_;
                    params_ = other.params_;
                } else {
                    val_ = other_or_address(other.val_, value);
                    locals_ = other_or_address(other.locals_, locals);
                    params_ = other_or_address(other.params_, params);
                }
            }

            // For making action contexts.
            template<typename OldAttr, typename OldWhere>
            parse_context(
                parse_context<
                    DoTrace,
                    UseCallbacks,
                    I,
                    S,
                    ErrorHandler,
                    GlobalState,
                    Callbacks,
                    OldAttr,
                    Val,
                    RuleTag,
                    RuleLocals,
                    RuleParams,
                    OldWhere> const & other,
                Attr & attr,
                Where const & where) :
                first_(other.first_),
                last_(other.last_),
                pass_(other.pass_),
                trace_indent_(other.trace_indent_),
                symbol_table_tries_(other.symbol_table_tries_),
                pending_symbol_table_operations_(
                    other.pending_symbol_table_operations_),
                error_handler_(other.error_handler_),
                globals_(other.globals_),
                callbacks_(other.callbacks_),
                attr_(nope_or_address(attr)),
                val_(other.val_),
                locals_(other.locals_),
                params_(other.params_),
                where_(nope_or_address(where)),
                no_case_depth_(other.no_case_depth_)
            {}
        };

        template<
            bool DoTrace,
            bool UseCallbacks,
            typename I,
            typename S,
            typename ErrorHandler,
            typename GlobalState,
            typename Callbacks,
            typename Val,
            typename RuleTag,
            typename RuleLocals,
            typename RuleParams,
            typename Attr,
            typename Where,
            typename OldAttr>
        auto make_action_context(
            parse_context<
                DoTrace,
                UseCallbacks,
                I,
                S,
                ErrorHandler,
                GlobalState,
                Callbacks,
                OldAttr,
                Val,
                RuleTag,
                RuleLocals,
                RuleParams> const & context,
            Attr & attr,
            Where const & where)
        {
            using result_type = parse_context<
                DoTrace,
                UseCallbacks,
                I,
                S,
                ErrorHandler,
                GlobalState,
                Callbacks,
                Attr,
                Val,
                RuleTag,
                RuleLocals,
                RuleParams,
                Where>;
            return result_type(context, attr, where);
        }

        template<
            bool DoTrace,
            bool UseCallbacks,
            typename I,
            typename S,
            typename ErrorHandler,
            typename GlobalState,
            typename Callbacks,
            typename Attr,
            typename Val,
            typename RuleTag,
            typename RuleLocals,
            typename RuleParams,
            typename NewVal,
            typename NewRuleTag,
            typename NewRuleLocals,
            typename NewRuleParams>
        auto make_rule_context(
            parse_context<
                DoTrace,
                UseCallbacks,
                I,
                S,
                ErrorHandler,
                GlobalState,
                Callbacks,
                Attr,
                Val,
                RuleTag,
                RuleLocals,
                RuleParams> const & context,
            NewRuleTag * tag_ptr,
            NewVal & value,
            NewRuleLocals & locals,
            NewRuleParams const & params)
        {
            using result_type = parse_context<
                DoTrace,
                UseCallbacks,
                I,
                S,
                ErrorHandler,
                GlobalState,
                Callbacks,
                Attr,
                std::conditional_t<std::is_same_v<NewVal, nope>, Val, NewVal>,
                NewRuleTag,
                std::conditional_t<
                    std::is_same_v<NewRuleLocals, nope>,
                    RuleLocals,
                    NewRuleLocals>,
                std::conditional_t<
                    std::is_same_v<NewRuleParams, nope>,
                    RuleParams,
                    NewRuleParams>>;
            return result_type(context, tag_ptr, value, locals, params);
        }

        template<
            bool DoTrace,
            bool UseCallbacks,
            typename Iter,
            typename Sentinel,
            typename ErrorHandler>
        auto make_context(
            Iter first,
            Sentinel last,
            bool & success,
            int & indent,
            ErrorHandler const & error_handler,
            nope & n,
            symbol_table_tries_t & symbol_table_tries,
            pending_symbol_table_operations_t &
                pending_symbol_table_operations) noexcept
        {
            return parse_context(
                std::bool_constant<DoTrace>{},
                std::bool_constant<UseCallbacks>{},
                first,
                last,
                success,
                indent,
                error_handler,
                n,
                symbol_table_tries,
                pending_symbol_table_operations);
        }

        template<
            bool DoTrace,
            bool UseCallbacks,
            typename Iter,
            typename Sentinel,
            typename ErrorHandler,
            typename GlobalState>
        auto make_context(
            Iter first,
            Sentinel last,
            bool & success,
            int & indent,
            ErrorHandler const & error_handler,
            GlobalState & globals,
            symbol_table_tries_t & symbol_table_tries,
            pending_symbol_table_operations_t &
                pending_symbol_table_operations) noexcept
        {
            return parse_context(
                std::bool_constant<DoTrace>{},
                std::bool_constant<UseCallbacks>{},
                first,
                last,
                success,
                indent,
                error_handler,
                globals,
                symbol_table_tries,
                pending_symbol_table_operations);
        }

        template<
            bool DoTrace,
            bool UseCallbacks,
            typename Iter,
            typename Sentinel,
            typename ErrorHandler,
            typename Callbacks>
        auto make_context(
            Iter first,
            Sentinel last,
            bool & success,
            int & indent,
            ErrorHandler const & error_handler,
            Callbacks const & callbacks,
            nope & n,
            symbol_table_tries_t & symbol_table_tries,
            pending_symbol_table_operations_t &
                pending_symbol_table_operations) noexcept
        {
            return parse_context(
                std::bool_constant<DoTrace>{},
                std::bool_constant<UseCallbacks>{},
                first,
                last,
                success,
                indent,
                error_handler,
                callbacks,
                n,
                symbol_table_tries,
                pending_symbol_table_operations);
        }

        template<
            bool DoTrace,
            bool UseCallbacks,
            typename Iter,
            typename Sentinel,
            typename ErrorHandler,
            typename Callbacks,
            typename GlobalState>
        auto make_context(
            Iter first,
            Sentinel last,
            bool & success,
            int & indent,
            ErrorHandler const & error_handler,
            Callbacks const & callbacks,
            GlobalState & globals,
            symbol_table_tries_t & symbol_table_tries,
            pending_symbol_table_operations_t &
                pending_symbol_table_operations) noexcept
        {
            return parse_context(
                std::bool_constant<DoTrace>{},
                std::bool_constant<UseCallbacks>{},
                first,
                last,
                success,
                indent,
                error_handler,
                callbacks,
                globals,
                symbol_table_tries,
                pending_symbol_table_operations);
        }


        template<unsigned int I>
        struct param_t
        {
            template<typename Context>
            decltype(auto) operator()(Context const & context) const
            {
                return parser::get(parser::_params(context), llong<I>{});
            }
        };


        template<typename T, typename... Args>
        using callable = decltype(std::declval<T>()(std::declval<Args>()...));

        template<
            typename Context,
            typename T,
            bool Callable = is_detected_v<callable, T const &, Context const &>>
        struct resolve_impl
        {
            static auto call(Context const &, T const & x) { return x; }
        };

        template<typename Context, typename T>
        struct resolve_impl<Context, T, true>
        {
            static auto call(Context const & context, T const & x)
            {
                return x(context);
            }
        };

        template<typename Context, typename T>
        auto resolve(Context const & context, T const & x)
        {
            return resolve_impl<Context, T>::call(context, x);
        }

        template<typename Context>
        auto resolve(Context const &, nope n)
        {
            return n;
        }


        template<typename Context, typename ParamsTuple>
        auto
        resolve_rule_params(Context const & context, ParamsTuple const & params)
        {
            return detail::hl::transform(params, [&](auto const & x) {
                return detail::resolve(context, x);
            });
        }

        template<typename Context>
        nope resolve_rule_params(Context const & context, nope)
        {
            return {};
        }

        template<typename LocalsType, typename Context>
        LocalsType make_locals_impl(Context const & context, std::true_type)
        {
            return LocalsType(context);
        }

        template<typename LocalsType, typename Context>
        LocalsType make_locals_impl(Context const & context, std::false_type)
        {
            return LocalsType();
        }

        template<typename LocalsType, typename Context>
        LocalsType make_locals(Context const & context)
        {
            return detail::make_locals_impl<LocalsType>(
                context,
                typename std::is_convertible<Context const &, LocalsType>::
                    type{});
        }


        template<typename Context>
        decltype(auto) _indent(Context const & context)
        {
            return *context.trace_indent_;
        }

        template<typename Context>
        decltype(auto) _callbacks(Context const & context)
        {
            return *context.callbacks_;
        }


        // Type traits.

        template<typename T>
        using remove_cv_ref_t = typename std::remove_cv<
            typename std::remove_reference<T>::type>::type;

        template<typename T, typename U>
        using comparison = decltype(std::declval<T>() == std::declval<U>());

        template<typename T, typename U>
        constexpr bool is_equality_comparable_with_v =
            is_detected_v<comparison, T, U>;

        template<typename T>
        struct is_nope : std::false_type
        {};
        template<>
        struct is_nope<nope> : std::true_type
        {};
        template<typename T>
        constexpr bool is_nope_v = is_nope<remove_cv_ref_t<T>>::value;

        template<typename T>
        struct is_eps_p : std::false_type
        {};
        template<typename T>
        struct is_eps_p<eps_parser<T>> : std::true_type
        {};

        template<typename T>
        struct is_unconditional_eps : std::false_type
        {};
        template<>
        struct is_unconditional_eps<eps_parser<nope>> : std::true_type
        {};
        template<typename T>
        constexpr bool is_unconditional_eps_v =
            is_unconditional_eps<remove_cv_ref_t<T>>::value;

        template<typename T>
        struct is_zero_plus_p : std::false_type
        {};
        template<typename T>
        struct is_zero_plus_p<zero_plus_parser<T>> : std::true_type
        {};

        template<typename T>
        struct is_or_p : std::false_type
        {};
        template<typename T>
        struct is_or_p<or_parser<T>> : std::true_type
        {};

        template<typename T>
        struct is_perm_p : std::false_type
        {};
        template<typename T>
        struct is_perm_p<perm_parser<T>> : std::true_type
        {};

        template<typename T>
        struct is_seq_p : std::false_type
        {};
        template<typename T, typename U, typename V>
        struct is_seq_p<seq_parser<T, U, V>> : std::true_type
        {};

        template<typename T>
        struct is_one_plus_p : std::false_type
        {};
        template<typename T>
        struct is_one_plus_p<one_plus_parser<T>> : std::true_type
        {};

        template<typename T>
        struct is_utf8_view : std::false_type
        {};
        template<typename V>
        struct is_utf8_view<text::utf8_view<V>> : std::true_type
        {};

        template<typename T>
        using optional_type = remove_cv_ref_t<decltype(*std::declval<T &>())>;

        template<typename F, typename... Args>
        constexpr bool is_invocable_v = is_detected_v<callable, F, Args...>;

        template<typename T>
        using has_begin =
            decltype(*detail::text::detail::begin(std::declval<T &>()));
        template<typename T>
        using has_end =
            decltype(detail::text::detail::end(std::declval<T &>()));

        template<typename T>
        constexpr bool is_range =
            is_detected_v<has_begin, T> && is_detected_v<has_end, T>;

        template<typename T>
        using has_push_back =
            decltype(std::declval<T &>().push_back(*std::declval<T>().begin()));

#if BOOST_PARSER_USE_CONCEPTS

        template<typename T>
        using iterator_t = std::ranges::iterator_t<T>;
        template<typename T>
        using sentinel_t = std::ranges::sentinel_t<T>;
        template<typename T>
        using iter_value_t = std::iter_value_t<T>;
        template<typename T>
        using iter_reference_t = std::iter_reference_t<T>;
        template<typename T>
        using range_value_t = std::ranges::range_value_t<T>;
        template<typename T>
        using range_reference_t = std::ranges::range_reference_t<T>;
        template<typename T>
        using range_rvalue_reference_t =
            std::ranges::range_rvalue_reference_t<T>;

        template<typename T>
        constexpr bool is_parsable_code_unit_v = code_unit<T>;

#else

        template<typename T>
        using iterator_t =
            decltype(detail::text::detail::begin(std::declval<T &>()));
        template<typename Range>
        using sentinel_t =
            decltype(detail::text::detail::end(std::declval<Range &>()));
        template<typename T>
        using iter_value_t = typename std::iterator_traits<T>::value_type;
        template<typename T>
        using iter_reference_t = decltype(*std::declval<T &>());
        template<typename T>
        using iter_rvalue_reference_t =
            decltype(std::move(*std::declval<T &>()));
        template<typename T>
        using range_value_t = iter_value_t<iterator_t<T>>;
        template<typename T>
        using range_reference_t = iter_reference_t<iterator_t<T>>;
        template<typename T>
        using range_rvalue_reference_t = iter_rvalue_reference_t<iterator_t<T>>;

        template<typename T>
        using has_insert = decltype(std::declval<T &>().insert(
            std::declval<T>().begin(), *std::declval<T>().begin()));
        template<typename T>
        using has_range_insert = decltype(std::declval<T &>().insert(
            std::declval<T>().begin(),
            std::declval<T>().begin(),
            std::declval<T>().end()));

        template<typename T>
        constexpr bool is_container_v = is_detected_v<has_insert, T>;

        template<typename T, typename U>
        constexpr bool container_and_value_type =
            is_container_v<T> &&
            (std::is_same_v<detected_t<range_value_t, T>, U> ||
             (std::is_same_v<T, std::string> && std::is_same_v<U, char32_t>));

        template<typename T>
        constexpr bool is_parsable_code_unit_impl =
            std::is_same_v<T, char> || std::is_same_v<T, wchar_t> ||
#if defined(__cpp_char8_t)
            std::is_same_v<T, char8_t> ||
#endif
            std::is_same_v<T, char16_t> || std::is_same_v<T, char32_t>;

        template<typename T>
        constexpr bool is_parsable_code_unit_v =
            is_parsable_code_unit_impl<std::remove_cv_t<T>>;

        template<typename T>
        constexpr bool is_parsable_iter_v = is_parsable_code_unit_v<
            remove_cv_ref_t<detected_t<iter_value_t, T>>>;

        template<typename T>
        constexpr bool is_parsable_range_v = is_parsable_code_unit_v<
            remove_cv_ref_t<detected_t<has_begin, T>>> &&
            is_detected_v<has_end, T>;

        template<typename T>
        constexpr bool is_parsable_pointer_v =
            std::is_pointer_v<remove_cv_ref_t<T>> && is_parsable_code_unit_v<
                std::remove_pointer_t<remove_cv_ref_t<T>>>;

        template<typename T>
        constexpr bool is_parsable_range_like_v =
            is_parsable_range_v<T> || is_parsable_pointer_v<T>;
    }

    template<typename T>
    constexpr bool container = detail::is_container_v<T>;

    namespace detail {
#endif

        template<typename T, bool = std::is_pointer_v<T>>
        constexpr bool is_code_unit_pointer_v = false;
        template<typename T>
        constexpr bool is_code_unit_pointer_v<T, true> =
            is_parsable_code_unit_v<std::remove_pointer_t<T>>;

        template<typename T>
        constexpr bool is_range_like = is_range<T> || is_code_unit_pointer_v<T>;

        template<typename I>
        constexpr bool is_char8_iter_v =
#if defined(__cpp_char8_t)
            std::is_same_v<iter_value_t<I>, char8_t>
#else
            false
#endif
            ;

        // Metafunctions.

        template<bool WrapInOptional, typename Tuple>
        struct to_hana_tuple_or_type_impl;

        template<typename... T>
        struct to_hana_tuple_or_type_impl<true, tuple<T...>>
        {
            using type = std::optional<std::variant<T...>>;
        };

        template<typename... T>
        struct to_hana_tuple_or_type_impl<false, tuple<T...>>
        {
            using type = std::variant<T...>;
        };

        template<typename T>
        struct to_hana_tuple_or_type_impl<true, tuple<T>>
        {
            // The reason this is not two separate specializations, one
            // for tuple<t> and on for tuple<optional<T>>, is because
            // MSVC.
            using type =
                std::conditional_t<is_optional_v<T>, T, std::optional<T>>;
        };

        template<typename T>
        struct to_hana_tuple_or_type_impl<false, tuple<T>>
        {
            using type = T;
        };

        template<>
        struct to_hana_tuple_or_type_impl<true, tuple<>>
        {
            using type = nope;
        };

        template<>
        struct to_hana_tuple_or_type_impl<false, tuple<>>
        {
            using type = nope;
        };

        template<typename Pair>
        struct to_hana_tuple_or_type;

        template<typename Tuple, typename TrueFalse>
        struct to_hana_tuple_or_type<tuple<Tuple, TrueFalse>>
        {
            // This has to be done in two steps like this because MSVC.
            using type =
                typename to_hana_tuple_or_type_impl<TrueFalse::value, Tuple>::
                    type;
        };

        template<typename T>
        using to_hana_tuple_or_type_t = typename to_hana_tuple_or_type<T>::type;

        template<typename T>
        auto make_sequence_of()
        {
            if constexpr (
                std::is_same_v<T, char> || std::is_same_v<T, char32_t>) {
                return std::string{};
            } else if constexpr (std::is_same_v<T, nope>) {
                return nope{};
            } else {
                return std::vector<T>{};
            }
        }

        template<typename T>
        constexpr bool is_char_type_v =
            std::is_same_v<T, char> || std::is_same_v<T, char32_t>;

        template<typename T>
        struct optional_of_impl
        {
            using type = std::optional<T>;
        };

        template<typename T>
        struct optional_of_impl<std::optional<T>>
        {
            using type = std::optional<T>;
        };

        template<>
        struct optional_of_impl<nope>
        {
            using type = nope;
        };

        template<typename T>
        using optional_of = typename optional_of_impl<T>::type;

        template<typename T>
        struct unwrapped_optional
        {
            using type = T;
        };
        template<typename T>
        struct unwrapped_optional<std::optional<T>>
        {
            using type = T;
        };
        template<typename T>
        using unwrapped_optional_t = typename unwrapped_optional<T>::type;



        // Etc.

        template<typename T>
        struct wrapper
        {
            using type = T;

            constexpr bool operator==(wrapper) const { return true; }
        };

        struct wrap
        {
            template<typename T>
            constexpr auto operator()(T type) const
            {
                return wrapper<T>{};
            }
        };

        struct unwrap
        {
            template<typename T>
            constexpr auto operator()(T wrapped_type) const
            {
                return typename T::type{};
            }
        };

        template<typename Container, typename T>
        void insert(Container & c, T && x)
        {
            if constexpr (is_detected_v<has_push_back, Container>) {
                c.push_back((T &&) x);
            } else {
                c.insert((T &&) x);
            }
        }

        template<typename Container, typename I>
        void insert(Container & c, I first, I last)
        {
            std::for_each(first, last, [&](auto && x) {
                using type = decltype(x);
                insert(c, (type &&) x);
            });
        }

        template<typename Container, typename T>
        constexpr bool needs_transcoding_to_utf8 =
            (std::is_same_v<range_value_t<remove_cv_ref_t<Container>>, char>
#if defined(__cpp_char8_t)
             || std::is_same_v<range_value_t<remove_cv_ref_t<Container>>, char8_t>
#endif
                ) && (std::is_same_v<remove_cv_ref_t<T>, char32_t>
#if !defined(_MSC_VER)
             || std::is_same_v<remove_cv_ref_t<T>, wchar_t>
#endif
             );

        template<typename Container, typename T>
        void append(Container & c, T && x, bool gen_attrs)
        {
            if (!gen_attrs)
                return;
            if constexpr (needs_transcoding_to_utf8<Container, T>) {
                char32_t cps[1] = {(char32_t)x};
                auto const r = cps | text::as_utf8;
                c.insert(c.end(), r.begin(), r.end());
            } else {
                detail::insert(c, std::move(x));
            }
        }

        template<typename Container, typename T>
        void append(std::optional<Container> & c, T && x, bool gen_attrs)
        {
            if (!gen_attrs)
                return;
            if (!c)
                c = Container();
            return detail::append(*c, (T &&) x, gen_attrs);
        }

        template<typename Container>
        void append(Container & c, nope &&, bool)
        {}

        template<typename T>
        void append(nope &, T &&, bool)
        {}

        inline void append(nope &, nope &&, bool) {}

        template<typename Container, typename Iter, typename Sentinel>
        void append(Container & c, Iter first, Sentinel last, bool gen_attrs)
        {
            if (!gen_attrs)
                return;
            if constexpr (needs_transcoding_to_utf8<
                              Container,
                              iter_value_t<Iter>>) {
                auto const r =
                    BOOST_PARSER_SUBRANGE(first, last) | text::as_utf8;
                c.insert(c.end(), r.begin(), r.end());
            } else {
                detail::insert(c, first, last);
            }
        }

        template<typename Container, typename Iter, typename Sentinel>
        void append(
            std::optional<Container> & c,
            Iter first,
            Sentinel last,
            bool gen_attrs)
        {
            if (!gen_attrs)
                return;
            if (!c)
                c = Container();
            return detail::append(*c, first, last, gen_attrs);
        }

        template<typename Iter, typename Sentinel>
        void append(nope &, Iter first, Sentinel last, bool gen_attrs)
        {}

        constexpr inline flags default_flags()
        {
            return flags(
                uint32_t(flags::gen_attrs) | uint32_t(flags::use_skip));
        }
        constexpr inline flags enable_skip(flags f)
        {
            return flags(uint32_t(f) | uint32_t(flags::use_skip));
        }
        constexpr inline flags disable_skip(flags f)
        {
            return flags(uint32_t(f) & ~uint32_t(flags::use_skip));
        }
        constexpr inline flags enable_attrs(flags f)
        {
            return flags(uint32_t(f) | uint32_t(flags::gen_attrs));
        }
        constexpr inline flags disable_attrs(flags f)
        {
            return flags(uint32_t(f) & ~uint32_t(flags::gen_attrs));
        }
        constexpr inline flags enable_trace(flags f)
        {
            return flags(uint32_t(f) | uint32_t(flags::trace));
        }
        constexpr inline flags disable_trace(flags f)
        {
            return flags(uint32_t(f) & ~uint32_t(flags::trace));
        }
        constexpr inline flags set_in_apply_parser(flags f)
        {
            return flags(uint32_t(f) | uint32_t(flags::in_apply_parser));
        }
        constexpr inline bool gen_attrs(flags f)
        {
            return (uint32_t(f) & uint32_t(flags::gen_attrs)) ==
                   uint32_t(flags::gen_attrs);
        }
        constexpr inline bool use_skip(flags f)
        {
            return (uint32_t(f) & uint32_t(flags::use_skip)) ==
                   uint32_t(flags::use_skip);
        }
        constexpr inline bool in_apply_parser(flags f)
        {
            return (uint32_t(f) & uint32_t(flags::in_apply_parser)) ==
                   uint32_t(flags::in_apply_parser);
        }

        struct null_parser
        {};

        struct skip_skipper
        {
            template<
                typename Iter,
                typename Sentinel,
                typename Context,
                typename SkipParser>
            nope operator()(
                Iter & first,
                Sentinel last,
                Context const & context,
                SkipParser const & skip,
                flags flags,
                bool & success) const noexcept
            {
                return {};
            }

            template<
                typename Iter,
                typename Sentinel,
                typename Context,
                typename SkipParser,
                typename Attribute>
            void operator()(
                Iter & first,
                Sentinel last,
                Context const & context,
                SkipParser const & skip,
                flags flags,
                bool & success,
                Attribute & retval) const
            {}
        };

        template<typename Iter, typename Sentinel>
        void
        skip(Iter & first, Sentinel last, null_parser const & skip_, flags f)
        {}

        template<typename Iter, typename Sentinel, typename SkipParser>
        void
        skip(Iter & first, Sentinel last, SkipParser const & skip_, flags f)
        {
            if (!detail::use_skip(f))
                return;
            bool success = true;
            int indent = 0;
            rethrow_error_handler eh;
            nope n;
            symbol_table_tries_t symbol_table_tries;
            pending_symbol_table_operations_t pending_symbol_table_operations;
            auto const context = detail::make_context<false, false>(
                first,
                last,
                success,
                indent,
                eh,
                n,
                symbol_table_tries,
                pending_symbol_table_operations);
            while (success) {
                skip_(
                    first,
                    last,
                    context,
                    skip_skipper{},
                    detail::disable_trace(detail::disable_skip(f)),
                    success);
            }
        }

        enum : int64_t { unbounded = -1 };

        template<typename T>
        std::optional<T> make_parse_result(T & x, bool success)
        {
            std::optional<T> retval;
            if (success)
                retval = x;
            return retval;
        }

        inline bool make_parse_result(nope &, bool success) { return success; }
        inline bool make_parse_result(none &, bool success) { return success; }

        template<typename LoType, typename HiType>
        struct char_pair
        {
            LoType lo_;
            HiType hi_;
        };

        using case_fold_array_t = std::array<char32_t, detail::longest_mapping>;

        template<typename I, typename S>
        struct no_case_iter : stl_interfaces::proxy_iterator_interface<
                                  no_case_iter<I, S>,
                                  std::forward_iterator_tag,
                                  char32_t>
        {
            no_case_iter() : it_(), last_(), idx_(0), last_idx_() {}
            no_case_iter(I it, S last) :
                it_(it), last_(last), idx_(0), last_idx_(0)
            {
                fold();
            }

            char32_t operator*() const { return folded_[idx_]; }
            no_case_iter & operator++()
            {
                ++idx_;
                if (last_idx_ <= idx_) {
                    ++it_;
                    fold();
                }
                return *this;
            }
            I base() const { return it_; }
            friend bool operator==(no_case_iter lhs, S rhs) noexcept
            {
                return lhs.it_ == rhs;
            }
            friend bool operator==(no_case_iter lhs, no_case_iter rhs) noexcept
            {
                return lhs.it_ == rhs.it_ && lhs.idx_ == rhs.idx_;
            }

            using base_type = stl_interfaces::proxy_iterator_interface<
                no_case_iter<I, S>,
                std::forward_iterator_tag,
                char32_t>;
            using base_type::operator++;

        private:
            void fold()
            {
                idx_ = 0;
                if (it_ == last_) {
                    folded_[0] = 0;
                    last_idx_ = 1;
                    return;
                }
                auto const folded_last =
                    detail::case_fold(*it_, folded_.begin());
                last_idx_ = int(folded_last - folded_.begin());
            }

            case_fold_array_t folded_;
            I it_;
            [[no_unique_address]] S last_;
            int idx_;
            int last_idx_;
        };

        template<typename V>
        struct case_fold_view
        {
            using iterator =
                no_case_iter<detail::iterator_t<V>, detail::sentinel_t<V>>;

            case_fold_view(V base) : base_(std::move(base)) {}

            iterator begin() const
            {
                return iterator(
                    text::detail::begin(base_), text::detail::end(base_));
            }
            auto end() const { return text::detail::end(base_); }

        private:
            V base_;
        };

        enum class symbol_table_op { insert, erase, clear };

        template<typename T>
        struct symbol_table_operation
        {
            std::string key_;
            std::optional<T> value_;
            symbol_table_op kind_;
        };

        template<typename T>
        void apply_symbol_table_operations(
            std::vector<std::pair<std::string, T>> & initial_elements,
            std::vector<symbol_table_operation<T>> & pending_operations)
        {
            auto lower_bound = [&initial_elements](std::string const & str) {
                return std::lower_bound(
                    initial_elements.begin(),
                    initial_elements.end(),
                    str,
                    [](auto const & a, auto b) {
                        return a.first < b;
                    });
            };

            for (auto & op : pending_operations) {
                if (op.kind_ == symbol_table_op::insert) {
                    auto it = lower_bound(op.key_);
                    if (it == initial_elements.end() ||
                        it->first != op.key_) {
                        initial_elements.insert(
                            it,
                            std::pair<std::string, T>(
                                std::move(op.key_), std::move(*op.value_)));
                    } else {
                        it->second = std::move(*op.value_);
                    }
                } else if (op.kind_ == symbol_table_op::erase) {
                    auto it = lower_bound(op.key_);
                    if (it != initial_elements.end() && it->first == op.key_)
                        initial_elements.erase(it);
                } else {
                    initial_elements.clear();
                }
            }

            pending_operations.clear();
        }

        template<typename Context, typename T>
        auto get_trie(
            Context const & context, symbol_parser<T> const & sym_parser)
        {
            using trie_t = text::trie_map<std::vector<char32_t>, T>;
            using result_type = std::pair<trie_t &, bool>;
            symbol_table_tries_t & symbol_table_tries =
                *context.symbol_table_tries_;

            auto & [any, has_case_folded] =
                symbol_table_tries[(void *)&sym_parser.ref()];

            bool const needs_case_folded = context.no_case_depth_;

            if (!any.has_value()) {
                any = trie_t{};
                has_case_folded = false;
                trie_t & trie = *std::any_cast<trie_t>(&any);
                for (auto const & e : sym_parser.initial_elements()) {
                    trie.insert(e.first | text::as_utf32, e.second);
                    if (needs_case_folded) {
                        trie.insert(
                            case_fold_view(e.first | text::as_utf32), e.second);
                        has_case_folded = true;
                    }
                }
                return result_type(trie, has_case_folded);
            } else {
                trie_t & trie = *std::any_cast<trie_t>(&any);
                if (needs_case_folded && !has_case_folded) {
                    trie_t new_trie = trie;
                    for (auto && [key, value] : trie) {
                        new_trie.insert(
                            case_fold_view(key | text::as_utf32), value);
                    }
                    std::swap(new_trie, trie);
                }
                return result_type(trie, has_case_folded);
             }
        }

        template<typename Context, typename T>
        decltype(auto) get_pending_symtab_ops(
            Context const & context, symbol_parser<T> const & sym_parser)
        {
            void const * ptr = static_cast<void const *>(&sym_parser);
            auto & entry = (*context.pending_symbol_table_operations_)[ptr];
            std::vector<detail::symbol_table_operation<T>> * retval = nullptr;
            if (entry.visit_) {
                retval = std::any_cast<
                    std::vector<detail::symbol_table_operation<T>>>(
                    &entry.ops_);
            } else {
                entry.ops_ = std::vector<detail::symbol_table_operation<T>>{};
                retval = std::any_cast<
                    std::vector<detail::symbol_table_operation<T>>>(
                    &entry.ops_);
                entry.visit_ = [&sym_parser, ops_ptr = retval] {
                    detail::apply_symbol_table_operations(
                        sym_parser.initial_elements_, *ops_ptr);
                };
            }
            return *retval;
        }

        template<>
        struct char_subranges<hex_digit_subranges>
        {
            static constexpr char_subrange ranges[] = {
                {U'0', U'9'},
                {U'A', U'F'},
                {U'a', U'f'},
                {U'\uff10', U'\uff19'},
                {U'\uff21', U'\uff26'},
                {U'\uff41', U'\uff46'}};
        };

        template<>
        struct char_subranges<control_subranges>
        {
            static constexpr char_subrange ranges[] = {
                {U'\u0000', U'\u001f'}, {U'\u007f', U'\u009f'}};
        };

        template<typename Iter, typename Sentinel, bool SortedUTF32>
        struct char_range
        {
            template<typename T, typename Context>
            bool contains(T c_, Context const & context) const
            {
                if constexpr (SortedUTF32) {
                    return std::binary_search(chars_.begin(), chars_.end(), c_);
                }

                if (context.no_case_depth_) {
                    case_fold_array_t folded;
                    auto folded_last = detail::case_fold(c_, folded.begin());
                    if constexpr (std::is_same_v<T, char32_t>) {
                        auto const cps = chars_ | text::as_utf32;
                        auto chars_first = no_case_iter(cps.begin(), cps.end());
                        auto chars_last = cps.end();
                        auto result = text::search(
                            chars_first,
                            chars_last,
                            folded.begin(),
                            folded_last);
                        return !result.empty();
                    } else {
                        auto chars_first =
                            no_case_iter(chars_.begin(), chars_.end());
                        auto chars_last = chars_.end();
                        auto result = text::search(
                            chars_first,
                            chars_last,
                            folded.begin(),
                            folded_last);
                        return !result.empty();
                    }
                } else {
                    if constexpr (std::is_same_v<T, char32_t>) {
                        auto const cps = chars_ | text::as_utf32;
                        return text::find(cps.begin(), cps.end(), c_) !=
                               cps.end();
                    } else {
                        using element_type =
                            remove_cv_ref_t<decltype(*chars_.begin())>;
                        element_type const c = c_;
                        return text::find(chars_.begin(), chars_.end(), c) !=
                               chars_.end();
                    }
                }
            }

            BOOST_PARSER_SUBRANGE<Iter, Sentinel> chars_;
        };

        template<bool SortedUTF32, typename Iter, typename Sentinel>
        constexpr auto make_char_range(Iter first, Sentinel last)
        {
            return char_range<Iter, Sentinel, SortedUTF32>{
                BOOST_PARSER_SUBRANGE<Iter, Sentinel>{first, last}};
        }

        template<bool SortedUTF32, typename R>
        constexpr auto make_char_range(R && r) noexcept
        {
            if constexpr (std::is_pointer_v<remove_cv_ref_t<R>>) {
                return detail::make_char_range<SortedUTF32>(
                    r, text::null_sentinel);
            } else {
                return detail::make_char_range<SortedUTF32>(
                    text::detail::begin(r), text::detail::end(r));
            }
        }

        template<bool Equal, typename Context>
        auto no_case_aware_compare(Context const & context)
        {
            return [no_case = context.no_case_depth_](char32_t a, char32_t b) {
                if (no_case) {
                    case_fold_array_t folded_a = {0, 0, 0};
                    detail::case_fold(a, folded_a.begin());
                    case_fold_array_t folded_b = {0, 0, 0};
                    detail::case_fold(b, folded_b.begin());
                    return Equal ? folded_a == folded_b : folded_a < folded_b;
                } else {
                    return Equal ? a == b : a < b;
                }
            };
        }

        template<typename T, typename U>
        constexpr bool both_character_types =
            is_parsable_code_unit_v<T> && is_parsable_code_unit_v<U>;

        template<typename T, typename U>
        using eq_comparable =
            decltype(std::declval<T &>() == std::declval<U &>());

        template<
            typename Context,
            typename CharType,
            typename Expected,
            bool BothCharacters = both_character_types<CharType, Expected>>
        struct unequal_impl
        {
            static bool
            call(Context const & context, CharType c, Expected const & expected)
            {
                auto resolved = detail::resolve(context, expected);
                if constexpr (is_detected_v<
                                  eq_comparable,
                                  CharType,
                                  decltype(resolved)>) {
                    auto const compare =
                        detail::no_case_aware_compare<true>(context);
                    return !compare(c, resolved);
                } else {
                    return !resolved.contains(c, context);
                }
            }
        };

        template<typename Context, typename CharType, typename Expected>
        struct unequal_impl<Context, CharType, Expected, true>
        {
            static bool
            call(Context const & context, CharType c, Expected expected)
            {

                return !detail::no_case_aware_compare<true>(context)(
                    c, expected);
            }
        };

        template<typename Context, typename CharType, typename Expected>
        bool unequal(Context const & context, CharType c, Expected expected)
        {
            return unequal_impl<Context, CharType, Expected>::call(
                context, c, expected);
        }

        template<
            typename Context,
            typename CharType,
            typename LoType,
            typename HiType>
        bool unequal(
            Context const & context,
            CharType c,
            char_pair<LoType, HiType> const & expected)
        {
            auto const less = detail::no_case_aware_compare<false>(context);
            {
                auto lo = detail::resolve(context, expected.lo_);
                if (less(c, lo))
                    return true;
            }
            {
                auto hi = detail::resolve(context, expected.hi_);
                if (less(hi, c))
                    return true;
            }
            return false;
        }

        template<typename Context, typename CharType>
        bool unequal(Context const &, CharType, nope)
        {
            return false;
        }

        template<typename Container, typename T>
        using insertable = decltype(std::declval<Container &>().insert(
            std::declval<Container &>().end(), std::declval<T>()));

        template<typename T, typename Tuple, int... Is>
        auto
        make_from_tuple_impl(Tuple && tup, std::integer_sequence<int, Is...>)
            -> decltype(T(parser::get(std::move(tup), llong<Is>{})...))
        {
            return T(parser::get(std::move(tup), llong<Is>{})...);
        }

        template<typename T, typename... Args>
        auto make_from_tuple(tuple<Args...> && tup)
            -> decltype(detail::make_from_tuple_impl<T>(
                std::move(tup),
                std::make_integer_sequence<int, tuple_size_<tuple<Args...>>>()))
        {
            return detail::make_from_tuple_impl<T>(
                std::move(tup),
                std::make_integer_sequence<int, tuple_size_<tuple<Args...>>>());
        }

        template<typename T, typename Tuple>
        using constructible_from_tuple_expr =
            decltype(detail::make_from_tuple<T>(std::declval<Tuple>()));

        template<typename T, typename Tuple, bool = is_tuple<Tuple>{}>
        constexpr bool is_constructible_from_tuple_v = false;
        template<typename T, typename Tuple>
        constexpr bool is_constructible_from_tuple_v<T, Tuple, true> =
            is_detected_v<constructible_from_tuple_expr, T, Tuple>;

        template<typename Container, typename U>
        constexpr void move_back_impl(Container & c, U && x)
        {
            using just_t = range_value_t<Container>;
            using just_u = remove_cv_ref_t<U>;
            if constexpr (needs_transcoding_to_utf8<Container, U>) {
                char32_t cps[1] = {(char32_t)x};
                auto const r = cps | text::as_utf8;
                c.insert(c.end(), r.begin(), r.end());
            } else if constexpr (std::is_convertible_v<just_u &&, just_t>) {
                detail::insert(c, std::move(x));
            } else if constexpr (
                !is_tuple<just_t>::value && is_tuple<just_u>::value &&
                std::is_aggregate_v<just_t> &&
                !is_detected_v<insertable, Container, just_u &&> &&
                is_struct_assignable_v<just_t, just_u>) {
                auto int_seq =
                    std::make_integer_sequence<int, tuple_size_<just_u>>();
                detail::insert(
                    c,
                    detail::tuple_to_aggregate<just_t>(std::move(x), int_seq));
            } else if constexpr (
                is_tuple<just_t>::value && !is_tuple<just_u>::value &&
                std::is_aggregate_v<just_u> &&
                !is_detected_v<insertable, Container, just_u &&> &&
                is_tuple_assignable_v<just_t, just_u>) {
                just_t t;
                auto tie = detail::tie_aggregate(x);
                detail::aggregate_to_tuple(
                    t,
                    tie,
                    std::make_integer_sequence<int, tuple_size_<just_t>>());
                detail::insert(c, std::move(t));
            } else if constexpr (is_constructible_from_tuple_v<
                                     just_t,
                                     just_u>) {
                detail::insert(
                    c, detail::make_from_tuple<just_t>(std::move(x)));
            } else {
                static_assert(
                    sizeof(U) && false,
                    "Could not insert value into container, by: just inserting "
                    "it; doing tuple -> aggregate or aggregate -> tuple "
                    "conversions; or tuple -> class type construction.");
            }
        }

        template<typename Container, typename T>
        constexpr void move_back(Container & c, T && x, bool gen_attrs)
        {
            if (!gen_attrs)
                return;
            detail::move_back_impl(c, std::move(x));
        }

        template<typename Container>
        constexpr void move_back(Container & c, Container & x, bool gen_attrs)
        {
            if (!gen_attrs)
                return;
            c.insert(c.end(), x.begin(), x.end());
        }

        template<typename Container>
        constexpr void
        move_back(Container & c, std::optional<Container> && x, bool gen_attrs)
        {
            if (!gen_attrs || !x)
                return;
            c.insert(c.end(), x->begin(), x->end());
        }

        template<typename Container, typename T>
        constexpr void
        move_back(Container & c, std::optional<T> & x, bool gen_attrs)
        {
            if (!gen_attrs || !x)
                return;
            detail::move_back_impl(c, std::move(*x));
        }

        template<
            typename Container,
            typename T,
            typename Enable = std::enable_if_t<!std::is_same_v<Container, T>>>
        constexpr void
        move_back(Container & c, std::optional<T> && x, bool gen_attrs)
        {
            if (!gen_attrs || !x)
                return;
            detail::move_back_impl(c, std::move(*x));
        }

        constexpr void move_back(nope, nope, bool gen_attrs) {}

        template<typename Container>
        constexpr void move_back(Container & c, nope, bool gen_attrs)
        {}

        template<typename From, typename To>
        using move_assignable_expr =
            decltype(std::declval<To &>() = std::declval<From &&>());
        template<typename From, typename To>
        constexpr bool is_move_assignable_v =
            is_detected_v<move_assignable_expr, From, To>;

        template<typename T, typename U>
        constexpr void assign(T & t, U && u)
        {
            using just_t = remove_cv_ref_t<T>;
            using just_u = remove_cv_ref_t<U>;
            if constexpr (is_move_assignable_v<just_u, just_t>) {
                static_assert(
                    (!std::is_same_v<just_t, std::string> ||
                     !std::is_arithmetic_v<just_u>),
                    "std::string is assignable from a char.  Due to implicit "
                    "conversions among arithmetic types, any arithmetic type "
                    "(like int or double) is assignable to std::string.  This "
                    "is almost certainly not what you meant to write, so "
                    "Boost.Parser disallows it.  If you want to do this, write "
                    "a semantic action and do it explicitly.");
                t = std::move(u);
            } else if constexpr (
                !is_tuple<just_t>::value && is_tuple<just_u>::value &&
                std::is_aggregate_v<just_t> &&
                !std::is_convertible_v<just_u &&, just_t> &&
                is_struct_assignable_v<just_t, just_u>) {
                auto int_seq =
                    std::make_integer_sequence<int, tuple_size_<just_u>>();
                t = detail::tuple_to_aggregate<just_t>(std::move(u), int_seq);
            } else if constexpr (
                is_tuple<just_t>::value && !is_tuple<just_u>::value &&
                std::is_aggregate_v<just_u> &&
                !std::is_convertible_v<just_u &&, just_t> &&
                is_tuple_assignable_v<just_t, just_u>) {
                auto tie = detail::tie_aggregate(u);
                detail::aggregate_to_tuple(
                    t,
                    tie,
                    std::make_integer_sequence<int, tuple_size_<just_t>>());
            } else if constexpr (is_constructible_from_tuple_v<
                                     just_t,
                                     just_u>) {
                t = detail::make_from_tuple<just_t>(std::move(u));
            } else {
                static_assert(
                    sizeof(T) && false,
                    "Could not assign value, by: just assigning it; doing tuple "
                    "-> aggregate or aggregate -> tuple conversions; or tuple "
                    "-> class type construction.");
            }
        }

        template<typename T>
        constexpr void assign(T &, nope)
        {}

        template<typename T, typename U>
        constexpr void assign_copy(T & t, U const & u)
        {
            t = u;
        }

        template<typename T>
        constexpr void assign_copy(T &, nope)
        {}

        template<
            typename Parser,
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename... T>
        void apply_parser(
            Parser const & parser,
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            flags flags,
            bool & success,
            std::optional<std::variant<T...>> & retval)
        {
            using attr_t = decltype(parser.call(
                first, last, context, skip, flags, success));
            if constexpr (std::is_same<
                              attr_t,
                              std::optional<std::variant<T...>>>{}) {
                parser.call(first, last, context, skip, flags, success, retval);
            } else if constexpr (is_nope_v<attr_t>) {
                parser.call(first, last, context, skip, flags, success);
            } else {
                auto attr =
                    parser.call(first, last, context, skip, flags, success);
                if (success)
                    detail::assign(retval, std::variant<T...>(std::move(attr)));
            }
        }

        template<
            typename Parser,
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename... T>
        void apply_parser(
            Parser const & parser,
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            flags flags,
            bool & success,
            std::variant<T...> & retval)
        {
            auto attr = parser.call(first, last, context, skip, flags, success);
            if (success)
                detail::assign(retval, std::move(attr));
        }

        template<
            typename Parser,
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename T>
        void apply_parser(
            Parser const & parser,
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            flags flags,
            bool & success,
            std::optional<T> & retval)
        {
            auto attr = parser.call(first, last, context, skip, flags, success);
            if (success)
                detail::assign(retval, std::move(attr));
        }

        template<
            typename Parser,
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void apply_parser(
            Parser const & parser,
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            flags flags,
            bool & success,
            Attribute & retval)
        {
            parser.call(first, last, context, skip, flags, success, retval);
        }



        // API implementations

        template<typename Iter, typename Sentinel, typename Parser>
        auto has_attribute(Iter first, Sentinel last, Parser parser);

        template<typename BaseIter, typename Iter>
        struct scoped_base_assign
        {
            scoped_base_assign(BaseIter & base, Iter & it) :
                base_(base), it_(it)
            {}
            ~scoped_base_assign() { base_ = it_.base(); }

            BaseIter & base_;
            Iter & it_;
        };

        template<typename Parser>
        using has_parser_data_member_expr =
            decltype(std::declval<Parser>().parser_);
        template<typename Parser>
        constexpr bool has_parser_data_member_v =
            is_detected_v<has_parser_data_member_expr, Parser>;

        template<typename Parser>
        using has_parsers_data_member_expr =
            decltype(std::declval<Parser>().parsers_);
        template<typename Parser>
        constexpr bool has_parsers_data_member_v =
            is_detected_v<has_parsers_data_member_expr, Parser>;

        struct scoped_apply_pending_symbol_table_operations
        {
            scoped_apply_pending_symbol_table_operations(
                pending_symbol_table_operations_t & pending_ops) :
                pending_ops_(pending_ops)
            {}

            ~scoped_apply_pending_symbol_table_operations()
            {
                for (auto & [_, entry] : pending_ops_) {
                    entry.visit_();
                }
            }

            pending_symbol_table_operations_t & pending_ops_;
        };

        template<
            bool Debug,
            typename Iter,
            typename Sentinel,
            typename Parser,
            typename Attr,
            typename ErrorHandler>
        bool parse_impl(
            Iter & first,
            Sentinel last,
            Parser const & parser,
            ErrorHandler const & error_handler,
            Attr & attr)
        {
            auto const initial_first = first;
            bool success = true;
            int trace_indent = 0;
            detail::symbol_table_tries_t symbol_table_tries;
            pending_symbol_table_operations_t pending_symbol_table_operations;
            scoped_apply_pending_symbol_table_operations apply_pending(
                pending_symbol_table_operations);
            auto context = detail::make_context<Debug, false>(
                first,
                last,
                success,
                trace_indent,
                error_handler,
                parser.globals_,
                symbol_table_tries,
                pending_symbol_table_operations);
            auto const flags =
                Debug ? detail::enable_trace(detail::flags::gen_attrs)
                      : detail::flags::gen_attrs;
            try {
                parser(
                    first,
                    last,
                    context,
                    detail::null_parser{},
                    flags,
                    success,
                    attr);
                if (Debug)
                    detail::final_trace(context, flags, attr);
                return success;
            } catch (parse_error<Iter> const & e) {
                if (error_handler(initial_first, last, e) ==
                    error_handler_result::rethrow) {
                    throw;
                }
                return false;
            }
        }

        template<
            bool Debug,
            typename Iter,
            typename Sentinel,
            typename Parser,
            typename ErrorHandler>
        auto parse_impl(
            Iter & first,
            Sentinel last,
            Parser const & parser,
            ErrorHandler const & error_handler)
        {
            auto const initial_first = first;
            bool success = true;
            int trace_indent = 0;
            detail::symbol_table_tries_t symbol_table_tries;
            pending_symbol_table_operations_t pending_symbol_table_operations;
            scoped_apply_pending_symbol_table_operations apply_pending(
                pending_symbol_table_operations);
            auto context = detail::make_context<Debug, false>(
                first,
                last,
                success,
                trace_indent,
                error_handler,
                parser.globals_,
                symbol_table_tries,
                pending_symbol_table_operations);
            auto const flags =
                Debug ? detail::enable_trace(detail::flags::gen_attrs)
                      : detail::flags::gen_attrs;
            using attr_t = typename detail::attribute_impl<
                BOOST_PARSER_SUBRANGE<std::remove_const_t<Iter>, Sentinel>,
                Parser>::type;
            try {
                attr_t attr_ = parser(
                    first,
                    last,
                    context,
                    detail::null_parser{},
                    flags,
                    success);
                if (Debug)
                    detail::final_trace(context, flags, nope{});
                return detail::make_parse_result(attr_, success);
            } catch (parse_error<Iter> const & e) {
                if (error_handler(initial_first, last, e) ==
                    error_handler_result::rethrow) {
                    throw;
                }
                attr_t attr_{};
                return detail::make_parse_result(attr_, false);
            }
        }

        template<
            bool Debug,
            typename Iter,
            typename Sentinel,
            typename Parser,
            typename ErrorHandler,
            typename Callbacks>
        bool callback_parse_impl(
            Iter & first,
            Sentinel last,
            Parser const & parser,
            ErrorHandler const & error_handler,
            Callbacks const & callbacks)
        {
            auto const initial_first = first;
            bool success = true;
            int trace_indent = 0;
            detail::symbol_table_tries_t symbol_table_tries;
            pending_symbol_table_operations_t pending_symbol_table_operations;
            scoped_apply_pending_symbol_table_operations apply_pending(
                pending_symbol_table_operations);
            auto context = detail::make_context<Debug, true>(
                first,
                last,
                success,
                trace_indent,
                error_handler,
                callbacks,
                parser.globals_,
                symbol_table_tries,
                pending_symbol_table_operations);
            auto const flags =
                Debug ? detail::enable_trace(detail::flags::gen_attrs)
                      : detail::flags::gen_attrs;
            try {
                parser(
                    first,
                    last,
                    context,
                    detail::null_parser{},
                    flags,
                    success);
                if (Debug)
                    detail::final_trace(context, flags, nope{});
                return success;
            } catch (parse_error<Iter> const & e) {
                if (error_handler(initial_first, last, e) ==
                    error_handler_result::rethrow) {
                    throw;
                }
                return false;
            }
        }

        template<
            bool Debug,
            typename Iter,
            typename Sentinel,
            typename Parser,
            typename SkipParser,
            typename Attr,
            typename ErrorHandler>
        bool skip_parse_impl(
            Iter & first,
            Sentinel last,
            Parser const & parser,
            SkipParser const & skip,
            ErrorHandler const & error_handler,
            Attr & attr)
        {
            auto const initial_first = first;
            bool success = true;
            int trace_indent = 0;
            detail::symbol_table_tries_t symbol_table_tries;
            pending_symbol_table_operations_t pending_symbol_table_operations;
            scoped_apply_pending_symbol_table_operations apply_pending(
                pending_symbol_table_operations);
            auto context = detail::make_context<Debug, false>(
                first,
                last,
                success,
                trace_indent,
                error_handler,
                parser.globals_,
                symbol_table_tries,
                pending_symbol_table_operations);
            auto const flags =
                Debug ? detail::enable_trace(detail::default_flags())
                      : detail::default_flags();
            detail::skip(first, last, skip, flags);
            try {
                parser(first, last, context, skip, flags, success, attr);
                detail::skip(first, last, skip, flags);
                if (Debug)
                    detail::final_trace(context, flags, attr);
                return success;
            } catch (parse_error<Iter> const & e) {
                if (error_handler(initial_first, last, e) ==
                    error_handler_result::rethrow) {
                    throw;
                }
                return false;
            }
        }

        template<
            bool Debug,
            typename Iter,
            typename Sentinel,
            typename Parser,
            typename SkipParser,
            typename ErrorHandler>
        auto skip_parse_impl(
            Iter & first,
            Sentinel last,
            Parser const & parser,
            SkipParser const & skip,
            ErrorHandler const & error_handler)
        {
            auto const initial_first = first;
            bool success = true;
            int trace_indent = 0;
            detail::symbol_table_tries_t symbol_table_tries;
            pending_symbol_table_operations_t pending_symbol_table_operations;
            scoped_apply_pending_symbol_table_operations apply_pending(
                pending_symbol_table_operations);
            auto context = detail::make_context<Debug, false>(
                first,
                last,
                success,
                trace_indent,
                error_handler,
                parser.globals_,
                symbol_table_tries,
                pending_symbol_table_operations);
            auto const flags =
                Debug ? detail::enable_trace(detail::default_flags())
                      : detail::default_flags();
            detail::skip(first, last, skip, flags);
            using attr_t = typename detail::attribute_impl<
                BOOST_PARSER_SUBRANGE<std::remove_const_t<Iter>, Sentinel>,
                Parser>::type;
            try {
                attr_t attr_ =
                    parser(first, last, context, skip, flags, success);
                detail::skip(first, last, skip, flags);
                if (Debug)
                    detail::final_trace(context, flags, nope{});
                return detail::make_parse_result(attr_, success);
            } catch (parse_error<Iter> const & e) {
                if (error_handler(initial_first, last, e) ==
                    error_handler_result::rethrow) {
                    throw;
                }
                attr_t attr_{};
                return detail::make_parse_result(attr_, false);
            }
        }

        template<
            bool Debug,
            typename Iter,
            typename Sentinel,
            typename Parser,
            typename SkipParser,
            typename ErrorHandler,
            typename Callbacks>
        bool callback_skip_parse_impl(
            Iter & first,
            Sentinel last,
            Parser const & parser,
            SkipParser const & skip,
            ErrorHandler const & error_handler,
            Callbacks const & callbacks)
        {
            auto const initial_first = first;
            bool success = true;
            int trace_indent = 0;
            detail::symbol_table_tries_t symbol_table_tries;
            pending_symbol_table_operations_t pending_symbol_table_operations;
            scoped_apply_pending_symbol_table_operations apply_pending(
                pending_symbol_table_operations);
            auto context = detail::make_context<Debug, true>(
                first,
                last,
                success,
                trace_indent,
                error_handler,
                callbacks,
                parser.globals_,
                symbol_table_tries,
                pending_symbol_table_operations);
            auto const flags =
                Debug ? detail::enable_trace(detail::default_flags())
                      : detail::default_flags();
            detail::skip(first, last, skip, flags);
            try {
                parser(first, last, context, skip, flags, success);
                detail::skip(first, last, skip, flags);
                if (Debug)
                    detail::final_trace(context, flags, nope{});
                return success;
            } catch (parse_error<Iter> const & e) {
                if (error_handler(initial_first, last, e) ==
                    error_handler_result::rethrow) {
                    throw;
                }
                return false;
            }
        }

        template<typename R>
        constexpr auto make_input_subrange(R && r) noexcept
        {
            using r_t = remove_cv_ref_t<R>;
            if constexpr (std::is_pointer_v<r_t>) {
                using value_type = iter_value_t<r_t>;
                if constexpr (std::is_same_v<value_type, char>) {
                    return BOOST_PARSER_SUBRANGE(r, text::null_sentinel);
                } else {
                    return r | text::as_utf32;
                }
            } else {
                using value_type = range_value_t<r_t>;
                if constexpr (text::detail::is_bounded_array_v<r_t>) {
                    if constexpr (std::is_same_v<value_type, char>) {
                        auto first = detail::text::detail::begin(r);
                        auto last = detail::text::detail::end(r);
                        if (first != last && !*std::prev(last))
                            --last;
                        return BOOST_PARSER_SUBRANGE(first, last);
                    } else {
                        return r | text::as_utf32;
                    }
                } else {
                    if constexpr (
                        std::is_same_v<value_type, char> &&
                        !is_utf8_view<r_t>::value) {
                        return BOOST_PARSER_SUBRANGE(
                            detail::text::detail::begin(r),
                            detail::text::detail::end(r));
                    } else {
                        return r | text::as_utf32;
                    }
                }
            }
        }

        template<typename R>
        constexpr auto make_view_begin(R & r) noexcept
        {
            if constexpr (std::is_pointer_v<std::decay_t<R>>) {
                return r;
            } else {
                return detail::text::detail::begin(r);
            }
        }

        template<typename R>
        constexpr auto make_view_end(R & r) noexcept
        {
            if constexpr (std::is_pointer_v<std::decay_t<R>>) {
                return text::null_sentinel;
            } else {
                return detail::text::detail::end(r);
            }
        }

        template<
            typename Iter1,
            typename Sentinel1,
            typename Iter2,
            typename Sentinel2,
            typename Pred>
        std::pair<Iter1, Iter2> mismatch(
            Iter1 first1,
            Sentinel1 last1,
            Iter2 first2,
            Sentinel2 last2,
            Pred pred)
        {
            std::pair<Iter1, Iter2> retval{first1, first2};
            while (retval.first != last1 && retval.second != last2 &&
                   pred(*retval.first, *retval.second)) {
                ++retval.first;
                ++retval.second;
            }
            return retval;
        }

        template<
            typename Iter1,
            typename Sentinel1,
            typename Iter2,
            typename Sentinel2>
        std::pair<Iter1, Iter2> no_case_aware_string_mismatch(
            Iter1 first1,
            Sentinel1 last1,
            Iter2 first2,
            Sentinel2 last2,
            bool no_case)
        {
            if (no_case) {
                auto it1 = no_case_iter(first1, last1);
                auto it2 = no_case_iter(first2, last2);
                auto const mismatch = detail::mismatch(
                    it1, last1, it2, last2, std::equal_to<char32_t>{});
                return std::pair<Iter1, Iter2>{
                    mismatch.first.base(), mismatch.second.base()};
            } else {
                return detail::mismatch(
                    first1, last1, first2, last2, std::equal_to<char32_t>{});
            }
        }

        template<typename I, typename S, typename T>
        std::optional<T>
        if_full_parse(I & first, S last, std::optional<T> retval)
        {
            if (first != last)
                retval = std::nullopt;
            return retval;
        }
        template<typename I, typename S>
        bool if_full_parse(I & first, S last, bool retval)
        {
            if (first != last)
                retval = false;
            return retval;
        }

        // The notion of comaptibility is that, given a parser with the
        // Attribute Tuple, we can parse into Struct instead.
        template<typename Struct, typename Tuple>
        constexpr auto is_struct_compatible();

        struct element_compatibility
        {
            template<typename T, typename U>
            constexpr auto operator()(T result, U x) const
            {
                using struct_elem =
                    remove_cv_ref_t<decltype(parser::get(x, llong<0>{}))>;
                using tuple_elem =
                    remove_cv_ref_t<decltype(parser::get(x, llong<1>{}))>;
                if constexpr (!T::value) {
                    return std::false_type{};
                } else if constexpr (
                    is_optional_v<struct_elem> && is_optional_v<tuple_elem>) {
                    using struct_opt_type = optional_type<struct_elem>;
                    using tuple_opt_type = optional_type<tuple_elem>;
                    using retval_t = decltype((*this)(
                        result,
                        detail::hl::make_tuple(
                            std::declval<struct_opt_type &>(),
                            std::declval<tuple_opt_type &>())));
                    return retval_t{};
                } else if constexpr (std::is_convertible_v<
                                         tuple_elem &&,
                                         struct_elem>) {
                    return std::true_type{};
                } else if constexpr (
                    container<struct_elem> && container<tuple_elem>) {
                    return detail::is_struct_compatible<
                        range_value_t<struct_elem>,
                        range_value_t<tuple_elem>>();
                } else {
                    return std::bool_constant<detail::is_struct_compatible<
                        struct_elem,
                        tuple_elem>()>{};
                }
            }
        };

        template<typename T, typename U>
        constexpr auto is_struct_compatible()
        {
            if constexpr (
                !std::is_aggregate_v<T> ||
                struct_arity_v<T> != tuple_size_<U>) {
                return std::false_type{};
            } else {
                using result_t = decltype(detail::hl::fold_left(
                    detail::hl::zip(
                        detail::tie_aggregate(std::declval<T &>()),
                        std::declval<U &>()),
                    std::true_type{},
                    element_compatibility{}));
                return result_t{};
            }
        }

        template<typename Struct, typename Tuple>
        constexpr bool is_struct_compatible_v =
            detail::is_struct_compatible<Struct, Tuple>();

        template<typename ParserAttr, typename GivenContainerAttr>
        constexpr auto parser_attr_or_container_value_type()
        {
            if constexpr (is_nope_v<ParserAttr>) {
                return nope{};
            } else {
                using value_type = range_value_t<GivenContainerAttr>;
                return std::conditional_t<
                    std::is_convertible_v<ParserAttr, value_type>,
                    ParserAttr,
                    value_type>{};
            }
        }
        template<typename ParserAttr, typename GivenContainerAttr>
        using parser_attr_or_container_value_type_v =
            decltype(parser_attr_or_container_value_type<
                     ParserAttr,
                     GivenContainerAttr>());

        template<typename T>
        constexpr auto tuple_or_struct_size(T && x)
        {
            if constexpr (is_tuple<remove_cv_ref_t<T>>{}) {
                return hl::size(x);
            } else {
                return llong<struct_arity_v<remove_cv_ref_t<T>>>{};
            }
        }

        template<typename T>
        struct attr_reset
        {
            attr_reset(T & x) : x_(std::addressof(x)) {}
            attr_reset(attr_reset const &) = delete;
            attr_reset(attr_reset &&) = delete;
            attr_reset & operator=(attr_reset const &) = delete;
            attr_reset & operator=(attr_reset &&) = delete;
            ~attr_reset()
            {
                if (x_)
                    *x_ = T();
            }

            bool operator=(bool b)
            {
                if (b)
                    x_ = nullptr;
                return b;
            }

        private:
            T * x_;
        };
    }

#ifndef BOOST_PARSER_DOXYGEN

    // This constraint is only here to allow the alternate-call semantic
    // action metaprogramming logic to function on MSVC.
    template<typename Context>
    auto _val(Context const & context) -> std::conditional_t<
        detail::is_nope_v<decltype(*context.val_)>,
        none,
        decltype(*context.val_)>
    {
        if constexpr (detail::is_nope_v<decltype(*context.val_)>)
            return none{};
        else
            return *context.val_;
    }

    template<typename Context>
    decltype(auto) _attr(Context const & context)
    {
        if constexpr (detail::is_nope_v<decltype(*context.attr_)>)
            return none{};
        else
            return *context.attr_;
    }

    template<typename Context>
    decltype(auto) _where(Context const & context)
    {
        return *context.where_;
    }

    template<typename Context>
    decltype(auto) _begin(Context const & context)
    {
        return context.first_;
    }

    template<typename Context>
    decltype(auto) _end(Context const & context)
    {
        return context.last_;
    }

    template<typename Context>
    decltype(auto) _pass(Context const & context)
    {
        return *context.pass_;
    }

    template<typename Context>
    decltype(auto) _locals(Context const & context)
    {
        if constexpr (detail::is_nope_v<decltype(*context.locals_)>)
            return none{};
        else
            return *context.locals_;
    }

    template<typename Context>
    decltype(auto) _params(Context const & context)
    {
        if constexpr (detail::is_nope_v<decltype(*context.params_)>)
            return none{};
        else
            return *context.params_;
    }

    template<typename Context>
    decltype(auto) _globals(Context const & context)
    {
        if constexpr (detail::is_nope_v<decltype(*context.globals_)>)
            return none{};
        else
            return *context.globals_;
    }

    template<typename Context>
    decltype(auto) _no_case(Context const & context)
    {
        return context.no_case_depth_;
    }

    template<typename Context>
    decltype(auto) _error_handler(Context const & context)
    {
        return *context.error_handler_;
    }

#if BOOST_PARSER_USE_CONCEPTS
    template<std::forward_iterator I, typename Context>
#else
    template<typename I, typename Context>
#endif
    void
    _report_error(Context const & context, std::string_view message, I location)
    {
        return context.error_handler_->diagnose(
            diagnostic_kind::error, message, context, location);
    }

    template<typename Context>
    void _report_error(Context const & context, std::string_view message)
    {
        return context.error_handler_->diagnose(
            diagnostic_kind::error, message, context);
    }

#if BOOST_PARSER_USE_CONCEPTS
    template<std::forward_iterator I, typename Context>
#else
    template<typename I, typename Context>
#endif
    void _report_warning(
        Context const & context, std::string_view message, I location)
    {
        return context.error_handler_->diagnose(
            diagnostic_kind::warning, message, context, location);
    }

    template<typename Context>
    void _report_warning(Context const & context, std::string_view message)
    {
        return context.error_handler_->diagnose(
            diagnostic_kind::warning, message, context);
    }

#endif

    /** An invocable that returns the `I`th parameter to the bottommost rule.
        This is useful for forwarding parameters to sub-rules. */
    template<unsigned int I>
    inline constexpr detail::param_t<I> _p = {};



    // Second order parsers.

    /** A very large sentinel value used to represent pseudo-infinity. */
    int64_t const Inf = detail::unbounded;

#ifndef BOOST_PARSER_DOXYGEN
    template<
        typename Parser,
        typename DelimiterParser,
        typename MinType,
        typename MaxType>
    struct repeat_parser
    {
        constexpr repeat_parser(
            Parser parser,
            MinType _min,
            MaxType _max,
            DelimiterParser delimiter_parser = DelimiterParser{}) :
            parser_(parser),
            delimiter_parser_(delimiter_parser),
            min_(_min),
            max_(_max)
        {}

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            using attr_t = decltype(parser_.call(
                first, last, context, skip, flags, success));
            auto retval = detail::make_sequence_of<attr_t>();
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this,
                first,
                last,
                context,
                detail::in_apply_parser(flags) ? detail::disable_trace(flags)
                                               : flags,
                retval);

            if constexpr (detail::is_optional_v<Attribute>) {
                detail::optional_type<Attribute> attr;
                detail::apply_parser(
                    *this,
                    first,
                    last,
                    context,
                    skip,
                    detail::set_in_apply_parser(flags),
                    success,
                    attr);
                if (success)
                    retval = std::move(attr);
            } else { // Otherwise, Attribute must be a container or a nope.
                using attr_t = detail::parser_attr_or_container_value_type_v<
                    decltype(parser_.call(
                        first, last, context, skip, flags, success)),
                    Attribute>;

                int64_t count = 0;

                for (int64_t end = detail::resolve(context, min_); count != end;
                     ++count) {
                    detail::skip(first, last, skip, flags);
                    attr_t attr{};
                    parser_.call(
                        first, last, context, skip, flags, success, attr);
                    if (!success) {
                        detail::assign(retval, Attribute());
                        return;
                    }
                    detail::move_back(
                        retval, std::move(attr), detail::gen_attrs(flags));
                }

                int64_t const end = detail::resolve(context, max_);

                // It looks like you've created a repeated epsilon parser, by
                // writing "*eps", "+eps", "repeat(2, Inf)[eps]", or similar.
                BOOST_PARSER_DEBUG_ASSERT(
                    !detail::is_unconditional_eps<Parser>{} || end < Inf);

                for (; count != end; ++count) {
                    auto const prev_first = first;
                    // This is only ever used in delimited_parser, which
                    // always has a min=1; we therefore know we're after a
                    // previous element when this executes.
                    if constexpr (!detail::is_nope_v<DelimiterParser>) {
                        detail::skip(first, last, skip, flags);
                        delimiter_parser_.call(
                            first,
                            last,
                            context,
                            skip,
                            detail::disable_attrs(flags),
                            success);
                        if (!success) {
                            success = true;
                            first = prev_first;
                            break;
                        }
                    }

                    detail::skip(first, last, skip, flags);
                    attr_t attr{};
                    parser_.call(
                        first, last, context, skip, flags, success, attr);
                    if (!success) {
                        success = true;
                        first = prev_first;
                        break;
                    }
                    detail::move_back(
                        retval, std::move(attr), detail::gen_attrs(flags));
                }
            }
        }

        Parser parser_;
        DelimiterParser delimiter_parser_;
        MinType min_;
        MaxType max_;
    };
#endif

    template<typename Parser>
    struct zero_plus_parser : repeat_parser<Parser>
    {
        constexpr zero_plus_parser(Parser parser) :
            repeat_parser<Parser>(parser, 0, Inf)
        {}
    };

    template<typename Parser>
    struct one_plus_parser : repeat_parser<Parser>
    {
        constexpr one_plus_parser(Parser parser) :
            repeat_parser<Parser>(parser, 1, Inf)
        {}
    };

    template<typename Parser, typename DelimiterParser>
    struct delimited_seq_parser : repeat_parser<Parser, DelimiterParser>
    {
        constexpr delimited_seq_parser(
            Parser parser, DelimiterParser delimiter_parser) :
            repeat_parser<Parser, DelimiterParser>(
                parser, 1, Inf, delimiter_parser)
        {}
    };

    //[ opt_parser_beginning
    template<typename Parser>
    struct opt_parser
    {
        //]
        //[ opt_parser_attr_call
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            using attr_t = decltype(parser_.call(
                first, last, context, skip, flags, success));
            detail::optional_of<attr_t> retval;
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }
        //]

        //[ opt_parser_out_param_call
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            //[ opt_parser_trace
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);
            //]

            //[ opt_parser_skip
            detail::skip(first, last, skip, flags);
            //]

            //[ opt_parser_no_gen_attr_path
            if (!detail::gen_attrs(flags)) {
                parser_.call(first, last, context, skip, flags, success);
                success = true;
                return;
            }
            //]

            //[ opt_parser_gen_attr_path
            parser_.call(first, last, context, skip, flags, success, retval);
            success = true;
            //]
        }
        //]

        //[ opt_parser_end
        Parser parser_;
    };
    //]

    template<typename ParserTuple>
    struct or_parser
    {
        constexpr or_parser(ParserTuple parsers) : parsers_(parsers) {}

#ifndef BOOST_PARSER_DOXYGEN

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        struct use_parser_t
        {
            template<typename Parser>
            auto operator()(Parser const & parser) const
            {
                detail::skip(first_, last_, skip_, flags_);
                success_ = true; // In case someone earlier already failed...
                return parser.call(
                    first_,
                    last_,
                    context_,
                    skip_,
                    flags_,
                    success_);
            }

            template<typename Parser, typename Attribute>
            void operator()(Parser const & parser, Attribute & retval) const
            {
                detail::skip(first_, last_, skip_, flags_);
                success_ = true; // In case someone earlier already failed...

                detail::apply_parser(
                    parser,
                    first_,
                    last_,
                    context_,
                    skip_,
                    flags_,
                    success_,
                    retval);
            }

            Iter & first_;
            Sentinel last_;
            Context const & context_;
            SkipParser const & skip_;
            detail::flags flags_;
            bool & success_;
        };

#endif

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            use_parser_t<Iter, Sentinel, Context, SkipParser> const use_parser{
                first, last, context, skip, flags, success};

            // A result type for each of the parsers in parsers_.
            using all_types =
                decltype(detail::hl::transform(parsers_, use_parser));

            // Same as above, wrapped in detail::wrapper.
            using all_types_wrapped =
                decltype(detail::hl::transform(all_types{}, detail::wrap{}));

            // Returns a tuple<> containing two things: 1) A tuple of only the
            // unique wrapped types from above, without nopes; this may be
            // empty. 2) std::true_type or std::false_type indicating whether
            // nopes were found; if so, the final result is an optional.
            auto append_unique = [](auto result, auto x) {
                using x_type = typename decltype(x)::type;
                if constexpr (detail::is_nope_v<x_type>) {
                    return detail::hl::make_pair(
                        detail::hl::first(result), std::true_type{});
                } else if constexpr (detail::hl::contains(
                                         detail::hl::first(result), x)) {
                    return result;
                } else {
                    return detail::hl::make_pair(
                        detail::hl::append(detail::hl::first(result), x),
                        detail::hl::second(result));
                }
            };
            using wrapped_unique_types = decltype(detail::hl::fold_left(
                all_types_wrapped{},
                detail::hl::make_pair(tuple<>{}, std::false_type{}),
                append_unique));

            // Same as above, with the tuple types unwrapped.
            using unwrapped_types = decltype(detail::hl::make_pair(
                detail::hl::transform(
                    detail::hl::first(wrapped_unique_types{}),
                    detail::unwrap{}),
                detail::hl::second(wrapped_unique_types{})));

            // Types above converted to a "variant", which may actually be a
            // non-variant type T if that is the only unique non-nope type, or a
            // nope if unwrapped_types is empty.
            using result_t = detail::to_hana_tuple_or_type_t<unwrapped_types>;

            result_t retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            use_parser_t<Iter, Sentinel, Context, SkipParser> const use_parser{
                first, last, context, skip, flags, success};

            bool done = false;
            auto try_parser = [prev_first = first,
                               use_parser,
                               &success,
                               flags,
                               &retval,
                               &done](auto const & parser) {
                if (done)
                    return;
                if (detail::gen_attrs(flags))
                    use_parser(parser, retval);
                else
                    use_parser(parser);
                if (success)
                    done = true;
                else
                    use_parser.first_ = prev_first;
            };
            detail::hl::for_each(parsers_, try_parser); // TODO: -> fold-expr

            if (!done)
                success = false;
        }

#ifndef BOOST_PARSER_DOXYGEN

        template<typename Parser>
        constexpr auto prepend(parser_interface<Parser> parser) const noexcept;
        template<typename Parser>
        constexpr auto append(parser_interface<Parser> parser) const noexcept;

#endif

        ParserTuple parsers_;
    };

    template<typename ParserTuple>
    struct perm_parser
    {
        constexpr perm_parser(ParserTuple parsers) : parsers_(parsers) {}

#ifndef BOOST_PARSER_DOXYGEN

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        struct use_parser_t
        {
            template<typename Parser>
            auto operator()(Parser const & parser) const
            {
                detail::skip(first_, last_, skip_, flags_);
                success_ = true; // In case someone earlier already failed...
                return parser.call(
                    first_,
                    last_,
                    context_,
                    skip_,
                    flags_,
                    success_);
            }

            template<typename Parser, typename Attribute>
            void operator()(Parser const & parser, Attribute & retval) const
            {
                detail::skip(first_, last_, skip_, flags_);
                success_ = true; // In case someone earlier already failed...

                detail::apply_parser(
                    parser,
                    first_,
                    last_,
                    context_,
                    skip_,
                    flags_,
                    success_,
                    retval);
            }

            Iter & first_;
            Sentinel last_;
            Context const & context_;
            SkipParser const & skip_;
            detail::flags flags_;
            bool & success_;
        };

#endif

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first_,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            Iter first = first_;

            use_parser_t<Iter, Sentinel, Context, SkipParser> const use_parser{
                first, last, context, skip, flags, success};
            using result_t =
                decltype(detail::hl::transform(parsers_, use_parser));
            result_t retval{};

            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first_, last, context, flags, retval);

            call_impl(
                first,
                last,
                context,
                skip,
                flags,
                success,
                retval,
                std::make_integer_sequence<
                    int,
                    detail::tuple_size_<ParserTuple>>{});

            if (success)
                first_ = first;

            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first_,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first_, last, context, flags, retval);

            Iter first = first_;
            use_parser_t<Iter, Sentinel, Context, SkipParser> const use_parser{
                first, last, context, skip, flags, success};
            using result_t =
                decltype(detail::hl::transform(parsers_, use_parser));

            constexpr auto indices = std::
                make_integer_sequence<int, detail::tuple_size_<ParserTuple>>{};

            if constexpr (detail::is_optional_v<Attribute>) {
                typename Attribute::value_type attr;
                call(first, last, context, skip, flags, success, attr);
                if (success)
                    detail::assign(retval, std::move(attr));
            } else if constexpr (
                detail::is_tuple<Attribute>{} ||
                detail::is_struct_compatible_v<Attribute, result_t>) {
                call_impl(
                    first,
                    last,
                    context,
                    skip,
                    flags,
                    success,
                    retval,
                    indices);

                if (!success)
                    detail::assign(retval, Attribute());
            } else if constexpr (detail::is_constructible_from_tuple_v<
                                     Attribute,
                                     result_t>) {
                result_t temp_retval{};
                call_impl(
                    first,
                    last,
                    context,
                    skip,
                    flags,
                    success,
                    temp_retval,
                    indices);

                if (success && detail::gen_attrs(flags)) {
                    detail::assign(
                        retval,
                        detail::make_from_tuple<Attribute>(
                            std::move(temp_retval)));
                }
            } else {
#if 0 // TODO Seems incompatible with this parser.
                // call_impl requires a tuple, so we must wrap this scalar.
                tuple<Attribute> temp_retval{};
                call_impl(
                    first,
                    last,
                    context,
                    skip,
                    flags,
                    success,
                    temp_retval,
                    indices);

                if (success && detail::gen_attrs(flags)) {
                    detail::assign(
                        retval, std::move(detail::hl::front(temp_retval)));
                }
#else
                static_assert(
                    std::is_same_v<Attribute, void> && false,
                    "It looks like you passed an attribute to this permutation "
                    "parser that is not capable of taking the number, or the "
                    "types of values compatible with the ones it produces.");
#endif
            }

            if (success)
                first_ = first;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename... Ts,
            int... Is>
        void call_impl(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            tuple<Ts...> & retval,
            std::integer_sequence<int, Is...>) const
        {
            std::array<bool, sizeof...(Ts)> used_parsers = {{}};

            // Use "parser" to fill in attribute "x", unless "parser" has
            // previously been used.
            auto parse_into = [&](int i, auto const & parser, auto & x) {
                if (used_parsers[i])
                    return false;
                detail::skip(first, last, skip, flags);
                parser.call(first, last, context, skip, flags, success, x);
                if (success) {
                    used_parsers[i] = true;
                    return true;
                }
                success = true;
                return false;
            };
            // Use one of the previously-unused parsers to parse one
            // alternative.
            auto parsed_one = [&](auto) {
                return (
                    parse_into(
                        Is,
                        parser::get(parsers_, llong<Is>{}),
                        parser::get(retval, llong<Is>{})) ||
                    ...);
            };
            success = (parsed_one(Is) && ...);

            if (!success)
                retval = tuple<Ts...>{};
        }

#ifndef BOOST_PARSER_DOXYGEN

        template<typename Parser>
        constexpr auto prepend(parser_interface<Parser> parser) const noexcept;
        template<typename Parser>
        constexpr auto append(parser_interface<Parser> parser) const noexcept;

#endif

        ParserTuple parsers_;
    };

    namespace detail {
        template<int N, int... I>
        constexpr auto
        make_default_combining_impl(std::integer_sequence<int, I...>)
        {
            return hl::make_tuple(((void)I, llong<N>{})...);
        }
        template<template<class...> class Tuple, typename... Args>
        constexpr auto make_default_combining(Tuple<Args...>)
        {
            return detail::make_default_combining_impl<0>(
                std::make_integer_sequence<int, sizeof...(Args)>());
        }
        template<typename ParserTuple>
        using default_combining_t = decltype(detail::make_default_combining(
            std::declval<ParserTuple>()));

        struct merge_t
        {};
        struct separate_t
        {};

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        struct dummy_use_parser_t
        {
            dummy_use_parser_t(
                Iter & first,
                Sentinel last,
                Context const & context,
                SkipParser const & skip,
                detail::flags flags,
                bool & success) :
                first_(first),
                last_(last),
                context_(context),
                skip_(skip),
                flags_(flags),
                success_(success)
            {}
            template<typename Parser>
            auto operator()(Parser const & parser) const
            {
                return parser.call(
                    first_,
                    last_,
                    context_,
                    skip_,
                    flags_,
                    success_);
            }
            Iter & first_;
            Sentinel last_;
            Context const & context_;
            SkipParser const & skip_;
            detail::flags flags_;
            bool & success_;
        };

        template<typename... Args>
        constexpr void static_assert_merge_attributes(tuple<Args...> parsers);

        // Combining groups are: 0, which is default merge behavior, as in
        // seq_parser::combine; -1, which is don't merge with anything, ever;
        // and N>0, which is merge with other members of group N.
        template<typename CombiningGroups, typename... Args>
        constexpr auto make_combining(tuple<Args...> parsers)
        {
            if constexpr (std::is_same_v<CombiningGroups, merge_t>) {
                detail::static_assert_merge_attributes(parsers);
                return detail::make_default_combining_impl<1>(
                    std::make_integer_sequence<int, sizeof...(Args)>());
            } else if constexpr (std::is_same_v<CombiningGroups, separate_t>) {
                return detail::make_default_combining_impl<-1>(
                    std::make_integer_sequence<int, sizeof...(Args)>());
            } else {
                return CombiningGroups{};
            }
        }
        template<typename ParserTuple, typename CombiningGroups>
        using combining_t = decltype(detail::make_combining<CombiningGroups>(
            std::declval<ParserTuple>()));

        struct max_
        {
            template<typename T, typename U>
            constexpr auto operator()(T x, U y) const
            {
                if constexpr (T::value < U::value)
                    return y;
                else
                    return x;
            }
        };
        template<int MaxGroupIdx>
        struct adjust_combining_groups
        {
            template<typename T, typename U>
            constexpr auto operator()(T result, U x) const
            {
                if constexpr (U::value <= 0)
                    return hl::append(result, x);
                else
                    return hl::append(result, llong<MaxGroupIdx + U::value>{});
            }
        };
        template<typename Tuple1, typename Tuple2>
        constexpr auto make_combined_combining(Tuple1 lhs, Tuple2 rhs)
        {
            auto max_group_idx = detail::hl::fold_left(lhs, llong<0>{}, max_{});
            auto rhs_adjusted = detail::hl::fold_left(
                rhs,
                tuple<>{},
                adjust_combining_groups<decltype(max_group_idx)::value>{});
            return hl::concat(lhs, rhs_adjusted);
        }
        template<typename CombiningGroups1, typename CombiningGroups2>
        using combined_combining_t = decltype(detail::make_combined_combining(
            std::declval<CombiningGroups1>(),
            std::declval<CombiningGroups2>()));

        enum class merge_kind { second_pass_detect, singleton, merged, group };

        template<merge_kind Kind>
        struct merge_kind_t
        {
            static constexpr merge_kind kind = Kind;
        };

        template<merge_kind Kind>
        static constexpr auto merge_wrap = merge_kind_t<Kind>{};
    }

#ifndef BOOST_PARSER_DOXYGEN

    template<
        typename ParserTuple,
        typename BacktrackingTuple,
        typename CombiningGroups>
    struct seq_parser
    {
        using backtracking = BacktrackingTuple;
        using combining_groups = CombiningGroups;

        constexpr seq_parser(ParserTuple parsers) : parsers_(parsers) {}

        static constexpr auto true_ = std::true_type{};
        static constexpr auto false_ = std::false_type{};

        struct combine
        {
            template<typename T, typename U>
            auto operator()(
                T result_merging_indices_and_prev_group, U x_and_group) const
            {
                using namespace literals;
                using detail::merge_wrap;
                using detail::merge_kind;

                auto x = parser::get(x_and_group, 0_c);
                auto group = parser::get(x_and_group, 1_c);

                auto result =
                    parser::get(result_merging_indices_and_prev_group, 0_c);
                using result_back_type =
                    typename std::decay_t<decltype(detail::hl::back(
                        result))>::type;
                using unwrapped_optional_result_back_type =
                    detail::unwrapped_optional_t<result_back_type>;

                auto merging =
                    parser::get(result_merging_indices_and_prev_group, 1_c);
                auto indices =
                    parser::get(result_merging_indices_and_prev_group, 2_c);
                auto prev_group =
                    parser::get(result_merging_indices_and_prev_group, 3_c);

                using x_type = typename decltype(x)::type;
                using unwrapped_optional_x_type =
                    detail::unwrapped_optional_t<x_type>;

                if constexpr (detail::is_nope_v<x_type>) {
                    if constexpr (
                        !detail::is_nope_v<result_back_type> &&
                        0 < decltype(group)::value &&
                        decltype(group)::value != decltype(prev_group)::value) {
                        // T >> merge[nope >> ...] -> nope
                        // This is a very special case.  If we see a nope at
                        // the begining of a group, and there's a non-nope
                        // before it, we put the nope in place in the result
                        // tuple temporarily, knowing that a non-nope will
                        // come along later in the group to replace it.
                        return detail::hl::make_tuple(
                            detail::hl::append(result, x),
                            detail::hl::append(
                                merging,
                                merge_wrap<merge_kind::second_pass_detect>),
                            detail::hl::append(
                                indices, detail::hl::size(result)),
                            group);
                    } else {
                        // T >> nope -> T
                        return detail::hl::make_tuple(
                            result,
                            detail::hl::append(
                                merging,
                                merge_wrap<merge_kind::second_pass_detect>),
                            detail::hl::append(
                                indices, detail::hl::size_minus_one(result)),
                            prev_group);
                    }
                } else if constexpr (detail::is_nope_v<result_back_type>) {
                    // tuple<nope> >> T -> tuple<T>
                    constexpr auto merge =
                        0 < decltype(group)::value
                            ? merge_kind::group
                            : (decltype(group)::value == -1
                                   ? merge_kind::singleton
                                   : merge_kind::second_pass_detect);
                    return detail::hl::make_tuple(
                        detail::hl::append(detail::hl::drop_back(result), x),
                        detail::hl::append(merging, merge_wrap<merge>),
                        detail::hl::append(
                            indices, detail::hl::size_minus_one(result)),
                        group);
                } else if constexpr (0 < decltype(group)::value) {
                    if constexpr (
                        decltype(prev_group)::value == decltype(group)::value) {
                        return detail::hl::make_tuple(
                            result,
                            detail::hl::append(
                                merging, merge_wrap<merge_kind::group>),
                            detail::hl::append(
                                indices, detail::hl::size_minus_one(result)),
                            group);
                    } else {
                        return detail::hl::make_tuple(
                            detail::hl::append(result, x),
                            detail::hl::append(
                                merging, merge_wrap<merge_kind::group>),
                            detail::hl::append(
                                indices, detail::hl::size(result)),
                            group);
                    }
                } else if constexpr (
                    decltype(group)::value == -1 ||
                    decltype(group)::value != decltype(prev_group)::value) {
                    constexpr auto merge = decltype(group)::value == -1
                                               ? merge_kind::singleton
                                               : merge_kind::second_pass_detect;
                    return detail::hl::make_tuple(
                        detail::hl::append(result, x),
                        detail::hl::append(merging, merge_wrap<merge>),
                        detail::hl::append(indices, detail::hl::size(result)),
                        group);
                } else if constexpr (
                    detail::is_char_type_v<result_back_type> &&
                    (detail::is_char_type_v<x_type> ||
                     detail::is_char_type_v<unwrapped_optional_x_type>)) {
                    // CHAR >> CHAR -> string
                    return detail::hl::make_tuple(
                        detail::hl::append(
                            detail::hl::drop_back(result),
                            detail::wrapper<std::string>{}),
                        detail::hl::append(
                            detail::hl::append(
                                detail::hl::drop_front(merging),
                                merge_wrap<merge_kind::second_pass_detect>),
                            merge_wrap<merge_kind::second_pass_detect>),
                        detail::hl::append(
                            indices, detail::hl::size_minus_one(result)),
                        group);
                } else if constexpr (
                    detail::
                        container_and_value_type<result_back_type, x_type> ||
                    detail::container_and_value_type<
                        result_back_type,
                        unwrapped_optional_x_type>) {
                    // C<T> >> T -> C<T>
                    // C<T> >> optional<T> -> C<T>
                    return detail::hl::make_tuple(
                        result,
                        detail::hl::append(
                            merging,
                            merge_wrap<merge_kind::second_pass_detect>),
                        detail::hl::append(
                            indices, detail::hl::size_minus_one(result)),
                        group);
                } else if constexpr (
                    detail::
                        container_and_value_type<x_type, result_back_type> ||
                    detail::container_and_value_type<
                        x_type,
                        unwrapped_optional_result_back_type>) {
                    // T >> C<T> -> C<T>
                    // optional<T> >> C<T> -> C<T>
                    return detail::hl::make_tuple(
                        detail::hl::append(detail::hl::drop_back(result), x),
                        detail::hl::append(
                            detail::hl::append(
                                detail::hl::drop_front(merging),
                                merge_wrap<merge_kind::second_pass_detect>),
                            merge_wrap<merge_kind::second_pass_detect>),
                        detail::hl::append(
                            indices, detail::hl::size_minus_one(result)),
                        group);
                } else {
                    // tuple<Ts...> >> T -> tuple<Ts..., T>
                    return detail::hl::make_tuple(
                        detail::hl::append(result, x),
                        detail::hl::append(
                            merging, merge_wrap<merge_kind::second_pass_detect>),
                        detail::hl::append(indices, detail::hl::size(result)),
                        group);
                }
            }
        };

        struct find_merged
        {
            template<typename T, typename U>
            auto operator()(
                T final_types_and_result, U x_index_and_prev_merged) const
            {
                using namespace literals;
                using detail::merge_wrap;
                using detail::merge_kind;

                auto final_types = parser::get(final_types_and_result, 0_c);
                auto result = parser::get(final_types_and_result, 1_c);

                auto x_type_wrapper = parser::get(x_index_and_prev_merged, 0_c);
                auto index = parser::get(x_index_and_prev_merged, 1_c);
                auto prev_merged = parser::get(x_index_and_prev_merged, 2_c);

                auto type_at_index_wrapper = parser::get(final_types, index);
                using x_type = typename decltype(x_type_wrapper)::type;
                using type_at_index =
                    typename decltype(type_at_index_wrapper)::type;
                if constexpr (
                    decltype(prev_merged)::kind ==
                    merge_kind::second_pass_detect) {
                    if constexpr (
                        !std::is_same_v<x_type, type_at_index> &&
                        container<type_at_index>) {
                        return detail::hl::make_tuple(
                            final_types,
                            detail::hl::append(
                                result, merge_wrap<merge_kind::merged>));
                    } else {
                        return detail::hl::make_tuple(
                            final_types,
                            detail::hl::append(
                                result, merge_wrap<merge_kind::singleton>));
                    }
                } else {
                    return detail::hl::make_tuple(
                        final_types, detail::hl::append(result, prev_merged));
                }
            }
        };

        template<long long I>
        static constexpr auto
        merging_from_group(integral_constant<long long, I>)
        {
            using detail::merge_wrap;
            using detail::merge_kind;
            if constexpr (0 < I)
                return merge_wrap<merge_kind::group>;
            else if constexpr (I == -1)
                return merge_wrap<merge_kind::singleton>;
            else
                return merge_wrap<merge_kind::second_pass_detect>;
        }

        // Returns the tuple of values produced by this parser, and the
        // indices into that tuple that each parser should use in turn.  The
        // case where the tuple only has one element is handled elsewhere.
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto make_temp_result(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            using namespace literals;

            detail::
                dummy_use_parser_t<Iter, Sentinel, Context, SkipParser> const
                    dummy_use_parser(
                        first, last, context, skip, flags, success);

            // A result type for each of the parsers in parsers_.
            using all_types =
                decltype(detail::hl::transform(parsers_, dummy_use_parser));

            // Same as above, wrapped in detail::wrapper.
            using all_types_wrapped =
                decltype(detail::hl::transform(all_types{}, detail::wrap{}));

            using combining_groups =
                detail::combining_t<ParserTuple, CombiningGroups>;
            constexpr auto first_group = detail::hl::front(combining_groups{});

            // Generate a tuple of outputs; the index that each parser should
            // use to write into its output; and whether the attribute for
            // each parser was merged into an adjacent container-attribute.
            constexpr auto combine_start = detail::hl::make_tuple(
                detail::hl::make_tuple(detail::hl::front(all_types_wrapped{})),
                detail::hl::make_tuple(merging_from_group(first_group)),
                tuple<llong<0>>{},
                first_group);
            using combined_types = decltype(detail::hl::fold_left(
                detail::hl::zip(
                    detail::hl::drop_front(all_types_wrapped{}),
                    detail::hl::drop_front(combining_groups{})),
                combine_start,
                combine{}));

            // Unwrap the result tuple's types.
            constexpr auto result_type_wrapped =
                parser::get(combined_types{}, 0_c);
            using result_type = decltype(detail::hl::transform(
                result_type_wrapped, detail::unwrap{}));

            using indices = decltype(parser::get(combined_types{}, 2_c));
            using first_pass_merged =
                decltype(parser::get(combined_types{}, 1_c));

            constexpr auto find_merged_start =
                detail::hl::make_tuple(result_type_wrapped, tuple<>{});
            using merged = decltype(detail::hl::fold_left(
                detail::hl::zip(
                    all_types_wrapped{}, indices{}, first_pass_merged{}),
                find_merged_start,
                find_merged{}));

            return detail::hl::make_tuple(
                result_type{}, indices{}, parser::get(merged{}, 1_c));
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first_,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            Iter first = first_;

            auto temp_result =
                make_temp_result(first, last, context, skip, flags, success);

            std::decay_t<decltype(parser::get(temp_result, llong<0>{}))>
                retval{};

            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this,
                first_,
                last,
                context,
                detail::in_apply_parser(flags) ? detail::disable_trace(flags)
                                               : flags,
                retval);

            std::decay_t<decltype(parser::get(temp_result, llong<1>{}))>
                indices;
            std::decay_t<decltype(parser::get(temp_result, llong<2>{}))>
                merged;
            call_impl(
                first,
                last,
                context,
                skip,
                flags,
                success,
                retval,
                indices,
                merged);

            if (success)
                first_ = first;

            // A 1-tuple is converted to a scalar.
            if constexpr (detail::hl::size(retval) == llong<1>{}) {
                using namespace literals;
                return parser::get(retval, 0_c);
            } else {
                return retval;
            }
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first_,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this,
                first_,
                last,
                context,
                detail::in_apply_parser(flags) ? detail::disable_trace(flags)
                                               : flags,
                retval);

            Iter first = first_;

            auto temp_result =
                make_temp_result(first, last, context, skip, flags, success);
            using temp_result_attr_t =
                std::decay_t<decltype(parser::get(temp_result, llong<0>{}))>;
            std::decay_t<decltype(parser::get(temp_result, llong<1>{}))>
                indices;
            std::decay_t<decltype(parser::get(temp_result, llong<2>{}))> merged;

            auto max_ = [](auto result, auto x) {
                if constexpr (decltype(result)::value < decltype(x)::value) {
                    return x;
                } else {
                    return result;
                }
            };
            using max_index_t =
                decltype(detail::hl::fold_left(indices, llong<0>{}, max_));

            if constexpr (detail::is_optional_v<Attribute>) {
                typename Attribute::value_type attr;
                call(first, last, context, skip, flags, success, attr);
                if (success)
                    detail::assign(retval, std::move(attr));
            } else if constexpr (
                detail::is_tuple<Attribute>{} ||
                detail::is_struct_compatible_v<Attribute, temp_result_attr_t>) {
                call_impl(
                    first,
                    last,
                    context,
                    skip,
                    flags,
                    success,
                    retval,
                    indices,
                    merged);

                if (!success)
                    detail::assign(retval, Attribute());
            } else if constexpr (
                0 < max_index_t::value && detail::is_constructible_from_tuple_v<
                                              Attribute,
                                              temp_result_attr_t>) {
                temp_result_attr_t temp_retval{};
                call_impl(
                    first,
                    last,
                    context,
                    skip,
                    flags,
                    success,
                    temp_retval,
                    indices,
                    merged);

                if (success && detail::gen_attrs(flags)) {
                    detail::assign(
                        retval,
                        detail::make_from_tuple<Attribute>(
                            std::move(temp_retval)));
                }
            } else {
                // call_impl requires a tuple, so we must wrap this scalar.
                tuple<Attribute> temp_retval{};
                call_impl(
                    first,
                    last,
                    context,
                    skip,
                    flags,
                    success,
                    temp_retval,
                    indices,
                    merged);

                if (success && detail::gen_attrs(flags)) {
                    detail::assign(
                        retval, std::move(detail::hl::front(temp_retval)));
                }
            }

            if (success)
                first_ = first;
        }

        // Invokes each parser, placing the resulting values (if any) into
        // retval, using the index mapping in indices.  The case of a tuple
        // containing only a single value is handled elsewhere.
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute,
            typename Indices,
            typename Merged>
        void call_impl(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval,
            Indices const & indices,
            Merged const & merged) const
        {
            using detail::merge_wrap;
            using detail::merge_kind;

            static_assert(
                detail::is_tuple<Attribute>{} || std::is_aggregate_v<Attribute>,
                "");

            auto use_parser = [&first,
                               last,
                               &context,
                               &skip,
                               flags_ = flags,
                               &success,
                               &retval](auto const &
                                            parser_index_merged_and_backtrack) {
                auto flags = flags_;
                using namespace literals;
                detail::skip(first, last, skip, flags);
                if (!success) // Someone earlier already failed...
                    return;

                auto const & parser =
                    parser::get(parser_index_merged_and_backtrack, 0_c);
                auto merge_kind_t_ =
                    parser::get(parser_index_merged_and_backtrack, 2_c);
                constexpr bool was_merged_into_adjacent_container =
                    decltype(merge_kind_t_)::kind == merge_kind::merged;
                constexpr bool is_in_a_group =
                    decltype(merge_kind_t_)::kind == merge_kind::group;
                bool const can_backtrack =
                    parser::get(parser_index_merged_and_backtrack, 3_c);

                if (!detail::gen_attrs(flags)) {
                    parser.call(first, last, context, skip, flags, success);
                    if (!success && !can_backtrack) {
                        std::stringstream oss;
                        detail::print_parser(context, parser, oss);
                        throw parse_error<Iter>(first, oss.str());
                    }
                    return;
                }

                auto const tuple_idx =
                    parser::get(parser_index_merged_and_backtrack, 1_c);
                auto const tuple_size = detail::tuple_or_struct_size(retval);
                static_assert(
                    decltype(tuple_idx)::value < decltype(tuple_size)::value,
                    "Looks like you're trying to write some attribute into an "
                    "out-of-bounds position in a tuple/struct.  In other "
                    "words, the attribute you're parsing into does not match "
                    "the default attribute used by this parser.  This may be "
                    "because you passed an out-param to parse() at the top "
                    "level that is not compatible with the attribute type "
                    "generated by the parser you passed to parse().");
                if constexpr (!(decltype(tuple_idx)::value <
                                decltype(tuple_size)::value)) {
                    [[maybe_unused]] detail::print_type<Attribute> _;
                }
                auto & out = parser::get(retval, tuple_idx);

                using attr_t = decltype(parser.call(
                    first, last, context, skip, flags, success));
                constexpr bool out_container =
                    container<std::decay_t<decltype(out)>>;
                constexpr bool attr_container = container<attr_t>;

                if constexpr (
                    (out_container == attr_container &&
                     !was_merged_into_adjacent_container) ||
                    is_in_a_group) {
                    parser.call(
                        first, last, context, skip, flags, success, out);
                    if (!success) {
                        if (!can_backtrack) {
                            std::stringstream oss;
                            detail::print_parser(context, parser, oss);
                            throw parse_error<Iter>(first, oss.str());
                        }
                        out = std::decay_t<decltype(out)>();
                        return;
                    }
                } else {
                    attr_t x =
                        parser.call(first, last, context, skip, flags, success);
                    if (!success) {
                        if (!can_backtrack) {
                            std::stringstream oss;
                            detail::print_parser(context, parser, oss);
                            throw parse_error<Iter>(first, oss.str());
                        }
                        return;
                    }
                    using just_x = attr_t;
                    using just_out = detail::remove_cv_ref_t<decltype(out)>;
                    if constexpr (
                        (!out_container ||
                         !std::is_same_v<just_x, just_out>) &&
                        std::is_assignable_v<just_out &, just_x &&> &&
                        (!std::is_same_v<just_out, std::string> ||
                         !std::is_integral_v<just_x>)) {
                        detail::assign(out, std::move(x));
                    } else {
                        detail::move_back(
                            out, std::move(x), detail::gen_attrs(flags));
                    }
                }
            };

            auto const parsers_and_indices =
                detail::hl::zip(parsers_, indices, merged, backtracking{});
            detail::hl::for_each(parsers_and_indices, use_parser);
        }

        template<bool AllowBacktracking, typename Parser>
        constexpr auto prepend(parser_interface<Parser> parser) const noexcept;
        template<bool AllowBacktracking, typename Parser>
        constexpr auto append(parser_interface<Parser> parser) const noexcept;

        ParserTuple parsers_;
    };

#endif

    namespace detail {
        // clang-format off
        template<typename Action, typename Attribute>
        using action_direct_call_expr =
            decltype(std::declval<Action>()(std::declval<Attribute>()));
        template<typename Action, typename Attribute>
        using action_apply_call_expr =
            decltype(hl::apply(std::declval<Action>(), std::declval<Attribute>()));
        template<typename Action, typename Attribute, typename Context>
        using action_assignable_to_val_direct_expr =
            decltype(_val(std::declval<Context>()) =
                     std::declval<Action>()(std::declval<Attribute>()));
        template<typename Action, typename Attribute, typename Context>
        using action_assignable_to_val_apply_expr =
            decltype(_val(std::declval<Context>()) =
                     hl::apply(std::declval<Action>(), std::declval<Attribute>()));
        // clang-format on

        template<typename Action, typename Attribute, typename Context>
        constexpr auto action_assignable_to_val_direct()
        {
            if constexpr (is_nope_v<decltype(*std::declval<Context>().val_)>) {
                return false;
            } else if constexpr (!is_detected_v<
                                     action_direct_call_expr,
                                     Action,
                                     Attribute>) {
                return false;
            } else if constexpr (std::is_same_v<
                                     action_direct_call_expr<Action, Attribute>,
                                     void>) {
                return false;
            } else {
                return is_detected_v<
                    action_assignable_to_val_direct_expr,
                    Action,
                    Attribute,
                    Context>;
            }
        }

        template<typename Action, typename Attribute, typename Context>
        constexpr auto action_assignable_to_val_apply()
        {
            if constexpr (is_nope_v<decltype(*std::declval<Context>().val_)>) {
                return false;
            } else if constexpr (!is_tuple<remove_cv_ref_t<Attribute>>{}) {
                return false;
            } else if constexpr (tuple_size_<remove_cv_ref_t<Attribute>> < 2) {
                return false;
            } else if constexpr (!is_detected_v<
                                     action_apply_call_expr,
                                     Action,
                                     Attribute>) {
                return false;
            } else if constexpr (std::is_same_v<
                                     action_apply_call_expr<Action, Attribute>,
                                     void>) {
                return false;
            } else {
                return is_detected_v<
                    action_assignable_to_val_apply_expr,
                    Action,
                    Attribute,
                    Context>;
            }
        }

        template<typename Context, typename TagType>
        constexpr bool in_recursion =
            std::is_same_v<typename Context::rule_tag, TagType> &&
            !std::is_same_v<typename Context::rule_tag, void>;
    }

#ifndef BOOST_PARSER_DOXYGEN

    template<typename Parser, typename Action>
    struct action_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        detail::nope call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            detail::nope retval;
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            auto const initial_first = first;
            auto attr = parser_.call(
                first,
                last,
                context,
                skip,
                detail::enable_attrs(flags),
                success);

            if (!success)
                return;

            if constexpr (detail::action_assignable_to_val_apply<
                              decltype(action_) &,
                              decltype(attr),
                              decltype(context)>()) {
                _val(context) = detail::hl::apply(action_, std::move(attr));
            } else {
                BOOST_PARSER_SUBRANGE const where(initial_first, first);
                auto const action_context =
                    detail::make_action_context(context, attr, where);
                if constexpr (detail::action_assignable_to_val_direct<
                                  decltype(action_) &,
                                  decltype(action_context) &,
                                  decltype(action_context) &>()) {
                    _val(action_context) = action_(action_context);
                } else if constexpr (std::is_same_v<
                                         decltype(action_(action_context)),
                                         void>) {
                    action_(action_context);
                } else {
                    // If you see an error here, it's because you are using an
                    // invocable for a semantic action that returns a non-void
                    // type Ret, but values fo type Ret is not assignable to
                    // _val(ctx).  To fix this, only use this invocable within
                    // a rule whose attribute type is assignable from Ret, or
                    // remove the non-void return statement(s) from your
                    // invocable.
                    [[maybe_unused]] none n = action_(action_context);
                }
            }
        }

        Parser parser_;
        Action action_;
    };

    template<typename Parser, typename F>
    struct transform_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, detail::global_nope);
            auto attr =
                parser_.call(first, last, context, skip, flags, success);
            if (success && detail::gen_attrs(flags))
                return f_(std::move(attr));
            else
                return decltype(f_(std::move(attr))){};
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);
            auto attr =
                parser_.call(first, last, context, skip, flags, success);
            if (success && detail::gen_attrs(flags))
                detail::assign(retval, f_(std::move(attr)));
        }

        Parser parser_;
        F f_;
    };

    template<typename Parser>
    struct omit_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        detail::nope call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, detail::global_nope);

            parser_.call(
                first,
                last,
                context,
                skip,
                detail::disable_attrs(flags),
                success);
            return {};
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            parser_.call(
                first,
                last,
                context,
                skip,
                detail::disable_attrs(flags),
                success);
        }

        Parser parser_;
    };

    template<typename Parser>
    struct raw_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        BOOST_PARSER_SUBRANGE<Iter> call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            BOOST_PARSER_SUBRANGE<Iter> retval;
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            auto const initial_first = first;
            parser_.call(
                first,
                last,
                context,
                skip,
                detail::disable_attrs(flags),
                success);
            if (success && detail::gen_attrs(flags))
                detail::assign(
                    retval, BOOST_PARSER_SUBRANGE<Iter>(initial_first, first));
        }

        Parser parser_;
    };

#if BOOST_PARSER_USE_CONCEPTS
    template<typename Parser>
    struct string_view_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            auto r =
                parser::detail::text::unpack_iterator_and_sentinel(first, last);
            static_assert(
                std::contiguous_iterator<decltype(r.first)>,
                "string_view_parser and the string_view[] directive that uses "
                "it requires that the underlying char sequence being parsed be "
                "a contiguous range.  If you're seeing this static_assert, you "
                "have not met this contract.");
            using char_type = detail::remove_cv_ref_t<decltype(*r.first)>;
            std::basic_string_view<char_type> retval;
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            auto const initial_first = first;
            parser_.call(
                first,
                last,
                context,
                skip,
                detail::disable_attrs(flags),
                success);

            if (!success || !detail::gen_attrs(flags))
                return;

            auto r = parser::detail::text::unpack_iterator_and_sentinel(
                initial_first, first);
            static_assert(
                std::contiguous_iterator<decltype(r.first)>,
                "string_view_parser and the string_view[] directive that uses "
                "it requires that the underlying char sequence being parsed be "
                "a contiguous range.  If you're seeing this static_assert, you "
                "have not met this contract.");
            using char_type = detail::remove_cv_ref_t<decltype(*r.first)>;
            if (initial_first == first) {
                detail::assign(retval, std::basic_string_view<char_type>{});
            } else {
                detail::assign(
                    retval,
                    std::basic_string_view<char_type>{
                        &*r.first, std::size_t(r.last - r.first)});
            }
        }

        Parser parser_;
    };
#endif

    template<typename Parser>
    struct lexeme_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            using attr_t = decltype(parser_.call(
                first, last, context, skip, flags, success));
            attr_t retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            parser_.call(
                first,
                last,
                context,
                skip,
                detail::disable_skip(flags),
                success,
                retval);
        }

        Parser parser_;
    };

    template<typename Parser>
    struct no_case_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context_,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            auto context = context_;
            ++context.no_case_depth_;

            using attr_t = decltype(parser_.call(
                first, last, context, skip, flags, success));
            attr_t retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context_,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            auto context = context_;
            ++context.no_case_depth_;

            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            parser_.call(first, last, context, skip, flags, success, retval);
        }

        Parser parser_;
    };

    template<typename Parser, typename SkipParser>
    struct skip_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser_>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser_ const & skip,
            detail::flags flags,
            bool & success) const
        {
            using attr_t = decltype(parser_.call(
                first, last, context, skip, flags, success));
            attr_t retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser_,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser_ const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            if constexpr (detail::is_nope_v<SkipParser>) {
                parser_.call(
                    first,
                    last,
                    context,
                    skip,
                    detail::enable_skip(flags),
                    success,
                    retval);
            } else {
                parser_.call(
                    first,
                    last,
                    context,
                    skip_parser_,
                    detail::enable_skip(flags),
                    success,
                    retval);
            }
        }

        Parser parser_;
        SkipParser skip_parser_;
    };

    template<typename Parser, bool FailOnMatch>
    struct expect_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        detail::nope call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            detail::nope retval;
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            auto first_copy = first;
            parser_.call(
                first_copy,
                last,
                context,
                skip,
                detail::disable_attrs(flags),
                success);
            if (FailOnMatch)
                success = !success;
        }

        Parser parser_;
    };

    template<typename T>
    struct symbol_parser
    {
        symbol_parser() : copied_from_(nullptr) {}
        explicit symbol_parser(std::string_view diagnostic_text) :
            copied_from_(nullptr), diagnostic_text_(diagnostic_text)
        {}
        symbol_parser(symbol_parser const & other) :
            initial_elements_(other.initial_elements_),
            copied_from_(other.copied_from_ ? other.copied_from_ : &other),
            diagnostic_text_(other.diagnostic_text_)
        {}
        symbol_parser(symbol_parser && other) :
            initial_elements_(std::move(other.initial_elements_)),
            copied_from_(other.copied_from_),
            diagnostic_text_(other.diagnostic_text_)
        {}

        /** Inserts an entry consisting of a UTF-8 string `str` to match, and
            an associated attribute `x`, to `*this`.  The entry is added for
            use in all subsequent top-level parses.  Subsequent lookups during
            the current top-level parse will not necessarily match `str`. */
        template<typename Context>
        void insert_for_next_parse(
            Context const & context, std::string_view str, T x)
        {
            auto & pending_ops =
                detail::get_pending_symtab_ops(context, ref());
            pending_ops.push_back(detail::symbol_table_operation<T>{
                std::string(str),
                std::move(x),
                detail::symbol_table_op::insert});
        }

        /** Erases the entry whose UTF-8 match string is `str`, from `*this`.
            The entry will no longer be available for use in all subsequent
            top-level parses.  `str` will not be removed from the symbols
            matched in the current top-level parse. */
        template<typename Context>
        void erase_for_next_parse(Context const & context, std::string_view str)
        {
            auto & pending_ops =
                detail::get_pending_symtab_ops(context, ref());
            pending_ops.push_back(detail::symbol_table_operation<T>{
                std::string(str),
                std::nullopt,
                detail::symbol_table_op::erase});
        }

        /** Erases all the entries from the copy of the symbol table inside
            the parse context `context`. */
        template<typename Context>
        void clear_for_next_parse(Context const & context)
        {
            auto & pending_ops =
                detail::get_pending_symtab_ops(context, ref());
            pending_ops.push_back(detail::symbol_table_operation<T>{
                {}, std::nullopt, detail::symbol_table_op::clear});
        }

        /** Uses UTF-8 string `str` to look up an attribute in the table
            during parsing, returning it as an optional reference.  The lookup
            is done on the copy of the symbol table inside the parse context
            `context`. */
        template<typename Context>
        parser::detail::text::optional_ref<T>
        find(Context const & context, std::string_view str) const
        {
            auto [trie, has_case_folded] = detail::get_trie(context, ref());
            if (context.no_case_depth_) {
                return trie[detail::case_fold_view(
                    str | detail::text::as_utf32)];
            } else {
                return trie[str | detail::text::as_utf32];
            }
        }

        /** Inserts an entry consisting of a UTF-8 string `str` to match, and
            an associtated attribute `x`, to the copy of the symbol table
            inside the parse context `context`. */
        template<typename Context>
        void insert(Context const & context, std::string_view str, T && x) const
        {
            auto [trie, has_case_folded] = detail::get_trie(context, ref());
            if (context.no_case_depth_) {
                trie.insert(
                    detail::case_fold_view(str | detail::text::as_utf32),
                    std::move(x));
            } else {
                trie.insert(str | detail::text::as_utf32, std::move(x));
            }
        }

        /** Erases the entry whose UTF-8 match string is `str` from the copy
            of the symbol table inside the parse context `context`. */
        template<typename Context>
        void erase(Context const & context, std::string_view str) const
        {
            auto [trie, has_case_folded] = detail::get_trie(context, ref());
            if (context.no_case_depth_) {
                trie.erase(
                    detail::case_fold_view(str | detail::text::as_utf32));
            } else {
                trie.erase(str | detail::text::as_utf32);
            }
        }

        /** Erases the entry whose UTF-8 match string is `str` from the copy
            of the symbol table inside the parse context `context`. */
        template<typename Context>
        void clear(Context const & context) const
        {
            auto [trie, _] = detail::get_trie(context, ref());
            trie.clear();
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        T call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            T retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            auto [trie, _0] = detail::get_trie(context, ref());
            auto const lookup = context.no_case_depth_
                                    ? trie.longest_match(detail::case_fold_view(
                                          BOOST_PARSER_SUBRANGE(first, last)))
                                    : trie.longest_match(first, last);
            if (lookup.match) {
                std::advance(first, lookup.size);
                detail::assign(retval, T{*trie[lookup]});
            } else {
                success = false;
            }
        }

        mutable std::vector<std::pair<std::string, T>> initial_elements_;
        symbol_parser const * copied_from_;

        symbol_parser const & ref() const noexcept
        {
            if (copied_from_)
                return *copied_from_;
            return *this;
        }
        std::vector<std::pair<std::string, T>> &
        initial_elements() const noexcept
        {
            return ref().initial_elements_;
        }

        std::string_view diagnostic_text_;
    };

    template<
        bool CanUseCallbacks,
        typename TagType,
        typename Attribute,
        typename LocalState,
        typename ParamsTuple>
    struct rule_parser
    {
        using tag_type = TagType;
        using attr_type = Attribute;
        using locals_type = LocalState;

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        std::conditional_t<
            detail::in_recursion<Context, tag_type>,
            detail::nope,
            attr_type>
        call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            constexpr bool in_recursion =
                detail::in_recursion<Context, tag_type>;

            if constexpr (in_recursion)
                flags = detail::disable_attrs(flags);

            attr_type retval{};
            locals_type locals = detail::make_locals<locals_type>(context);
            auto params = detail::resolve_rule_params(context, params_);
            tag_type * const tag_ptr = nullptr;
            auto const rule_context = detail::make_rule_context(
                context, tag_ptr, retval, locals, params);
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, rule_context, flags, retval);

            bool dont_assign = false;
            if constexpr (in_recursion) {
                // We have to use this out-arg overload for iterations >= 1 in
                // recursive rules, since every iteration past the first is
                // defined to return nope.
                parse_rule(
                    tag_ptr,
                    first,
                    last,
                    rule_context,
                    skip,
                    flags,
                    success,
                    dont_assign,
                    retval);
            } else {
                auto attr = parse_rule(
                    tag_ptr,
                    first,
                    last,
                    rule_context,
                    skip,
                    flags,
                    success,
                    dont_assign);
                if (success && !dont_assign) {
                    if constexpr (!detail::is_nope_v<decltype(attr)>)
                        detail::assign(retval, attr);
                }
            }

            if constexpr (
                CanUseCallbacks && Context::use_callbacks && !in_recursion) {
                if (!success)
                    return attr_type{};

                auto const & callbacks = _callbacks(context);

                if constexpr (detail::is_nope_v<attr_type>) {
                    static_assert(
                        detail::is_invocable_v<decltype(callbacks), tag_type>,
                        "For rules without attributes, Callbacks must be a "
                        "struct with overloads of the form void(tag_type).  If "
                        "you're seeing an error here, you probably have not "
                        "met this contract.");
                    callbacks(tag_type{});
                } else {
                    static_assert(
                        detail::is_invocable_v<
                            decltype(callbacks),
                            tag_type,
                            decltype(std::move(retval))>,
                        "For rules with attributes, Callbacks must be a struct "
                        "with overloads of the form void(tag_type, attr_type). "
                        "If you're seeing an error here, you probably have not "
                        "met this contract.");
                    callbacks(tag_type{}, std::move(retval));
                }

                return attr_type{};
            } else {
                if (!success && !in_recursion)
                    detail::assign(retval, attr_type());
                if constexpr (in_recursion)
                    return detail::nope{};
                else
                    return retval;
            }
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute_>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute_ & retval) const
        {
            if constexpr (CanUseCallbacks && Context::use_callbacks) {
                call(first, last, context, skip, flags, success);
            } else {
                auto attr = call(first, last, context, skip, flags, success);
                if (success)
                    detail::assign(retval, std::move(attr));
            }
        }

        std::string_view diagnostic_text_;
        ParamsTuple params_;
    };

#endif

    // Parser interface.

    template<typename Parser, typename GlobalState, typename ErrorHandler>
    struct parser_interface
    {
        using parser_type = Parser;
        using global_state_type = GlobalState;
        using error_handler_type = ErrorHandler;

        constexpr parser_interface() : parser_() {}
        constexpr parser_interface(parser_type p) : parser_(std::move(p)) {}
        constexpr parser_interface(
            parser_type p, global_state_type gs, error_handler_type eh) :
            parser_(p), globals_(gs), error_handler_(eh)
        {}

        /** Returns a `parser_interface` containing a parser equivalent to an
            `expect_parser` containing `parser_`, with `FailOnMatch ==
            true`. */
        constexpr auto operator!() const noexcept
        {
            return parser::parser_interface{
                expect_parser<parser_type, true>{parser_}};
        }

        /** Returns a `parser_interface` containing a parser equivalent to an
            `expect_parser` containing `parser_`, with `FailOnMatch ==
            false`. */
        constexpr auto operator&() const noexcept
        {
            return parser::parser_interface{
                expect_parser<parser_type, false>{parser_}};
        }

        /** Returns a `parser_interface` containing a parser equivalent to a
            `zero_plus_parser` containing `parser_`. */
        constexpr auto operator*() const noexcept
        {
            if constexpr (detail::is_zero_plus_p<parser_type>{}) {
                return *this;
            } else if constexpr (detail::is_one_plus_p<parser_type>{}) {
                using inner_parser = decltype(parser_type::parser_);
                return parser::parser_interface{
                    zero_plus_parser<inner_parser>(parser_.parser_)};
            } else {
                return parser::parser_interface{
                    zero_plus_parser<parser_type>(parser_)};
            }
        }

        /** Returns a `parser_interface` containing a parser equivalent to a
            `one_plus_parser` containing `parser_`. */
        constexpr auto operator+() const noexcept
        {
            if constexpr (detail::is_zero_plus_p<parser_type>{}) {
                using inner_parser = decltype(parser_type::parser_);
                return parser::parser_interface{
                    zero_plus_parser<inner_parser>(parser_.parser_)};
            } else if constexpr (detail::is_one_plus_p<parser_type>{}) {
                return *this;
            } else {
                return parser::parser_interface{
                    one_plus_parser<parser_type>(parser_)};
            }
        }

        /** Returns a `parser_interface` containing a parser equivalent to a
            `opt_parser` containing `parser_`. */
        constexpr auto operator-() const noexcept
        {
            return parser::parser_interface{opt_parser<parser_type>{parser_}};
        }

        /** Returns a `parser_interface` containing a parser equivalent to a
            `seq_parser` containing `parser_` followed by `rhs.parser_`. */
        template<typename ParserType2>
        constexpr auto
        operator>>(parser_interface<ParserType2> rhs) const noexcept
        {
            if constexpr (detail::is_seq_p<parser_type>{}) {
                return parser_.template append<true>(rhs);
            } else if constexpr (detail::is_seq_p<ParserType2>{}) {
                return rhs.parser_.template prepend<true>(*this);
            } else {
                using parser_t = seq_parser<
                    tuple<parser_type, ParserType2>,
                    tuple<std::true_type, std::true_type>,
                    tuple<llong<0>, llong<0>>>;
                return parser::parser_interface{parser_t{
                    tuple<parser_type, ParserType2>{parser_, rhs.parser_}}};
            }
        }

        /** Returns a `parser_interface` containing a parser equivalent to a
            `seq_parser` containing `parser_` followed by `lit(rhs)`. */
        constexpr auto operator>>(char rhs) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to a
            `seq_parser` containing `parser_` followed by `lit(rhs)`. */
        constexpr auto operator>>(char32_t rhs) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to a
            `seq_parser` containing `parser_` followed by `lit(rhs)`. */
#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R>
#else
        template<
            typename R,
            typename Enable =
                std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
        constexpr auto operator>>(R && r) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to a
            `seq_parser` containing `parser_` followed by `rhs.parser_`.  No
            back-tracking is allowed after `parser_` succeeds; if
            `rhs.parser_` fails after `parser_` succeeds, the top-level parse
            fails. */
        template<typename ParserType2>
        constexpr auto
        operator>(parser_interface<ParserType2> rhs) const noexcept
        {
            if constexpr (detail::is_seq_p<parser_type>{}) {
                return parser_.template append<false>(rhs);
            } else if constexpr (detail::is_seq_p<ParserType2>{}) {
                return rhs.parser_.template prepend<false>(*this);
            } else {
                using parser_t = seq_parser<
                    tuple<parser_type, ParserType2>,
                    tuple<std::true_type, std::false_type>,
                    tuple<llong<0>, llong<0>>>;
                return parser::parser_interface{parser_t{
                    tuple<parser_type, ParserType2>{parser_, rhs.parser_}}};
            }
        }

        /** Returns a `parser_interface` containing a parser equivalent to a
            `seq_parser` containing `parser_` followed by `lit(rhs)`.  No
            back-tracking is allowed after `parser_` succeeds; if `lit(rhs)`
            fails after `parser_` succeeds, the top-level parse fails. */
        constexpr auto operator>(char rhs) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to a
            `seq_parser` containing `parser_` followed by `lit(rhs)`.  No
            back-tracking is allowed after `parser_` succeeds; if `lit(rhs)`
            fails after `parser_` succeeds, the top-level parse fails. */
        constexpr auto operator>(char32_t rhs) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to a
            `seq_parser` containing `parser_` followed by `lit(rhs)`.  No
            back-tracking is allowed after `parser_` succeeds; if `lit(rhs)`
            fails after `parser_` succeeds, the top-level parse fails. */
#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R>
#else
        template<
            typename R,
            typename Enable =
                std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
        constexpr auto operator>(R && r) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to an
            `or_parser` containing `parser_` followed by `rhs.parser_`. */
        template<typename ParserType2>
        constexpr auto
        operator|(parser_interface<ParserType2> rhs) const noexcept
        {
            if constexpr (detail::is_or_p<parser_type>{}) {
                return parser_.append(rhs);
            } else if constexpr (detail::is_or_p<ParserType2>{}) {
                return rhs.parser_.prepend(*this);
            } else {
                // If you're seeing this as a compile- or run-time failure,
                // you've tried to put an eps parser at the beginning of an
                // alternative-parser, such as "eps | int_".  This is not what
                // you meant.  Since eps always matches any input, "eps |
                // int_" is just an awkward spelling for "eps".  To fix this
                // this, put the eps as the last alternative, so the other
                // alternatives get a chance.  Possibly, you may have meant to
                // add a condition to the eps, like "eps(condition) | int_",
                // which also is meaningful, and so is allowed.
                BOOST_PARSER_ASSERT(
                    !detail::is_unconditional_eps<parser_type>{});
                return parser::parser_interface{
                    or_parser<tuple<parser_type, ParserType2>>{
                        tuple<parser_type, ParserType2>{parser_, rhs.parser_}}};
            }
        }

        /** Returns a `parser_interface` containing a parser equivalent to a
            `perm_parser` containing `parser_` followed by `rhs.parser_`.  It
            is an error to use `eps` (conditional or not) with this
            operator. */
        template<typename ParserType2>
        constexpr auto
        operator||(parser_interface<ParserType2> rhs) const noexcept
        {
            // If you're seeing this as a compile- or run-time failure, you've
            // tried to put an eps parser in a permutation-parser, such as
            // "eps || int_".
            BOOST_PARSER_ASSERT(!detail::is_eps_p<parser_type>{});
            BOOST_PARSER_ASSERT(!detail::is_eps_p<ParserType2>{});
            if constexpr (detail::is_perm_p<parser_type>{}) {
                return parser_.append(rhs);
            } else if constexpr (detail::is_perm_p<ParserType2>{}) {
                return rhs.parser_.prepend(*this);
            } else {
                return parser::parser_interface{
                    perm_parser<tuple<parser_type, ParserType2>>{
                        tuple<parser_type, ParserType2>{parser_, rhs.parser_}}};
            }
        }

        /** Returns a `parser_interface` containing a parser equivalent to an
            `or_parser` containing `parser_` followed by `lit(rhs)`. */
        constexpr auto operator|(char rhs) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to an
            `or_parser` containing `parser_` followed by `lit(rhs)`. */
        constexpr auto operator|(char32_t rhs) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to an
            `or_parser` containing `parser_` followed by `lit(rhs)`. */
#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R>
#else
        template<
            typename R,
            typename Enable =
                std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
        constexpr auto operator|(R && r) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to
            `!rhs >> *this`. */
        template<typename ParserType2>
        constexpr auto
        operator-(parser_interface<ParserType2> rhs) const noexcept
        {
            return !rhs >> *this;
        }

        /** Returns a `parser_interface` containing a parser equivalent to
            `!lit(rhs) >> *this`. */
        constexpr auto operator-(char rhs) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to
            `!lit(rhs) >> *this`. */
        constexpr auto operator-(char32_t rhs) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to
            `!lit(rhs) >> *this`. */
#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R>
#else
        template<
            typename R,
            typename Enable =
                std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
        constexpr auto operator-(R && r) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to an
           `delimited_seq_parser` containing `parser_` and `rhs.parser_`. */
        template<typename ParserType2>
        constexpr auto
        operator%(parser_interface<ParserType2> rhs) const noexcept
        {
            return parser::parser_interface{
                delimited_seq_parser<parser_type, ParserType2>(
                    parser_, rhs.parser_)};
        }

        /** Returns a `parser_interface` containing a parser equivalent to an
           `delimited_seq_parser` containing `parser_` and `lit(rhs)`. */
        constexpr auto operator%(char rhs) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to an
           `delimited_seq_parser` containing `parser_` and `lit(rhs)`. */
        constexpr auto operator%(char32_t rhs) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to an
           `delimited_seq_parser` containing `parser_` and `lit(rhs)`. */
#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R>
#else
        template<
            typename R,
            typename Enable =
                std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
        constexpr auto operator%(R && r) const noexcept;

        /** Returns a `parser_interface` containing a parser equivalent to an
           `action_parser` containing `parser_`, with semantic action
           `action`. */
        template<typename Action>
        constexpr auto operator[](Action action) const
        {
            using action_parser_t = action_parser<parser_type, Action>;
            return parser::parser_interface{action_parser_t{parser_, action}};
        }

        /** Returns `parser_((Arg &&)arg, (Args &&)args...)`.  This is useful
            for those parsers that have `operator()` overloads,
            e.g. `char_('x')`.  By convention, parsers' `operator()`s return
            `parser_interface`s.

            This function does not participate in overload resolution unless
            `parser_((Arg &&)arg, (Args &&)args...)` is well-formed. */
        template<typename Arg, typename... Args>
        constexpr auto operator()(Arg && arg, Args &&... args) const noexcept
            -> decltype(std::declval<parser_type const &>()(
                (Arg &&) arg, (Args &&) args...))
        {
            return parser_((Arg &&) arg, (Args &&) args...);
        }

#ifndef BOOST_PARSER_DOXYGEN

        /** Applies `parser_`, returning the parsed attribute, if any, unless
            the attribute is reported via callback. */
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParserType>
        auto operator()(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParserType const & skip,
            detail::flags flags,
            bool & success) const
        {
            return parser_.call(first, last, context, skip, flags, success);
        }

        /** Applies `parser_`, assiging the parsed attribute, if any, to
            `attr`, unless the attribute is reported via callback. */
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParserType,
            typename Attribute>
        void operator()(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParserType const & skip,
            detail::flags flags,
            bool & success,
            Attribute & attr) const
        {
            parser_.call(first, last, context, skip, flags, success, attr);
        }

        parser_type parser_;
        global_state_type globals_;
        error_handler_type error_handler_;

#endif

        using parser_interface_derivation_tag = int;
    };

    /** Returns a `parser_interface` with the same parser and error handler,
        with `globals` added.  The resut of passing any non-top-level parser
        for the `parser` argument is undefined. */
    template<typename Parser, typename GlobalState, typename ErrorHandler>
    auto with_globals(
        parser_interface<Parser, detail::nope, ErrorHandler> const & parser,
        GlobalState & globals)
    {
        return parser_interface<Parser, GlobalState &, ErrorHandler>{
            parser.parser_, globals, parser.error_handler_};
    }

    /** Returns a `parser_interface` with the same parser and globals, with
        `error_handler` added.  The resut of passing any non-top-level parser
        for the `parser` argument is undefined. */
    template<typename Parser, typename GlobalState, typename ErrorHandler>
    auto with_error_handler(
        parser_interface<Parser, GlobalState, default_error_handler> const &
            parser,
        ErrorHandler & error_handler)
    {
        return parser_interface<Parser, GlobalState, ErrorHandler &>{
            parser.parser_, parser.globals_, error_handler};
    }


    /** A `symbols<T>` represents the initial state of a symbol table parser
        that produces attributes of type `T`.  The entries in the symbol table
        can be changed during parsing, but those mutations to not affect the
        `symbols<T>` object itself; all mutations happen to a copy of the
        symbol table in the parse context.  For table entries that should be
        used during every parse, add entries via `add()` or `operator()`.  For
        mid-parse mutations, use `insert()` and `erase()`. */
    template<typename T>
    struct symbols : parser_interface<symbol_parser<T>>
    {
        symbols() {}
        symbols(char const * diagnostic_text) :
            parser_interface<symbol_parser<T>>(
                symbol_parser<T>(diagnostic_text))
        {}
        symbols(std::initializer_list<std::pair<std::string_view, T>> il)
        {
            this->parser_.initial_elements_.resize(il.size());
            std::copy(il.begin(), il.end(),
                      this->parser_.initial_elements_.begin());
        }
        symbols(
            char const * diagnostic_text,
            std::initializer_list<std::pair<std::string_view, T>> il) :
            parser_interface<symbol_parser<T>>(
                symbol_parser<T>(diagnostic_text))
        {
            this->parser_.initial_elements_.resize(il.size());
            std::copy(il.begin(), il.end(),
                      this->parser_.initial_elements_.begin());
        }

        /** Inserts an entry consisting of a UTF-8 string `str` to match, and
            an associated attribute `x`, to `*this`.  The entry is added for
            use in all subsequent top-level parses.  Subsequent lookups during
            the current top-level parse will not necessarily match `str`. */
        void insert_for_next_parse(std::string_view str, T x)
        {
            this->parser_.initial_elements_.push_back(
                std::pair(std::string(str), std::move(x)));
        }

        /** Erases the entry whose UTF-8 match string is `str`, from `*this`.
            The entry will no longer be available for use in all subsequent
            top-level parses.  `str` will not be removed from the symbols
            matched in the current top-level parse. */
        void erase_for_next_parse(std::string_view str)
        {
            auto it = std::find_if(
                this->parser_.initial_elements_.begin(),
                this->parser_.initial_elements_.end(),
                [str](auto const & x) { return x.first == str; });
            this->parser_.initial_elements_.erase(it);
        }

        /** Erases all the entries from the copy of the symbol table inside
            the parse context `context`. */
        void clear_for_next_parse() { this->parser_.initial_elements_.clear(); }

        /** Inserts an entry consisting of a UTF-8 string `str` to match, and
            an associated attribute `x`, to `*this`.  The entry is added for
            use in all subsequent top-level parses.  Subsequent lookups during
            the current top-level parse will not necessarily match `str`. */
        template<typename Context>
        void insert_for_next_parse(
            Context const & context, std::string_view str, T x)
        {
            this->parser_.insert_for_next_parse(context, str, std::move(x));
        }

        /** Erases the entry whose UTF-8 match string is `str`, from `*this`.
            The entry will no longer be available for use in all subsequent
            top-level parses.  `str` will not be removed from the symbols
            matched in the current top-level parse. */
        template<typename Context>
        void erase_for_next_parse(Context const & context, std::string_view str)
        {
            this->parser_.erase_for_next_parse(context, str);
        }

        /** Erases all the entries from the copy of the symbol table inside
            the parse context `context`. */
        template<typename Context>
        void clear_for_next_parse(Context const & context)
        {
            this->parser_.clear_for_next_parse(context);
        }

        /** Uses UTF-8 string `str` to look up an attribute in the table
            during parsing, returning it as an optional reference.  The lookup
            is done on the copy of the symbol table inside the parse context
            `context`, not `*this`. */
        template<typename Context>
        parser::detail::text::optional_ref<T>
        find(Context const & context, std::string_view str) const
        {
            return this->parser_.find(context, str);
        }

        /** Inserts an entry consisting of a UTF-8 string to match `str`, and
            an associtated attribute `x`, to the copy of the symbol table
            inside the parse context `context`. */
        template<typename Context>
        void insert(Context const & context, std::string_view str, T x) const
        {
            this->parser_.insert(context, str, std::move(x));
        }

        /** Erases the entry whose UTF-8 match string is `str` from the copy
            of the symbol table inside the parse context `context`. */
        template<typename Context>
        void erase(Context const & context, std::string_view str) const
        {
            this->parser_.erase(context, str);
        }

        /** Erases all the entries from the copy of the symbol table inside
            the parse context `context`. */
        template<typename Context>
        void clear(Context const & context) const
        {
            this->parser_.clear(context);
        }
   };

#ifndef BOOST_PARSER_DOXYGEN

    template<
        typename TagType,
        typename Attribute,
        typename LocalState,
        typename ParamsTuple>
    struct rule
        : parser_interface<
              rule_parser<false, TagType, Attribute, LocalState, ParamsTuple>>
    {
        static_assert(
            !std::is_same_v<TagType, void>,
            "void is not a valid tag type for a rule.");

        constexpr rule(char const * diagnostic_text)
        {
            this->parser_.diagnostic_text_ = diagnostic_text;
        }

        template<typename T, typename... Ts>
        constexpr auto with(T && x, Ts &&... xs) const
        {
            BOOST_PARSER_ASSERT(
                (detail::is_nope_v<ParamsTuple> &&
                 "If you're seeing this, you tried to chain calls on a rule, "
                 "like 'rule.with(foo).with(bar)'.  Quit it!'"));
            using params_tuple_type = decltype(detail::hl::make_tuple(
                static_cast<T &&>(x), static_cast<Ts &&>(xs)...));
            using rule_parser_type = rule_parser<
                false,
                TagType,
                Attribute,
                LocalState,
                params_tuple_type>;
            using result_type = parser_interface<rule_parser_type>;
            return result_type{rule_parser_type{
                this->parser_.diagnostic_text_,
                detail::hl::make_tuple(
                    static_cast<T &&>(x), static_cast<Ts &&>(xs)...)}};
        }
    };

    template<
        typename TagType,
        typename Attribute,
        typename LocalState,
        typename ParamsTuple>
    struct callback_rule
        : parser_interface<
              rule_parser<true, TagType, Attribute, LocalState, ParamsTuple>>
    {
        constexpr callback_rule(char const * diagnostic_text)
        {
            this->parser_.diagnostic_text_ = diagnostic_text;
        }

        template<typename T, typename... Ts>
        constexpr auto with(T && x, Ts &&... xs) const
        {
            BOOST_PARSER_ASSERT(
                (detail::is_nope_v<ParamsTuple> &&
                 "If you're seeing this, you tried to chain calls on a "
                 "callback_rule, like 'rule.with(foo).with(bar)'.  Quit it!'"));
            using params_tuple_type = decltype(detail::hl::make_tuple(
                static_cast<T &&>(x), static_cast<Ts &&>(xs)...));
            using rule_parser_type = rule_parser<
                true,
                TagType,
                Attribute,
                LocalState,
                params_tuple_type>;
            using result_type = parser_interface<rule_parser_type>;
            return result_type{rule_parser_type{
                this->parser_.diagnostic_text_,
                detail::hl::make_tuple(
                    static_cast<T &&>(x), static_cast<Ts &&>(xs)...)}};
        }
    };

//[ define_rule_definition
#define BOOST_PARSER_DEFINE_IMPL(_, rule_name_)                                \
    template<                                                                  \
        typename Iter,                                                         \
        typename Sentinel,                                                     \
        typename Context,                                                      \
        typename SkipParser>                                                   \
    decltype(rule_name_)::parser_type::attr_type parse_rule(                   \
        decltype(rule_name_)::parser_type::tag_type *,                         \
        Iter & first,                                                          \
        Sentinel last,                                                         \
        Context const & context,                                               \
        SkipParser const & skip,                                               \
        boost::parser::detail::flags flags,                                    \
        bool & success,                                                        \
        bool & dont_assign)                                                    \
    {                                                                          \
        auto const & parser = BOOST_PARSER_PP_CAT(rule_name_, _def);           \
        using attr_t =                                                         \
            decltype(parser(first, last, context, skip, flags, success));      \
        using attr_type = decltype(rule_name_)::parser_type::attr_type;        \
        if constexpr (boost::parser::detail::is_nope_v<attr_t>) {              \
            dont_assign = true;                                                \
            parser(first, last, context, skip, flags, success);                \
            return {};                                                         \
        } else if constexpr (std::is_same_v<attr_type, attr_t>) {              \
            return parser(first, last, context, skip, flags, success);         \
        } else if constexpr (std::is_constructible_v<attr_type, attr_t>) {     \
            return attr_type(                                                  \
                parser(first, last, context, skip, flags, success));           \
        } else {                                                               \
            attr_type attr{};                                                  \
            parser(first, last, context, skip, flags, success, attr);          \
            return attr;                                                       \
        }                                                                      \
    }                                                                          \
                                                                               \
    template<                                                                  \
        typename Iter,                                                         \
        typename Sentinel,                                                     \
        typename Context,                                                      \
        typename SkipParser,                                                   \
        typename Attribute>                                                    \
    void parse_rule(                                                           \
        decltype(rule_name_)::parser_type::tag_type *,                         \
        Iter & first,                                                          \
        Sentinel last,                                                         \
        Context const & context,                                               \
        SkipParser const & skip,                                               \
        boost::parser::detail::flags flags,                                    \
        bool & success,                                                        \
        bool & dont_assign,                                                    \
        Attribute & retval)                                                    \
    {                                                                          \
        auto const & parser = BOOST_PARSER_PP_CAT(rule_name_, _def);           \
        using attr_t =                                                         \
            decltype(parser(first, last, context, skip, flags, success));      \
        if constexpr (boost::parser::detail::is_nope_v<attr_t>) {              \
            parser(first, last, context, skip, flags, success);                \
        } else {                                                               \
            parser(first, last, context, skip, flags, success, retval);        \
        }                                                                      \
    }
    //]

#endif

        /** For each given token `t`, defines a pair of `parse_rule()`
           overloads, used internally within Boost.Parser.  Each such pair
           implements the parsing behavior rule `t`, using the parser `t_def`.
           This implementation is in the form of a pair of function templates.
           You should therefore write this macro only at namespace scope. */
#define BOOST_PARSER_DEFINE_RULES(...)                                         \
    BOOST_PARSER_PP_FOR_EACH(BOOST_PARSER_DEFINE_IMPL, _, __VA_ARGS__)


#ifndef BOOST_PARSER_DOXYGEN

    template<typename ParserTuple>
    template<typename Parser>
    constexpr auto or_parser<ParserTuple>::prepend(
        parser_interface<Parser> parser) const noexcept
    {
        // If you're seeing this as a compile- or run-time failure, you've
        // tried to put an eps parser at the beginning of an
        // alternative-parser, such as "eps | (int_ | double_)".  This is not
        // what you meant.  Since eps always matches any input, "eps | (int_ |
        // double_)" is just an awkward spelling for "eps".  To fix this this,
        // put the eps as the last alternative, so the other alternatives get
        // a chance.  Possibly, you may have meant to add a condition to the
        // eps, like "eps(condition) | (int_ | double_)", which also is
        // meaningful, and so is allowed.
        BOOST_PARSER_ASSERT(!detail::is_unconditional_eps<Parser>{});
        return parser_interface{
            or_parser<decltype(detail::hl::prepend(parsers_, parser.parser_))>{
                detail::hl::prepend(parsers_, parser.parser_)}};
    }

    template<typename ParserTuple>
    template<typename Parser>
    constexpr auto or_parser<ParserTuple>::append(
        parser_interface<Parser> parser) const noexcept
    {
        // If you're seeing this as a compile- or run-time failure, you've
        // tried to put an eps parser in the middle of an alternative-parser,
        // such as "int_ | eps | double_".  This is not what you meant.  Since
        // eps always matches any input, "int_ | eps | double_" is just an
        // awkward spelling for "int_ | eps".  To fix this this, put the eps
        // as the last alternative, so the other alternatives get a chance.
        // Possibly, you may have meant to add a condition to the eps, like
        // "int_ | eps(condition) | double_", which also is meaningful, and so
        // is allowed.
        BOOST_PARSER_ASSERT(!detail::is_unconditional_eps_v<decltype(
                                detail::hl::back(parsers_))>);
        if constexpr (detail::is_or_p<Parser>{}) {
            return parser_interface{or_parser<decltype(
                detail::hl::concat(parsers_, parser.parser_.parsers_))>{
                detail::hl::concat(parsers_, parser.parser_.parsers_)}};
        } else {
            return parser_interface{or_parser<decltype(
                detail::hl::append(parsers_, parser.parser_))>{
                detail::hl::append(parsers_, parser.parser_)}};
        }
    }

    template<typename ParserTuple>
    template<typename Parser>
    constexpr auto perm_parser<ParserTuple>::prepend(
        parser_interface<Parser> parser) const noexcept
    {
        // If you're seeing this as a compile- or run-time failure, you've
        // tried to put an eps parser in a permutation-parser, such as "eps ||
        // int_".
        BOOST_PARSER_ASSERT(!detail::is_eps_p<Parser>{});
        return parser_interface{perm_parser<decltype(detail::hl::prepend(
            parsers_, parser.parser_))>{
            detail::hl::prepend(parsers_, parser.parser_)}};
    }

    template<typename ParserTuple>
    template<typename Parser>
    constexpr auto perm_parser<ParserTuple>::append(
        parser_interface<Parser> parser) const noexcept
    {
        // If you're seeing this as a compile- or run-time failure, you've
        // tried to put an eps parser in a permutation-parser, such as "int_
        // || eps".
        BOOST_PARSER_ASSERT(!detail::is_eps_p<Parser>{});
        if constexpr (detail::is_perm_p<Parser>{}) {
            return parser_interface{perm_parser<decltype(detail::hl::concat(
                parsers_, parser.parser_.parsers_))>{
                detail::hl::concat(parsers_, parser.parser_.parsers_)}};
        } else {
            return parser_interface{perm_parser<decltype(detail::hl::append(
                parsers_, parser.parser_))>{
                detail::hl::append(parsers_, parser.parser_)}};
        }
    }

    template<
        typename ParserTuple,
        typename BacktrackingTuple,
        typename CombiningGroups>
    template<bool AllowBacktracking, typename Parser>
    constexpr auto
    seq_parser<ParserTuple, BacktrackingTuple, CombiningGroups>::prepend(
        parser_interface<Parser> parser) const noexcept
    {
        using combining_groups =
            detail::combining_t<ParserTuple, CombiningGroups>;
        using final_combining_groups =
            decltype(detail::hl::prepend(combining_groups{}, llong<0>{}));
        using backtracking = decltype(detail::hl::prepend(
            detail::hl::prepend(
                detail::hl::drop_front(BacktrackingTuple{}),
                std::bool_constant<AllowBacktracking>{}),
            std::true_type{}));
        using parser_t = seq_parser<
            decltype(detail::hl::prepend(parsers_, parser.parser_)),
            backtracking,
            final_combining_groups>;
        return parser_interface{
            parser_t{detail::hl::prepend(parsers_, parser.parser_)}};
    }

    template<
        typename ParserTuple,
        typename BacktrackingTuple,
        typename CombiningGroups>
    template<bool AllowBacktracking, typename Parser>
    constexpr auto
    seq_parser<ParserTuple, BacktrackingTuple, CombiningGroups>::append(
        parser_interface<Parser> parser) const noexcept
    {
        using combining_groups =
            detail::combining_t<ParserTuple, CombiningGroups>;
        if constexpr (detail::is_seq_p<Parser>{}) {
            using parser_combining_groups = detail::combining_t<
                decltype(parser.parser_.parsers_),
                typename Parser::combining_groups>;
            using final_combining_groups = detail::
                combined_combining_t<combining_groups, parser_combining_groups>;
            using rhs_backtracking = decltype(detail::hl::prepend(
                detail::hl::drop_front(typename Parser::backtracking{}),
                std::bool_constant<AllowBacktracking>{}));
            using backtracking = decltype(detail::hl::concat(
                BacktrackingTuple{}, rhs_backtracking{}));
            using parser_t = seq_parser<
                decltype(detail::hl::concat(parsers_, parser.parser_.parsers_)),
                backtracking,
                final_combining_groups>;
            return parser_interface{parser_t{
                detail::hl::concat(parsers_, parser.parser_.parsers_)}};
        } else {
            using final_combining_groups =
                decltype(detail::hl::append(combining_groups{}, llong<0>{}));
            using backtracking = decltype(detail::hl::append(
                BacktrackingTuple{}, std::bool_constant<AllowBacktracking>{}));
            using parser_t = seq_parser<
                decltype(detail::hl::append(parsers_, parser.parser_)),
                backtracking,
                final_combining_groups>;
            return parser_interface{
                parser_t{detail::hl::append(parsers_, parser.parser_)}};
        }
    }

#endif



    // Directives.

    /** Represents a unparameterized higher-order parser (e.g. `omit_parser`)
        as a directive (e.g. `omit[other_parser]`). */
    template<template<class> class Parser>
    struct directive
    {
        template<typename Parser2>
        constexpr auto operator[](parser_interface<Parser2> rhs) const noexcept
        {
            return parser_interface{Parser<Parser2>{rhs.parser_}};
        }
    };

    /** The `omit` directive, whose `operator[]` returns a
        `parser_interface<omit_parser<P>>` from a given parser of type
        `parser_interface<P>`. */
    inline constexpr directive<omit_parser> omit;

    /** The `raw` directive, whose `operator[]` returns a
        `parser_interface<raw_parser<P>>` from a given parser of type
        `parser_interface<P>`. */
    inline constexpr directive<raw_parser> raw;

#if defined(BOOST_PARSER_DOXYGEN) || BOOST_PARSER_USE_CONCEPTS
    /** The `string_view` directive, whose `operator[]` returns a
        `parser_interface<string_view_parser<P>>` from a given parser of type
        `parser_interface<P>`.  This is only available in C++20 and later. */
    inline constexpr directive<string_view_parser> string_view;
#endif

    /** The `lexeme` directive, whose `operator[]` returns a
        `parser_interface<lexeme_parser<P>>` from a given parser of type
        `parser_interface<P>`. */
    inline constexpr directive<lexeme_parser> lexeme;

    /** The `no_case` directive, whose `operator[]` returns a
        `parser_interface<no_case_parser<P>>` from a given parser of type
        `parser_interface<P>`. */
    inline constexpr directive<no_case_parser> no_case;

    /** Represents a `repeat_parser` as a directive
        (e.g. `repeat[other_parser]`). */
    template<typename MinType, typename MaxType>
    struct repeat_directive
    {
        template<typename Parser2>
        constexpr auto operator[](parser_interface<Parser2> rhs) const noexcept
        {
            using repeat_parser_type =
                repeat_parser<Parser2, detail::nope, MinType, MaxType>;
            return parser_interface{
                repeat_parser_type{rhs.parser_, min_, max_}};
        }

        MinType min_;
        MaxType max_;
    };

    /** Returns a `repeat_directive` that repeats exactly `n` times, and whose
        `operator[]` returns a `parser_interface<repeat_parser<P>>` from a
        given parser of type `parser_interface<P>`. */
    template<typename T>
    constexpr repeat_directive<T, T> repeat(T n) noexcept
    {
        return repeat_directive<T, T>{n, n};
    }

    /** Returns a `repeat_directive` that repeats between `min_` and `max_`
        times, inclusive, and whose `operator[]` returns a
        `parser_interface<repeat_parser<P>>` from a given parser of type
        `parser_interface<P>`. */
    template<typename MinType, typename MaxType>
    constexpr repeat_directive<MinType, MaxType>
    repeat(MinType min_, MaxType max_) noexcept
    {
        return repeat_directive<MinType, MaxType>{min_, max_};
    }

    /** Represents a skip parser as a directive.  When used without a skip
        parser, e.g. `skip[parser_in_which_to_do_skipping]`, the skipper for
        the entire parse is used.  When given another parser, e.g.
        `skip(skip_parser)[parser_in_which_to_do_skipping]`, that other parser
        is used as the skipper within the directive. */
    template<typename SkipParser = detail::nope>
    struct skip_directive
    {
        template<typename Parser>
        constexpr auto operator[](parser_interface<Parser> rhs) const noexcept
        {
            return parser_interface{
                skip_parser<Parser, SkipParser>{rhs.parser_, skip_parser_}};
        }

        /** Returns a `skip_directive` with `skip_parser` as its skipper. */
        template<typename SkipParser2>
        constexpr auto
        operator()(parser_interface<SkipParser2> skip_parser) const noexcept
        {
            BOOST_PARSER_ASSERT(
                (detail::is_nope_v<SkipParser> &&
                 "If you're seeing this, you tried to chain calls on skip, "
                 "like 'skip(foo)(bar)'.  Quit it!'"));
            return skip_directive<parser_interface<SkipParser2>>{skip_parser};
        }

        SkipParser skip_parser_;
    };

    /** The `skip_directive`, whose `operator[]` returns a
        `parser_interface<skip_parser<P>>` from a given parser of type
        `parser_interface<P>`. */
    inline constexpr skip_directive<> skip;

    /** A directive type that can only be used on sequence parsers, that
        forces the merge of all the sequence_parser's subparser's attributes
        into a single attribute. */
    struct merge_directive
    {
        template<
            typename ParserTuple,
            typename BacktrackingTuple,
            typename CombiningGroups>
        constexpr auto
        operator[](parser_interface<
                   seq_parser<ParserTuple, BacktrackingTuple, CombiningGroups>>
                       rhs) const noexcept
        {
            return parser_interface{
                seq_parser<ParserTuple, BacktrackingTuple, detail::merge_t>{
                    rhs.parser_.parsers_}};
        }
    };

    /** The `merge_directive`, whose `operator[]` returns a
        `parser_interface<P2>`, from a given parser of type
        `parser_interface<P>`, where `P` is a `seq_parser`.  `P2` is the same
        as `P`, except that its `CombiningGroups` template parameter is
        replaced with a tag type that causes the subparser's attributes to be
        merged into a single attribute. */
    inline constexpr merge_directive merge;

    /** A directive type that can only be used on sequence parsers, that
        prevents each of the sequence_parser's subparser's attributes from
        merging with any other subparser's attribute. */
    struct separate_directive
    {
        template<
            typename ParserTuple,
            typename BacktrackingTuple,
            typename CombiningGroups>
        constexpr auto
        operator[](parser_interface<
                   seq_parser<ParserTuple, BacktrackingTuple, CombiningGroups>>
                       rhs) const noexcept
        {
            return parser_interface{
                seq_parser<ParserTuple, BacktrackingTuple, detail::separate_t>{
                    rhs.parser_.parsers_}};
        }
    };

    /** The `separate_directive`, whose `operator[]` returns a
        `parser_interface<P2>`, from a given parser of type
        `parser_interface<P>`, where `P` is a `seq_parser`.  `P2` is the same
        as `P`, except that its `CombiningGroups` template parameter is
        replaced with a tag type that prevents each subparser's attribute from
        merging with any other subparser's attribute. */
    inline constexpr separate_directive separate;

    /** A directive that transforms the attribute generated by a parser.
        `operator[]` returns a `parser_interface<transform_parser<Parser,
        F>>`. */
    template<typename F>
    struct transform_directive
    {
        template<typename Parser>
        constexpr auto operator[](parser_interface<Parser> rhs) const noexcept
        {
            return parser_interface{
                transform_parser<Parser, F>{rhs.parser_, f_}};
        }

        F f_;
    };

    /** Returns a `transform_directive` that uses invocable `F` to do its
        work. */
    template<typename F>
    auto transform(F f)
    {
        return transform_directive<F>{std::move(f)};
    }


    // First order parsers.

#ifndef BOOST_PARSER_DOXYGEN

    template<typename Predicate>
    struct eps_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        detail::nope call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const noexcept
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, detail::global_nope);
            BOOST_PARSER_SUBRANGE const where(first, first);
            auto const predicate_context = detail::make_action_context(
                context, detail::global_nope, where);
            // Predicate must be a parse predicate.  If you see an error here,
            // you have not met this contract.  See the terminology section of
            // the online docs if you don't know what that a parse predicate
            // is.
            success = pred_(predicate_context);
            return {};
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);
            BOOST_PARSER_SUBRANGE const where(first, first);
            auto const predicate_context = detail::make_action_context(
                context, detail::global_nope, where);
            // Predicate must be a parse predicate.  If you see an error here,
            // you have not met this contract.  See the terminology section of
            // the online docs if you don't know what that a parse predicate
            // is.
            success = pred_(predicate_context);
        }

        /** Returns a `parser_interface` containing an `eps_parser` that will
            fail if `pred` evaluates to false. */
        template<typename Predicate2>
        constexpr auto operator()(Predicate2 pred) const noexcept
        {
            BOOST_PARSER_ASSERT(
                (detail::is_nope_v<Predicate> &&
                 "If you're seeing this, you tried to chain calls on eps, "
                 "like 'eps(foo)(bar)'.  Quit it!'"));
            return parser_interface{eps_parser<Predicate2>{std::move(pred)}};
        }

        Predicate pred_;
    };

#endif

    /** The epsilon parser.  This matches anything, and consumes no input.  If
        used with an optional predicate, like `eps(pred)`, it matches iff
        `pred(ctx)` evaluates to true, where `ctx` is the parser context. */
    inline constexpr parser_interface<eps_parser<detail::nope>> eps;

#ifndef BOOST_PARSER_DOXYGEN

    struct eoi_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        detail::nope call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, detail::global_nope);
            if (first != last)
                success = false;
            return {};
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);
            if (first != last)
                success = false;
        }
    };

#endif

    /** The end-of-input parser.  It matches only the end of input. */
    inline constexpr parser_interface<eoi_parser> eoi;

#ifndef BOOST_PARSER_DOXYGEN

    template<typename Attribute>
    struct attr_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const &,
            detail::flags flags,
            bool &) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, detail::global_nope);
            return detail::resolve(context, attr_);
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute_>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute_ & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);
            if (detail::gen_attrs(flags))
                detail::assign_copy(retval, detail::resolve(context, attr_));
        }

        Attribute attr_;
    };

#endif

    /** Returns an `attr_parser` which matches anything, and consumes no
        input, and which produces `a` as its attribute. */
    template<typename Attribute>
    constexpr auto attr(Attribute a) noexcept
    {
        return parser_interface{attr_parser<Attribute>{std::move(a)}};
    }

#ifndef BOOST_PARSER_DOXYGEN

    template<typename Expected, typename AttributeType>
    struct char_parser
    {
        constexpr char_parser() {}
        constexpr char_parser(Expected expected) : expected_(expected) {}

        template<typename T>
        using attribute_type = std::conditional_t<
            std::is_same_v<AttributeType, void>,
            std::decay_t<T>,
            AttributeType>;

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const -> attribute_type<decltype(*first)>
        {
            attribute_type<decltype(*first)> retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            if (first == last) {
                success = false;
                return;
            }
            attribute_type<decltype(*first)> const x = *first;
            if (detail::unequal(context, x, expected_)) {
                success = false;
                return;
            }
            detail::assign(retval, x);
            ++first;
        }

        /** Returns a `parser_interface` containing a `char_parser` that
            matches `x`.

            \tparam T Constrained by `!parsable_range_like<T>`. */
#if BOOST_PARSER_USE_CONCEPTS
        template<typename T>
        // clang-format off
        requires (!parsable_range_like<T>)
#else
        template<
            typename T,
            typename Enable =
                std::enable_if_t<!detail::is_parsable_range_like_v<T>>>
#endif
        constexpr auto operator()(T x) const noexcept
        // clang-format on
        {
            BOOST_PARSER_ASSERT(
                (detail::is_nope_v<Expected> &&
                 "If you're seeing this, you tried to chain calls on char_, "
                 "like 'char_('a')('b')'.  Quit it!'"));
            return parser_interface{
                char_parser<T, AttributeType>{std::move(x)}};
        }

        /** Returns a `parser_interface` containing a `char_parser` that
            matches any value in `[lo, hi]`. */
        template<typename LoType, typename HiType>
        constexpr auto operator()(LoType lo, HiType hi) const noexcept
        {
            BOOST_PARSER_ASSERT(
                (detail::is_nope_v<Expected> &&
                 "If you're seeing this, you tried to chain calls on char_, "
                 "like 'char_('a', 'b')('c', 'd')'.  Quit it!'"));
            using char_pair_t = detail::char_pair<LoType, HiType>;
            using char_parser_t = char_parser<char_pair_t, AttributeType>;
            return parser_interface(
                char_parser_t(char_pair_t{std::move(lo), std::move(hi)}));
        }

        /** Returns a `parser_interface` containing a `char_parser` that
            matches one of the values in `r`.  If the character being matched
            during the parse is a `char32_t` value, the elements of `r` are
            transcoded from their presumed encoding to UTF-32 during the
            comparison.  Otherwise, the character begin matched is directly
            compared to the elements of `r`. */
#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R>
#else
        template<
            typename R,
            typename Enable =
                std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
        constexpr auto operator()(R && r) const noexcept
        {
            BOOST_PARSER_ASSERT(
                ((!std::is_rvalue_reference_v<R &&> ||
                  !detail::is_range<detail::remove_cv_ref_t<R>>) &&
                     "It looks like you tried to pass an rvalue range to "
                     "char_().  Don't do that, or you'll end up with dangling "
                     "references."));
            BOOST_PARSER_ASSERT(
                (detail::is_nope_v<Expected> &&
                 "If you're seeing this, you tried to chain calls on char_, "
                 "like 'char_(char-set)(char-set)'.  Quit it!'"));
            auto chars = detail::make_char_range<false>(r);
            using char_range_t = decltype(chars);
            using char_parser_t = char_parser<char_range_t, AttributeType>;
            return parser_interface(char_parser_t(chars));
        }

        /** Returns a `parser_interface` containing a `char_parser` that
            matches one of the values in `r`.  `r` must be a sorted,
            random-access sequence of `char32_t`.  The character begin matched
            is directly compared to the elements of `r`.  The match is found
            via binary search.  No case folding is performed.

            \tparam R Additionally constrained by
            `std::same_as<std::ranges::range_value_t<R>, char32_t>`. */
        // clang-format off
#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R>
        requires std::same_as<std::ranges::range_value_t<R>, char32_t>
#else
        template<
            typename R,
            typename Enable = std::enable_if_t<
                detail::is_parsable_range_like_v<R> &&
                std::is_same_v<detail::range_value_t<R>, char32_t>>>
#endif
        constexpr auto operator()(sorted_t, R && r) const noexcept
        // clang-format on
        {
            BOOST_PARSER_ASSERT(
                ((!std::is_rvalue_reference_v<R &&> ||
                  !detail::is_range<detail::remove_cv_ref_t<R>>) &&
                     "It looks like you tried to pass an rvalue range to "
                     "char_().  Don't do that, or you'll end up with dangling "
                     "references."));
            BOOST_PARSER_ASSERT(
                (detail::is_nope_v<Expected> &&
                 "If you're seeing this, you tried to chain calls on char_, "
                 "like 'char_(char-set)(char-set)'.  Quit it!'"));
            auto chars = detail::make_char_range<true>(r);
            using char_range_t = decltype(chars);
            using char_parser_t = char_parser<char_range_t, AttributeType>;
            return parser_interface(char_parser_t(chars));
        }

        Expected expected_;
    };

    struct digit_parser
    {
        constexpr digit_parser() {}

        template<typename T>
        using attribute_type = std::decay_t<T>;

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const -> attribute_type<decltype(*first)>
        {
            attribute_type<decltype(*first)> retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            if (first == last) {
                success = false;
                return;
            }
            attribute_type<decltype(*first)> const x = *first;
            char32_t const x_cmp = x;
            if (x_cmp < U'\x0100' && (x_cmp < U'0' || U'9' < x_cmp)) {
                success = false;
                return;
            }
            char32_t const * it = std::upper_bound(
                std::begin(zero_cps) + 1, std::end(zero_cps), x_cmp);
            if (it == std::begin(zero_cps) || x_cmp < *(it - 1) ||
                *(it - 1) + 9 < x_cmp) {
                success = false;
                return;
            }
            detail::assign(retval, x);
            ++first;
        }

        // Produced from
        // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp, using
        // "[:nt=De:]" for the Input field.
        static constexpr char32_t zero_cps[] = {
            U'\u0030',     // U+0030 DIGIT ZERO
            U'\u0660',     // U+0660 ARABIC-INDIC DIGIT ZERO
            U'\u06F0',     // U+06F0 EXTENDED ARABIC-INDIC DIGIT ZERO
            U'\u07C0',     // U+07C0 NKO DIGIT ZERO
            U'\u0966',     // U+0966 DEVANAGARI DIGIT ZERO
            U'\u09E6',     // U+09E6 BENGALI DIGIT ZERO
            U'\u0A66',     // U+0A66 GURMUKHI DIGIT ZERO
            U'\u0AE6',     // U+0AE6 GUJARATI DIGIT ZERO
            U'\u0B66',     // U+0B66 ORIYA DIGIT ZERO
            U'\u0BE6',     // U+0BE6 TAMIL DIGIT ZERO
            U'\u0C66',     // U+0C66 TELUGU DIGIT ZERO
            U'\u0CE6',     // U+0CE6 KANNADA DIGIT ZERO
            U'\u0D66',     // U+0D66 MALAYALAM DIGIT ZERO
            U'\u0DE6',     // U+0DE6 SINHALA LITH DIGIT ZERO
            U'\u0E50',     // U+0E50 THAI DIGIT ZERO
            U'\u0ED0',     // U+0ED0 LAO DIGIT ZERO
            U'\u0F20',     // U+0F20 TIBETAN DIGIT ZERO
            U'\u1040',     // U+1040 MYANMAR DIGIT ZERO
            U'\u1090',     // U+1090 MYANMAR SHAN DIGIT ZERO
            U'\u17E0',     // U+17E0 KHMER DIGIT ZERO
            U'\u1810',     // U+1810 MONGOLIAN DIGIT ZERO
            U'\u1946',     // U+1946 LIMBU DIGIT ZERO
            U'\u19D0',     // U+19D0 NEW TAI LUE DIGIT ZERO
            U'\u1A80',     // U+1A80 TAI THAM HORA DIGIT ZERO
            U'\u1A90',     // U+1A90 TAI THAM THAM DIGIT ZERO
            U'\u1B50',     // U+1B50 BALINESE DIGIT ZERO
            U'\u1BB0',     // U+1BB0 SUNDANESE DIGIT ZERO
            U'\u1C40',     // U+1C40 LEPCHA DIGIT ZERO
            U'\u1C50',     // U+1C50 OL CHIKI DIGIT ZERO
            U'\uA620',     // U+A620 VAI DIGIT ZERO
            U'\uA8D0',     // U+A8D0 SAURASHTRA DIGIT ZERO
            U'\uA900',     // U+A900 KAYAH LI DIGIT ZERO
            U'\uA9D0',     // U+A9D0 JAVANESE DIGIT ZERO
            U'\uA9F0',     // U+A9F0 MYANMAR TAI LAING DIGIT ZERO
            U'\uAA50',     // U+AA50 CHAM DIGIT ZERO
            U'\uABF0',     // U+ABF0 MEETEI MAYEK DIGIT ZERO
            U'\uFF10',     // U+FF10 FULLWIDTH DIGIT ZERO
            U'\U000104A0', // U+104A0 OSMANYA DIGIT ZERO
            U'\U00010D30', // U+10D30 HANIFI ROHINGYA DIGIT ZERO
            U'\U00011066', // U+11066 BRAHMI DIGIT ZERO
            U'\U000110F0', // U+110F0 SORA SOMPENG DIGIT ZERO
            U'\U00011136', // U+11136 CHAKMA DIGIT ZERO
            U'\U000111D0', // U+111D0 SHARADA DIGIT ZERO
            U'\U000112F0', // U+112F0 KHUDAWADI DIGIT ZERO
            U'\U00011450', // U+11450 NEWA DIGIT ZERO
            U'\U000114D0', // U+114D0 TIRHUTA DIGIT ZERO
            U'\U00011650', // U+11650 MODI DIGIT ZERO
            U'\U000116C0', // U+116C0 TAKRI DIGIT ZERO
            U'\U00011730', // U+11730 AHOM DIGIT ZERO
            U'\U000118E0', // U+118E0 WARANG CITI DIGIT ZERO
            U'\U00011950', // U+11950 DIVES AKURU DIGIT ZERO
            U'\U00011C50', // U+11C50 BHAIKSUKI DIGIT ZERO
            U'\U00011D50', // U+11D50 MASARAM GONDI DIGIT ZERO
            U'\U00011DA0', // U+11DA0 GUNJALA GONDI DIGIT ZERO
            U'\U00011F50', // U+11F50 KAWI DIGIT ZERO
            U'\U00016A60', // U+16A60 MRO DIGIT ZERO
            U'\U00016AC0', // U+16AC0 TANGSA DIGIT ZERO
            U'\U00016B50', // U+16B50 PAHAWH HMONG DIGIT ZERO
            U'\U0001D7CE', // U+1D7CE MATHEMATICAL BOLD DIGIT ZERO
            U'\U0001D7D8', // U+1D7D8 MATHEMATICAL DOUBLE-STRUCK DIGIT ZERO
            U'\U0001D7E2', // U+1D7E2 MATHEMATICAL SANS-SERIF DIGIT ZERO
            U'\U0001D7EC', // U+1D7EC MATHEMATICAL SANS-SERIF BOLD DIGIT ZERO
            U'\U0001D7F6', // U+1D7F6 MATHEMATICAL MONOSPACE DIGIT ZERO
            U'\U0001E140', // U+1E140 NYIAKENG PUACHUE HMONG DIGIT ZERO
            U'\U0001E2F0', // U+1E2F0 WANCHO DIGIT ZERO
            U'\U0001E4F0', // U+1E4F0 NAG MUNDARI DIGIT ZERO
            U'\U0001E950', // U+1E950 ADLAM DIGIT ZERO
            U'\U0001FBF0'  // U+1FBF0 SEGMENTED DIGIT ZERO
        };
    };

    template<typename Tag>
    struct char_set_parser
    {
        BOOST_PARSER_ALGO_CONSTEXPR char_set_parser()
        {
            auto const & chars = detail::char_set<Tag>::chars;
            auto const first = std::begin(chars);
            auto const last = std::end(chars);
            auto it = std::upper_bound(first, last, 0x100, [](auto x, auto y){
                using common_t = std::common_type_t<decltype(x), decltype(x)>;
                return (common_t)x < (common_t)y;
            });
            if (it != last)
                one_byte_offset_ = int(it - first);
        }

        template<typename T>
        using attribute_type = std::decay_t<T>;

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const -> attribute_type<decltype(*first)>
        {
            attribute_type<decltype(*first)> retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            if (first == last) {
                success = false;
                return;
            }

            auto const & chars = detail::char_set<Tag>::chars;
            attribute_type<decltype(*first)> const x = *first;
            uint32_t const x_cmp = x;
            if (x_cmp < U'\x0100') {
                uint32_t const * it = std::lower_bound(
                    std::begin(chars),
                    std::begin(chars) + one_byte_offset_,
                    x_cmp);
                if (it != std::end(chars) && *it == x_cmp) {
                    detail::assign(retval, x_cmp);
                    ++first;
                } else {
                    success = false;
                }
                return;
            }

            uint32_t const * it = std::lower_bound(
                std::begin(chars) + one_byte_offset_, std::end(chars), x_cmp);
            if (it != std::end(chars) && *it == x_cmp) {
                detail::assign(retval, x_cmp);
                ++first;
                return;
            }

            success = false;
        }

        int one_byte_offset_ = 0;
    };

    template<typename Tag>
    struct char_subrange_parser
    {
        constexpr char_subrange_parser() {}

        template<typename T>
        using attribute_type = std::decay_t<T>;

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const -> attribute_type<decltype(*first)>
        {
            attribute_type<decltype(*first)> retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            if (first == last) {
                success = false;
                return;
            }
            attribute_type<decltype(*first)> const x = *first;
            char32_t const x_cmp = x;
            success = false;
            for (auto subrange : detail::char_subranges<Tag>::ranges) {
                if (subrange.lo_ <= x_cmp && x_cmp <= subrange.hi_) {
                    success = true;
                    detail::assign(retval, x);
                    ++first;
                    return;
                }
            }
        }
    };

#endif

    /** The single-character parser.  The produced attribute is the type of
        the matched code point (`char` or `char32_t`).  Used as-is, `char_`
        matches any code point.  `char_` can also can be used to create code
        point parsers that match one or more specific code point values, by
        calling it with: a single value comparable to a code point; a closed
        range of code point values `[lo, hi]`, or a set of code point values
        passed as a range.  When calling with a range, only the iterators that
        bound the range are stored.  Make sure the range you pass outlives the
        use of the resulting parser.  Note that a string literal is a range,
        and that it outlives any parser it is used to construct. */
    inline constexpr parser_interface<char_parser<detail::nope>> char_;

    /** The code point parser.  It produces a `char32_t` attribute.  Used
        as-is, `cp` matches any code point.  `cp` can also can be used to
        create code point parsers that match one or more specific code point
        values, by calling it with: a single value comparable to a code point;
        a closed range of code point values `[lo, hi]`, or a set of code point
        values passed as a range.  When calling with a range, only the
        iterators that bound the range are stored.  Make sure the range you
        pass outlives the use of the resulting parser.  Note that a string
        literal is a range, and that it outlives any parser it is used to
        construct. */
    inline constexpr parser_interface<char_parser<detail::nope, char32_t>> cp;

    /** The code unit parser.  It produces a `char` attribute.  Used as-is,
        `cu` matches any code point.  `cu` can also can be used to create code
        point parsers that match one or more specific code point values, by
        calling it with: a single value comparable to a code point; a closed
        range of code point values `[lo, hi]`, or a set of code point values
        passed as a range.  When calling with a range, only the iterators that
        bound the range are stored.  Make sure the range you pass outlives the
        use of the resulting parser.  Note that a string literal is a range,
        and that it outlives any parser it is used to construct. */
    inline constexpr parser_interface<char_parser<detail::nope, char>> cu;

    /** Returns a literal code point parser that produces no attribute. */
    inline constexpr auto lit(char c) noexcept { return omit[char_(c)]; }

#if defined(__cpp_char8_t) || defined(BOOST_PARSER_DOXYGEN)
    /** Returns a literal code point parser that produces no attribute. */
    inline constexpr auto lit(char8_t c) noexcept { return omit[char_(c)]; }
#endif

    /** Returns a literal code point parser that produces no attribute. */
    inline constexpr auto lit(char32_t c) noexcept { return omit[char_(c)]; }

#ifndef BOOST_PARSER_DOXYGEN

    template<typename StrIter, typename StrSentinel>
    struct string_parser
    {
        constexpr string_parser() : expected_first_(), expected_last_() {}

#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R>
#else
        template<
            typename R,
            typename Enable =
                std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
        constexpr string_parser(R && r) :
            expected_first_(detail::make_view_begin(r)),
            expected_last_(detail::make_view_end(r))
        {}

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        std::string call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            std::string retval;
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            if (first == last) {
                success = false;
                return;
            }

            if constexpr (std::is_same_v<
                              detail::remove_cv_ref_t<decltype(*first)>,
                              char32_t>) {
                auto const cps =
                    BOOST_PARSER_SUBRANGE(expected_first_, expected_last_) |
                    detail::text::as_utf32;

                auto const mismatch = detail::no_case_aware_string_mismatch(
                    first,
                    last,
                    cps.begin(),
                    cps.end(),
                    context.no_case_depth_);
                if (mismatch.second != cps.end()) {
                    success = false;
                    return;
                }

                detail::append(
                    retval, first, mismatch.first, detail::gen_attrs(flags));

                first = mismatch.first;
            } else {
                auto const mismatch = detail::no_case_aware_string_mismatch(
                    first,
                    last,
                    expected_first_,
                    expected_last_,
                    context.no_case_depth_);
                if (mismatch.second != expected_last_) {
                    success = false;
                    return;
                }

                detail::append(
                    retval, first, mismatch.first, detail::gen_attrs(flags));

                first = mismatch.first;
            }
        }

        StrIter expected_first_;
        StrSentinel expected_last_;
    };

#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R>
#else
    template<typename R>
#endif
    string_parser(R r) -> string_parser<
        decltype(detail::make_view_begin(r)),
        decltype(detail::make_view_end(r))>;

#endif

    /** Returns a parser that matches `str` that produces the matched string
        as its attribute. */
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R>
#else
    template<typename R>
#endif
    constexpr auto string(R && str) noexcept
    {
        return parser_interface{string_parser(str)};
    }

    template<typename Quotes, typename Escapes>
    struct quoted_string_parser
    {
        constexpr quoted_string_parser() : chs_(), ch_('"') {}

#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R>
#else
        template<
            typename R,
            typename Enable =
                std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
        constexpr quoted_string_parser(R && r) : chs_((R &&) r), ch_(0)
        {
            BOOST_PARSER_DEBUG_ASSERT(r.begin() != r.end());
        }

#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R>
#else
        template<
            typename R,
            typename Enable =
                std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
        constexpr quoted_string_parser(R && r, Escapes escapes) :
            chs_((R &&) r), escapes_(escapes), ch_(0)
        {
            BOOST_PARSER_DEBUG_ASSERT(r.begin() != r.end());
        }

        constexpr quoted_string_parser(char32_t cp) : chs_(), ch_(cp) {}

        constexpr quoted_string_parser(char32_t cp, Escapes escapes) :
            chs_(), escapes_(escapes), ch_(cp)
        {}

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        std::string call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            std::string retval;
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            if (first == last) {
                success = false;
                return;
            }

            auto const prev_first = first;

            auto append = [&retval,
                           gen_attrs = detail::gen_attrs(flags)](auto & ctx) {
                detail::move_back(retval, _attr(ctx), gen_attrs);
            };

            auto quote_ch = [&]() {
                if constexpr (detail::is_nope_v<Quotes>) {
                    detail::remove_cv_ref_t<decltype(*first)> curr = *first;
                    if ((char32_t)curr == ch_)
                        ++first;
                    else
                        success = false;
                    return ch_;
                } else {
                    detail::remove_cv_ref_t<decltype(*first)> const ch = *first;
                    bool found = false;
                    if constexpr (std::
                                      is_same_v<decltype(ch), char32_t const>) {
                        auto r = chs_ | detail::text::as_utf32;
                        found = detail::text::find(r.begin(), r.end(), ch) !=
                                r.end();
                    } else {
                        found = detail::text::find(
                                    chs_.begin(), chs_.end(), ch) != chs_.end();
                    }
                    if (found)
                        ++first;
                    else
                        success = false;
                    return ch;
                }
            };

            auto const ch = quote_ch();
            if (!success)
                return;

            decltype(ch) const backslash_and_delim[] = {'\\', ch};
            auto const back_delim = char_(backslash_and_delim);

            auto make_parser = [&]() {
                if constexpr (detail::is_nope_v<Escapes>) {
                    return *((lit('\\') >> back_delim) |
                             (char_ - back_delim))[append] > ch;
                } else {
                    return *((lit('\\') >> back_delim)[append] |
                             (lit('\\') >> parser_interface(escapes_))[append] |
                             (char_ - back_delim)[append]) > ch;
                }
            };

            auto const p = make_parser();
            p.parser_.call(
                first,
                last,
                context,
                skip,
                detail::disable_skip(flags),
                success);

            if (!success) {
                retval = Attribute();
                first = prev_first;
            }
        }

        /** Returns a `parser_interface` containing a `quoted_string_parser`
            that uses `x` as its quotation marks. */
#if BOOST_PARSER_USE_CONCEPTS
        template<typename T>
        // clang-format off
        requires (!parsable_range_like<T>)
#else
        template<
            typename T,
            typename Enable =
                std::enable_if_t<!detail::is_parsable_range_like_v<T>>>
#endif
        constexpr auto operator()(T x) const noexcept
        // clang-format on
        {
            if constexpr (!detail::is_nope_v<Quotes>) {
                BOOST_PARSER_ASSERT(
                    (chs_.empty() && ch_ == '"' &&
                     "If you're seeing this, you tried to chain calls on "
                     "quoted_string, like 'quoted_string('\"')('\\'')'.  Quit "
                     "it!'"));
            }
            return parser_interface(quoted_string_parser(std::move(x)));
        }

        /** Returns a `parser_interface` containing a `quoted_string_parser`
            that accepts any of the values in `r` as its quotation marks.  If
            the input being matched during the parse is a a sequence of
            `char32_t`, the elements of `r` are transcoded from their presumed
            encoding to UTF-32 during the comparison.  Otherwise, the
            character begin matched is directly compared to the elements of
            `r`. */
#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R>
#else
        template<
            typename R,
            typename Enable =
                std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
        constexpr auto operator()(R && r) const noexcept
        {
            BOOST_PARSER_ASSERT(((
                !std::is_rvalue_reference_v<R &&> ||
                !detail::is_range<detail::remove_cv_ref_t<
                    R>>)&&"It looks like you tried to pass an rvalue range to "
                          "quoted_string().  Don't do that, or you'll end up "
                          "with dangling references."));
            if constexpr (!detail::is_nope_v<Quotes>) {
                BOOST_PARSER_ASSERT(
                    (chs_.empty() && ch_ == '"' &&
                     "If you're seeing this, you tried to chain calls on "
                     "quoted_string, like "
                     "'quoted_string(char-range)(char-range)'.  Quit it!'"));
            }
            return parser_interface(
                quoted_string_parser<decltype(BOOST_PARSER_SUBRANGE(
                    detail::make_view_begin(r), detail::make_view_end(r)))>(
                    BOOST_PARSER_SUBRANGE(
                        detail::make_view_begin(r), detail::make_view_end(r))));
        }

        /** Returns a `parser_interface` containing a `quoted_string_parser`
            that uses `x` as its quotation marks.  `symbols` provides a list
            of strings that may appear after a backslash to form an escape
            sequence, and what character(s) each escape sequence represents.
            Note that `"\\"` and `"\ch"` are always valid escape sequences. */
#if BOOST_PARSER_USE_CONCEPTS
        template<typename T, typename U>
        // clang-format off
        requires (!parsable_range_like<T>)
#else
        template<
            typename T,
            typename U,
            typename Enable =
                std::enable_if_t<!detail::is_parsable_range_like_v<T>>>
#endif
        auto operator()(T x, symbols<U> const & escapes) const noexcept
        // clang-format on
        {
            if constexpr (!detail::is_nope_v<Quotes>) {
                BOOST_PARSER_ASSERT(
                    (chs_.empty() && ch_ == '"' &&
                     "If you're seeing this, you tried to chain calls on "
                     "quoted_string, like 'quoted_string('\"')('\\'')'.  Quit "
                     "it!'"));
            }
            auto symbols = symbol_parser(escapes.parser_);
            auto parser =
                quoted_string_parser<detail::nope, decltype(symbols)>(
                    char32_t(x), symbols);
            return parser_interface(parser);
        }

        /** Returns a `parser_interface` containing a `quoted_string_parser`
            that accepts any of the values in `r` as its quotation marks.  If
            the input being matched during the parse is a a sequence of
            `char32_t`, the elements of `r` are transcoded from their presumed
            encoding to UTF-32 during the comparison.  Otherwise, the
            character begin matched is directly compared to the elements of
            `r`.  `symbols` provides a list of strings that may appear after a
            backslash to form an escape sequence, and what character(s) each
            escape sequence represents.  Note that `"\\"` and `"\ch"` are
            always valid escape sequences. */
#if BOOST_PARSER_USE_CONCEPTS
        template<parsable_range_like R, typename T>
#else
        template<
            typename R,
            typename T,
            typename Enable =
                std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
        auto operator()(R && r, symbols<T> const & escapes) const noexcept
        {
            BOOST_PARSER_ASSERT(((
                !std::is_rvalue_reference_v<R &&> ||
                !detail::is_range<detail::remove_cv_ref_t<
                    R>>)&&"It looks like you tried to pass an rvalue range to "
                          "quoted_string().  Don't do that, or you'll end up "
                          "with dangling references."));
            if constexpr (!detail::is_nope_v<Quotes>) {
                BOOST_PARSER_ASSERT(
                    (chs_.empty() && ch_ == '"' &&
                     "If you're seeing this, you tried to chain calls on "
                     "quoted_string, like "
                     "'quoted_string(char-range)(char-range)'.  Quit it!'"));
            }
            auto symbols = symbol_parser(escapes.parser_);
            auto quotes = BOOST_PARSER_SUBRANGE(
                detail::make_view_begin(r), detail::make_view_end(r));
            auto parser =
                quoted_string_parser<decltype(quotes), decltype(symbols)>(
                    quotes, symbols);
            return parser_interface(parser);
        }

        Quotes chs_;
        Escapes escapes_;
        char32_t ch_;
    };

    /** Parses a string delimited by quotation marks.  This parser can be used
        to create parsers that accept one or more specific quotation mark
        characters.  By default, the quotation marks are `'"'`; an alternate
        quotation mark can be specified by calling this parser with a single
        character, or a range of characters.  If a range is specified, the
        opening quote must be one of the characters specified, and the closing
        quote must match the opening quote.  Quotation marks may appear within
        the string if escaped with a backslash, and a pair of backslashes is
        treated as a single escaped backslash; all other backslashes cause the
        parse to fail, unless a symbol table is in use.  A symbol table can be
        provided as a second parameter after the single character or range
        described above.  The symbol table is used to recognize escape
        sequences.  Each escape sequence is a backslash followed by a value in
        the symbol table.  When using a symbol table, any backslash that is
        not followed by another backslash, the opening quote character, or a
        symbol from the symbol table will cause the parse to fail.  Skipping
        is disabled during parsing of the entire quoted string, including the
        quotation marks.  There is an expectation point before the closing
        quotation mark.  Produces a `std::string` attribute. */
    inline constexpr parser_interface<quoted_string_parser<>> quoted_string;

    /** Returns a parser that matches `str` that produces no attribute. */
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R>
#else
    template<typename R>
#endif
    constexpr auto lit(R && str) noexcept
    {
        return omit[parser::string(str)];
    }

#ifndef BOOST_PARSER_DOXYGEN

    template<bool NewlinesOnly, bool NoNewlines>
    struct ws_parser
    {
        constexpr ws_parser() {}

        static_assert(!NewlinesOnly || !NoNewlines);

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        detail::nope call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            detail::nope nope;
            call(first, last, context, skip, flags, success, nope);
            return {};
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            if (first == last) {
                success = false;
                return;
            }
            int const x = *first;
            if constexpr (NewlinesOnly) {
                if (x == 0x000a) { // lf
                    ++first;
                    return;
                }
                if (x == 0x000d) { // cr
                    ++first;
                    if (first != last && *first == 0x000a) // lf
                        ++first;
                    return;
                }
                if (0x000b == x || x == 0x000c || x == 0x0085 || x == 0x2028 ||
                    x == 0x2029) {
                    ++first;
                    return;
                }
                success = false;
            } else if constexpr (NoNewlines) {
                if (x == 0x0020) { // space
                    ++first;
                    return;
                }
                if (x == 0x0009) { // tab
                    ++first;
                    return;
                }
                if (x == 0x00a0 || x == 0x1680 ||
                    (0x2000 <= x && x <= 0x200a) || x == 0x202F ||
                    x == 0x205F || x == 0x3000) {
                    ++first;
                    return;
                }
                success = false;
            } else {
                if (x == 0x0020 || x == 0x000a) { // space, lf
                    ++first;
                    return;
                }
                if (x == 0x000d) { // cr
                    ++first;
                    if (first != last && *first == 0x000a) // lf
                        ++first;
                    return;
                }
                if (0x0009 <= x && x <= 0x000c) { // tab through cr
                    ++first;
                    return;
                }
                if (x == 0x0085 || x == 0x00a0 || x == 0x1680 ||
                    (0x2000 <= x && x <= 0x200a) || x == 0x2028 ||
                    x == 0x2029 || x == 0x202F || x == 0x205F || x == 0x3000) {
                    ++first;
                    return;
                }
                success = false;
            }
        }
    };

#endif

    /** The end-of-line parser.  This matches "\r\n", or any one of the line
        break code points from the Unicode Line Break Algorithm, described in
        https://unicode.org/reports/tr14.  Produces no attribute. */
    inline constexpr parser_interface<ws_parser<true, false>> eol;

    /** The whitespace parser.  This matches "\r\n", or any one of the Unicode
        code points with the White_Space property, as defined in
        https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt.  Produces
        no attribute. */
    inline constexpr parser_interface<ws_parser<false, false>> ws;

    /** The whitespace parser that does not match end-of-line.  This matches
        any one of the Unicode code points with the White_Space property, as
        defined in https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt,
        except for the ones matched by `eol`.  Produces no attribute. */
    inline constexpr parser_interface<ws_parser<false, true>> blank;

    /** The decimal digit parser.  Matches the full set of Unicode decimal
        digits; in other words, all Unicode code points with the "Nd"
        character property.  Note that this covers all Unicode scripts, only a
        few of which are Latin. */
    inline constexpr parser_interface<digit_parser> digit;

    /** The hexidecimal digit parser.  Matches the full set of Unicode
        hexidecimal digits (upper or lower case); in other words, all Unicode
        code points with the "Hex_Digit" character property. */
    inline constexpr parser_interface<
        char_subrange_parser<detail::hex_digit_subranges>>
        hex_digit;

    /** The control character parser.  Matches the all Unicode code points
        with the "Cc" ("control character") character property. */
    inline constexpr parser_interface<
        char_subrange_parser<detail::control_subranges>>
        control;

    /** The punctuation character parser.  Matches the full set of Unicode
        punctuation clases (specifically, "Pc", "Pd", "Pe", "Pf", "Pi", "Ps",
        and "Po"). */
    inline BOOST_PARSER_ALGO_CONSTEXPR
        parser_interface<char_set_parser<detail::punct_chars>>
            punct;

    /** The lower case character parser.  Matches the full set of Unicode
        lower case code points (class "Ll"). */
    inline BOOST_PARSER_ALGO_CONSTEXPR
        parser_interface<char_set_parser<detail::lower_case_chars>>
            lower;

    /** The lower case character parser.  Matches the full set of Unicode
        lower case code points (class "Lu"). */
    inline BOOST_PARSER_ALGO_CONSTEXPR
        parser_interface<char_set_parser<detail::upper_case_chars>>
            upper;

#ifndef BOOST_PARSER_DOXYGEN

    struct bool_parser
    {
        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        bool call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            bool retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);

            auto compare =
                [no_case = context.no_case_depth_](char32_t a, char32_t b) {
                    if (no_case && 0x41 <= b && b < 0x5b)
                        b += 0x20;
                    return a == b;
                };

            // The lambda quiets a signed/unsigned mismatch warning when
            // comparing the chars here to code points.
            char const t[] = "true";
            if (detail::mismatch(t, t + 4, first, last, compare).first ==
                t + 4) {
                std::advance(first, 4);
                detail::assign(retval, true);
                return;
            }
            char const f[] = "false";
            if (detail::mismatch(f, f + 5, first, last, compare).first ==
                f + 5) {
                std::advance(first, 5);
                detail::assign(retval, false);
                return;
            }
            success = false;
        }
    };

#endif

    /** The Boolean parser.  Parses "true" and "false", producing attributes
        `true` and `false`, respectively, and fails on any other input. */
    inline constexpr parser_interface<bool_parser> bool_;

#ifndef BOOST_PARSER_DOXYGEN

    template<
        typename T,
        int Radix,
        int MinDigits,
        int MaxDigits,
        typename Expected>
    struct uint_parser
    {
        static_assert(2 <= Radix && Radix <= 36, "Unsupported radix.");

        constexpr uint_parser() {}
        explicit constexpr uint_parser(Expected expected) : expected_(expected)
        {}

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        T call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            T retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);
            T attr = 0;
            auto const initial = first;
            success =
                detail::numeric::parse_int<false, Radix, MinDigits, MaxDigits>(
                    first, last, attr);
            if (first == initial || attr != detail::resolve(context, expected_))
                success = false;
            if (success)
                detail::assign(retval, attr);
        }

        /** Returns a `parser_interface` containing a `uint_parser` that
            matches the exact value `expected`. */
        template<typename Expected2>
        constexpr auto operator()(Expected2 expected) const noexcept
        {
            BOOST_PARSER_ASSERT(
                (detail::is_nope_v<Expected> &&
                 "If you're seeing this, you tried to chain calls on this "
                 "parser, like 'uint_(2)(3)'.  Quit it!'"));
            using parser_t =
                uint_parser<T, Radix, MinDigits, MaxDigits, Expected2>;
            return parser_interface{parser_t{expected}};
        }

        Expected expected_;
    };

#endif

    /** The binary unsigned integer parser.  Produces an `unsigned int`
        attribute.  To parse a particular value `x`, use `bin(x)`. */
    inline constexpr parser_interface<uint_parser<unsigned int, 2>> bin;

    /** The octal unsigned integer parser.  Produces an `unsigned int`
        attribute.  To parse a particular value `x`, use `oct(x)`. */
    inline constexpr parser_interface<uint_parser<unsigned int, 8>> oct;

    /** The hexadecimal unsigned integer parser.  Produces an `unsigned int`
        attribute.  To parse a particular value `x`, use `hex(x)`. */
    inline constexpr parser_interface<uint_parser<unsigned int, 16>> hex;

    /** The `unsigned short` parser.  Produces an `unsigned short` attribute.
        To parse a particular value `x`, use `ushort_(x)`. */
    inline constexpr parser_interface<uint_parser<unsigned short>> ushort_;

    /** The `unsigned int` parser.  Produces an `unsigned int` attribute.  To
        parse a particular value `x`, use `uint_(x)`. */
    inline constexpr parser_interface<uint_parser<unsigned int>> uint_;

    /** The `unsigned long` parser.  Produces an `unsigned long` attribute.
        To parse a particular value `x`, use `ulong_(x)`. */
    inline constexpr parser_interface<uint_parser<unsigned long>> ulong_;

    /** The `unsigned long long` parser.  Produces an `unsigned long long`
        attribute.  To parse a particular value `x`, use `ulong_long(x)`. */
    inline constexpr parser_interface<uint_parser<unsigned long long>>
        ulong_long;

#ifndef BOOST_PARSER_DOXYGEN

    template<
        typename T,
        int Radix,
        int MinDigits,
        int MaxDigits,
        typename Expected>
    struct int_parser
    {
        static_assert(
            Radix == 2 || Radix == 8 || Radix == 10 || Radix == 16,
            "Unsupported radix.");

        constexpr int_parser() {}
        explicit constexpr int_parser(Expected expected) : expected_(expected)
        {}

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        T call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            T retval{};
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);
            T attr = 0;
            auto const initial = first;
            success =
                detail::numeric::parse_int<true, Radix, MinDigits, MaxDigits>(
                    first, last, attr);
            if (first == initial || attr != detail::resolve(context, expected_))
                success = false;
            if (success)
                detail::assign(retval, attr);
        }

        /** Returns a `parser_interface` containing an `int_parser` that
            matches the exact value `expected`. */
        template<typename Expected2>
        constexpr auto operator()(Expected2 expected) const noexcept
        {
            BOOST_PARSER_ASSERT(
                (detail::is_nope_v<Expected> &&
                 "If you're seeing this, you tried to chain calls on this "
                 "parser, like 'int_(2)(3)'.  Quit it!'"));
            using parser_t =
                int_parser<T, Radix, MinDigits, MaxDigits, Expected2>;
            return parser_interface{parser_t{expected}};
        }

        Expected expected_;
    };

#endif

    /** The `short` parser.  Produces a `short` attribute.  To parse a
        particular value `x`, use `short_(x)`. */
    inline constexpr parser_interface<int_parser<short>> short_;

    /** The `int` parser.  Produces an `int` attribute.  To parse a particular
        value `x`, use `int_(x)`. */
    inline constexpr parser_interface<int_parser<int>> int_;

    /** The `long` parser.  Produces a `long` attribute.  To parse a particular
        value `x`, use `long_(x)`. */
    inline constexpr parser_interface<int_parser<long>> long_;

    /** The `long long` parser.  Produces a `long long` attribute.  To parse a
        particular value `x`, use `long_long(x)`. */
    inline constexpr parser_interface<int_parser<long long>> long_long;

#ifndef BOOST_PARSER_DOXYGEN

    template<typename T>
    struct float_parser
    {
        constexpr float_parser() {}

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        T call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            T retval = 0;
            call(first, last, context, skip, flags, success, retval);
            return retval;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);
            T attr = 0;
            auto const initial = first;
            success = detail::numeric::parse_real(first, last, attr);
            if (first == initial)
                success = false;
            if (success)
                detail::assign(retval, attr);
        }
    };

#endif

    /** The `float` parser.  Produces a `float` attribute. */
    inline constexpr parser_interface<float_parser<float>> float_;

    /** The `double` parser.  Produces a `double` attribute. */
    inline constexpr parser_interface<float_parser<double>> double_;


    /** Represents a sequence parser, the first parser of which is an
        `epsilon_parser` with predicate, as a directive
        (e.g. `if_(pred)[p]`). */
    template<typename Predicate>
    struct if_directive
    {
        template<typename Parser2>
        constexpr auto operator[](parser_interface<Parser2> rhs) const noexcept
        {
            return eps(pred_) >> rhs;
        }

        Predicate pred_;
    };

    /** Returns an `if_directive` that fails if the given predicate `pred` is
        `false`, and otherwise, applies another parser.  For instance, in
        `if_(pred)[p]`, `p` is only applied if `pred` is true. */
    template<typename Predicate>
    constexpr auto if_(Predicate pred) noexcept
    {
        return if_directive<Predicate>{pred};
    }

    namespace detail {
        template<typename SwitchValue, typename Value>
        struct switch_parser_equal
        {
            template<typename Context>
            bool operator()(Context & context) const
            {
                auto const switch_value =
                    detail::resolve(context, switch_value_);
                auto const value = detail::resolve(context, value_);
                return value == switch_value;
            }
            SwitchValue switch_value_;
            Value value_;
        };
    }

#ifndef BOOST_PARSER_DOXYGEN

    template<typename SwitchValue, typename OrParser>
    struct switch_parser
    {
        switch_parser() {}
        switch_parser(SwitchValue switch_value) : switch_value_(switch_value) {}
        switch_parser(SwitchValue switch_value, OrParser or_parser) :
            switch_value_(switch_value), or_parser_(or_parser)
        {}

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser>
        auto call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success) const
        {
            BOOST_PARSER_ASSERT(
                (!detail::is_nope_v<OrParser> &&
                 "It looks like you tried to write switch_(val).  You need at "
                 "least one alternative, like: switch_(val)(value_1, "
                 "parser_1)(value_2, parser_2)..."));
            using attr_t = decltype(or_parser_.call(
                first, last, context, skip, flags, success));
            attr_t attr{};
            [[maybe_unused]] auto _ =
                detail::scoped_trace(*this, first, last, context, flags, attr);
            attr = or_parser_.call(first, last, context, skip, flags, success);
            return attr;
        }

        template<
            typename Iter,
            typename Sentinel,
            typename Context,
            typename SkipParser,
            typename Attribute>
        void call(
            Iter & first,
            Sentinel last,
            Context const & context,
            SkipParser const & skip,
            detail::flags flags,
            bool & success,
            Attribute & retval) const
        {
            BOOST_PARSER_ASSERT(
                (!detail::is_nope_v<OrParser> &&
                 "It looks like you tried to write switch_(val).  You need at "
                 "least one alternative, like: switch_(val)(value_1, "
                 "parser_1)(value_2, parser_2)..."));
            [[maybe_unused]] auto _ = detail::scoped_trace(
                *this, first, last, context, flags, retval);
            or_parser_.call(first, last, context, skip, flags, success, retval);
        }

        /** Returns a `parser_interface` containing a `switch_parser`, with
            the case `value_`,`rhs` appended to its `or_parser_`. */
        template<typename Value, typename Parser2>
        constexpr auto
        operator()(Value value_, parser_interface<Parser2> rhs) const noexcept
        {
            auto const match = detail::switch_parser_equal<SwitchValue, Value>{
                switch_value_, value_};
            auto or_parser = make_or_parser(or_parser_, eps(match) >> rhs);
            using switch_parser_type =
                switch_parser<SwitchValue, decltype(or_parser)>;
            return parser_interface{
                switch_parser_type{switch_value_, or_parser}};
        }

#ifndef BOOST_PARSER_DOXYGEN

        template<typename Parser1, typename Parser2>
        static constexpr auto
        make_or_parser(Parser1 parser1, parser_interface<Parser2> parser2)
        {
            return (parser_interface{parser1} | parser2).parser_;
        }

        template<typename Parser>
        static constexpr auto
        make_or_parser(detail::nope, parser_interface<Parser> parser)
        {
            return parser.parser_;
        }

#endif

        SwitchValue switch_value_;
        OrParser or_parser_;
    };

#endif

    /** Returns a `switch`-like parser.  The resulting parser uses the given
        value `x` to select one of the following value/parser pairs, and to
        apply the selected parser.  `x` may be a value to be used directly, or
        a unary invocable that takes a reference to the parse context, and
        returns the value to use.  You can add more value/parser cases to the
        returned parser, using its call operator, e.g. `switch_(x)(y1, p1)(y2,
        p2)`.  As with the `x` passed to this function, each `yN` value can be
        a value or a unary invocable. */
    template<typename T>
    constexpr auto switch_(T x) noexcept
    {
        return switch_parser<T>{x};
    }


#ifndef BOOST_PARSER_DOXYGEN

    template<typename Parser, typename GlobalState, typename ErrorHandler>
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator>>(
        char rhs) const noexcept
    {
        return *this >> parser::lit(rhs);
    }

    template<typename Parser, typename GlobalState, typename ErrorHandler>
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator>>(
        char32_t rhs) const noexcept
    {
        return *this >> parser::lit(rhs);
    }

    template<typename Parser, typename GlobalState, typename ErrorHandler>
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R>
#else
    template<typename R, typename>
#endif
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator>>(
        R && r) const noexcept
    {
        return *this >> parser::lit(r);
    }

#endif

    /** Returns a parser equivalent to `lit(c) >> rhs`. */
    template<typename Parser>
    constexpr auto operator>>(char c, parser_interface<Parser> rhs) noexcept
    {
        if constexpr (detail::is_seq_p<Parser>{}) {
            return rhs.parser_.template prepend<true>(parser::lit(c));
        } else {
            return parser::lit(c) >> rhs;
        }
    }

    /** Returns a parser equivalent to `lit(c) >> rhs`. */
    template<typename Parser>
    constexpr auto operator>>(char32_t c, parser_interface<Parser> rhs) noexcept
    {
        if constexpr (detail::is_seq_p<Parser>{}) {
            return rhs.parser_.template prepend<true>(parser::lit(c));
        } else {
            return parser::lit(c) >> rhs;
        }
    }

    /** Returns a parser equivalent to `lit(str) >> rhs`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R, typename Parser>
#else
    template<
        typename R,
        typename Parser,
        typename Enable = std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
    constexpr auto operator>>(R && r, parser_interface<Parser> rhs) noexcept
    {
        if constexpr (detail::is_seq_p<Parser>{}) {
            return rhs.parser_.template prepend<true>(parser::lit(r));
        } else {
            return parser::lit(r) >> rhs;
        }
    }

#ifndef BOOST_PARSER_DOXYGEN

    template<typename Parser, typename GlobalState, typename ErrorHandler>
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator>(
        char rhs) const noexcept
    {
        return *this > parser::lit(rhs);
    }

    template<typename Parser, typename GlobalState, typename ErrorHandler>
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator>(
        char32_t rhs) const noexcept
    {
        return *this > parser::lit(rhs);
    }

    template<typename Parser, typename GlobalState, typename ErrorHandler>
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R>
#else
    template<typename R, typename>
#endif
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator>(
        R && r) const noexcept
    {
        return *this > parser::lit(r);
    }

#endif

    /** Returns a parser equivalent to `lit(c) > rhs`. */
    template<typename Parser>
    constexpr auto operator>(char c, parser_interface<Parser> rhs) noexcept
    {
        if constexpr (detail::is_seq_p<Parser>{}) {
            return rhs.parser_.template prepend<false>(parser::lit(c));
        } else {
            return parser::lit(c) > rhs;
        }
    }

    /** Returns a parser equivalent to `lit(c) > rhs`. */
    template<typename Parser>
    constexpr auto operator>(char32_t c, parser_interface<Parser> rhs) noexcept
    {
        if constexpr (detail::is_seq_p<Parser>{}) {
            return rhs.parser_.template prepend<false>(parser::lit(c));
        } else {
            return parser::lit(c) > rhs;
        }
    }

    /** Returns a parser equivalent to `lit(str) > rhs`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R, typename Parser>
#else
    template<
        typename R,
        typename Parser,
        typename Enable = std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
    constexpr auto operator>(R && r, parser_interface<Parser> rhs) noexcept
    {
        if constexpr (detail::is_seq_p<Parser>{}) {
            return rhs.parser_.template prepend<false>(parser::lit(r));
        } else {
            return parser::lit(r) > rhs;
        }
    }

#ifndef BOOST_PARSER_DOXYGEN

    template<typename Parser, typename GlobalState, typename ErrorHandler>
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator|(
        char rhs) const noexcept
    {
        return *this | parser::lit(rhs);
    }

    template<typename Parser, typename GlobalState, typename ErrorHandler>
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator|(
        char32_t rhs) const noexcept
    {
        return *this | parser::lit(rhs);
    }

    template<typename Parser, typename GlobalState, typename ErrorHandler>
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R>
#else
    template<typename R, typename>
#endif
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator|(
        R && r) const noexcept
    {
        return *this | parser::lit(r);
    }

#endif

    /** Returns a parser equivalent to `lit(c) | rhs`. */
    template<typename Parser>
    constexpr auto operator|(char c, parser_interface<Parser> rhs) noexcept
    {
        if constexpr (detail::is_or_p<Parser>{}) {
            return rhs.parser_.prepend(parser::lit(c));
        } else {
            return parser::lit(c) | rhs;
        }
    }

    /** Returns a parser equivalent to `lit(c) | rhs`. */
    template<typename Parser>
    constexpr auto operator|(char32_t c, parser_interface<Parser> rhs) noexcept
    {
        if constexpr (detail::is_or_p<Parser>{}) {
            return rhs.parser_.prepend(parser::lit(c));
        } else {
            return parser::lit(c) | rhs;
        }
    }

    /** Returns a parser equivalent to `lit(str) | rhs`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R, typename Parser>
#else
    template<
        typename R,
        typename Parser,
        typename Enable = std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
    constexpr auto operator|(R && r, parser_interface<Parser> rhs) noexcept
    {
        if constexpr (detail::is_or_p<Parser>{}) {
            return rhs.parser_.prepend(parser::lit(r));
        } else {
            return parser::lit(r) | rhs;
        }
    }

#ifndef BOOST_PARSER_DOXYGEN

    template<typename Parser, typename GlobalState, typename ErrorHandler>
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator-(
        char rhs) const noexcept
    {
        return !parser::lit(rhs) >> *this;
    }

    template<typename Parser, typename GlobalState, typename ErrorHandler>
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator-(
        char32_t rhs) const noexcept
    {
        return !parser::lit(rhs) >> *this;
    }

    template<typename Parser, typename GlobalState, typename ErrorHandler>
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R>
#else
    template<typename R, typename>
#endif
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator-(
        R && r) const noexcept
    {
        return !parser::lit(r) >> *this;
    }

#endif

    /** Returns a parser equivalent to `!rhs >> lit(c)`. */
    template<typename Parser>
    constexpr auto operator-(char c, parser_interface<Parser> rhs) noexcept
    {
        return !rhs >> parser::lit(c);
    }

    /** Returns a parser equivalent to `!rhs >> lit(c)`. */
    template<typename Parser>
    constexpr auto operator-(char32_t c, parser_interface<Parser> rhs) noexcept
    {
        return !rhs >> parser::lit(c);
    }

    /** Returns a parser equivalent to `!rhs >> lit(str)`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R, typename Parser>
#else
    template<
        typename R,
        typename Parser,
        typename Enable = std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
    constexpr auto operator-(R && r, parser_interface<Parser> rhs) noexcept
    {
        return !rhs >> parser::lit(r);
    }

#ifndef BOOST_PARSER_DOXYGEN

    template<typename Parser, typename GlobalState, typename ErrorHandler>
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator%(
        char rhs) const noexcept
    {
        return *this % parser::lit(rhs);
    }

    template<typename Parser, typename GlobalState, typename ErrorHandler>
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator%(
        char32_t rhs) const noexcept
    {
        return *this % parser::lit(rhs);
    }

    template<typename Parser, typename GlobalState, typename ErrorHandler>
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R>
#else
    template<typename R, typename>
#endif
    constexpr auto
    parser_interface<Parser, GlobalState, ErrorHandler>::operator%(
        R && r) const noexcept
    {
        return *this % parser::lit(r);
    }

#endif

    /** Returns a parser equivalent to `lit(c) % rhs`. */
    template<typename Parser>
    constexpr auto operator%(char c, parser_interface<Parser> rhs) noexcept
    {
        return parser::lit(c) % rhs;
    }

    /** Returns a parser equivalent to `lit(c) % rhs`. */
    template<typename Parser>
    constexpr auto operator%(char32_t c, parser_interface<Parser> rhs) noexcept
    {
        return parser::lit(c) % rhs;
    }

    /** Returns a parser equivalent to `lit(str) % rhs`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<parsable_range_like R, typename Parser>
#else
    template<
        typename R,
        typename Parser,
        typename Enable = std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
    constexpr auto operator%(R && r, parser_interface<Parser> rhs) noexcept
    {
        return parser::lit(r) % rhs;
    }

}}

#include <boost/parser/detail/printing_impl.hpp>

namespace boost { namespace parser {

    /** An enumeration used for parameters to enable and disable trace in the
        `*parse()` functions. */
    enum class trace { off, on };

    // Parse API.

    /** Parses `[first, last)` using `parser`, and returns whether the parse
        was successful.  On success, `attr` will be assigned the value of the
        attribute produced by `parser`.  If `trace_mode == trace::on`, a
        verbose trace of the parse will be streamed to `std::cout`.

        \tparam Attr Constrained by
        `!detail::derived_from_parser_interface_v<std::remove_cvref_t<Attr>`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_iter I,
        std::sentinel_for<I> S,
        typename Parser,
        typename GlobalState,
        error_handler<I, S, GlobalState> ErrorHandler,
        typename Attr>
#else
    template<
        typename I,
        typename S,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename Attr,
        typename Enable = std::enable_if_t<
            detail::is_parsable_iter_v<I> &&
            detail::is_equality_comparable_with_v<I, S> &&
            !detail::derived_from_parser_interface_v<
                detail::remove_cv_ref_t<Attr>>>>
#endif
    bool prefix_parse(
        I & first,
        S last,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        Attr & attr,
        trace trace_mode = trace::off)
#if BOOST_PARSER_USE_CONCEPTS
        // clang-format off
        requires (
            !detail::derived_from_parser_interface_v<std::remove_cvref_t<Attr>>)
    // clang-format on
#endif
    {
        detail::attr_reset reset(attr);
        if constexpr (!detail::is_char8_iter_v<I>) {
            static_assert(
                decltype(detail::has_attribute(first, last, parser)){},
                "If you're seeing this error, you're trying to get parse() to "
                "fill in attr above, using the attribute generated by parser. "
                "However, parser does not generate an attribute.");
            if (trace_mode == trace::on) {
                return reset = detail::parse_impl<true>(
                           first, last, parser, parser.error_handler_, attr);
            } else {
                return reset = detail::parse_impl<false>(
                           first, last, parser, parser.error_handler_, attr);
            }
        } else {
            auto r =
                BOOST_PARSER_SUBRANGE(first, last) | detail::text::as_utf32;
            auto f = r.begin();
            auto const l = r.end();
            auto _ = detail::scoped_base_assign(first, f);
            static_assert(
                decltype(detail::has_attribute(f, l, parser)){},
                "If you're seeing this error, you're trying to get parse() to "
                "fill in attr above, using the attribute generated by parser. "
                "However, parser does not generate an attribute.");
            if (trace_mode == trace::on) {
                return reset = detail::parse_impl<true>(
                           f, l, parser, parser.error_handler_, attr);
            } else {
                return reset = detail::parse_impl<false>(
                           f, l, parser, parser.error_handler_, attr);
            }
        }
    }

    /** Parses `r` using `parser`, and returns whether the parse was
        successful.  The entire input range `r` must be consumed for the parse
        to be considered successful.  On success, `attr` will be assigned the
        value of the attribute produced by `parser`.  If `trace_mode ==
        trace::on`, a verbose trace of the parse will be streamed to
        `std::cout`.

        \tparam ErrorHandler Constrained by `error_handler<ErrorHandler,std::ranges::iterator_t<decltype(subrange_of(r))>, std::ranges::sentinel_t<decltype(subrange_of(r))>, GlobalState>`,
            where `subrange_of` is an implementation detail that: creates
            subranges out of pointers; trims trailing zeros off of bounded
            arrays (such as string literals); and transcodes to UTF-32 if the
            input is non-`char`.
        \tparam Attr Constrained by
        `!detail::derived_from_parser_interface_v<std::remove_cvref_t<Attr>`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_range R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename Attr>
#else
    template<
        typename R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename Attr,
        typename Enable = std::enable_if_t<
            detail::is_parsable_range_v<R> &&
            !detail::derived_from_parser_interface_v<
                detail::remove_cv_ref_t<Attr>>>>
#endif
    bool parse(
        R const & r,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        Attr & attr,
        trace trace_mode = trace::off)
#if BOOST_PARSER_USE_CONCEPTS
        // clang-format off
        requires error_handler<
            ErrorHandler,
            std::ranges::iterator_t<decltype(detail::make_input_subrange(r))>,
            std::ranges::sentinel_t<decltype(detail::make_input_subrange(r))>,
            GlobalState> &&
        (!detail::derived_from_parser_interface_v<std::remove_cvref_t<Attr>>)
    // clang-format on
#endif
    {
        detail::attr_reset reset(attr);
        auto r_ = detail::make_input_subrange(r);
        auto first = r_.begin();
        auto const last = r_.end();
        return reset = detail::if_full_parse(
                   first,
                   last,
                   parser::prefix_parse(first, last, parser, attr, trace_mode));
    }

    /** Parses `[first, last)` using `parser`.  Returns a `std::optional`
        containing the attribute produced by `parser` on parse success, and
        `std::nullopt` on parse failure.  If `trace_mode == trace::on`, a
        verbose trace of the parse will be streamed to `std::cout`.

        \tparam Attr Constrained by
        `!detail::derived_from_parser_interface_v<std::remove_cvref_t<Attr>`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_iter I,
        std::sentinel_for<I> S,
        typename Parser,
        typename GlobalState,
        error_handler<I, S, GlobalState> ErrorHandler>
#else
    template<
        typename I,
        typename S,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename Enable = std::enable_if_t<
            detail::is_parsable_iter_v<I> &&
            detail::is_equality_comparable_with_v<I, S>>>
#endif
    auto prefix_parse(
        I & first,
        S last,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        trace trace_mode = trace::off)
    {
        if constexpr (!detail::is_char8_iter_v<I>) {
            if (trace_mode == trace::on) {
                return detail::parse_impl<true>(
                    first, last, parser, parser.error_handler_);
            } else {
                return detail::parse_impl<false>(
                    first, last, parser, parser.error_handler_);
            }
        } else {
            auto r =
                BOOST_PARSER_SUBRANGE(first, last) | detail::text::as_utf32;
            auto f = r.begin();
            auto const l = r.end();
            auto _ = detail::scoped_base_assign(first, f);
            if (trace_mode == trace::on) {
                return detail::parse_impl<true>(
                    f, l, parser, parser.error_handler_);
            } else {
                return detail::parse_impl<false>(
                    f, l, parser, parser.error_handler_);
            }
        }
    }

    /** Parses `r` using `parser`.  Returns a `std::optional` containing the
        attribute produced by `parser` on parse success, and `std::nullopt` on
        parse failure.  The entire input range `r` must be consumed for the
        parse to be considered successful.  If `trace_mode == trace::on`, a
        verbose trace of the parse will be streamed to `std::cout`.

        \tparam ErrorHandler Constrained by `error_handler<ErrorHandler,std::ranges::iterator_t<decltype(subrange_of(r))>, std::ranges::sentinel_t<decltype(subrange_of(r))>, GlobalState>`,
            where `subrange_of` is an implementation detail that: creates
            subranges out of pointers; trims trailing zeros off of bounded
            arrays (such as string literals); and transcodes to UTF-32 if the
            input is non-`char`.
        \tparam Attr Constrained by
        `!detail::derived_from_parser_interface_v<std::remove_cvref_t<Attr>`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_range R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler>
#else
    template<
        typename R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename Enable = std::enable_if_t<detail::is_parsable_range_v<R>>>
#endif
    auto parse(
        R const & r,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        trace trace_mode = trace::off)
#if BOOST_PARSER_USE_CONCEPTS
        // clang-format off
        requires error_handler<
            ErrorHandler,
            std::ranges::iterator_t<decltype(detail::make_input_subrange(r))>,
            std::ranges::sentinel_t<decltype(detail::make_input_subrange(r))>,
            GlobalState>
    // clang-format on
#endif
    {
        auto r_ = detail::make_input_subrange(r);
        auto first = r_.begin();
        auto const last = r_.end();
        return detail::if_full_parse(
            first, last, parser::prefix_parse(first, last, parser, trace_mode));
    }

    /** Parses `[first, last)` using `parser`, skipping all input recognized
        by `skip` between the application of any two parsers, and returns
        whether the parse was successful.  On success, `attr` will be assigned
        the value of the attribute produced by `parser`.  If `trace_mode ==
        trace::on`, a verbose trace of the parse will be streamed to
        `std::cout`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_iter I,
        std::sentinel_for<I> S,
        typename Parser,
        typename GlobalState,
        error_handler<I, S, GlobalState> ErrorHandler,
        typename SkipParser,
        typename Attr>
#else
    template<
        typename I,
        typename S,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename SkipParser,
        typename Attr,
        typename Enable = std::enable_if_t<
            detail::is_parsable_iter_v<I> &&
            detail::is_equality_comparable_with_v<I, S>>>
#endif
    bool prefix_parse(
        I & first,
        S last,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        parser_interface<SkipParser> const & skip,
        Attr & attr,
        trace trace_mode = trace::off)
    {
        detail::attr_reset reset(attr);
        if constexpr (!detail::is_char8_iter_v<I>) {
            static_assert(
                decltype(detail::has_attribute(first, last, parser)){},
                "If you're seeing this error, you're trying to get parse() to "
                "fill in attr above, using the attribute generated by parser. "
                "However, parser does not generate an attribute.");
            if (trace_mode == trace::on) {
                return reset = detail::skip_parse_impl<true>(
                           first,
                           last,
                           parser,
                           skip,
                           parser.error_handler_,
                           attr);
            } else {
                return reset = detail::skip_parse_impl<false>(
                           first,
                           last,
                           parser,
                           skip,
                           parser.error_handler_,
                           attr);
            }
        } else {
            auto r =
                BOOST_PARSER_SUBRANGE(first, last) | detail::text::as_utf32;
            auto f = r.begin();
            auto const l = r.end();
            auto _ = detail::scoped_base_assign(first, f);
            static_assert(
                decltype(detail::has_attribute(f, l, parser)){},
                "If you're seeing this error, you're trying to get parse() to "
                "fill in attr above, using the attribute generated by parser. "
                "However, parser does not generate an attribute.");
            if (trace_mode == trace::on) {
                return reset = detail::skip_parse_impl<true>(
                           f, l, parser, skip, parser.error_handler_, attr);
            } else {
                return reset = detail::skip_parse_impl<false>(
                           f, l, parser, skip, parser.error_handler_, attr);
            }
        }
    }

    /** Parses `r` using `parser`, skipping all input recognized by `skip`
        between the application of any two parsers, and returns whether the
        parse was successful.  The entire input range `r` must be consumed for
        the parse to be considered successful.  On success, `attr` will be
        assigned the value of the attribute produced by `parser`.  If
        `trace_mode == trace::on`, a verbose trace of the parse will be
        streamed to `std::cout`.

        \tparam ErrorHandler Constrained by `error_handler<ErrorHandler,std::ranges::iterator_t<decltype(subrange_of(r))>, std::ranges::sentinel_t<decltype(subrange_of(r))>, GlobalState>`,
            where `subrange_of` is an implementation detail that: creates
            subranges out of pointers; trims trailing zeros off of bounded
            arrays (such as string literals); and transcodes to UTF-32 if the
            input is non-`char`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_range R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename SkipParser,
        typename Attr>
#else
    template<
        typename R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename SkipParser,
        typename Attr,
        typename Enable = std::enable_if_t<detail::is_parsable_range_v<R>>>
#endif
    bool parse(
        R const & r,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        parser_interface<SkipParser> const & skip,
        Attr & attr,
        trace trace_mode = trace::off)
#if BOOST_PARSER_USE_CONCEPTS
        // clang-format off
        requires error_handler<
            ErrorHandler,
            std::ranges::iterator_t<decltype(detail::make_input_subrange(r))>,
            std::ranges::sentinel_t<decltype(detail::make_input_subrange(r))>,
            GlobalState>
    // clang-format on
#endif
    {
        detail::attr_reset reset(attr);
        auto r_ = detail::make_input_subrange(r);
        auto first = r_.begin();
        auto const last = r_.end();
        return reset = detail::if_full_parse(
                   first,
                   last,
                   parser::prefix_parse(
                       first, last, parser, skip, attr, trace_mode));
    }

    /** Parses `[first, last)` using `parser`, skipping all input recognized
        by `skip` between the application of any two parsers.  Returns a
        `std::optional` containing the attribute produced by `parser` on parse
        success, and `std::nullopt` on parse failure.  If `trace_mode ==
        trace::on`, a verbose trace of the parse will be streamed to
        `std::cout`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_iter I,
        std::sentinel_for<I> S,
        typename Parser,
        typename GlobalState,
        error_handler<I, S, GlobalState> ErrorHandler,
        typename SkipParser>
#else
    template<
        typename I,
        typename S,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename SkipParser,
        typename Enable = std::enable_if_t<
            detail::is_parsable_iter_v<I> &&
            detail::is_equality_comparable_with_v<I, S>>>
#endif
    auto prefix_parse(
        I & first,
        S last,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        parser_interface<SkipParser> const & skip,
        trace trace_mode = trace::off)
    {
        if constexpr (!detail::is_char8_iter_v<I>) {
            if (trace_mode == trace::on) {
                return detail::skip_parse_impl<true>(
                    first, last, parser, skip, parser.error_handler_);
            } else {
                return detail::skip_parse_impl<false>(
                    first, last, parser, skip, parser.error_handler_);
            }
        } else {
            auto r =
                BOOST_PARSER_SUBRANGE(first, last) | detail::text::as_utf32;
            auto f = r.begin();
            auto const l = r.end();
            auto _ = detail::scoped_base_assign(first, f);
            if (trace_mode == trace::on) {
                return detail::skip_parse_impl<true>(
                    f, l, parser, skip, parser.error_handler_);
            } else {
                return detail::skip_parse_impl<false>(
                    f, l, parser, skip, parser.error_handler_);
            }
        }
    }

    /** Parses `r` using `parser`, skipping all input recognized by `skip`
        between the application of any two parsers.  Returns a `std::optional`
        containing the attribute produced by `parser` on parse success, and
        `std::nullopt` on parse failure.  The entire input range `r` must be
        consumed for the parse to be considered successful.  If `trace_mode ==
        trace::on`, a verbose trace of the parse will be streamed to
        `std::cout`.

        \tparam ErrorHandler Constrained by `error_handler<ErrorHandler,std::ranges::iterator_t<decltype(subrange_of(r))>, std::ranges::sentinel_t<decltype(subrange_of(r))>, GlobalState>`,
            where `subrange_of` is an implementation detail that: creates
            subranges out of pointers; trims trailing zeros off of bounded
            arrays (such as string literals); and transcodes to UTF-32 if the
            input is non-`char`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_range R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename SkipParser>
#else
    template<
        typename R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename SkipParser,
        typename Enable = std::enable_if_t<detail::is_parsable_range_v<R>>>
#endif
    auto parse(
        R const & r,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        parser_interface<SkipParser> const & skip,
        trace trace_mode = trace::off)
#if BOOST_PARSER_USE_CONCEPTS
        // clang-format off
        requires error_handler<
            ErrorHandler,
            std::ranges::iterator_t<decltype(detail::make_input_subrange(r))>,
            std::ranges::sentinel_t<decltype(detail::make_input_subrange(r))>,
            GlobalState>
    // clang-format on
#endif
    {
        auto r_ = detail::make_input_subrange(r);
        auto first = r_.begin();
        auto const last = r_.end();
        return detail::if_full_parse(
            first,
            last,
            parser::prefix_parse(first, last, parser, skip, trace_mode));
    }

    /** Parses `[first, last)` using `parser`, and returns whether the parse
        was successful.  When a callback rule `r` is successful during the
        parse, one of two things happens: 1) if `r` has an attribute,
        `callbacks(tag, x)` will be called (where `tag` is
        `decltype(r)::tag_type{}`, and `x` is the attribute produced by `r`);
        or 2) if `r` has no attribute, `callbacks(tag)` will be called.
        `Callbacks` is expected to be an invocable with the correct overloads
        required to support all successful rule parses that might occur.  If
        `trace_mode == trace::on`, a verbose trace of the parse will be
        streamed to `std::cout`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_iter I,
        std::sentinel_for<I> S,
        typename Parser,
        typename GlobalState,
        error_handler<I, S, GlobalState> ErrorHandler,
        typename Callbacks>
#else
    template<
        typename I,
        typename S,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename Callbacks,
        typename Enable = std::enable_if_t<
            detail::is_parsable_iter_v<I> &&
            detail::is_equality_comparable_with_v<I, S>>>
#endif
    bool callback_prefix_parse(
        I & first,
        S last,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        Callbacks const & callbacks,
        trace trace_mode = trace::off)
    {
        if constexpr (!detail::is_char8_iter_v<I>) {
            if (trace_mode == trace::on) {
                return detail::callback_parse_impl<true>(
                    first, last, parser, parser.error_handler_, callbacks);
            } else {
                return detail::callback_parse_impl<false>(
                    first, last, parser, parser.error_handler_, callbacks);
            }
        } else {
            auto r =
                BOOST_PARSER_SUBRANGE(first, last) | detail::text::as_utf32;
            auto f = r.begin();
            auto const l = r.end();
            auto _ = detail::scoped_base_assign(first, f);
            if (trace_mode == trace::on) {
                return detail::callback_parse_impl<true>(
                    f, l, parser, parser.error_handler_, callbacks);
            } else {
                return detail::callback_parse_impl<false>(
                    f, l, parser, parser.error_handler_, callbacks);
            }
        }
    }

    /** Parses `r` using `parser`, and returns whether the parse was
        successful.  The entire input range `r` must be consumed for the parse
        to be considered successful.  When a callback rule `r` is successful
        during the parse, one of two things happens: 1) if `r` has an
        attribute, `callbacks(tag, x)` will be called (where `tag` is
        `decltype(r)::tag_type{}`, and `x` is the attribute produced by `r`);
        or 2) if `r` has no attribute, `callbacks(tag)` will be called.
        `Callbacks` is expected to be an invocable with the correct overloads
        required to support all successful rule parses that might occur.  If
        `trace_mode == trace::on`, a verbose trace of the parse will be
        streamed to `std::cout`.

        \tparam ErrorHandler Constrained by `error_handler<ErrorHandler,std::ranges::iterator_t<decltype(subrange_of(r))>, std::ranges::sentinel_t<decltype(subrange_of(r))>, GlobalState>`,
            where `subrange_of` is an implementation detail that: creates
            subranges out of pointers; trims trailing zeros off of bounded
            arrays (such as string literals); and transcodes to UTF-32 if the
            input is non-`char`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_range R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename Callbacks>
#else
    template<
        typename R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename Callbacks,
        typename Enable = std::enable_if_t<detail::is_parsable_range_v<R>>>
#endif
    bool callback_parse(
        R const & r,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        Callbacks const & callbacks,
        trace trace_mode = trace::off)
#if BOOST_PARSER_USE_CONCEPTS
        // clang-format off
        requires error_handler<
            ErrorHandler,
            std::ranges::iterator_t<decltype(detail::make_input_subrange(r))>,
            std::ranges::sentinel_t<decltype(detail::make_input_subrange(r))>,
            GlobalState>
    // clang-format on
#endif
    {
        auto r_ = detail::make_input_subrange(r);
        auto first = r_.begin();
        auto const last = r_.end();
        return detail::if_full_parse(
            first,
            last,
            parser::callback_prefix_parse(first, last, parser, callbacks));
    }

    /** Parses `[first, last)` using `parser`, skipping all input recognized
        by `skip` between the application of any two parsers, and returns
        whether the parse was successful.  When a callback rule `r` is
        successful during the parse, one of two things happens: 1) if `r` has
        an attribute, `callbacks(tag, x)` will be called (where `tag` is
        `decltype(r)::tag_type{}`, and `x` is the attribute produced by `r`);
        or 2) if `r` has no attribute, `callbacks(tag)` will be called.
        `Callbacks` is expected to be an invocable with the correct overloads
        required to support all successful rule parses that might occur.  If
        `trace_mode == trace::on`, a verbose trace of the parse will be
        streamed to `std::cout`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_iter I,
        std::sentinel_for<I> S,
        typename Parser,
        typename GlobalState,
        error_handler<I, S, GlobalState> ErrorHandler,
        typename SkipParser,
        typename Callbacks>
#else
    template<
        typename I,
        typename S,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename SkipParser,
        typename Callbacks,
        typename Enable = std::enable_if_t<
            detail::is_parsable_iter_v<I> &&
            detail::is_equality_comparable_with_v<I, S>>>
#endif
    bool callback_prefix_parse(
        I & first,
        S last,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        parser_interface<SkipParser> const & skip,
        Callbacks const & callbacks,
        trace trace_mode = trace::off)
    {
        if constexpr (!detail::is_char8_iter_v<I>) {
            if (trace_mode == trace::on) {
                return detail::callback_skip_parse_impl<true>(
                    first,
                    last,
                    parser,
                    skip,
                    parser.error_handler_,
                    callbacks);
            } else {
                return detail::callback_skip_parse_impl<false>(
                    first,
                    last,
                    parser,
                    skip,
                    parser.error_handler_,
                    callbacks);
            }
        } else {
            auto r =
                BOOST_PARSER_SUBRANGE(first, last) | detail::text::as_utf32;
            auto f = r.begin();
            auto const l = r.end();
            auto _ = detail::scoped_base_assign(first, f);
            if (trace_mode == trace::on) {
                return detail::callback_skip_parse_impl<true>(
                    f, l, parser, skip, parser.error_handler_, callbacks);
            } else {
                return detail::callback_skip_parse_impl<false>(
                    f, l, parser, skip, parser.error_handler_, callbacks);
            }
        }
    }

    /** Parses `r` using `parser`, skipping all input recognized by `skip`
        between the application of any two parsers, and returns whether the
        parse was successful.  The entire input range `r` must be consumed for
        the parse to be considered successful.  When a callback rule `r` is
        successful during the parse, one of two things happens: 1) if `r` has
        an attribute, `callbacks(tag, x)` will be called (where `tag` is
        `decltype(r)::tag_type{}`, and `x` is the attribute produced by `r`);
        or 2) if `r` has no attribute, `callbacks(tag)` will be called.
        `Callbacks` is expected to be an invocable with the correct overloads
        required to support all successful rule parses that might occur.  If
        `trace_mode == trace::on`, a verbose trace of the parse will be
        streamed to `std::cout`.

        \tparam ErrorHandler Constrained by `error_handler<ErrorHandler,std::ranges::iterator_t<decltype(subrange_of(r))>, std::ranges::sentinel_t<decltype(subrange_of(r))>, GlobalState>`,
            where `subrange_of` is an implementation detail that: creates
            subranges out of pointers; trims trailing zeros off of bounded
            arrays (such as string literals); and transcodes to UTF-32 if the
            input is non-`char`. */
#if BOOST_PARSER_USE_CONCEPTS
    template<
        parsable_range R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename SkipParser,
        typename Callbacks>
#else
    template<
        typename R,
        typename Parser,
        typename GlobalState,
        typename ErrorHandler,
        typename SkipParser,
        typename Callbacks,
        typename Enable = std::enable_if_t<detail::is_parsable_range_v<R>>>
#endif
    bool callback_parse(
        R const & r,
        parser_interface<Parser, GlobalState, ErrorHandler> const & parser,
        parser_interface<SkipParser> const & skip,
        Callbacks const & callbacks,
        trace trace_mode = trace::off)
#if BOOST_PARSER_USE_CONCEPTS
        // clang-format off
        requires error_handler<
            ErrorHandler,
            std::ranges::iterator_t<decltype(detail::make_input_subrange(r))>,
            std::ranges::sentinel_t<decltype(detail::make_input_subrange(r))>,
            GlobalState>
    // clang-format on
#endif
    {
        auto r_ = detail::make_input_subrange(r);
        auto first = r_.begin();
        auto const last = r_.end();
        return detail::if_full_parse(
            first,
            last,
            parser::callback_prefix_parse(
                first, last, parser, skip, callbacks, trace_mode));
    }

    namespace literals {
        /** Returns a literal parser equivalent to `lit(c)`. */
        constexpr auto operator""_l(char c) { return parser::lit(c); }
#if defined(__cpp_char8_t) || defined(BOOST_PARSER_DOXYGEN)
        /** Returns a literal parser equivalent to `lit(c)`. */
        constexpr auto operator""_l(char8_t c) { return parser::lit(c); }
#endif
        /** Returns a literal parser equivalent to `lit(c)`. */
        constexpr auto operator""_l(char32_t c) { return parser::lit(c); }
        /** Returns a literal parser equivalent to `lit(str)`. */
        constexpr auto operator""_l(char const * str, std::size_t)
        {
            return parser::lit(str);
        }
#if defined(__cpp_char8_t) || defined(BOOST_PARSER_DOXYGEN)
        /** Returns a literal parser equivalent to `lit(str)`. */
        constexpr auto operator""_l(char8_t const * str, std::size_t)
        {
            return parser::lit(str);
        }
#endif
        /** Returns a literal parser equivalent to `lit(str)`. */
        constexpr auto operator""_l(char32_t const * str, std::size_t)
        {
            return parser::lit(str);
        }

        /** Returns a character parser equivalent to `char_(c)`. */
        constexpr auto operator""_p(char c) { return char_(c); }
#if defined(__cpp_char8_t) || defined(BOOST_PARSER_DOXYGEN)
        /** Returns a character parser equivalent to `char_(c)`. */
        constexpr auto operator""_p(char8_t c) { return char_(c); }
#endif
        /** Returns a character parser equivalent to `char_(c)`. */
        constexpr auto operator""_p(char32_t c) { return char_(c); }
        /** Returns a string parser equivalent to `string(str)`. */
        constexpr auto operator""_p(char const * str, std::size_t)
        {
            return parser::string(str);
        }
#if defined(__cpp_char8_t) || defined(BOOST_PARSER_DOXYGEN)
        /** Returns a string parser equivalent to `string(str)`. */
        constexpr auto operator""_p(char8_t const * str, std::size_t)
        {
            return parser::string(str);
        }
#endif
        /** Returns a string parser equivalent to `string(str)`. */
        constexpr auto operator""_p(char32_t const * str, std::size_t)
        {
            return parser::string(str);
        }
    }

    namespace detail {
        template<typename R, typename Parser>
        struct attribute_impl
        {
            using parser_type = typename Parser::parser_type;
            using global_state_type = typename Parser::global_state_type;
            using error_handler_type = typename Parser::error_handler_type;

            using iterator = detail::iterator_t<R>;
            using sentinel = detail::sentinel_t<R>;

            using context = decltype(detail::make_context<false, false>(
                std::declval<iterator>(),
                std::declval<sentinel>(),
                std::declval<bool &>(),
                std::declval<int &>(),
                std::declval<error_handler_type>(),
                std::declval<global_state_type &>(),
                std::declval<detail::symbol_table_tries_t &>(),
                std::declval<detail::pending_symbol_table_operations_t &>()));
            using type = decltype(std::declval<Parser>()(
                std::declval<iterator &>(),
                std::declval<sentinel>(),
                std::declval<context>(),
                detail::null_parser{},
                detail::flags::gen_attrs,
                std::declval<bool &>()));
        };

        template<typename Iter, typename Sentinel, typename Parser>
        auto has_attribute(Iter first, Sentinel last, Parser parser)
        {
            using attr_t = typename attribute_impl<
                BOOST_PARSER_SUBRANGE<Iter, Sentinel>,
                Parser>::type;
            return std::integral_constant<bool, !is_nope_v<attr_t>>{};
        }

        template<typename T>
        constexpr wrapper<T> attr_wrapped_final;
        template<>
        inline constexpr wrapper<none> attr_wrapped_final<nope>;
    }

    template<typename R, typename Parser>
    struct attribute
    {
        using initial_type = typename detail::attribute_impl<
            decltype(detail::make_input_subrange(std::declval<R>())),
            Parser>::type;
        using type =
            typename decltype(detail::attr_wrapped_final<initial_type>)::type;
    };


    namespace detail {
        template<typename... Args>
        constexpr void static_assert_merge_attributes(tuple<Args...> parsers)
        {
            // This code chokes older GCCs.  I can't figure out why, and this
            // is an optional check, so I'm disabling it for those GCCs.
#if !defined(__GNUC__) || 13 <= __GNUC__
            using context_t = parse_context<
                false,
                false,
                char const *,
                char const *,
                default_error_handler>;
            using skipper_t = parser_interface<ws_parser<false, false>>;
            using use_parser_t = dummy_use_parser_t<
                char const *,
                char const *,
                context_t,
                skipper_t> const;
            using all_types =
                decltype(hl::transform(parsers, std::declval<use_parser_t>()));
            auto all_types_wrapped = hl::transform(all_types{}, detail::wrap{});
            auto first_non_nope = hl::fold_left(
                all_types_wrapped,
                wrapper<nope>{},
                [=](auto result, auto type) {
                    if constexpr (is_nope_v<typename decltype(result)::type>) {
                        return type;
                    } else {
                        return result;
                    }
                });
            using first_t = typename decltype(first_non_nope)::type;
            static_assert(
                !is_nope_v<first_t>,
                "It looks like you wrote merge[p1 >> p2 >> ... pn], and none "
                "of the parsers p1, p2, ... pn produces an attribute.  Please "
                "fix.");
            if constexpr (is_nope_v<first_t>) {
                [[maybe_unused]] detail::print_type<tuple<Args...>> tuple_types;
                [[maybe_unused]] detail::print_type<all_types> attribute_types;
            }
            hl::for_each(all_types_wrapped, [=](auto type) {
                using t = typename decltype(type)::type;
                if constexpr (!is_nope_v<t>) {
                    static_assert(
                        std::is_same_v<t, first_t>,
                        "If you see an error here, you wrote merge[p1 >> "
                        "p2 >> ... pn] where at least one of the types in "
                        "ATTR(p1), ATTR(p2), ... ATTR(pn) is not the same "
                        "type as one of the others.");
                    if constexpr (!std::is_same_v<t, first_t>) {
                        [[maybe_unused]] detail::print_type<tuple<Args...>>
                            tuple_types;
                        [[maybe_unused]] detail::print_type<all_types>
                            attribute_types;
                        [[maybe_unused]] detail::print_type<first_t> first_type;
                        [[maybe_unused]] detail::print_type<t> this_type;
                    }
                }
            });
#endif
        }
    }
}}

#endif