changeset 28:f3b758646c58

Implement Lazy combinator
author Lewin Bormann <lewin@lewin-bormann.info>
date Thu, 06 Jun 2019 22:28:00 +0200
parents 385f1b74b9e5
children 09d274a46fa4
files src/combinators.rs
diffstat 1 files changed, 86 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/combinators.rs	Thu Jun 06 22:27:45 2019 +0200
+++ b/src/combinators.rs	Thu Jun 06 22:28:00 2019 +0200
@@ -384,6 +384,51 @@
     }
 }
 
+/// Lazy is a helper for a typical situation where you have an `Alternative` or a `Sequence` and
+/// don't want to construct an expensive parser every time just in order for it to be dropped
+/// without having parsed anything. For example:
+///
+/// ```ignore
+/// // Let's say dict is really expensive! the first ones not as much
+/// let mut p = Alternative::new((number(), string(), atom(), dict()));
+/// ```
+///
+/// Then you can wrap the `dict` parser constructor in a `Lazy` parser. Then it will only be
+/// constructed if the `Alternative` actually needs a `dict` parser:
+///
+/// ```ignore
+/// let mut p = Alternative::new((number(), string(), atom(), Lazy::new(dict)));
+/// ```
+///
+/// Constructing a `Lazy` combinator is in comparison quite cheap, as it only involves copying a
+/// function pointer. `Lazy` also caches the result of the function, meaning it will be called at
+/// most once.
+pub struct Lazy<P, F: FnMut() -> P>(F, Option<P>);
+
+impl<R, P: Parser<Result = R>, F: FnMut() -> P> Lazy<P, F> {
+    /// Create a new instance of `Lazy`:
+    ///
+    /// ```ignore
+    /// let l = Lazy::new(|| some_expensive_function());
+    /// ```
+    pub fn new(f: F) -> Lazy<P, F> {
+        Lazy(f, None)
+    }
+}
+
+impl<R, P: Parser<Result = R>, F: FnMut() -> P> Parser for Lazy<P, F> {
+    type Result = R;
+    fn parse(
+        &mut self,
+        st: &mut ParseState<impl Iterator<Item = char>>,
+    ) -> ParseResult<Self::Result> {
+        if self.1.is_none() {
+            self.1 = Some((self.0)());
+        }
+        self.1.as_mut().unwrap().parse(st)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -500,4 +545,45 @@
             p.parse(&mut ps)
         );
     }
+
+    #[test]
+    fn test_lazy() {
+        let mut ps = ParseState::new("123");
+        let mut p = Alternative::new((
+            Uint8::new(),
+            Lazy::new(|| {
+                panic!("lazy should not run this function!");
+                Uint8::new()
+            }),
+        ));
+        assert_eq!(Ok(123), p.parse(&mut ps));
+    }
+
+    #[test]
+    fn test_lazy2() {
+        let mut ps = ParseState::new("123");
+        let mut p = Alternative::new((
+            string_none_of("01234", RepeatSpec::Min(1)),
+            Lazy::new(|| Uint8::new().apply(|i| Ok(i.to_string()))),
+        ));
+        assert_eq!(Ok("123".to_string()), p.parse(&mut ps));
+    }
+
+    #[test]
+    fn test_lazy3() {
+        let mut i = 0;
+        let mut ps = ParseState::new("123 124");
+        let mut lzy = || {
+                assert_eq!(0, i);
+                i += 1;
+                string_of("0123456789", RepeatSpec::Min(1))
+            };
+        let mut p = Alternative::new((
+            string_of("a", RepeatSpec::Min(1)),
+            Lazy::new(lzy),
+        ));
+        assert_eq!(Ok("123".to_string()), p.parse(&mut ps));
+        assert!(whitespace().parse(&mut ps).is_ok());
+        assert_eq!(Ok("124".to_string()), p.parse(&mut ps));
+    }
 }