TLA 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/buffer_slice.hpp>
16 : #include <coroutine>
17 : #include <boost/capy/ex/io_env.hpp>
18 :
19 : #include <algorithm>
20 : #include <cstddef>
21 : #include <type_traits>
22 : #include <utility>
23 :
24 : namespace boost {
25 : namespace capy {
26 : namespace test {
27 :
28 : /** A test utility for iterating buffer sequence split points.
29 :
30 : This class iterates through all possible ways to split a buffer
31 : sequence into two parts (b1, b2) where concatenating them yields
32 : the original sequence. It uses an async-generator-like pattern
33 : that allows `co_await` between iterations.
34 :
35 : The split type automatically preserves mutability: passing a
36 : `MutableBufferSequence` yields halves that model
37 : @ref MutableBufferSequence, while passing a `ConstBufferSequence`
38 : yields halves that model @ref ConstBufferSequence. Each half is
39 : the buffer-sequence view exposed by a @ref buffer_slice over the
40 : corresponding byte range, and can be passed directly to
41 : `read_some`, `write_some`, `buffer_size`, etc.
42 :
43 : @par Thread Safety
44 : Not thread-safe.
45 :
46 : @par Example
47 : @code
48 : // Test all split points of a buffer
49 : std::string data = "hello world";
50 : auto cb = make_buffer( data );
51 :
52 : fuse f;
53 : auto r = f.inert( [&]( fuse& ) -> task<> {
54 : bufgrind bg( cb );
55 : while( bg ) {
56 : auto [b1, b2] = co_await bg.next();
57 : // b1 contains first N bytes (as a buffer sequence)
58 : // b2 contains remaining bytes (as a buffer sequence)
59 : // concatenating b1 + b2 equals original
60 : co_await some_async_operation( b1, b2 );
61 : }
62 : } );
63 : @endcode
64 :
65 : @par Mutable Buffer Example
66 : @code
67 : // Mutable buffers preserve mutability
68 : char data[100];
69 : mutable_buffer mb( data, sizeof( data ) );
70 :
71 : bufgrind bg( mb );
72 : while( bg ) {
73 : auto [b1, b2] = co_await bg.next();
74 : // b1, b2 yield mutable_buffer when iterated
75 : }
76 : @endcode
77 :
78 : @par Step Size Example
79 : @code
80 : // Skip by 10 bytes for faster iteration
81 : bufgrind bg( cb, 10 );
82 : while( bg ) {
83 : auto [b1, b2] = co_await bg.next();
84 : // Visits positions 0, 10, 20, ..., and always size
85 : }
86 : @endcode
87 :
88 : @see buffer_slice
89 : */
90 : template<ConstBufferSequence BS>
91 : class bufgrind
92 : {
93 : BS const& bs_;
94 : std::size_t size_;
95 : std::size_t step_;
96 : std::size_t pos_ = 0;
97 :
98 : public:
99 : /// The slice type produced for each half of a split.
100 : using slice_type = std::decay_t<
101 : decltype(buffer_slice(std::declval<BS const&>()))>;
102 :
103 : /// The type returned by @ref next. Each half is a Slice; use
104 : /// `.data()` to obtain the buffer sequence view.
105 : using split_type = std::pair<slice_type, slice_type>;
106 :
107 : /** Construct a buffer grinder.
108 :
109 : @param bs The buffer sequence to iterate over.
110 :
111 : @param step The number of bytes to advance on each call to
112 : @ref next. A value of 0 is treated as 1. The final split
113 : at `buffer_size( bs )` is always included regardless of
114 : step alignment.
115 : */
116 : explicit
117 HIT 178 : bufgrind(
118 : BS const& bs,
119 : std::size_t step = 1) noexcept
120 178 : : bs_(bs)
121 178 : , size_(buffer_size(bs))
122 178 : , step_(step > 0 ? step : 1)
123 : {
124 178 : }
125 :
126 : /** Check if more split points remain.
127 :
128 : @return `true` if @ref next can be called, `false` otherwise.
129 : */
130 986 : explicit operator bool() const noexcept
131 : {
132 986 : return pos_ <= size_;
133 : }
134 :
135 : /** Awaitable returned by @ref next.
136 : */
137 : struct next_awaitable
138 : {
139 : bufgrind* self_;
140 :
141 944 : bool await_ready() const noexcept { return true; }
142 MIS 0 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> h, io_env const*) const noexcept { return h; }
143 :
144 : split_type
145 HIT 944 : await_resume()
146 : {
147 944 : split_type result{
148 944 : buffer_slice(self_->bs_, 0, self_->pos_),
149 944 : buffer_slice(self_->bs_, self_->pos_)
150 : };
151 944 : if(self_->pos_ < self_->size_)
152 888 : self_->pos_ = (std::min)(self_->pos_ + self_->step_, self_->size_);
153 : else
154 56 : ++self_->pos_;
155 944 : return result;
156 : }
157 : };
158 :
159 : /** Return the next split point.
160 :
161 : Returns an awaitable that yields the current (b1, b2) pair
162 : and advances to the next split point.
163 :
164 : @par Preconditions
165 : `static_cast<bool>( *this )` is `true`.
166 :
167 : @return An awaitable that await-returns `split_type`.
168 : */
169 : next_awaitable
170 944 : next() noexcept
171 : {
172 944 : return {this};
173 : }
174 : };
175 :
176 : } // test
177 : } // capy
178 : } // boost
179 :
180 : #endif
|