diff --git a/src/util/mod.rs b/src/util/mod.rs index 0d0153b..682059d 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,6 +2,8 @@ pub mod bear; pub mod crypto; pub mod http; pub mod password; +/// Cursor utilities for parsers. +pub mod slice; pub mod token; pub mod transcode; pub mod validate; diff --git a/src/util/slice.rs b/src/util/slice.rs new file mode 100644 index 0000000..1fd0b9d --- /dev/null +++ b/src/util/slice.rs @@ -0,0 +1,259 @@ +use std::ops::{Add, AddAssign, Not, Sub, SubAssign}; + +/// Helper utility for parsers operating on slices +pub struct SliceCursor<'a, T> { + data: &'a [T], + pos: Position, + chop: usize, +} + +/// Helper for the [`SliceCursor`] helper. +struct Position { + /// Always within -1 and `end` (both inclusive). + pos: isize, + /// Length of the slice (i.e. first out-of-bound index). + end: usize, +} + +impl<'a, T> SliceCursor<'a, T> { + pub fn new(data: &'a [T]) -> Self { + assert!(data.len() <= isize::MAX as usize); + + Self { + data, + pos: Position::new(data.len()), + chop: 0, + } + } +} + +impl<'a, T> SliceCursor<'a, T> { + /// Return the item at the current position, if any. + pub fn current(&self) -> Option<&'a T> { + self.pos.index().map(|index| &self.data[index]) + } + + /// Advance the cursor to the next item and return that item. + pub fn next(&mut self) -> Option<&'a T> { + self.pos.advance().map(|index| &self.data[index]) + } + + /// Return the next item without advancing the cursor. + pub fn peek(&self) -> Option<&'a T> { + self.pos.next_index().map(|index| &self.data[index]) + } + + /// Return the current item and reverse the cursor. + pub fn prev(&mut self) -> Option<&'a T> { + self.pos.reverse().map(|index| &self.data[index]) + } + + /// Peek for the next item and advance the cursor if `predicate` is true. + pub fn next_if(&mut self, predicate: F) -> Option<&'a T> + where + F: FnOnce(&'a T) -> bool, + { + let next = self.peek()?; + if predicate(next) { + self.pos.advance(); + Some(next) + } else { + None + } + } + + pub fn next_while(&mut self, mut predicate: F) -> &'a [T] + where + F: FnMut(&'a T) -> bool, + { + let start = self.pos.next_index_or_end(); + let len = self.data[start..] + .iter() + .enumerate() + .find_map(|(i, v)| predicate(v).not().then_some(i)) + .unwrap_or(self.data.len() - start); + let end = start + len; + self.pos += len; + &self.data[start..end] + } + + /// Return a slice over all elements since the last time this method was called. + /// If the cursor went backwards, the slice is empty. + pub fn chop(&mut self) -> &'a [T] { + let start = self.chop; + let end = self.pos.next_index_or_end(); + + let slice = if start < end { + &self.data[start..end] + } else { + &[] + }; + + self.chop = end; + slice + } + + /// Return how many items remain in the cursor. + /// This indicates how many times it is possible + /// to call [`Self::next()`] before it returns `None`. + pub fn remaining(&self) -> usize { + self.data.len() - self.pos.next_index_or_end() + } +} + +impl Position { + pub fn new(end: usize) -> Position { + assert!(end <= isize::MAX as usize); + Position { pos: -1, end } + } + + pub fn advance(&mut self) -> Option { + *self += 1; + self.index() + } + + pub fn reverse(&mut self) -> Option { + *self -= 1; + self.index() + } + + pub fn jump_to(&mut self, index: usize) { + assert!(index <= self.end); + self.pos = index as isize; + } + + pub fn index(&self) -> Option { + (0..self.end as isize) + .contains(&self.pos) + .then_some(self.pos as usize) + } + + pub fn next_index(&self) -> Option { + let next_pos = assert_usize(self.pos + 1); + (next_pos < self.end).then_some(next_pos) + } + + pub fn next_index_or_end(&self) -> usize { + self.next_index().unwrap_or(self.end) + } +} + +impl Add for Position { + type Output = Position; + fn add(self, rhs: usize) -> Position { + let rhs = assert_isize(rhs); + let result = self.pos.saturating_add(rhs); + Position { + pos: result.min(self.end as isize), + end: self.end, + } + } +} + +impl AddAssign for Position { + fn add_assign(&mut self, rhs: usize) { + let rhs = assert_isize(rhs); + self.pos = self.pos.saturating_add(rhs).min(self.end as isize); + } +} + +impl Sub for Position { + type Output = Position; + fn sub(self, rhs: usize) -> Position { + let rhs = assert_isize(rhs); + let result = self.pos.saturating_sub(rhs); + Position { + pos: result.max(-1), + end: self.end, + } + } +} + +impl SubAssign for Position { + fn sub_assign(&mut self, rhs: usize) { + let rhs = assert_isize(rhs); + self.pos = self.pos.saturating_sub(rhs).max(-1); + } +} + +fn assert_usize(i: isize) -> usize { + assert!(i >= 0); + i as usize +} + +fn assert_isize(u: usize) -> isize { + assert!(u <= isize::MAX as usize); + u as isize +} + +#[cfg(test)] +mod tests { + use crate::util::slice::SliceCursor; + + #[test] + fn next_and_prev() { + let data: Vec = (0..10).collect(); + let mut cursor = SliceCursor::new(&data); + assert_eq!(cursor.remaining(), 10); + + for (i, v) in data.iter().enumerate() { + assert_eq!(cursor.remaining(), data.len() - i); + assert_eq!(cursor.next(), Some(v)); + } + assert_eq!(cursor.remaining(), 0); + assert!(cursor.next().is_none()); + assert_eq!(cursor.remaining(), 0); + + for (i, v) in data.iter().rev().enumerate() { + assert_eq!(cursor.prev(), Some(v)); + assert_eq!(cursor.remaining(), i); + } + assert_eq!(cursor.prev(), None); + assert_eq!(cursor.remaining(), 10); + } + + #[test] + fn chop() { + let data: Vec = (0..10).collect(); + let mut cursor = SliceCursor::new(&data); + + assert_eq!(cursor.chop(), &data[0..0]); + + cursor.next(); + assert_eq!(cursor.chop(), &data[0..1]); + + for _ in 0..3 { + cursor.next(); + } + assert_eq!(cursor.chop(), &data[1..4]); + + cursor.prev(); + assert_eq!(cursor.chop(), &data[3..3]); + + while cursor.next().is_some() {} + assert_eq!(cursor.chop(), &data[3..10]); + + for i in (0u8..10).rev() { + assert_eq!(cursor.prev(), Some(&i)); + } + assert!(cursor.prev().is_none()); + assert_eq!(cursor.chop(), &data[0..0]); + + while cursor.next().is_some() {} + assert_eq!(cursor.chop(), &data[..]); + assert_eq!(cursor.chop(), &data[10..10]); + } + + #[test] + fn predicates() { + let data: Vec = (0..10).collect(); + let mut cursor = SliceCursor::new(&data); + + assert_eq!(cursor.next_if(|c| *c == 0), Some(&data[0])); + assert_eq!(cursor.next_if(|c| *c == 0), None); + + assert_eq!(cursor.next_while(|c| *c < 5), &data[1..5]); + assert_eq!(cursor.current(), Some(&4)); + assert_eq!(cursor.chop(), &data[0..5]); + } +}