diff options
author | Greg Brown <gmb60@cam.ac.uk> | 2020-11-09 14:40:14 +0000 |
---|---|---|
committer | Greg Brown <gmb60@cam.ac.uk> | 2020-11-09 14:40:14 +0000 |
commit | 787de6d0a0c9d4e6331351a471da6790bfa99e49 (patch) | |
tree | 3a11318ff39324a1bd2cadc8aac1dcaed39fa087 /src/nibble/parse | |
parent | 055cf5beabd6b397168023341e01cf5e94a0f060 (diff) |
Parser with some tests
Diffstat (limited to 'src/nibble/parse')
-rw-r--r-- | src/nibble/parse/iter.rs | 149 | ||||
-rw-r--r-- | src/nibble/parse/mod.rs | 61 |
2 files changed, 210 insertions, 0 deletions
diff --git a/src/nibble/parse/iter.rs b/src/nibble/parse/iter.rs new file mode 100644 index 0000000..1c635a3 --- /dev/null +++ b/src/nibble/parse/iter.rs @@ -0,0 +1,149 @@ +use std::iter::Peekable; + +pub use self::take_while::PeekableTakeWhile; + +/// An iterator with a `peek()` that returns an optional reference to the next +/// element. +/// +/// This trait is an extension of the `Peekable` type in the standard library. +pub trait Peek: Iterator { + /// Returns a reference to the [`next`] value without advancing the iterator. + /// + /// Like [`next`], if there is a value, it is wrapped in [`Some`]. But if + /// iteration is over, [`None`] is returned. + fn peek(&mut self) -> Option<&Self::Item>; +} + +impl<I: Peek> Peek for &mut I { + fn peek(&mut self) -> Option<&Self::Item> { + I::peek(self) + } +} + +impl<I> Peek for Box<&mut dyn Peek<Item = I>> { + fn peek(&mut self) -> Option<&Self::Item> { + (**self).peek() + } +} + +impl<I: Iterator> Peek for Peekable<I> { + fn peek(&mut self) -> Option<&Self::Item> { + Peekable::peek(self) + } +} + +pub trait PeekExt: Peek { + /// Returns the [`next`] value and advance the iterator only if `pred` is true. + fn next_if<P: FnOnce(&Self::Item) -> bool>( + &mut self, + pred: P, + ) -> Option<Result<Self::Item, &mut Self>> { + let i = self.peek()?; + + if pred(i) { + Some(Ok(self.next().unwrap())) + } else { + Some(Err(self)) + } + } + + fn next_if_then< + P: FnOnce(&Self::Item) -> bool, + F: FnOnce(Self::Item) -> T, + G: FnOnce(&mut Self) -> T, + T, + >( + &mut self, + pred: P, + matched: F, + unmatched: G, + ) -> Option<T> { + let i = self.peek()?; + + if pred(i) { + Some(matched(self.next().unwrap())) + } else { + Some(unmatched(self)) + } + } + + /// Advances the iterator only if the [`next`] item equals `val`. + fn next_if_eq<T>(&mut self, val: &T) -> Option<Result<(), &Self::Item>> + where + Self::Item: PartialEq<T>, + { + self.next_if(|i| PartialEq::eq(i, val)) + .map(|r| r.map(|_| ()).map_err(|i| i.peek().unwrap())) + } + + fn consume_if_next<T>(&mut self, val: &T) -> bool + where + Self::Item: PartialEq<T>, + { + self.next_if_eq(val).map(|r| r.is_ok()).unwrap_or(false) + } + + /// Advance the iterator until `pred` is false, or `peek` returns [`None`]. + fn advance_while<P: FnMut(&Self::Item) -> bool>(&mut self, mut pred: P) { + while self.next_if(&mut pred).map(|r| r.is_ok()).unwrap_or(false) { + // Condition already loops for us + } + } +} + +impl<I: Peek> PeekExt for I {} + +mod take_while { + use super::Peek; + + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct PeekableTakeWhile<I, F> { + inner: I, + predicate: Option<F>, + } + + impl<I, F> PeekableTakeWhile<I, F> + where + I: Peek, + F: FnMut(&<I as Iterator>::Item) -> bool, + { + pub fn new(iter: I, predicate: F) -> Self { + Self { + inner: iter, + predicate: Some(predicate), + } + } + } + + impl<I, F> Iterator for PeekableTakeWhile<I, F> + where + I: Peek, + F: FnMut(&<I as Iterator>::Item) -> bool, + { + type Item = <I as Iterator>::Item; + + fn next(&mut self) -> Option<Self::Item> { + // Can't inline because of lifetimes + let inner = &mut self.inner; + if self.predicate.as_mut().and_then(|p| inner.peek().map(p))? { + self.inner.next() + } else { + self.predicate = None; + None + } + } + } + + impl<I, F> Peek for PeekableTakeWhile<I, F> + where + I: Peek, + F: FnMut(&<I as Iterator>::Item) -> bool, + { + fn peek(&mut self) -> Option<&Self::Item> { + let inner = &mut self.inner; + self.predicate + .as_mut() + .and_then(move |p| inner.peek().filter(|i| p(i))) + } + } +} diff --git a/src/nibble/parse/mod.rs b/src/nibble/parse/mod.rs new file mode 100644 index 0000000..13c4c0e --- /dev/null +++ b/src/nibble/parse/mod.rs @@ -0,0 +1,61 @@ +pub mod iter; + +use self::iter::Peek; +use self::iter::PeekExt; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ParseError<E> { + EndOfFile, + InvalidCharacter(char), + Other(E), +} + +impl<U> ParseError<U> { + pub fn map<T: Into<U>>(other: ParseError<T>) -> Self { + match other { + ParseError::EndOfFile => Self::EndOfFile, + ParseError::InvalidCharacter(c) => Self::InvalidCharacter(c), + ParseError::Other(t) => Self::Other(t.into()) + } + } +} + +/// Parse a value from an iterator. +pub trait Parse: Sized { + /// The associated error that can be returned from parsing. + type Err; + /// Parses `iter` to return a value of this type. + /// + /// This method may not reach the end of `iter`. If so, it must not consume any + /// characters in the stream that do not form part of the value. + /// + /// If parsing succeeds, return the value inside [`Ok`]. Otherwise, when the + /// iterator is ill-formatted, return an error specific to the inside [`Err`]. + /// The error type is specific to the implementation of the trait. + fn parse<I: PeekExt<Item = char>>(iter: I) -> Result<Self, ParseError<Self::Err>>; +} + +/// Consumes the next item in `iter` if it equals `c`. Otherwise, returns an +/// appropriate error without advancing the iterator. +/// +/// # Examples +/// Basic usage +/// ``` +/// use chomp::nibble::parse::{requires, Parse, ParseError}; +/// use std::convert::Infallible; +/// +/// let s = "hello".to_owned(); +/// let mut iter = s.chars().peekable(); +/// assert_eq!(requires::<_, Infallible>(&mut iter, 'h'), Ok(())); +/// assert_eq!(requires::<_, Infallible>(&mut iter, 'e'), Ok(())); +/// assert_eq!(requires::<_, Infallible>(&mut iter, 'z'), Err(ParseError::InvalidCharacter('l'))); +/// assert_eq!(iter.next(), Some('l')); +/// assert_eq!(iter.next(), Some('l')); +/// assert_eq!(iter.next(), Some('o')); +/// assert_eq!(requires::<_, Infallible>(iter, '!'), Err(ParseError::EndOfFile)); +/// ``` +pub fn requires<I: Peek<Item = char>, E>(mut iter: I, c: char) -> Result<(), ParseError<E>> { + iter.next_if_eq(&c) + .ok_or(ParseError::EndOfFile)? + .map_err(|&c| ParseError::InvalidCharacter(c)) +} |