1//! Common splitter for strings and slices
2//!
3//! This module is private, so these items are effectively `pub(super)`
4
5use crate::iter::plumbing::{Folder, UnindexedProducer};
6
7/// Common producer for splitting on a predicate.
8pub(super) struct SplitProducer<'p, P, V, const INCL: bool = false> {
9 data: V,
10 separator: &'p P,
11
12 /// Marks the endpoint beyond which we've already found no separators.
13 tail: usize,
14}
15
16pub(super) type SplitInclusiveProducer<'p, P, V> = SplitProducer<'p, P, V, true>;
17
18/// Helper trait so `&str`, `&[T]`, and `&mut [T]` can share `SplitProducer`.
19pub(super) trait Fissile<P>: Sized {
20 fn length(&self) -> usize;
21 fn midpoint(&self, end: usize) -> usize;
22 fn find(&self, separator: &P, start: usize, end: usize) -> Option<usize>;
23 fn rfind(&self, separator: &P, end: usize) -> Option<usize>;
24 fn split_once<const INCL: bool>(self, index: usize) -> (Self, Self);
25 fn fold_splits<F, const INCL: bool>(self, separator: &P, folder: F, skip_last: bool) -> F
26 where
27 F: Folder<Self>,
28 Self: Send;
29}
30
31impl<'p, P, V> SplitProducer<'p, P, V>
32where
33 V: Fissile<P> + Send,
34{
35 pub(super) fn new(data: V, separator: &'p P) -> Self {
36 SplitProducer {
37 tail: data.length(),
38 data,
39 separator,
40 }
41 }
42}
43
44impl<'p, P, V> SplitInclusiveProducer<'p, P, V>
45where
46 V: Fissile<P> + Send,
47{
48 pub(super) fn new_incl(data: V, separator: &'p P) -> Self {
49 SplitProducer {
50 tail: data.length(),
51 data,
52 separator,
53 }
54 }
55}
56
57impl<'p, P, V, const INCL: bool> SplitProducer<'p, P, V, INCL>
58where
59 V: Fissile<P> + Send,
60{
61 /// Common `fold_with` implementation, integrating `SplitTerminator`'s
62 /// need to sometimes skip its final empty item.
63 pub(super) fn fold_with<F>(self, folder: F, skip_last: bool) -> F
64 where
65 F: Folder<V>,
66 {
67 let SplitProducer {
68 data,
69 separator,
70 tail,
71 } = self;
72
73 if tail == data.length() {
74 // No tail section, so just let `fold_splits` handle it.
75 data.fold_splits::<F, INCL>(separator, folder, skip_last)
76 } else if let Some(index) = data.rfind(separator, tail) {
77 // We found the last separator to complete the tail, so
78 // end with that slice after `fold_splits` finds the rest.
79 let (left, right) = data.split_once::<INCL>(index);
80 let folder = left.fold_splits::<F, INCL>(separator, folder, false);
81 if skip_last || folder.full() {
82 folder
83 } else {
84 folder.consume(right)
85 }
86 } else {
87 // We know there are no separators at all. Return our whole data.
88 if skip_last {
89 folder
90 } else {
91 folder.consume(data)
92 }
93 }
94 }
95}
96
97impl<'p, P, V, const INCL: bool> UnindexedProducer for SplitProducer<'p, P, V, INCL>
98where
99 V: Fissile<P> + Send,
100 P: Sync,
101{
102 type Item = V;
103
104 fn split(self) -> (Self, Option<Self>) {
105 // Look forward for the separator, and failing that look backward.
106 let mid = self.data.midpoint(self.tail);
107 let index = match self.data.find(self.separator, mid, self.tail) {
108 Some(i) => Some(mid + i),
109 None => self.data.rfind(self.separator, mid),
110 };
111
112 if let Some(index) = index {
113 let len = self.data.length();
114 let (left, right) = self.data.split_once::<INCL>(index);
115
116 let (left_tail, right_tail) = if index < mid {
117 // If we scanned backwards to find the separator, everything in
118 // the right side is exhausted, with no separators left to find.
119 (index, 0)
120 } else {
121 let right_index = len - right.length();
122 (mid, self.tail - right_index)
123 };
124
125 // Create the left split before the separator.
126 let left = SplitProducer {
127 data: left,
128 tail: left_tail,
129 ..self
130 };
131
132 // Create the right split following the separator.
133 let right = SplitProducer {
134 data: right,
135 tail: right_tail,
136 ..self
137 };
138
139 (left, Some(right))
140 } else {
141 // The search is exhausted, no more separators...
142 (SplitProducer { tail: 0, ..self }, None)
143 }
144 }
145
146 fn fold_with<F>(self, folder: F) -> F
147 where
148 F: Folder<Self::Item>,
149 {
150 self.fold_with(folder, false)
151 }
152}
153