konst/string/splitting.rs
1//! string splitting that requires Rust 1.64.0 to be efficient.
2
3use crate::{
4 iter::{IntoIterKind, IsIteratorKind},
5 string::{self, str_from, str_up_to},
6};
7
8use konst_macro_rules::iterator_shared;
9
10/// A const-equivalent of the [`str::split_once`] method.
11///
12/// This only accepts `&str` as the delimiter.
13///
14/// # Version compatibility
15///
16/// This requires the `"rust_1_64"` feature.
17///
18/// # Example
19///
20/// ```rust
21/// use konst::string;
22///
23/// assert_eq!(string::split_once("", "-"), None);
24/// assert_eq!(string::split_once("foo", "-"), None);
25/// assert_eq!(string::split_once("foo-", "-"), Some(("foo", "")));
26/// assert_eq!(string::split_once("foo-bar", "-"), Some(("foo", "bar")));
27/// assert_eq!(string::split_once("foo-bar-baz", "-"), Some(("foo", "bar-baz")));
28///
29/// ```
30#[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
31pub const fn split_once<'a>(this: &'a str, delim: &str) -> Option<(&'a str, &'a str)> {
32 if delim.is_empty() {
33 // using split_at so that the pointer points within the string
34 Some(string::split_at(this, 0))
35 } else {
36 crate::option::map! {
37 string::find(this, delim, 0),
38 |pos| (str_up_to(this, pos), str_from(this, pos + delim.len()))
39 }
40 }
41}
42
43/// A const-equivalent of the [`str::rsplit_once`] method.
44///
45/// This only accepts `&str` as the delimiter.
46///
47/// # Version compatibility
48///
49/// This requires the `"rust_1_64"` feature.
50///
51/// # Example
52///
53/// ```rust
54/// use konst::string;
55///
56/// assert_eq!(string::rsplit_once("", "-"), None);
57/// assert_eq!(string::rsplit_once("foo", "-"), None);
58/// assert_eq!(string::rsplit_once("foo-", "-"), Some(("foo", "")));
59/// assert_eq!(string::rsplit_once("foo-bar", "-"), Some(("foo", "bar")));
60/// assert_eq!(string::rsplit_once("foo-bar-baz", "-"), Some(("foo-bar", "baz")));
61///
62/// ```
63#[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
64pub const fn rsplit_once<'a>(this: &'a str, delim: &str) -> Option<(&'a str, &'a str)> {
65 if delim.is_empty() {
66 // using split_at so that the pointer points within the string
67 Some(string::split_at(this, this.len()))
68 } else {
69 crate::option::map! {
70 string::rfind(this, delim, this.len()),
71 |pos| (str_up_to(this, pos), str_from(this, pos + delim.len()))
72 }
73 }
74}
75
76//////////////////////////////////////////////////
77
78/// Const equivalent of [`str::split`], which only takes a `&str` delimiter.
79///
80/// # Version compatibility
81///
82/// This requires the `"rust_1_64"` feature.
83///
84/// # Example
85///
86/// ```rust
87/// use konst::string;
88/// use konst::iter::collect_const;
89///
90/// const STRS: [&str; 3] = collect_const!(&str => string::split("foo-bar-baz", "-"));
91///
92/// assert_eq!(STRS, ["foo", "bar", "baz"]);
93/// ```
94#[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
95pub const fn split<'a, 'b>(this: &'a str, delim: &'b str) -> Split<'a, 'b> {
96 Split {
97 this,
98 state: if delim.is_empty() {
99 State::Empty(EmptyState::Start)
100 } else {
101 State::Normal { delim }
102 },
103 }
104}
105
106/// Const equivalent of [`str::rsplit`], which only takes a `&str` delimiter.
107///
108/// # Version compatibility
109///
110/// This requires the `"rust_1_64"` feature.
111///
112/// # Example
113///
114/// ```rust
115/// use konst::string;
116/// use konst::iter::collect_const;
117///
118/// const STRS: [&str; 3] = collect_const!(&str => string::rsplit("foo-bar-baz", "-"));
119///
120/// assert_eq!(STRS, ["baz", "bar", "foo"]);
121/// ```
122#[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
123pub const fn rsplit<'a, 'b>(this: &'a str, delim: &'b str) -> RSplit<'a, 'b> {
124 split(this, delim).rev()
125}
126
127#[derive(Copy, Clone)]
128enum State<'a> {
129 Normal { delim: &'a str },
130 Empty(EmptyState),
131 Finished,
132}
133
134#[derive(Copy, Clone)]
135enum EmptyState {
136 Start,
137 Continue,
138}
139
140macro_rules! split_shared {
141 (is_forward = $is_forward:ident) => {
142 const fn next_from_empty(mut self, es: EmptyState) -> Option<(&'a str, Self)> {
143 match es {
144 EmptyState::Start => {
145 self.state = State::Empty(EmptyState::Continue);
146 Some(("", self))
147 }
148 EmptyState::Continue => {
149 let this = self.this;
150
151 if this.is_empty() {
152 self.state = State::Finished;
153 }
154 let next_char = string::find_next_char_boundary(this.as_bytes(), 0);
155 let (next_char, rem) = string::split_at(this, next_char);
156 self.this = rem;
157 Some((next_char, self))
158 }
159 }
160 }
161
162 const fn next_back_from_empty(mut self, es: EmptyState) -> Option<(&'a str, Self)> {
163 match es {
164 EmptyState::Start => {
165 self.state = State::Empty(EmptyState::Continue);
166 Some(("", self))
167 }
168 EmptyState::Continue => {
169 let this = self.this;
170
171 if self.this.is_empty() {
172 self.state = State::Finished;
173 }
174 let next_char = string::find_prev_char_boundary(this.as_bytes(), this.len());
175 let (rem, next_char) = string::split_at(this, next_char);
176 self.this = rem;
177 Some((next_char, self))
178 }
179 }
180 }
181
182 iterator_shared! {
183 is_forward = $is_forward,
184 item = &'a str,
185 iter_forward = Split<'a, 'b>,
186 iter_reversed = RSplit<'a, 'b>,
187 next(self){
188 let Self {
189 this,
190 state,
191 } = self;
192
193 match state {
194 State::Normal{delim} => {
195 match string::find(this, delim, 0) {
196 Some(pos) => {
197 self.this = str_from(this, pos + delim.len());
198 Some((str_up_to(this, pos), self))
199 }
200 None => {
201 self.this = "";
202 self.state = State::Finished;
203 Some((this, self))
204 }
205 }
206 }
207 State::Empty(es) => self.next_from_empty(es),
208 State::Finished => None,
209 }
210 },
211 next_back{
212 let Self {
213 this,
214 state,
215 } = self;
216 match state {
217 State::Normal{delim} => {
218 match string::rfind(this, delim, this.len()) {
219 Some(pos) => {
220 self.this = str_up_to(this, pos);
221 Some((str_from(this, pos + delim.len()), self))
222 }
223 None => {
224 self.this = "";
225 self.state = State::Finished;
226 Some((this, self))
227 }
228 }
229 }
230 State::Empty(es) => self.next_back_from_empty(es),
231 State::Finished => None,
232 }
233 },
234 fields = {this, state},
235 }
236 };
237}
238
239/// Const equivalent of `core::str::Split<'a, &'b str>`
240///
241/// This is constructed with [`split`] like this:
242/// ```rust
243/// # let string = "";
244/// # let delim = "";
245/// # let _ =
246/// konst::string::split(string, delim)
247/// # ;
248/// ```
249///
250/// # Version compatibility
251///
252/// This requires the `"rust_1_64"` feature.
253///
254#[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
255pub struct Split<'a, 'b> {
256 this: &'a str,
257 state: State<'b>,
258}
259impl IntoIterKind for Split<'_, '_> {
260 type Kind = IsIteratorKind;
261}
262
263impl<'a, 'b> Split<'a, 'b> {
264 split_shared! {is_forward = true}
265
266 /// Gets the remainder of the string.
267 ///
268 /// # Example
269 ///
270 /// ```rust
271 /// let iter = konst::string::split("foo-bar-baz", "-");
272 /// assert_eq!(iter.remainder(), "foo-bar-baz");
273 ///
274 /// let (elem, iter) = iter.next().unwrap();
275 /// assert_eq!(elem, "foo");
276 /// assert_eq!(iter.remainder(), "bar-baz");
277 ///
278 /// let (elem, iter) = iter.next().unwrap();
279 /// assert_eq!(elem, "bar");
280 /// assert_eq!(iter.remainder(), "baz");
281 ///
282 /// let (elem, iter) = iter.next().unwrap();
283 /// assert_eq!(elem, "baz");
284 /// assert_eq!(iter.remainder(), "");
285 ///
286 /// ```
287 pub const fn remainder(&self) -> &'a str {
288 self.this
289 }
290}
291
292/// Const equivalent of `core::str::RSplit<'a, &'b str>`
293///
294/// This is constructed with [`rsplit`] like this:
295/// ```rust
296/// # let string = "";
297/// # let delim = "";
298/// # let _ =
299/// konst::string::rsplit(string, delim)
300/// # ;
301/// ```
302///
303/// # Version compatibility
304///
305/// This requires the `"rust_1_64"` feature.
306///
307#[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
308pub struct RSplit<'a, 'b> {
309 this: &'a str,
310 state: State<'b>,
311}
312impl IntoIterKind for RSplit<'_, '_> {
313 type Kind = IsIteratorKind;
314}
315
316impl<'a, 'b> RSplit<'a, 'b> {
317 split_shared! {is_forward = false}
318
319 /// Gets the remainder of the string.
320 ///
321 /// # Example
322 ///
323 /// ```rust
324 /// let iter = konst::string::rsplit("foo-bar-baz", "-");
325 /// assert_eq!(iter.remainder(), "foo-bar-baz");
326 ///
327 /// let (elem, iter) = iter.next().unwrap();
328 /// assert_eq!(elem, "baz");
329 /// assert_eq!(iter.remainder(), "foo-bar");
330 ///
331 /// let (elem, iter) = iter.next().unwrap();
332 /// assert_eq!(elem, "bar");
333 /// assert_eq!(iter.remainder(), "foo");
334 ///
335 /// let (elem, iter) = iter.next().unwrap();
336 /// assert_eq!(elem, "foo");
337 /// assert_eq!(iter.remainder(), "");
338 ///
339 /// ```
340 pub const fn remainder(&self) -> &'a str {
341 self.this
342 }
343}