Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_TEST_BUFGRIND_HPP
11 : #define BOOST_CAPY_TEST_BUFGRIND_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/buffers.hpp>
15 : #include <boost/capy/buffers/slice.hpp>
16 : #include <coroutine>
17 : #include <boost/capy/ex/io_env.hpp>
18 :
19 : #include <algorithm>
20 : #include <cstddef>
21 : #include <utility>
22 :
23 : namespace boost {
24 : namespace capy {
25 : namespace test {
26 :
27 : /** A test utility for iterating buffer sequence split points.
28 :
29 : This class iterates through all possible ways to split a buffer
30 : sequence into two parts (b1, b2) where concatenating them yields
31 : the original sequence. It uses an async-generator-like pattern
32 : that allows `co_await` between iterations.
33 :
34 : The split type automatically preserves mutability: passing a
35 : `MutableBufferSequence` yields mutable slices, while passing a
36 : `ConstBufferSequence` yields const slices. This is handled
37 : automatically through `slice_type<BS>`.
38 :
39 : @par Thread Safety
40 : Not thread-safe.
41 :
42 : @par Example
43 : @code
44 : // Test all split points of a buffer
45 : std::string data = "hello world";
46 : auto cb = make_buffer( data );
47 :
48 : fuse f;
49 : auto r = f.inert( [&]( fuse& ) -> task<> {
50 : bufgrind bg( cb );
51 : while( bg ) {
52 : auto [b1, b2] = co_await bg.next();
53 : // b1 contains first N bytes
54 : // b2 contains remaining bytes
55 : // concatenating b1 + b2 equals original
56 : co_await some_async_operation( b1, b2 );
57 : }
58 : } );
59 : @endcode
60 :
61 : @par Mutable Buffer Example
62 : @code
63 : // Mutable buffers preserve mutability
64 : char data[100];
65 : mutable_buffer mb( data, sizeof( data ) );
66 :
67 : bufgrind bg( mb );
68 : while( bg ) {
69 : auto [b1, b2] = co_await bg.next();
70 : // b1, b2 yield mutable_buffer when iterated
71 : }
72 : @endcode
73 :
74 : @par Step Size Example
75 : @code
76 : // Skip by 10 bytes for faster iteration
77 : bufgrind bg( cb, 10 );
78 : while( bg ) {
79 : auto [b1, b2] = co_await bg.next();
80 : // Visits positions 0, 10, 20, ..., and always size
81 : }
82 : @endcode
83 :
84 : @see prefix, sans_prefix, slice_type
85 : */
86 : template<ConstBufferSequence BS>
87 : class bufgrind
88 : {
89 : BS const& bs_;
90 : std::size_t size_;
91 : std::size_t step_;
92 : std::size_t pos_ = 0;
93 :
94 : public:
95 : /// The type returned by @ref next.
96 : using split_type = std::pair<slice_type<BS>, slice_type<BS>>;
97 :
98 : /** Construct a buffer grinder.
99 :
100 : @param bs The buffer sequence to iterate over.
101 :
102 : @param step The number of bytes to advance on each call to
103 : @ref next. A value of 0 is treated as 1. The final split
104 : at `buffer_size( bs )` is always included regardless of
105 : step alignment.
106 : */
107 : explicit
108 178 : bufgrind(
109 : BS const& bs,
110 : std::size_t step = 1) noexcept
111 178 : : bs_(bs)
112 178 : , size_(buffer_size(bs))
113 178 : , step_(step > 0 ? step : 1)
114 : {
115 178 : }
116 :
117 : /** Check if more split points remain.
118 :
119 : @return `true` if @ref next can be called, `false` otherwise.
120 : */
121 986 : explicit operator bool() const noexcept
122 : {
123 986 : return pos_ <= size_;
124 : }
125 :
126 : /** Awaitable returned by @ref next.
127 : */
128 : struct next_awaitable
129 : {
130 : bufgrind* self_;
131 :
132 944 : bool await_ready() const noexcept { return true; }
133 0 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> h, io_env const*) const noexcept { return h; }
134 :
135 : split_type
136 944 : await_resume()
137 : {
138 944 : auto b1 = prefix(self_->bs_, self_->pos_);
139 944 : auto b2 = sans_prefix(self_->bs_, self_->pos_);
140 944 : if(self_->pos_ < self_->size_)
141 888 : self_->pos_ = (std::min)(self_->pos_ + self_->step_, self_->size_);
142 : else
143 56 : ++self_->pos_;
144 944 : return {std::move(b1), std::move(b2)};
145 : }
146 : };
147 :
148 : /** Return the next split point.
149 :
150 : Returns an awaitable that yields the current (b1, b2) pair
151 : and advances to the next split point.
152 :
153 : @par Preconditions
154 : `static_cast<bool>( *this )` is `true`.
155 :
156 : @return An awaitable yielding `split_type`.
157 : */
158 : next_awaitable
159 944 : next() noexcept
160 : {
161 944 : return {this};
162 : }
163 : };
164 :
165 : } // test
166 : } // capy
167 : } // boost
168 :
169 : #endif
|