From afa580efa6391b0c98612b55c230b94f35898989 Mon Sep 17 00:00:00 2001 From: Alan de Freitas Date: Thu, 6 Nov 2025 23:10:42 -0500 Subject: [PATCH 1/2] ci: update to cpp-actions v1.9.0 in CI --- .github/workflows/ci.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41be1a587..0e4549476 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: matrix: ${{ steps.cpp-matrix.outputs.matrix }} steps: - name: Generate Test Matrix - uses: alandefreitas/cpp-actions/cpp-matrix@v1.8.10 + uses: alandefreitas/cpp-actions/cpp-matrix@v1.9.0 id: cpp-matrix with: compilers: | @@ -111,7 +111,7 @@ jobs: uses: actions/checkout@v4 - name: Setup C++ - uses: alandefreitas/cpp-actions/setup-cpp@v1.8.10 + uses: alandefreitas/cpp-actions/setup-cpp@v1.9.0 id: setup-cpp with: compiler: ${{ matrix.compiler }} @@ -119,13 +119,13 @@ jobs: - name: Install packages if: matrix.install != '' - uses: alandefreitas/cpp-actions/package-install@v1.8.10 + uses: alandefreitas/cpp-actions/package-install@v1.9.0 id: package-install with: apt-get: ${{ matrix.install }} - name: Clone Boost - uses: alandefreitas/cpp-actions/boost-clone@v1.8.10 + uses: alandefreitas/cpp-actions/boost-clone@v1.9.0 id: boost-clone with: branch: ${{ (github.ref_name == 'master' && github.ref_name) || 'develop' }} @@ -203,7 +203,7 @@ jobs: corpus- - name: CMake Workflow - uses: alandefreitas/cpp-actions/cmake-workflow@v1.8.10 + uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.0 if: matrix.is-no-factor-intermediary != 'true' with: source-dir: ../boost-root @@ -226,7 +226,7 @@ jobs: trace-commands: true - name: CMake Integration Workflow - uses: alandefreitas/cpp-actions/cmake-workflow@v1.8.10 + uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.0 if: matrix.is-no-factor-intermediary != 'true' with: source-dir: ../boost-root/libs/${{ steps.patch.outputs.module }}/test/cmake_test @@ -244,7 +244,7 @@ jobs: trace-commands: true - name: CMake Root Workflow - uses: alandefreitas/cpp-actions/cmake-workflow@v1.8.10 + uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.0 if: matrix.is-no-factor-intermediary != 'true' with: source-dir: . @@ -263,7 +263,7 @@ jobs: trace-commands: true - name: B2 Workflow - uses: alandefreitas/cpp-actions/b2-workflow@v1.8.10 + uses: alandefreitas/cpp-actions/b2-workflow@v1.9.0 env: # Set flags via B2 options exclusively CFLAGS: '' @@ -291,7 +291,7 @@ jobs: warnings-as-errors: true - name: FlameGraph - uses: alandefreitas/cpp-actions/flamegraph@v1.8.10 + uses: alandefreitas/cpp-actions/flamegraph@v1.9.0 if: matrix.time-trace with: source-dir: ../boost-root/libs/url @@ -362,7 +362,7 @@ jobs: fetch-depth: 100 - name: Changelog - uses: alandefreitas/cpp-actions/create-changelog@v1.8.10 + uses: alandefreitas/cpp-actions/create-changelog@v1.9.0 with: thank-non-regular: ${{ startsWith(github.ref, 'refs/tags/') }} github-token: ${{ secrets.GITHUB_TOKEN }} @@ -385,7 +385,7 @@ jobs: shell: bash steps: - name: Install packages - uses: alandefreitas/cpp-actions/package-install@v1.8.10 + uses: alandefreitas/cpp-actions/package-install@v1.9.0 with: apt-get: git cmake @@ -393,7 +393,7 @@ jobs: uses: actions/checkout@v4 - name: Clone Boost - uses: alandefreitas/cpp-actions/boost-clone@v1.8.10 + uses: alandefreitas/cpp-actions/boost-clone@v1.9.0 id: boost-clone with: branch: ${{ (github.ref_name == 'master' && github.ref_name) || 'develop' }} From e00a164b17a53edbd26a198e942df50a2e3357d4 Mon Sep 17 00:00:00 2001 From: Alan de Freitas Date: Thu, 6 Nov 2025 22:33:38 -0500 Subject: [PATCH 2/2] feat: grammar::range user-provided RangeRule #943 --- doc/mrdocs.yml | 2 + .../boost/url/grammar/detail/range_rule.hpp | 95 ++++ include/boost/url/grammar/impl/range_rule.hpp | 449 ++++++++++++------ include/boost/url/grammar/range_rule.hpp | 223 +++++++-- test/unit/grammar/range_rule.cpp | 165 +++++++ 5 files changed, 727 insertions(+), 207 deletions(-) create mode 100644 include/boost/url/grammar/detail/range_rule.hpp diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index d53d4e843..9722780b7 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -41,6 +41,8 @@ implementation-defined: - 'boost::urls::string_token::*_t' exclude-symbols: - 'boost::urls::void_t' + # any_rule was previously excluded by the detail path rule + - 'boost::urls::any_rule' # Metadata Extraction private-bases: false diff --git a/include/boost/url/grammar/detail/range_rule.hpp b/include/boost/url/grammar/detail/range_rule.hpp new file mode 100644 index 000000000..3f5bfb58b --- /dev/null +++ b/include/boost/url/grammar/detail/range_rule.hpp @@ -0,0 +1,95 @@ +// +// Copyright (c) 2025 Alan de Freitas (vinnie dot falco at gmail dot com) +// +// 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) +// +// Official repository: https://github.com/boostorg/url +// + +#ifndef BOOST_URL_GRAMMAR_DETAIL_RANGE_RULE_HPP +#define BOOST_URL_GRAMMAR_DETAIL_RANGE_RULE_HPP + +#include +#include +#include + +namespace boost { +namespace urls { +namespace grammar { +namespace detail { + +template::value> +class range_base_storage; + +template +class range_base_storage +{ + RangeRule rule_; + +protected: + range_base_storage() = default; + + explicit + range_base_storage(RangeRule const& rule) + : rule_(rule) + { + } + + explicit + range_base_storage(RangeRule&& rule) + : rule_(std::move(rule)) + { + } + + RangeRule& + rule() noexcept + { + return rule_; + } + + RangeRule const& + rule() const noexcept + { + return rule_; + } +}; + +template +class range_base_storage + : private RangeRule +{ +protected: + range_base_storage() = default; + + explicit + range_base_storage(RangeRule const& rule) + : RangeRule(rule) + { + } + + explicit + range_base_storage(RangeRule&& rule) + : RangeRule(std::move(rule)) + { + } + + RangeRule& + rule() noexcept + { + return *this; + } + + RangeRule const& + rule() const noexcept + { + return *this; + } +}; + +} // detail +} // grammar +} // urls +} // boost + +#endif diff --git a/include/boost/url/grammar/impl/range_rule.hpp b/include/boost/url/grammar/impl/range_rule.hpp index 280b23eb0..484ab2a22 100644 --- a/include/boost/url/grammar/impl/range_rule.hpp +++ b/include/boost/url/grammar/impl/range_rule.hpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include // ::max_align_t @@ -26,28 +28,23 @@ namespace boost { namespace urls { namespace grammar { -// VFALCO This could be reused for -// other things that need to type-erase - //------------------------------------------------ // // any_rule // //------------------------------------------------ -// base class for the type-erased rule pair template -struct range:: - any_rule +struct any_rule::impl_base { virtual - ~any_rule() = default; + ~impl_base() = default; virtual void move(void* dest) noexcept { - ::new(dest) any_rule( + ::new(dest) impl_base( std::move(*this)); } @@ -55,7 +52,7 @@ struct range:: void copy(void* dest) const noexcept { - ::new(dest) any_rule(*this); + ::new(dest) impl_base(*this); } virtual @@ -82,8 +79,8 @@ struct range:: // small template template -struct range::impl1 - : any_rule +struct any_rule::impl1 + : impl_base , private empty_value { explicit @@ -139,8 +136,8 @@ struct range::impl1 // big template template -struct range::impl1 - : any_rule +struct any_rule::impl1 + : impl_base { explicit impl1(R const& next) noexcept @@ -215,8 +212,8 @@ struct range::impl1 template template< class R0, class R1, bool Small> -struct range::impl2 - : any_rule +struct any_rule::impl2 + : impl_base , private empty_value , private empty_value { @@ -278,8 +275,8 @@ struct range::impl2 template template< class R0, class R1> -struct range::impl2 - : any_rule +struct any_rule::impl2 + : impl_base { impl2( R0 const& first, @@ -351,14 +348,253 @@ struct range::impl2 } }; +//------------------------------------------------ + +template +typename any_rule::impl_base& +any_rule:: +get() noexcept +{ + return *reinterpret_cast< + impl_base*>(sb_.addr()); +} + +template +typename any_rule::impl_base const& +any_rule:: +get() const noexcept +{ + return *reinterpret_cast< + impl_base const*>(sb_.addr()); +} + + +template +any_rule:: +any_rule() noexcept +{ + ::new(sb_.addr()) impl_base{}; + char const* it = nullptr; + get().first(it, nullptr); + get().next(it, nullptr); +} + + +template +any_rule:: +any_rule(any_rule&& other) noexcept +{ + other.get().move(sb_.addr()); +} + + +template +any_rule:: +any_rule(any_rule const& other) noexcept +{ + other.get().copy(sb_.addr()); +} + + +template +any_rule& +any_rule:: +operator=(any_rule&& other) noexcept +{ + if(this == &other) + return *this; + get().~impl_base(); + other.get().move(sb_.addr()); + return *this; +} + + +template +any_rule& +any_rule:: +operator=(any_rule const& other) noexcept +{ + if(this == &other) + return *this; + get().~impl_base(); + other.get().copy(sb_.addr()); + return *this; +} + + +template +any_rule:: +~any_rule() +{ + get().~impl_base(); +} + + +template +template +any_rule:: +any_rule( + R const& next) +{ + static_assert( + ::boost::urls::grammar::is_rule::value, + "Rule requirements not met"); + static_assert( + std::is_same::value, + "Rule value_type mismatch"); + + BOOST_CORE_STATIC_ASSERT( + sizeof(impl1) <= + BufferSize); + + ::new(sb_.addr()) impl1) <= + BufferSize>(next); +} + +//------------------------------------------------ + +template +template< + class R0, class R1> +any_rule:: +any_rule( + R0 const& first, + R1 const& next) +{ + static_assert( + ::boost::urls::grammar::is_rule::value, + "Rule requirements not met"); + static_assert( + ::boost::urls::grammar::is_rule::value, + "Rule requirements not met"); + static_assert( + std::is_same::value, + "First rule value_type mismatch"); + static_assert( + std::is_same::value, + "Next rule value_type mismatch"); + + BOOST_CORE_STATIC_ASSERT( + sizeof(impl2) <= + BufferSize); + + ::new(sb_.addr()) impl2 + ) <= BufferSize>( + first, next); +} + +//------------------------------------------------ + +template +system::result +any_rule:: +first( + char const*& it, + char const* end) const noexcept +{ + return get().first(it, end); +} + +//------------------------------------------------ + +template +system::result +any_rule:: +next( + char const*& it, + char const* end) const noexcept +{ + return get().next(it, end); +} + +//------------------------------------------------ +// +// range +// +//------------------------------------------------ + +template +range:: +~range() = default; + +template +range:: +range() noexcept = default; + +template +range:: +range( + range&& other) noexcept + : detail::range_base_storage< + RangeRule>(std::move(other.rule())) + , s_(other.s_) + , n_(other.n_) +{ + other.s_ = {}; + other.n_ = 0; +} + +template +range:: +range( + range const& other) noexcept + : detail::range_base_storage< + RangeRule>(other.rule()) + , s_(other.s_) + , n_(other.n_) +{ +} + +template +auto +range:: +operator=(range&& other) noexcept + -> range& +{ + if(this == &other) + return *this; + static_cast< + detail::range_base_storage< + RangeRule>&>(*this) = + std::move(static_cast< + detail::range_base_storage< + RangeRule>&>(other)); + s_ = other.s_; + n_ = other.n_; + other.s_ = {}; + other.n_ = 0; + return *this; +} + +template +auto +range:: +operator=(range const& other) noexcept + -> range& +{ + if(this == &other) + return *this; + static_cast< + detail::range_base_storage< + RangeRule>&>(*this) = + static_cast< + detail::range_base_storage< + RangeRule> const&>(other); + s_ = other.s_; + n_ = other.n_; + return *this; +} + //------------------------------------------------ // // iterator // //------------------------------------------------ -template -class range:: +template +class range:: iterator { public: @@ -408,7 +644,7 @@ class range:: auto const end = r_->s_.data() + r_->s_.size(); - rv_ = r_->get().next(p_, end); + rv_ = r_->rule().next(p_, end); if( !rv_ ) p_ = nullptr; return *this; @@ -423,28 +659,28 @@ class range:: } private: - friend class range; + friend class range; - range const* r_ = nullptr; + range const* r_ = nullptr; char const* p_ = nullptr; system::result rv_; iterator( - range const& r) noexcept + range const& r) noexcept : r_(&r) , p_(r.s_.data()) { auto const end = r_->s_.data() + r_->s_.size(); - rv_ = r_->get().first(p_, end); + rv_ = r_->rule().first(p_, end); if( !rv_ ) p_ = nullptr; } constexpr iterator( - range const& r, + range const& r, int) noexcept : r_(&r) , p_(nullptr) @@ -454,146 +690,52 @@ class range:: //------------------------------------------------ -template -template -range:: -range( - core::string_view s, - std::size_t n, - R const& next) - : s_(s) - , n_(n) +template +typename range::iterator +range:: +begin() const noexcept { - BOOST_CORE_STATIC_ASSERT( - sizeof(impl1) <= - BufferSize); + return iterator(*this); +} - ::new(&get()) impl1) <= - BufferSize>(next); +//------------------------------------------------ + +template +typename range::iterator +range:: +end() const noexcept +{ + return iterator(*this, 0); } //------------------------------------------------ -template -template< - class R0, class R1> -range:: +template +range:: range( core::string_view s, std::size_t n, - R0 const& first, - R1 const& next) - : s_(s) + RangeRule const& rule) noexcept + : detail::range_base_storage< + RangeRule>(rule) + , s_(s) , n_(n) { - BOOST_CORE_STATIC_ASSERT( - sizeof(impl2) <= - BufferSize); - - ::new(&get()) impl2 - ) <= BufferSize>( - first, next); } //------------------------------------------------ -template -range:: -~range() -{ - get().~any_rule(); -} - -template -range:: -range() noexcept -{ - ::new(&get()) any_rule{}; - char const* it = nullptr; - get().first(it, nullptr); - get().next(it, nullptr); -} - -template -range:: -range( - range&& other) noexcept - : s_(other.s_) - , n_(other.n_) -{ - other.s_ = {}; - other.n_ = {}; - other.get().move(&get()); - other.get().~any_rule(); - ::new(&other.get()) any_rule{}; -} - -template -range:: +template +range:: range( - range const& other) noexcept - : s_(other.s_) - , n_(other.n_) -{ - other.get().copy(&get()); -} - -template -auto -range:: -operator=( - range&& other) noexcept -> - range& -{ - s_ = other.s_; - n_ = other.n_; - other.s_ = {}; - other.n_ = 0; - // VFALCO we rely on nothrow move - // construction here, but if necessary we - // could move to a local buffer first. - get().~any_rule(); - other.get().move(&get()); - other.get().~any_rule(); - ::new(&other.get()) any_rule{}; - return *this; -} - -template -auto -range:: -operator=( - range const& other) noexcept -> - range& -{ - s_ = other.s_; - n_ = other.n_; - // VFALCO we rely on nothrow copy - // construction here, but if necessary we - // could construct to a local buffer first. - get().~any_rule(); - other.get().copy(&get()); - return *this; -} - -template -auto -range:: -begin() const noexcept -> - iterator -{ - return { *this }; -} - -template -auto -range:: -end() const noexcept -> - iterator + core::string_view s, + std::size_t n, + RangeRule&& rule) noexcept + : detail::range_base_storage< + RangeRule>(std::move(rule)) + , s_(s) + , n_(n) { - return { *this, 0 }; } //------------------------------------------------ @@ -629,7 +771,7 @@ parse( // good return range( core::string_view(it0, it - it0), - n, next_); + n, any_rule(next_)); } for(;;) { @@ -662,7 +804,7 @@ parse( // good return range( core::string_view(it0, it - it0), - n, next_); + n, any_rule(next_)); } //------------------------------------------------ @@ -687,19 +829,16 @@ parse( { if(rv.error() != error::end_of_range) { - // rewind unless error::end_of_range it = it1; } if(n < N_) { - // too few BOOST_URL_RETURN_EC( error::mismatch); } - // good return range( core::string_view(it0, it - it0), - n, first_, next_); + n, any_rule(first_, next_)); } for(;;) { @@ -732,7 +871,7 @@ parse( // good return range( core::string_view(it0, it - it0), - n, first_, next_); + n, any_rule(first_, next_)); } } // grammar diff --git a/include/boost/url/grammar/range_rule.hpp b/include/boost/url/grammar/range_rule.hpp index 56776101f..9cf2f7a36 100644 --- a/include/boost/url/grammar/range_rule.hpp +++ b/include/boost/url/grammar/range_rule.hpp @@ -15,10 +15,12 @@ #include #include #include +#include #include #include #include #include +#include #include // ::max_align_t namespace boost { @@ -29,6 +31,143 @@ template struct range_rule_t; } // implementation_defined +namespace implementation_defined +{ +template +struct range_value_type +{ + using type = void; +}; + +template +struct range_value_type< + RangeRule, + urls::void_t> +{ + using type = typename RangeRule::value_type; +}; + +template +struct is_range_rule : std::false_type +{ +}; + +template +struct is_range_rule< + RangeRule, + ValueType, + urls::void_t< + decltype(std::declval().first( + std::declval(), + std::declval())), + decltype(std::declval().next( + std::declval(), + std::declval()))>> + : std::integral_constant().first( + std::declval(), + std::declval())), + system::result>::value && + std::is_same< + decltype(std::declval().next( + std::declval(), + std::declval())), + system::result>::value> +{ +}; +} + +template +using is_range_rule = implementation_defined::is_range_rule< + RangeRule, + typename implementation_defined::range_value_type< + RangeRule>::type>; + +#ifdef BOOST_URL_HAS_CONCEPTS +template +concept RangeRule = + requires (T r, char const*& it, char const* end) + { + typename T::value_type; + { r.first(it, end) } -> std::same_as>; + { r.next(it, end) } -> std::same_as>; + }; +#endif + +template +class any_rule; + +template +class any_rule +{ +public: + using value_type = T; + + any_rule() noexcept; + any_rule(any_rule const&) noexcept; + any_rule(any_rule&&) noexcept; + any_rule& operator=(any_rule const&) noexcept; + any_rule& operator=(any_rule&&) noexcept; + ~any_rule(); + + template + explicit + any_rule(R const& next); + + template + any_rule( + R0 const& first, + R1 const& next); + + system::result + first( + char const*& it, + char const* end) const noexcept; + + system::result + next( + char const*& it, + char const* end) const noexcept; + +private: + static constexpr + std::size_t BufferSize = 128; + + struct small_buffer + { + alignas(alignof(::max_align_t)) + unsigned char buf[BufferSize]; + + void const* addr() const noexcept + { + return buf; + } + + void* addr() noexcept + { + return buf; + } + }; + + struct impl_base; + + template + struct impl1; + + template< + class R0, class R1, bool> + struct impl2; + + impl_base& + get() noexcept; + + impl_base const& + get() const noexcept; + + small_buffer sb_; +}; + /** A forward range of parsed elements Objects of this type are forward ranges @@ -43,8 +182,9 @@ struct range_rule_t; @note - The implementation may use temporary, - recycled storage for type-erasure. Objects + The implementation may type-erase the + rule responsible for iterating the + underlying character buffer. Objects of type `range` are intended to be used ephemerally. That is, for short durations such as within a function scope. If it is @@ -54,81 +194,60 @@ struct range_rule_t; contents to an object of a different type. @tparam T The value type of the range + @tparam RangeRule The implementation used to + iterate the range. The default is a + type-erased rule. @see @ref parse, @ref range_rule. */ -template +template< + class T, + class RangeRule = any_rule> class range + : private detail::range_base_storage< + RangeRule> { - // buffer size for type-erased rule - static constexpr - std::size_t BufferSize = 128; +private: +#ifdef BOOST_URL_HAS_CONCEPTS + static_assert( + ::boost::urls::grammar::RangeRule, + "RangeRule requirements not met"); +#else + static_assert( + ::boost::urls::grammar::is_range_rule::value, + "RangeRule requirements not met"); +#endif - struct small_buffer - { - alignas(alignof(::max_align_t)) - unsigned char buf[BufferSize]; + static_assert( + std::is_class< + detail::range_base_storage< + RangeRule>>::value, + "range_base_storage requirements not met"); - void const* addr() const noexcept - { - return buf; - } + using storage_type = + detail::range_base_storage< + RangeRule>; - void* addr() noexcept - { - return buf; - } - }; + using storage_type::rule; - small_buffer sb_; core::string_view s_; std::size_t n_ = 0; - //-------------------------------------------- - - struct any_rule; - - template - struct impl1; - - template< - class R0, class R1, bool> - struct impl2; - template< class R0, class R1> friend struct implementation_defined::range_rule_t; - any_rule& - get() noexcept - { - return *reinterpret_cast< - any_rule*>(sb_.addr()); - } - - any_rule const& - get() const noexcept - { - return *reinterpret_cast< - any_rule const*>( - sb_.addr()); - } - - template range( core::string_view s, std::size_t n, - R const& r); + RangeRule const& rule) noexcept; - template< - class R0, class R1> range( core::string_view s, std::size_t n, - R0 const& first, - R1 const& next); + RangeRule&& rule) noexcept; public: /** The type of each element of the range diff --git a/test/unit/grammar/range_rule.cpp b/test/unit/grammar/range_rule.cpp index af7caeae1..1cc64cf3e 100644 --- a/test/unit/grammar/range_rule.cpp +++ b/test/unit/grammar/range_rule.cpp @@ -20,11 +20,89 @@ #include #include +#include namespace boost { namespace urls { namespace grammar { +struct stateless_range_rule +{ + using value_type = core::string_view; + + system::result + first( + char const*& it, + char const* end) const noexcept + { + if(it == end) + return error::mismatch; + return core::string_view(it, 0); + } + + system::result + next( + char const*&, + char const*) const noexcept + { + return error::end_of_range; + } +}; + +struct bad_range_rule +{ + using value_type = core::string_view; +}; + +struct missing_value_type_rule +{ + system::result + first( + char const*&, + char const*) const noexcept + { + return error::end_of_range; + } + + system::result + next( + char const*&, + char const*) const noexcept + { + return error::end_of_range; + } +}; + +static_assert( + is_range_rule>::value, + "any_rule must satisfy is_range_rule"); + +static_assert( + is_range_rule::value, + "custom range rule must satisfy is_range_rule"); + +static_assert( + ! is_range_rule::value, + "trait must reject incomplete implementations"); + +static_assert( + ! is_range_rule::value, + "trait must reject types without value_type"); + +#ifdef BOOST_URL_HAS_CONCEPTS +static_assert( + RangeRule>, + "any_rule must satisfy RangeRule concept"); + +static_assert( + RangeRule, + "custom range rule must satisfy RangeRule concept"); + +static_assert( + ! RangeRule, + "concept must reject incomplete implementations"); +#endif + struct range_rule_test { struct big_rule @@ -50,6 +128,27 @@ struct range_rule_test } }; + struct big_first_rule + { + char unused[4096]{}; + using value_type = core::string_view; + + system::result + parse( + char const*& it, + char const* end) const noexcept + { + if(it == end) + return error::mismatch; + if(*it == ';') + return error::mismatch; + auto const start = it; + while(it != end && *it != ';') + ++it; + return core::string_view(start, it - start); + } + }; + template static void @@ -211,6 +310,72 @@ struct range_rule_test bad(r, ";a;b;c;d;e", error::mismatch); } } + + // big any_rule copies (single rule) + { + constexpr auto big = range_rule( + big_rule{}, 1, 4); + auto v = parse(";a;b", big).value(); + range copy(v); + BOOST_TEST_EQ(copy.size(), v.size()); + + auto other = parse(";x", big).value(); + other = v; + BOOST_TEST_EQ(other.size(), v.size()); + } + + // big any_rule copies (first/next pair) + { + const auto big_pair = range_rule( + big_first_rule{}, + big_rule{}, + 1, 4); + + auto v = parse("a;b;c", big_pair).value(); + range copy(v); + BOOST_TEST_EQ(copy.size(), v.size()); + + auto other = parse("x;y", big_pair).value(); + other = v; + BOOST_TEST_EQ(other.size(), v.size()); + } + +#if defined(__clang__) && defined(__has_warning) +# if __has_warning("-Wself-assign-overloaded") +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wself-assign-overloaded" +# endif +#endif + + // any_rule self-assignment (copy) + { + any_rule ar(big_rule{}); + auto& copy_ref = (ar = ar); + BOOST_TEST(©_ref == &ar); + } + + // range self-assignment (copy) + { + auto v = parse(";a;b", r0).value(); + auto& copy_ref = (v = v); + BOOST_TEST(©_ref == &v); + } + +#if defined(__clang__) && defined(__has_warning) +# if __has_warning("-Wself-assign-overloaded") +# pragma clang diagnostic pop +# endif +#endif + + + // zero-match success path (default N == 0) + { + constexpr auto r = range_rule( + token_rule(alpha_chars)); + auto rv = parse("", r); + BOOST_TEST(rv.has_value()); + BOOST_TEST(rv->empty()); + } } void