konst/string/
split_terminator_items.rs

1use crate::{
2    iter::{IntoIterKind, IsIteratorKind},
3    string::{self, str_from, str_up_to},
4};
5
6use konst_macro_rules::iterator_shared;
7
8/// Const equivalent of [`str::split_terminator`], which only takes a `&str` delimiter.
9///
10/// The same as [`split`](crate::string::split),
11/// except that, if the string after the last delimiter is empty, it is skipped.
12///
13/// # Version compatibility
14///
15/// This requires the `"rust_1_64"` feature.
16///
17/// # Example
18///
19/// ```rust
20/// use konst::string;
21/// use konst::iter::for_each;
22///
23/// const STRS: &[&str] = &{
24///     let mut arr = [""; 3];
25///     for_each!{(i, sub) in string::split_terminator("foo,bar,baz,", ","),enumerate() =>
26///         arr[i] = sub;
27///     }
28///     arr
29/// };
30///
31/// assert_eq!(STRS, ["foo", "bar", "baz"]);
32/// ```
33#[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
34pub const fn split_terminator<'a, 'b>(this: &'a str, delim: &'b str) -> SplitTerminator<'a, 'b> {
35    SplitTerminator {
36        this,
37        state: if delim.is_empty() {
38            State::Empty(EmptyState::Start)
39        } else {
40            State::Normal { delim }
41        },
42    }
43}
44
45/// Const equivalent of [`str::rsplit_terminator`], which only takes a `&str` delimiter.
46///
47/// The same as [`rsplit`](crate::string::rsplit),
48/// except that, if the string before the first delimiter is empty, it is skipped.
49///
50/// # Version compatibility
51///
52/// This requires the `"rust_1_64"` feature.
53///
54/// # Example
55///
56/// ```rust
57/// use konst::string;
58/// use konst::iter::for_each;
59///
60/// const STRS: &[&str] = &{
61///     let mut arr = [""; 3];
62///     for_each!{(i, sub) in string::rsplit_terminator(":foo:bar:baz", ":"),enumerate() =>
63///         arr[i] = sub;
64///     }
65///     arr
66/// };
67///
68/// assert_eq!(STRS, ["baz", "bar", "foo"]);
69/// ```
70#[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
71pub const fn rsplit_terminator<'a, 'b>(this: &'a str, delim: &'b str) -> RSplitTerminator<'a, 'b> {
72    let SplitTerminator { this, state } = split_terminator(this, delim);
73    RSplitTerminator { this, state }
74}
75
76#[derive(Copy, Clone)]
77enum State<'a> {
78    Normal { delim: &'a str },
79    Empty(EmptyState),
80}
81
82#[derive(Copy, Clone)]
83enum EmptyState {
84    Start,
85    Continue,
86}
87
88/// Const equivalent of `core::str::SplitTerminator<'a, &'b str>`
89///
90/// This is constructed with [`split_terminator`] like this:
91/// ```rust
92/// # let string = "";
93/// # let delim = "";
94/// # let _: konst::string::SplitTerminator<'_, '_> =
95/// konst::string::split_terminator(string, delim)
96/// # ;
97/// ```
98///
99/// # Version compatibility
100///
101/// This requires the `"rust_1_64"` feature.
102///
103#[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
104pub struct SplitTerminator<'a, 'b> {
105    this: &'a str,
106    state: State<'b>,
107}
108impl IntoIterKind for SplitTerminator<'_, '_> {
109    type Kind = IsIteratorKind;
110}
111
112impl<'a, 'b> SplitTerminator<'a, 'b> {
113    iterator_shared! {
114        is_forward = true,
115        item = &'a str,
116        iter_forward = SplitTerminator<'a, 'b>,
117        next(self){
118            let Self {
119                this,
120                state,
121            } = self;
122
123            match state {
124                State::Empty(EmptyState::Start) => {
125                    self.state = State::Empty(EmptyState::Continue);
126                    Some(("", self))
127                }
128                _ if this.is_empty() => {
129                    None
130                }
131                State::Normal{delim} => {
132                    let (next, ret) = match string::find(this, delim, 0) {
133                        Some(pos) => (pos + delim.len(), pos),
134                        None => (this.len(), this.len()),
135                    };
136                    self.this = str_from(this, next);
137                    Some((str_up_to(this, ret), self))
138                }
139                State::Empty(EmptyState::Continue) => {
140                    let next_char = string::find_next_char_boundary(self.this.as_bytes(), 0);
141                    let (next_char, rem) = string::split_at(self.this, next_char);
142                    self.this = rem;
143                    Some((next_char, self))
144                }
145            }
146        },
147        fields = {this, state},
148    }
149
150    /// Gets the remainder of the string.
151    ///
152    /// # Example
153    ///
154    /// ```rust
155    /// let iter = konst::string::split_terminator("foo,bar,baz,", ",");
156    /// assert_eq!(iter.remainder(), "foo,bar,baz,");
157    ///
158    /// let (elem, iter) = iter.next().unwrap();
159    /// assert_eq!(elem, "foo");
160    /// assert_eq!(iter.remainder(), "bar,baz,");
161    ///
162    /// let (elem, iter) = iter.next().unwrap();
163    /// assert_eq!(elem, "bar");
164    /// assert_eq!(iter.remainder(), "baz,");
165    ///
166    /// let (elem, iter) = iter.next().unwrap();
167    /// assert_eq!(elem, "baz");
168    /// assert_eq!(iter.remainder(), "");
169    ///
170    /// ```
171    pub const fn remainder(&self) -> &'a str {
172        self.this
173    }
174}
175
176/// Const equivalent of `core::str::RSplitTerminator<'a, &'b str>`
177///
178/// This is constructed with [`rsplit_terminator`] like this:
179/// ```rust
180/// # let string = "";
181/// # let delim = "";
182/// # let _: konst::string::RSplitTerminator<'_, '_> =
183/// konst::string::rsplit_terminator(string, delim)
184/// # ;
185/// ```
186///
187/// # Version compatibility
188///
189/// This requires the `"rust_1_64"` feature.
190///
191#[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
192pub struct RSplitTerminator<'a, 'b> {
193    this: &'a str,
194    state: State<'b>,
195}
196impl IntoIterKind for RSplitTerminator<'_, '_> {
197    type Kind = IsIteratorKind;
198}
199
200impl<'a, 'b> RSplitTerminator<'a, 'b> {
201    iterator_shared! {
202        is_forward = true,
203        item = &'a str,
204        iter_forward = RSplitTerminator<'a, 'b>,
205        next(self){
206            let Self {
207                this,
208                state,
209            } = self;
210
211            match state {
212                State::Empty(EmptyState::Start) => {
213                    self.state = State::Empty(EmptyState::Continue);
214                    Some(("", self))
215                }
216                _ if this.is_empty() => {
217                    None
218                }
219                State::Normal{delim} => {
220                    let (next, ret) = match string::rfind(this, delim, this.len()) {
221                        Some(pos) => (pos, pos + delim.len()),
222                        None => (0, 0),
223                    };
224                    self.this = str_up_to(this, next);
225                    Some((str_from(this, ret), self))
226                }
227                State::Empty(EmptyState::Continue) => {
228                    let bytes = self.this.as_bytes();
229                    let next_char = string::find_prev_char_boundary(bytes, bytes.len());
230                    let (rem, next_char) = string::split_at(self.this, next_char);
231                    self.this = rem;
232                    Some((next_char, self))
233                }
234            }
235        },
236        fields = {this, state},
237    }
238
239    /// Gets the remainder of the string.
240    ///
241    /// # Example
242    ///
243    /// ```rust
244    /// let iter = konst::string::rsplit_terminator("=foo=bar=baz", "=");
245    /// assert_eq!(iter.remainder(), "=foo=bar=baz");
246    ///
247    /// let (elem, iter) = iter.next().unwrap();
248    /// assert_eq!(elem, "baz");
249    /// assert_eq!(iter.remainder(), "=foo=bar");
250    ///
251    /// let (elem, iter) = iter.next().unwrap();
252    /// assert_eq!(elem, "bar");
253    /// assert_eq!(iter.remainder(), "=foo");
254    ///
255    /// let (elem, iter) = iter.next().unwrap();
256    /// assert_eq!(elem, "foo");
257    /// assert_eq!(iter.remainder(), "");
258    ///
259    /// ```
260    pub const fn remainder(&self) -> &'a str {
261        self.this
262    }
263}