Repository: rayon-rs/rayon
Pull request #1120 (2024-01-26 23:53:36)

Add inclusive splits on strings and slices
The standard library versions were stabilized in rust-lang/rust#77858.

- Add `ParallelString::par_split_inclusive`
- Add `ParallelSlice::par_split_inclusive`
- Add `ParallelSliceMut::par_split_inclusive_mut`




Patch:
diff --git a/src/iter/find_first_last/mod.rs b/src/iter/find_first_last/mod.rs
index a241373..e5da8f0 100644
--- a/src/iter/find_first_last/mod.rs
+++ b/src/iter/find_first_last/mod.rs
@@ -181,17 +181,25 @@ impl<'p, P: 'p + Fn(&T) -> bool, T> Folder<T> for FindFolder<'p, T, P> {
         };
 
         if !found_best_in_range && (self.find_op)(&item) {
-            // Update the best found index if ours is better.
-            let update =
-                self.best_found
-                    .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
-                        better_position(self.boundary, current, self.match_position)
-                            .then_some(self.boundary)
-                    });
-
-            // Save this item if our index was better or equal.
-            if update.is_ok() || update == Err(self.boundary) {
-                self.item = Some(item);
+            // Continuously try to set best_found until we succeed or we
+            // discover a better match was already found.
+            let mut current = self.best_found.load(Ordering::Relaxed);
+            loop {
+                if better_position(current, self.boundary, self.match_position) {
+                    break;
+                }
+                match self.best_found.compare_exchange_weak(
+                    current,
+                    self.boundary,
+                    Ordering::Relaxed,
+                    Ordering::Relaxed,
+                ) {
+                    Ok(_) => {
+                        self.item = Some(item);
+                        break;
+                    }
+                    Err(v) => current = v,
+                }
             }
         }
         self
diff --git a/src/iter/par_bridge.rs b/src/iter/par_bridge.rs
index 9927f4f..17bc15b 100644
--- a/src/iter/par_bridge.rs
+++ b/src/iter/par_bridge.rs
@@ -109,11 +109,24 @@ impl<Iter: Iterator + Send> UnindexedProducer for &IterParallelProducer<'_, Iter
     type Item = Iter::Item;
 
     fn split(self) -> (Self, Option<Self>) {
-        // Check if the iterator is exhausted
-        let update = self
-            .split_count
-            .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |c| c.checked_sub(1));
-        (self, update.is_ok().then_some(self))
+        let mut count = self.split_count.load(Ordering::SeqCst);
+
+        loop {
+            // Check if the iterator is exhausted
+            if let Some(new_count) = count.checked_sub(1) {
+                match self.split_count.compare_exchange_weak(
+                    count,
+                    new_count,
+                    Ordering::SeqCst,
+                    Ordering::SeqCst,
+                ) {
+                    Ok(_) => return (self, Some(self)),
+                    Err(last_count) => count = last_count,
+                }
+            } else {
+                return (self, None);
+            }
+        }
     }
 
     fn fold_with<F>(self, mut folder: F) -> F
diff --git a/src/slice/mod.rs b/src/slice/mod.rs
index dab56de..9e8dcc9 100644
--- a/src/slice/mod.rs
+++ b/src/slice/mod.rs
@@ -38,11 +38,11 @@ pub trait ParallelSlice<T: Sync> {
     ///
     /// ```
     /// use rayon::prelude::*;
-    /// let smallest = [1, 2, 3, 0, 2, 4, 8, 0, 3, 6, 9]
+    /// let products: Vec<_> = [1, 2, 3, 0, 2, 4, 8, 0, 3, 6, 9]
     ///     .par_split(|i| *i == 0)
-    ///     .map(|numbers| numbers.iter().min().unwrap())
-    ///     .min();
-    /// assert_eq!(Some(&1), smallest);
+    ///     .map(|numbers| numbers.iter().product::<i32>())
+    ///     .collect();
+    /// assert_eq!(products, [6, 64, 162]);
     /// ```
     fn par_split<P>(&self, separator: P) -> Split<'_, T, P>
     where
@@ -54,6 +54,29 @@ pub trait ParallelSlice<T: Sync> {
         }
     }
 
+    /// Returns a parallel iterator over subslices separated by elements that
+    /// match the separator, including the matched part as a terminator.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use rayon::prelude::*;
+    /// let lengths: Vec<_> = [1, 2, 3, 0, 2, 4, 8, 0, 3, 6, 9]
+    ///     .par_split_inclusive(|i| *i == 0)
+    ///     .map(|numbers| numbers.len())
+    ///     .collect();
+    /// assert_eq!(lengths, [4, 4, 3]);
+    /// ```
+    fn par_split_inclusive<P>(&self, separator: P) -> SplitInclusive<'_, T, P>
+    where
+        P: Fn(&T) -> bool + Sync + Send,
+    {
+        SplitInclusive {
+            slice: self.as_parallel_slice(),
+            separator,
+        }
+    }
+
     /// Returns a parallel iterator over all contiguous windows of length
     /// `window_size`. The windows overlap.
     ///
@@ -187,6 +210,28 @@ pub trait ParallelSliceMut<T: Send> {
         }
     }
 
+    /// Returns a parallel iterator over mutable subslices separated by elements
+    /// that match the separator, including the matched part as a terminator.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use rayon::prelude::*;
+    /// let mut array = [1, 2, 3, 0, 2, 4, 8, 0, 3, 6, 9];
+    /// array.par_split_inclusive_mut(|i| *i == 0)
+    ///      .for_each(|slice| slice.reverse());
+    /// assert_eq!(array, [0, 3, 2, 1, 0, 8, 4, 2, 9, 6, 3]);
+    /// ```
+    fn par_split_inclusive_mut<P>(&mut self, separator: P) -> SplitInclusiveMut<'_, T, P>
+    where
+        P: Fn(&T) -> bool + Sync + Send,
+    {
+        SplitInclusiveMut {
+            slice: self.as_parallel_slice_mut(),
+            separator,
+        }
+    }
+
     /// Returns a parallel iterator over at most `chunk_size` elements of
     /// `self` at a time. The chunks are mutable and do not overlap.
     ///
@@ -932,6 +977,46 @@ where
     }
 }
 
+/// Parallel iterator over slices separated by a predicate,
+/// including the matched part as a terminator.
+pub struct SplitInclusive<'data, T, P> {
+    slice: &'data [T],
+    separator: P,
+}
+
+impl<'data, T, P: Clone> Clone for SplitInclusive<'data, T, P> {
+    fn clone(&self) -> Self {
+        SplitInclusive {
+            separator: self.separator.clone(),
+            ..*self
+        }
+    }
+}
+
+impl<'data, T: Debug, P> Debug for SplitInclusive<'data, T, P> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("SplitInclusive")
+            .field("slice", &self.slice)
+            .finish()
+    }
+}
+
+impl<'data, T, P> ParallelIterator for SplitInclusive<'data, T, P>
+where
+    P: Fn(&T) -> bool + Sync + Send,
+    T: Sync,
+{
+    type Item = &'data [T];
+
+    fn drive_unindexed<C>(self, consumer: C) -> C::Result
+    where
+        C: UnindexedConsumer<Self::Item>,
+    {
+        let producer = SplitInclusiveProducer::new_incl(self.slice, &self.separator);
+        bridge_unindexed(producer, consumer)
+    }
+}
+
 /// Implement support for `SplitProducer`.
 impl<'data, T, P> Fissile<P> for &'data [T]
 where
@@ -953,21 +1038,31 @@ where
         self[..end].iter().rposition(separator)
     }
 
-    fn split_once(self, index: usize) -> (Self, Self) {
-        let (left, right) = self.split_at(index);
-        (left, &right[1..]) // skip the separator
+    fn split_once<const INCL: bool>(self, index: usize) -> (Self, Self) {
+        if INCL {
+            // include the separator in the left side
+            self.split_at(index + 1)
+        } else {
+            let (left, right) = self.split_at(index);
+            (left, &right[1..]) // skip the separator
+        }
     }
 
-    fn fold_splits<F>(self, separator: &P, folder: F, skip_last: bool) -> F
+    fn fold_splits<F, const INCL: bool>(self, separator: &P, folder: F, skip_last: bool) -> F
     where
         F: Folder<Self>,
         Self: Send,
     {
-        let mut split = self.split(separator);
-        if skip_last {
-            split.next_back();
+        if INCL {
+            debug_assert!(!skip_last);
+            folder.consume_iter(self.split_inclusive(separator))
+        } else {
+            let mut split = self.split(separator);
+            if skip_last {
+                split.next_back();
+            }
+            folder.consume_iter(split)
         }
-        folder.consume_iter(split)
     }
 }
 
@@ -1001,6 +1096,37 @@ where
     }
 }
 
+/// Parallel iterator over mutable slices separated by a predicate,
+/// including the matched part as a terminator.
+pub struct SplitInclusiveMut<'data, T, P> {
+    slice: &'data mut [T],
+    separator: P,
+}
+
+impl<'data, T: Debug, P> Debug for SplitInclusiveMut<'data, T, P> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("SplitInclusiveMut")
+            .field("slice", &self.slice)
+            .finish()
+    }
+}
+
+impl<'data, T, P> ParallelIterator for SplitInclusiveMut<'data, T, P>
+where
+    P: Fn(&T) -> bool + Sync + Send,
+    T: Send,
+{
+    type Item = &'data mut [T];
+
+    fn drive_unindexed<C>(self, consumer: C) -> C::Result
+    where
+        C: UnindexedConsumer<Self::Item>,
+    {
+        let producer = SplitInclusiveProducer::new_incl(self.slice, &self.separator);
+        bridge_unindexed(producer, consumer)
+    }
+}
+
 /// Implement support for `SplitProducer`.
 impl<'data, T, P> Fissile<P> for &'data mut [T]
 where
@@ -1022,20 +1148,30 @@ where
         self[..end].iter().rposition(separator)
     }
 
-    fn split_once(self, index: usize) -> (Self, Self) {
-        let (left, right) = self.split_at_mut(index);
-        (left, &mut right[1..]) // skip the separator
+    fn split_once<const INCL: bool>(self, index: usize) -> (Self, Self) {
+        if INCL {
+            // include the separator in the left side
+            self.split_at_mut(index + 1)
+        } else {
+            let (left, right) = self.split_at_mut(index);
+            (left, &mut right[1..]) // skip the separator
+        }
     }
 
-    fn fold_splits<F>(self, separator: &P, folder: F, skip_last: bool) -> F
+    fn fold_splits<F, const INCL: bool>(self, separator: &P, folder: F, skip_last: bool) -> F
     where
         F: Folder<Self>,
         Self: Send,
     {
-        let mut split = self.split_mut(separator);
-        if skip_last {
-            split.next_back();
+        if INCL {
+            debug_assert!(!skip_last);
+            folder.consume_iter(self.split_inclusive_mut(separator))
+        } else {
+            let mut split = self.split_mut(separator);
+            if skip_last {
+                split.next_back();
+            }
+            folder.consume_iter(split)
         }
-        folder.consume_iter(split)
     }
 }
diff --git a/src/split_producer.rs b/src/split_producer.rs
index 568657a..6e15b79 100644
--- a/src/split_producer.rs
+++ b/src/split_producer.rs
@@ -5,7 +5,7 @@
 use crate::iter::plumbing::{Folder, UnindexedProducer};
 
 /// Common producer for splitting on a predicate.
-pub(super) struct SplitProducer<'p, P, V> {
+pub(super) struct SplitProducer<'p, P, V, const INCL: bool = false> {
     data: V,
     separator: &'p P,
 
@@ -13,14 +13,16 @@ pub(super) struct SplitProducer<'p, P, V> {
     tail: usize,
 }
 
+pub(super) type SplitInclusiveProducer<'p, P, V> = SplitProducer<'p, P, V, true>;
+
 /// Helper trait so `&str`, `&[T]`, and `&mut [T]` can share `SplitProducer`.
 pub(super) trait Fissile<P>: Sized {
     fn length(&self) -> usize;
     fn midpoint(&self, end: usize) -> usize;
     fn find(&self, separator: &P, start: usize, end: usize) -> Option<usize>;
     fn rfind(&self, separator: &P, end: usize) -> Option<usize>;
-    fn split_once(self, index: usize) -> (Self, Self);
-    fn fold_splits<F>(self, separator: &P, folder: F, skip_last: bool) -> F
+    fn split_once<const INCL: bool>(self, index: usize) -> (Self, Self);
+    fn fold_splits<F, const INCL: bool>(self, separator: &P, folder: F, skip_last: bool) -> F
     where
         F: Folder<Self>,
         Self: Send;
@@ -37,7 +39,25 @@ where
             separator,
         }
     }
+}
+
+impl<'p, P, V> SplitInclusiveProducer<'p, P, V>
+where
+    V: Fissile<P> + Send,
+{
+    pub(super) fn new_incl(data: V, separator: &'p P) -> Self {
+        SplitProducer {
+            tail: data.length(),
+            data,
+            separator,
+        }
+    }
+}
 
+impl<'p, P, V, const INCL: bool> SplitProducer<'p, P, V, INCL>
+where
+    V: Fissile<P> + Send,
+{
     /// Common `fold_with` implementation, integrating `SplitTerminator`'s
     /// need to sometimes skip its final empty item.
     pub(super) fn fold_with<F>(self, folder: F, skip_last: bool) -> F
@@ -52,12 +72,12 @@ where
 
         if tail == data.length() {
             // No tail section, so just let `fold_splits` handle it.
-            data.fold_splits(separator, folder, skip_last)
+            data.fold_splits::<F, INCL>(separator, folder, skip_last)
         } else if let Some(index) = data.rfind(separator, tail) {
             // We found the last separator to complete the tail, so
             // end with that slice after `fold_splits` finds the rest.
-            let (left, right) = data.split_once(index);
-            let folder = left.fold_splits(separator, folder, false);
+            let (left, right) = data.split_once::<INCL>(index);
+            let folder = left.fold_splits::<F, INCL>(separator, folder, false);
             if skip_last || folder.full() {
                 folder
             } else {
@@ -74,7 +94,7 @@ where
     }
 }
 
-impl<'p, P, V> UnindexedProducer for SplitProducer<'p, P, V>
+impl<'p, P, V, const INCL: bool> UnindexedProducer for SplitProducer<'p, P, V, INCL>
 where
     V: Fissile<P> + Send,
     P: Sync,
@@ -91,7 +111,7 @@ where
 
         if let Some(index) = index {
             let len = self.data.length();
-            let (left, right) = self.data.split_once(index);
+            let (left, right) = self.data.split_once::<INCL>(index);
 
             let (left_tail, right_tail) = if index < mid {
                 // If we scanned backwards to find the separator, everything in
diff --git a/src/str.rs b/src/str.rs
index 89aaabd..1e560a2 100644
--- a/src/str.rs
+++ b/src/str.rs
@@ -157,6 +157,27 @@ pub trait ParallelString {
         Split::new(self.as_parallel_string(), separator)
     }
 
+    /// Returns a parallel iterator over substrings separated by a
+    /// given character or predicate, keeping the matched part as a terminator
+    /// of the substring similar to `str::split_inclusive`.
+    ///
+    /// Note: the `Pattern` trait is private, for use only by Rayon itself.
+    /// It is implemented for `char`, `&[char]`, `[char; N]`, `&[char; N]`,
+    /// and any function or closure `F: Fn(char) -> bool + Sync + Send`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use rayon::prelude::*;
+    /// let lines: Vec<_> = "Mary had a little lamb\nlittle lamb\nlittle lamb."
+    ///    .par_split_inclusive('\n')
+    ///    .collect();
+    /// assert_eq!(lines, ["Mary had a little lamb\n", "little lamb\n", "little lamb."]);
+    /// ```
+    fn par_split_inclusive<P: Pattern>(&self, separator: P) -> SplitInclusive<'_, P> {
+        SplitInclusive::new(self.as_parallel_string(), separator)
+    }
+
     /// Returns a parallel iterator over substrings terminated by a
     /// given character or predicate, similar to `str::split_terminator`.
     /// It's equivalent to `par_split`, except it doesn't produce an empty
@@ -348,6 +369,9 @@ mod private {
         fn rfind_in(&self, haystack: &str) -> Option<usize>;
         fn is_suffix_of(&self, haystack: &str) -> bool;
         fn fold_splits<'ch, F>(&self, haystack: &'ch str, folder: F, skip_last: bool) -> F
+        where
+            F: Folder<&'ch str>;
+        fn fold_inclusive_splits<'ch, F>(&self, haystack: &'ch str, folder: F) -> F
         where
             F: Folder<&'ch str>;
         fn fold_matches<'ch, F>(&self, haystack: &'ch str, folder: F) -> F
@@ -395,6 +419,13 @@ macro_rules! impl_pattern {
             folder.consume_iter(split)
         }
 
+        fn fold_inclusive_splits<'ch, F>(&$self, chars: &'ch str, folder: F) -> F
+        where
+            F: Folder<&'ch str>,
+        {
+            folder.consume_iter(chars.split_inclusive($pattern))
+        }
+
         fn fold_matches<'ch, F>(&$self, chars: &'ch str, folder: F) -> F
         where
             F: Folder<&'ch str>,
@@ -668,18 +699,56 @@ impl<'ch, P: Pattern> Fissile<P> for &'ch str {
         separator.rfind_in(&self[..end])
     }
 
-    fn split_once(self, index: usize) -> (Self, Self) {
-        let (left, right) = self.split_at(index);
-        let mut right_iter = right.chars();
-        right_iter.next(); // skip the separator
-        (left, right_iter.as_str())
+    fn split_once<const INCL: bool>(self, index: usize) -> (Self, Self) {
+        if INCL {
+            // include the separator in the left side
+            let separator = self[index..].chars().next().unwrap();
+            self.split_at(index + separator.len_utf8())
+        } else {
+            let (left, right) = self.split_at(index);
+            let mut right_iter = right.chars();
+            right_iter.next(); // skip the separator
+            (left, right_iter.as_str())
+        }
     }
 
-    fn fold_splits<F>(self, separator: &P, folder: F, skip_last: bool) -> F
+    fn fold_splits<F, const INCL: bool>(self, separator: &P, folder: F, skip_last: bool) -> F
     where
         F: Folder<Self>,
     {
-        separator.fold_splits(self, folder, skip_last)
+        if INCL {
+            debug_assert!(!skip_last);
+            separator.fold_inclusive_splits(self, folder)
+        } else {
+            separator.fold_splits(self, folder, skip_last)
+        }
+    }
+}
+
+// /////////////////////////////////////////////////////////////////////////
+
+/// Parallel iterator over substrings separated by a pattern
+#[derive(Debug, Clone)]
+pub struct SplitInclusive<'ch, P: Pattern> {
+    chars: &'ch str,
+    separator: P,
+}
+
+impl<'ch, P: Pattern> SplitInclusive<'ch, P> {
+    fn new(chars: &'ch str, separator: P) -> Self {
+        SplitInclusive { chars, separator }
+    }
+}
+
+impl<'ch, P: Pattern> ParallelIterator for SplitInclusive<'ch, P> {
+    type Item = &'ch str;
+
+    fn drive_unindexed<C>(self, consumer: C) -> C::Result
+    where
+        C: UnindexedConsumer<Self::Item>,
+    {
+        let producer = SplitInclusiveProducer::new_incl(self.chars, &self.separator);
+        bridge_unindexed(producer, consumer)
     }
 }
 


Test patch:
diff --git a/src/iter/test.rs b/src/iter/test.rs
index 45fb971..09bee1d 100644
--- a/src/iter/test.rs
+++ b/src/iter/test.rs
@@ -983,6 +983,25 @@ fn check_slice_split() {
     assert_eq!(v, &[&slice[..1], &slice[..0], &slice[3..]]);
 }
 
+#[test]
+fn check_slice_split_inclusive() {
+    let v: Vec<_> = (0..1000).collect();
+    for m in 1..100 {
+        let a: Vec<_> = v.split_inclusive(|x| x % m == 0).collect();
+        let b: Vec<_> = v.par_split_inclusive(|x| x % m == 0).collect();
+        assert_eq!(a, b);
+    }
+
+    // same as std::slice::split_inclusive() examples
+    let slice = [10, 40, 33, 20];
+    let v: Vec<_> = slice.par_split_inclusive(|num| num % 3 == 0).collect();
+    assert_eq!(v, &[&slice[..3], &slice[3..]]);
+
+    let slice = [3, 10, 40, 33];
+    let v: Vec<_> = slice.par_split_inclusive(|num| num % 3 == 0).collect();
+    assert_eq!(v, &[&slice[..1], &slice[1..]]);
+}
+
 #[test]
 fn check_slice_split_mut() {
     let mut v1: Vec<_> = (0..1000).collect();
@@ -1001,6 +1020,26 @@ fn check_slice_split_mut() {
     assert_eq!(v, [1, 40, 30, 1, 60, 1]);
 }
 
+#[test]
+fn check_slice_split_inclusive_mut() {
+    let mut v1: Vec<_> = (0..1000).collect();
+    let mut v2 = v1.clone();
+    for m in 1..100 {
+        let a: Vec<_> = v1.split_inclusive_mut(|x| x % m == 0).collect();
+        let b: Vec<_> = v2.par_split_inclusive_mut(|x| x % m == 0).collect();
+        assert_eq!(a, b);
+    }
+
+    // same as std::slice::split_inclusive_mut() example
+    let mut v = [10, 40, 30, 20, 60, 50];
+    v.par_split_inclusive_mut(|num| num % 3 == 0)
+        .for_each(|group| {
+            let terminator_idx = group.len() - 1;
+            group[terminator_idx] = 1;
+        });
+    assert_eq!(v, [10, 40, 1, 20, 1, 1]);
+}
+
 #[test]
 fn check_chunks() {
     let a: Vec<i32> = vec![1, 5, 10, 4, 100, 3, 1000, 2, 10000, 1];
diff --git a/tests/clones.rs b/tests/clones.rs
index c6c374f..aaa1539 100644
--- a/tests/clones.rs
+++ b/tests/clones.rs
@@ -99,6 +99,7 @@ fn clone_str() {
     check(s.par_chars());
     check(s.par_lines());
     check(s.par_split('\n'));
+    check(s.par_split_inclusive('\n'));
     check(s.par_split_terminator('\n'));
     check(s.par_split_whitespace());
     check(s.par_split_ascii_whitespace());
@@ -114,6 +115,7 @@ fn clone_vec() {
     check(v.par_rchunks_exact(42));
     check(v.par_windows(42));
     check(v.par_split(|x| x % 3 == 0));
+    check(v.par_split_inclusive(|x| x % 3 == 0));
     check(v.into_par_iter());
 }
 
diff --git a/tests/debug.rs b/tests/debug.rs
index 411a2c9..fc85229 100644
--- a/tests/debug.rs
+++ b/tests/debug.rs
@@ -104,6 +104,7 @@ fn debug_str() {
     check(s.par_chars());
     check(s.par_lines());
     check(s.par_split('\n'));
+    check(s.par_split_inclusive('\n'));
     check(s.par_split_terminator('\n'));
     check(s.par_split_whitespace());
     check(s.par_split_ascii_whitespace());
@@ -130,7 +131,9 @@ fn debug_vec() {
     check(v.par_rchunks_exact_mut(42));
     check(v.par_windows(42));
     check(v.par_split(|x| x % 3 == 0));
+    check(v.par_split_inclusive(|x| x % 3 == 0));
     check(v.par_split_mut(|x| x % 3 == 0));
+    check(v.par_split_inclusive_mut(|x| x % 3 == 0));
     check(v.par_drain(..));
     check(v.into_par_iter());
 }
diff --git a/tests/str.rs b/tests/str.rs
index 96b882d..2c7e549 100644
--- a/tests/str.rs
+++ b/tests/str.rs
@@ -56,6 +56,8 @@ pub fn execute_strings_split() {
         ("foo\nbar\n\r\nbaz", '\n'),
         ("A few words", ' '),
         (" Mary   had\ta\u{2009}little  \n\t lamb", ' '),
+        ("Mary had a little lamb\nlittle lamb\nlittle lamb.", '\n'),
+        ("Mary had a little lamb\nlittle lamb\nlittle lamb.\n", '\n'),
         (include_str!("str.rs"), ' '),
     ];
 
@@ -90,6 +92,7 @@ pub fn execute_strings_split() {
     }
 
     check_separators!(split, par_split);
+    check_separators!(split_inclusive, par_split_inclusive);
     check_separators!(split_terminator, par_split_terminator);
 
     for &(string, _) in &tests {