summaryrefslogtreecommitdiff
path: root/src/nibble/parse
diff options
context:
space:
mode:
authorGreg Brown <gmb60@cam.ac.uk>2020-11-09 14:40:14 +0000
committerGreg Brown <gmb60@cam.ac.uk>2020-11-09 14:40:14 +0000
commit787de6d0a0c9d4e6331351a471da6790bfa99e49 (patch)
tree3a11318ff39324a1bd2cadc8aac1dcaed39fa087 /src/nibble/parse
parent055cf5beabd6b397168023341e01cf5e94a0f060 (diff)
Parser with some tests
Diffstat (limited to 'src/nibble/parse')
-rw-r--r--src/nibble/parse/iter.rs149
-rw-r--r--src/nibble/parse/mod.rs61
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))
+}