From 34a579332d66457f55edd27412e0831f4af07ccd Mon Sep 17 00:00:00 2001 From: Myrddin Dundragon Date: Mon, 5 May 2025 18:08:39 -0400 Subject: [PATCH] Added a Lookahead iterator. This adds a Lookahead iterator so that while parsing it is easier to peek ahead however much the parser needs. Basic parsers may not need any, but a lot of parsers have two token lookahead. I've even seen some with three. --- src/iter.rs | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + tests/iter.rs | 43 +++++++++++ 3 files changed, 254 insertions(+) create mode 100644 src/iter.rs create mode 100644 tests/iter.rs diff --git a/src/iter.rs b/src/iter.rs new file mode 100644 index 0000000..dc7d3d4 --- /dev/null +++ b/src/iter.rs @@ -0,0 +1,209 @@ +//! An iterator adapter for arbitrary lookahead functionality. +//! +//! This module provides [`Lookahead`], an adapter for any iterator that allows +//! you to peek ahead by any number of elements, without consuming them. +//! +//! ## Example +//! ``` +//! use rune::LookaheadExt; +//! +//! let mut it = vec![10, 20, 30].into_iter().lookahead(); +//! +//! assert_eq!(it.peek(0), Some(&10)); +//! assert_eq!(it.peek(1), Some(&20)); +//! assert_eq!(it.next(), Some(10)); +//! assert_eq!(it.peek(0), Some(&20)); +//! ``` + +use std::collections::VecDeque; +use std::fmt; +use std::iter::{Fuse, FusedIterator}; + + + +/// An iterator adapter that allows arbitrary lookahead peeking. +/// +/// This struct wraps an iterator and buffers items so that any future +/// item can be accessed by index without consuming them. Similar to +/// [`std::iter::Peekable`], but supports peeking any number of steps ahead. +pub struct Lookahead + where I: Iterator +{ + iter: Fuse, + buffer: VecDeque +} + + + +impl Lookahead where I: Iterator +{ + /// Creates a new [`Lookahead`] from the given iterator. + /// + /// This constructor is typically used indirectly via the + /// [`LookaheadExt::lookahead()`] method or [`lookahead()`] free function. + #[must_use] + pub fn new(iter: I) -> Self + { + Lookahead { iter: iter.fuse(), + buffer: VecDeque::new() } + } + + /// Returns a reference to the `n`th upcoming item, if it exists. + /// + /// `peek(0)` is the same as peeking at the next item. + /// + /// This does **not consume** any items from the iterator. + /// + /// # Examples + /// ``` + /// use rune::LookaheadExt; + /// + /// let mut it = vec![1, 2, 3].into_iter().lookahead(); + /// + /// assert_eq!(it.peek(1), Some(&2)); + /// assert_eq!(it.next(), Some(1)); + /// ``` + pub fn peek(&mut self, n: usize) -> Option<&I::Item> + { + while self.buffer.len() <= n + { + if let Some(item) = self.iter.next() + { + self.buffer.push_back(item); + } + else + { + break; + } + } + self.buffer.get(n) + } + + /// Returns a mutable reference to the `n`th upcoming item, if it exists. + /// + /// This allows in-place modification of peeked items before consumption. + /// + /// # Examples + /// ``` + /// use rune::LookaheadExt; + /// + /// let mut it = vec![1, 2, 3].into_iter().lookahead(); + /// if let Some(x) = it.peek_mut(1) + /// { + /// *x *= 10; + /// } + /// assert_eq!(it.next(), Some(1)); + /// assert_eq!(it.next(), Some(20)); + /// ``` + pub fn peek_mut(&mut self, n: usize) -> Option<&mut I::Item> + { + while self.buffer.len() <= n + { + if let Some(item) = self.iter.next() + { + self.buffer.push_back(item); + } + else + { + break; + } + } + self.buffer.get_mut(n) + } +} + +impl Iterator for Lookahead where I: Iterator +{ + type Item = I::Item; + + /// Retrieves the next item, consuming it. + /// + /// If any items were previously peeked and buffered, they are returned + /// first before accessing the underlying iterator. + fn next(&mut self) -> Option + { + if let Some(front) = self.buffer.pop_front() + { + Some(front) + } + else + { + self.iter.next() + } + } + + /// Provides a size hint accounting for both buffered and remaining elements. + fn size_hint(&self) -> (usize, Option) + { + let (low, high) = self.iter.size_hint(); + let buffered = self.buffer.len(); + (low.saturating_add(buffered), high.and_then(|h| h.checked_add(buffered))) + } +} + +impl Clone for Lookahead + where I: Iterator + Clone, + I::Item: Clone +{ + fn clone(&self) -> Self + { + Lookahead { iter: self.iter.clone(), + buffer: self.buffer.clone() } + } +} + +impl fmt::Debug for Lookahead + where I: Iterator + fmt::Debug, + I::Item: fmt::Debug +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + f.debug_struct("Lookahead") + .field("iter", &self.iter) + .field("buffer", &self.buffer) + .finish() + } +} + +impl FusedIterator for Lookahead where I: Iterator + FusedIterator {} + + + +/// Extension trait to provide `.lookahead()` on all iterators. +/// +/// This lets you easily call `.lookahead()` on any iterator to +/// create a [`Lookahead`] instance. +pub trait LookaheadExt: Iterator + Sized +{ + /// Wraps the iterator in a [`Lookahead`] adapter. + fn lookahead(self) -> Lookahead; +} + +impl LookaheadExt for I +{ + fn lookahead(self) -> Lookahead + { + Lookahead::new(self) + } +} + + + +/// Creates a [`Lookahead`] from any iterable. +/// +/// This is a convenience function for use in functional-style code or +/// when not using the extension trait. +/// +/// # Example +/// ``` +/// use rune::lookahead; +/// +/// let mut it = lookahead(vec![1, 2, 3]); +/// +/// assert_eq!(it.peek(2), Some(&3)); +/// ``` +pub fn lookahead(iterable: I) -> Lookahead + where I: IntoIterator +{ + Lookahead::new(iterable.into_iter()) +} diff --git a/src/lib.rs b/src/lib.rs index 0532f24..297aa55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub mod library; mod ast; mod error; +mod iter; mod lexer; mod position; mod token; @@ -17,6 +18,7 @@ mod token; pub use crate::ast::*; pub use crate::error::*; +pub use crate::iter::*; pub use crate::lexer::*; pub use crate::position::*; pub use crate::token::*; diff --git a/tests/iter.rs b/tests/iter.rs new file mode 100644 index 0000000..83770f9 --- /dev/null +++ b/tests/iter.rs @@ -0,0 +1,43 @@ +use rune::*; + +#[test] +fn peek_works() +{ + let mut it = vec![1, 2, 3].into_iter().lookahead(); + assert_eq!(it.peek(0), Some(&1)); + assert_eq!(it.peek(1), Some(&2)); + assert_eq!(it.peek(2), Some(&3)); + assert_eq!(it.peek(3), None); +} + +#[test] +fn peek_mut_modifies_item() +{ + let mut it = vec![10, 20, 30].into_iter().lookahead(); + if let Some(x) = it.peek_mut(1) + { + *x += 100; + } + assert_eq!(it.next(), Some(10)); + assert_eq!(it.next(), Some(120)); +} + +#[test] +fn iterates_correctly() +{ + let mut it = vec![1, 2, 3].into_iter().lookahead(); + assert_eq!(it.next(), Some(1)); + assert_eq!(it.next(), Some(2)); + assert_eq!(it.next(), Some(3)); + assert_eq!(it.next(), None); +} + +#[test] +fn size_hint_accounts_for_buffer() +{ + let mut it = vec![1, 2, 3, 4].into_iter().lookahead(); + it.peek(2); + let (low, high) = it.size_hint(); + assert!(low >= 4); + assert_eq!(high, Some(4)); +}