From d2ea6f3565c891a3e0c871c2c92ec7bd2850697f Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 5 Mar 2020 20:32:49 -0500 Subject: [PATCH] streams: add "stream with state" APIs This is apparently useful in some cases when access to the underlying automaton's state can produce useful information about a match. Regretably, the implementation requires the states to satisfy `Clone`, or else we would otherwise need to execute the state transition twice. In practice, states are usually `Copy` and quite small. Closes #60, Closes #61 --- src/map.rs | 161 +++++++++++++++++++++++++- src/raw/mod.rs | 299 ++++++++++++++++++++++++++++++++++++------------- src/set.rs | 168 ++++++++++++++++++++++++--- 3 files changed, 530 insertions(+), 98 deletions(-) diff --git a/src/map.rs b/src/map.rs index c53b930..d6e2053 100644 --- a/src/map.rs +++ b/src/map.rs @@ -263,12 +263,13 @@ impl> Map { /// # Example /// /// An implementation of regular expressions for `Automaton` is available - /// in the `fst-regex` crate, which can be used to search maps. + /// in the `regex-automata` crate with the `fst1` feature enabled, which + /// can be used to search maps. /// /// # Example /// /// An implementation of subsequence search for `Automaton` can be used - /// to search sets: + /// to search maps: /// /// ```rust /// use fst::automaton::Subsequence; @@ -303,6 +304,62 @@ impl> Map { StreamBuilder(self.0.search(aut)) } + /// Executes an automaton on the keys of this map and yields matching + /// keys along with the corresponding matching states in the given + /// automaton. + /// + /// Note that this returns a `StreamWithStateBuilder`, which can be used to + /// add a range query to the search (see the `range` method). + /// + /// Memory requirements are the same as described on `Map::stream`. + /// + #[cfg_attr( + feature = "levenshtein", + doc = r##" +# Example + +An implementation of fuzzy search using Levenshtein automata can be used +to search maps: + +```rust +use fst::automaton::Levenshtein; +use fst::{IntoStreamer, Streamer, Map}; + +# fn main() { example().unwrap(); } +fn example() -> Result<(), Box> { + let map = Map::from_iter(vec![ + ("foo", 1), + ("foob", 2), + ("foobar", 3), + ("fozb", 4), + ]).unwrap(); + + let query = Levenshtein::new("foo", 2)?; + let mut stream = map.search_with_state(&query).into_stream(); + + let mut kvs = vec![]; + while let Some((k, v, s)) = stream.next() { + kvs.push((String::from_utf8(k.to_vec())?, v, s)); + } + // Currently, there isn't much interesting that you can do with the states. + assert_eq!(kvs, vec![ + ("foo".to_string(), 1, Some(183)), + ("foob".to_string(), 2, Some(123)), + ("fozb".to_string(), 4, Some(83)), + ]); + + Ok(()) +} +``` +"## + )] + pub fn search_with_state( + &self, + aut: A, + ) -> StreamWithStateBuilder<'_, A> { + StreamWithStateBuilder(self.0.search_with_state(aut)) + } + /// Returns the number of elements in this map. #[inline] pub fn len(&self) -> usize { @@ -667,6 +724,30 @@ impl<'m, A: Automaton> Stream<'m, A> { } } +/// A lexicographically ordered stream of key-value-state triples from a map +/// and an automaton. +/// +/// The key-values are from the map while the states are from the automaton. +/// +/// The `A` type parameter corresponds to an optional automaton to filter +/// the stream. By default, no filtering is done. +/// +/// The `'m` lifetime parameter refers to the lifetime of the underlying map. +pub struct StreamWithState<'m, A = AlwaysMatch>(raw::StreamWithState<'m, A>) +where + A: Automaton; + +impl<'a, 'm, A: 'a + Automaton> Streamer<'a> for StreamWithState<'m, A> +where + A::State: Clone, +{ + type Item = (&'a [u8], u64, A::State); + + fn next(&'a mut self) -> Option<(&'a [u8], u64, A::State)> { + self.0.next().map(|(key, out, state)| (key, out.value(), state)) + } +} + /// A lexicographically ordered stream of keys from a map. /// /// The `'m` lifetime parameter refers to the lifetime of the underlying map. @@ -712,22 +793,22 @@ pub struct StreamBuilder<'m, A = AlwaysMatch>(raw::StreamBuilder<'m, A>); impl<'m, A: Automaton> StreamBuilder<'m, A> { /// Specify a greater-than-or-equal-to bound. - pub fn ge>(self, bound: T) -> Self { + pub fn ge>(self, bound: T) -> StreamBuilder<'m, A> { StreamBuilder(self.0.ge(bound)) } /// Specify a greater-than bound. - pub fn gt>(self, bound: T) -> Self { + pub fn gt>(self, bound: T) -> StreamBuilder<'m, A> { StreamBuilder(self.0.gt(bound)) } /// Specify a less-than-or-equal-to bound. - pub fn le>(self, bound: T) -> Self { + pub fn le>(self, bound: T) -> StreamBuilder<'m, A> { StreamBuilder(self.0.le(bound)) } /// Specify a less-than bound. - pub fn lt>(self, bound: T) -> Self { + pub fn lt>(self, bound: T) -> StreamBuilder<'m, A> { StreamBuilder(self.0.lt(bound)) } } @@ -741,6 +822,74 @@ impl<'m, 'a, A: Automaton> IntoStreamer<'a> for StreamBuilder<'m, A> { } } +/// A builder for constructing range queries on streams that include automaton +/// states. +/// +/// In general, one should use `StreamBuilder` unless you have a specific need +/// for accessing the states of the underlying automaton that is being used to +/// filter this stream. +/// +/// Once all bounds are set, one should call `into_stream` to get a +/// `Stream`. +/// +/// Bounds are not additive. That is, if `ge` is called twice on the same +/// builder, then the second setting wins. +/// +/// The `A` type parameter corresponds to an optional automaton to filter +/// the stream. By default, no filtering is done. +/// +/// The `'m` lifetime parameter refers to the lifetime of the underlying map. +pub struct StreamWithStateBuilder<'m, A = AlwaysMatch>( + raw::StreamWithStateBuilder<'m, A>, +); + +impl<'m, A: Automaton> StreamWithStateBuilder<'m, A> { + /// Specify a greater-than-or-equal-to bound. + pub fn ge>( + self, + bound: T, + ) -> StreamWithStateBuilder<'m, A> { + StreamWithStateBuilder(self.0.ge(bound)) + } + + /// Specify a greater-than bound. + pub fn gt>( + self, + bound: T, + ) -> StreamWithStateBuilder<'m, A> { + StreamWithStateBuilder(self.0.gt(bound)) + } + + /// Specify a less-than-or-equal-to bound. + pub fn le>( + self, + bound: T, + ) -> StreamWithStateBuilder<'m, A> { + StreamWithStateBuilder(self.0.le(bound)) + } + + /// Specify a less-than bound. + pub fn lt>( + self, + bound: T, + ) -> StreamWithStateBuilder<'m, A> { + StreamWithStateBuilder(self.0.lt(bound)) + } +} + +impl<'m, 'a, A: 'a + Automaton> IntoStreamer<'a> + for StreamWithStateBuilder<'m, A> +where + A::State: Clone, +{ + type Item = (&'a [u8], u64, A::State); + type Into = StreamWithState<'m, A>; + + fn into_stream(self) -> StreamWithState<'m, A> { + StreamWithState(self.0.into_stream()) + } +} + /// A builder for collecting map streams on which to perform set operations /// on the keys of maps. /// diff --git a/src/raw/mod.rs b/src/raw/mod.rs index fc70167..67386cd 100644 --- a/src/raw/mod.rs +++ b/src/raw/mod.rs @@ -451,12 +451,23 @@ impl> Fst { StreamBuilder::new(self.as_ref(), AlwaysMatch) } - /// Executes an automaton on the keys of this map. + /// Executes an automaton on the keys of this FST. #[inline] pub fn search(&self, aut: A) -> StreamBuilder<'_, A> { StreamBuilder::new(self.as_ref(), aut) } + /// Executes an automaton on the keys of this FST and yields matching + /// keys along with the corresponding matching states in the given + /// automaton. + #[inline] + pub fn search_with_state( + &self, + aut: A, + ) -> StreamWithStateBuilder<'_, A> { + StreamWithStateBuilder::new(self.as_ref(), aut) + } + /// Returns the number of keys in this fst. #[inline] pub fn len(&self) -> usize { @@ -724,25 +735,25 @@ impl<'f, A: Automaton> StreamBuilder<'f, A> { } /// Specify a greater-than-or-equal-to bound. - pub fn ge>(mut self, bound: T) -> Self { + pub fn ge>(mut self, bound: T) -> StreamBuilder<'f, A> { self.min = Bound::Included(bound.as_ref().to_owned()); self } /// Specify a greater-than bound. - pub fn gt>(mut self, bound: T) -> Self { + pub fn gt>(mut self, bound: T) -> StreamBuilder<'f, A> { self.min = Bound::Excluded(bound.as_ref().to_owned()); self } /// Specify a less-than-or-equal-to bound. - pub fn le>(mut self, bound: T) -> Self { + pub fn le>(mut self, bound: T) -> StreamBuilder<'f, A> { self.max = Bound::Included(bound.as_ref().to_owned()); self } /// Specify a less-than bound. - pub fn lt>(mut self, bound: T) -> Self { + pub fn lt>(mut self, bound: T) -> StreamBuilder<'f, A> { self.max = Bound::Excluded(bound.as_ref().to_owned()); self } @@ -757,6 +768,90 @@ impl<'a, 'f, A: Automaton> IntoStreamer<'a> for StreamBuilder<'f, A> { } } +/// A builder for constructing range queries on streams that include automaton +/// states. +/// +/// In general, one should use `StreamBuilder` unless you have a specific need +/// for accessing the states of the underlying automaton that is being used to +/// filter this stream. +/// +/// Once all bounds are set, one should call `into_stream` to get a +/// `Stream`. +/// +/// Bounds are not additive. That is, if `ge` is called twice on the same +/// builder, then the second setting wins. +/// +/// The `A` type parameter corresponds to an optional automaton to filter +/// the stream. By default, no filtering is done. +/// +/// The `'f` lifetime parameter refers to the lifetime of the underlying fst. +pub struct StreamWithStateBuilder<'f, A = AlwaysMatch> { + fst: FstRef<'f>, + aut: A, + min: Bound, + max: Bound, +} + +impl<'f, A: Automaton> StreamWithStateBuilder<'f, A> { + fn new(fst: FstRef<'f>, aut: A) -> StreamWithStateBuilder<'f, A> { + StreamWithStateBuilder { + fst, + aut, + min: Bound::Unbounded, + max: Bound::Unbounded, + } + } + + /// Specify a greater-than-or-equal-to bound. + pub fn ge>( + mut self, + bound: T, + ) -> StreamWithStateBuilder<'f, A> { + self.min = Bound::Included(bound.as_ref().to_owned()); + self + } + + /// Specify a greater-than bound. + pub fn gt>( + mut self, + bound: T, + ) -> StreamWithStateBuilder<'f, A> { + self.min = Bound::Excluded(bound.as_ref().to_owned()); + self + } + + /// Specify a less-than-or-equal-to bound. + pub fn le>( + mut self, + bound: T, + ) -> StreamWithStateBuilder<'f, A> { + self.max = Bound::Included(bound.as_ref().to_owned()); + self + } + + /// Specify a less-than bound. + pub fn lt>( + mut self, + bound: T, + ) -> StreamWithStateBuilder<'f, A> { + self.max = Bound::Excluded(bound.as_ref().to_owned()); + self + } +} + +impl<'a, 'f, A: 'a + Automaton> IntoStreamer<'a> + for StreamWithStateBuilder<'f, A> +where + A::State: Clone, +{ + type Item = (&'a [u8], Output, A::State); + type Into = StreamWithState<'f, A>; + + fn into_stream(self) -> StreamWithState<'f, A> { + StreamWithState::new(self.fst, self.aut, self.min, self.max) + } +} + #[derive(Debug)] enum Bound { Included(Vec), @@ -795,7 +890,94 @@ impl Bound { /// the stream. By default, no filtering is done. /// /// The `'f` lifetime parameter refers to the lifetime of the underlying fst. -pub struct Stream<'f, A = AlwaysMatch> +pub struct Stream<'f, A: Automaton = AlwaysMatch>(StreamWithState<'f, A>); + +impl<'f, A: Automaton> Stream<'f, A> { + fn new(fst: FstRef<'f>, aut: A, min: Bound, max: Bound) -> Stream<'f, A> { + Stream(StreamWithState::new(fst, aut, min, max)) + } + + /// Convert this stream into a vector of byte strings and outputs. + /// + /// Note that this creates a new allocation for every key in the stream. + pub fn into_byte_vec(mut self) -> Vec<(Vec, u64)> { + let mut vs = vec![]; + while let Some((k, v)) = self.next() { + vs.push((k.to_vec(), v.value())); + } + vs + } + + /// Convert this stream into a vector of Unicode strings and outputs. + /// + /// If any key is not valid UTF-8, then iteration on the stream is stopped + /// and a UTF-8 decoding error is returned. + /// + /// Note that this creates a new allocation for every key in the stream. + pub fn into_str_vec(mut self) -> Result> { + let mut vs = vec![]; + while let Some((k, v)) = self.next() { + let k = String::from_utf8(k.to_vec()).map_err(Error::from)?; + vs.push((k, v.value())); + } + Ok(vs) + } + + /// Convert this stream into a vector of byte strings. + /// + /// Note that this creates a new allocation for every key in the stream. + pub fn into_byte_keys(mut self) -> Vec> { + let mut vs = vec![]; + while let Some((k, _)) = self.next() { + vs.push(k.to_vec()); + } + vs + } + + /// Convert this stream into a vector of Unicode strings. + /// + /// If any key is not valid UTF-8, then iteration on the stream is stopped + /// and a UTF-8 decoding error is returned. + /// + /// Note that this creates a new allocation for every key in the stream. + pub fn into_str_keys(mut self) -> Result> { + let mut vs = vec![]; + while let Some((k, _)) = self.next() { + let k = String::from_utf8(k.to_vec()).map_err(Error::from)?; + vs.push(k); + } + Ok(vs) + } + + /// Convert this stream into a vector of outputs. + pub fn into_values(mut self) -> Vec { + let mut vs = vec![]; + while let Some((_, v)) = self.next() { + vs.push(v.value()); + } + vs + } +} + +impl<'f, 'a, A: Automaton> Streamer<'a> for Stream<'f, A> { + type Item = (&'a [u8], Output); + + fn next(&'a mut self) -> Option { + self.0.next_with(|_| ()).map(|(key, out, _)| (key, out)) + } +} + +/// A lexicographically ordered stream of key-value-state triples from an fst +/// and an automaton. +/// +/// The key-values are from the underyling FSTP while the states are from the +/// automaton. +/// +/// The `A` type parameter corresponds to an optional automaton to filter +/// the stream. By default, no filtering is done. +/// +/// The `'m` lifetime parameter refers to the lifetime of the underlying map. +pub struct StreamWithState<'f, A = AlwaysMatch> where A: Automaton, { @@ -815,9 +997,14 @@ struct StreamState<'f, S> { aut_state: S, } -impl<'f, A: Automaton> Stream<'f, A> { - fn new(fst: FstRef<'f>, aut: A, min: Bound, max: Bound) -> Stream<'f, A> { - let mut rdr = Stream { +impl<'f, A: Automaton> StreamWithState<'f, A> { + fn new( + fst: FstRef<'f>, + aut: A, + min: Bound, + max: Bound, + ) -> StreamWithState<'f, A> { + let mut rdr = StreamWithState { fst, aut, inp: Vec::with_capacity(16), @@ -916,79 +1103,19 @@ impl<'f, A: Automaton> Stream<'f, A> { } } - /// Convert this stream into a vector of byte strings and outputs. - /// - /// Note that this creates a new allocation for every key in the stream. - pub fn into_byte_vec(mut self) -> Vec<(Vec, u64)> { - let mut vs = vec![]; - while let Some((k, v)) = self.next() { - vs.push((k.to_vec(), v.value())); - } - vs - } - - /// Convert this stream into a vector of Unicode strings and outputs. - /// - /// If any key is not valid UTF-8, then iteration on the stream is stopped - /// and a UTF-8 decoding error is returned. - /// - /// Note that this creates a new allocation for every key in the stream. - pub fn into_str_vec(mut self) -> Result> { - let mut vs = vec![]; - while let Some((k, v)) = self.next() { - let k = String::from_utf8(k.to_vec()).map_err(Error::from)?; - vs.push((k, v.value())); - } - Ok(vs) - } - - /// Convert this stream into a vector of byte strings. - /// - /// Note that this creates a new allocation for every key in the stream. - pub fn into_byte_keys(mut self) -> Vec> { - let mut vs = vec![]; - while let Some((k, _)) = self.next() { - vs.push(k.to_vec()); - } - vs - } - - /// Convert this stream into a vector of Unicode strings. - /// - /// If any key is not valid UTF-8, then iteration on the stream is stopped - /// and a UTF-8 decoding error is returned. - /// - /// Note that this creates a new allocation for every key in the stream. - pub fn into_str_keys(mut self) -> Result> { - let mut vs = vec![]; - while let Some((k, _)) = self.next() { - let k = String::from_utf8(k.to_vec()).map_err(Error::from)?; - vs.push(k); - } - Ok(vs) - } - - /// Convert this stream into a vector of outputs. - pub fn into_values(mut self) -> Vec { - let mut vs = vec![]; - while let Some((_, v)) = self.next() { - vs.push(v.value()); - } - vs - } -} - -impl<'f, 'a, A: Automaton> Streamer<'a> for Stream<'f, A> { - type Item = (&'a [u8], Output); - - fn next(&'a mut self) -> Option { + fn next_with( + &mut self, + mut map: impl FnMut(&A::State) -> T, + ) -> Option<(&[u8], Output, T)> { if let Some(out) = self.empty_output.take() { if self.end_at.exceeded_by(&[]) { self.stack.clear(); return None; } - if self.aut.is_match(&self.aut.start()) { - return Some((&[], out)); + + let start = self.aut.start(); + if self.aut.is_match(&start) { + return Some((&[], out, map(&start))); } } while let Some(state) = self.stack.pop() { @@ -1003,6 +1130,7 @@ impl<'f, 'a, A: Automaton> Streamer<'a> for Stream<'f, A> { let trans = state.node.transition(state.trans); let out = state.out.cat(trans.out); let next_state = self.aut.accept(&state.aut_state, trans.inp); + let t = map(&next_state); let is_match = self.aut.is_match(&next_state); let next_node = self.fst.node(trans.addr); self.inp.push(trans.inp); @@ -1019,13 +1147,28 @@ impl<'f, 'a, A: Automaton> Streamer<'a> for Stream<'f, A> { return None; } if next_node.is_final() && is_match { - return Some((&self.inp, out.cat(next_node.final_output()))); + return Some(( + &self.inp, + out.cat(next_node.final_output()), + t, + )); } } None } } +impl<'a, 'f, A: 'a + Automaton> Streamer<'a> for StreamWithState<'f, A> +where + A::State: Clone, +{ + type Item = (&'a [u8], Output, A::State); + + fn next(&'a mut self) -> Option<(&'a [u8], Output, A::State)> { + self.next_with(|state| state.clone()) + } +} + /// An output is a value that is associated with a key in a finite state /// transducer. /// diff --git a/src/set.rs b/src/set.rs index 6ae5fc2..412a5d0 100644 --- a/src/set.rs +++ b/src/set.rs @@ -192,6 +192,62 @@ impl> Set { StreamBuilder(self.0.search(aut)) } + /// Executes an automaton on the values of this set and yields matching + /// values along with the corresponding matching states in the given + /// automaton. + /// + /// Note that this returns a `StreamWithStateBuilder`, which can be used to + /// add a range query to the search (see the `range` method). + /// + /// Memory requirements are the same as described on `Map::stream`. + /// + #[cfg_attr( + feature = "levenshtein", + doc = r##" +# Example + +An implementation of fuzzy search using Levenshtein automata can be used +to search sets: + +```rust +use fst::automaton::Levenshtein; +use fst::{IntoStreamer, Streamer, Set}; + +# fn main() { example().unwrap(); } +fn example() -> Result<(), Box> { + let set = Set::from_iter(vec![ + "foo", + "foob", + "foobar", + "fozb", + ]).unwrap(); + + let query = Levenshtein::new("foo", 2)?; + let mut stream = set.search_with_state(&query).into_stream(); + + let mut vs = vec![]; + while let Some((v, s)) = stream.next() { + vs.push((String::from_utf8(v.to_vec())?, s)); + } + // Currently, there isn't much interesting that you can do with the states. + assert_eq!(vs, vec![ + ("foo".to_string(), Some(183)), + ("foob".to_string(), Some(123)), + ("fozb".to_string(), Some(83)), + ]); + + Ok(()) +} +``` +"## + )] + pub fn search_with_state( + &self, + aut: A, + ) -> StreamWithStateBuilder<'_, A> { + StreamWithStateBuilder(self.0.search_with_state(aut)) + } + /// Returns the number of elements in this set. #[inline] pub fn len(&self) -> usize { @@ -551,14 +607,6 @@ where A: Automaton; impl<'s, A: Automaton> Stream<'s, A> { - /// Creates a new set stream from an fst stream. - /// - /// Not part of the public API, but useful in sibling module `map`. - #[doc(hidden)] - pub fn new(fst_stream: raw::Stream<'s, A>) -> Self { - Stream(fst_stream) - } - /// Convert this stream into a vector of Unicode strings. /// /// If any key is not valid UTF-8, then iteration on the stream is stopped @@ -580,11 +628,35 @@ impl<'s, A: Automaton> Stream<'s, A> { impl<'a, 's, A: Automaton> Streamer<'a> for Stream<'s, A> { type Item = &'a [u8]; - fn next(&'a mut self) -> Option { + fn next(&'a mut self) -> Option<&'a [u8]> { self.0.next().map(|(key, _)| key) } } +/// A lexicographically ordered stream of key-state pairs from a set and +/// an automaton. +/// +/// The keys are from the set while the states are from the automaton. +/// +/// The `A` type parameter corresponds to an optional automaton to filter +/// the stream. By default, no filtering is done. +/// +/// The `'m` lifetime parameter refers to the lifetime of the underlying set. +pub struct StreamWithState<'m, A = AlwaysMatch>(raw::StreamWithState<'m, A>) +where + A: Automaton; + +impl<'a, 'm, A: 'a + Automaton> Streamer<'a> for StreamWithState<'m, A> +where + A::State: Clone, +{ + type Item = (&'a [u8], A::State); + + fn next(&'a mut self) -> Option<(&'a [u8], A::State)> { + self.0.next().map(|(key, _, state)| (key, state)) + } +} + /// A builder for constructing range queries on streams. /// /// Once all bounds are set, one should call `into_stream` to get a @@ -601,22 +673,22 @@ pub struct StreamBuilder<'s, A = AlwaysMatch>(raw::StreamBuilder<'s, A>); impl<'s, A: Automaton> StreamBuilder<'s, A> { /// Specify a greater-than-or-equal-to bound. - pub fn ge>(self, bound: T) -> Self { + pub fn ge>(self, bound: T) -> StreamBuilder<'s, A> { StreamBuilder(self.0.ge(bound)) } /// Specify a greater-than bound. - pub fn gt>(self, bound: T) -> Self { + pub fn gt>(self, bound: T) -> StreamBuilder<'s, A> { StreamBuilder(self.0.gt(bound)) } /// Specify a less-than-or-equal-to bound. - pub fn le>(self, bound: T) -> Self { + pub fn le>(self, bound: T) -> StreamBuilder<'s, A> { StreamBuilder(self.0.le(bound)) } /// Specify a less-than bound. - pub fn lt>(self, bound: T) -> Self { + pub fn lt>(self, bound: T) -> StreamBuilder<'s, A> { StreamBuilder(self.0.lt(bound)) } } @@ -625,11 +697,79 @@ impl<'s, 'a, A: Automaton> IntoStreamer<'a> for StreamBuilder<'s, A> { type Item = &'a [u8]; type Into = Stream<'s, A>; - fn into_stream(self) -> Self::Into { + fn into_stream(self) -> Stream<'s, A> { Stream(self.0.into_stream()) } } +/// A builder for constructing range queries on streams that include automaton +/// states. +/// +/// In general, one should use `StreamBuilder` unless you have a specific need +/// for accessing the states of the underlying automaton that is being used to +/// filter this stream. +/// +/// Once all bounds are set, one should call `into_stream` to get a +/// `Stream`. +/// +/// Bounds are not additive. That is, if `ge` is called twice on the same +/// builder, then the second setting wins. +/// +/// The `A` type parameter corresponds to an optional automaton to filter +/// the stream. By default, no filtering is done. +/// +/// The `'s` lifetime parameter refers to the lifetime of the underlying set. +pub struct StreamWithStateBuilder<'s, A = AlwaysMatch>( + raw::StreamWithStateBuilder<'s, A>, +); + +impl<'s, A: Automaton> StreamWithStateBuilder<'s, A> { + /// Specify a greater-than-or-equal-to bound. + pub fn ge>( + self, + bound: T, + ) -> StreamWithStateBuilder<'s, A> { + StreamWithStateBuilder(self.0.ge(bound)) + } + + /// Specify a greater-than bound. + pub fn gt>( + self, + bound: T, + ) -> StreamWithStateBuilder<'s, A> { + StreamWithStateBuilder(self.0.gt(bound)) + } + + /// Specify a less-than-or-equal-to bound. + pub fn le>( + self, + bound: T, + ) -> StreamWithStateBuilder<'s, A> { + StreamWithStateBuilder(self.0.le(bound)) + } + + /// Specify a less-than bound. + pub fn lt>( + self, + bound: T, + ) -> StreamWithStateBuilder<'s, A> { + StreamWithStateBuilder(self.0.lt(bound)) + } +} + +impl<'s, 'a, A: 'a + Automaton> IntoStreamer<'a> + for StreamWithStateBuilder<'s, A> +where + A::State: Clone, +{ + type Item = (&'a [u8], A::State); + type Into = StreamWithState<'s, A>; + + fn into_stream(self) -> StreamWithState<'s, A> { + StreamWithState(self.0.into_stream()) + } +} + /// A builder for collecting set streams on which to perform set operations. /// /// Set operations include intersection, union, difference and symmetric