From 5dc04fb0ce19d2dd6d2ed9d54c5d74c1358c0b01 Mon Sep 17 00:00:00 2001 From: blt__ Date: Mon, 10 Apr 2023 12:28:45 +0400 Subject: [PATCH] Implement two-way iterators and peekables --- .vscode/settings.json | 3 + src/iter.rs | 104 +++++++++++++++----- src/lib.rs | 1 + src/two_way_peekable.rs | 213 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 299 insertions(+), 22 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/two_way_peekable.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b48948ab --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.showUnlinkedFileNotification": false +} diff --git a/src/iter.rs b/src/iter.rs index 13ff5780..ca997bcb 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -73,6 +73,7 @@ use crate::str_utils::{ last_line_start_byte_idx, line_to_byte_idx, trim_line_break, }; use crate::tree::{Count, Node, TextInfo}; +use crate::two_way_peekable::{TwoWayIterator, TwoWayPeekable}; //========================================================== @@ -211,16 +212,20 @@ impl<'a> Bytes<'a> { self } + /// Return peekable version of the iterator + #[inline(always)] + pub fn two_way_peekable(self) -> TwoWayPeekable { + // this is needed, so that users can call `.two_way_peekable()` without importing TwoWayIterator + TwoWayIterator::two_way_peekable(self) + } + /// Advances the iterator backwards and returns the previous value. /// /// Runs in amortized O(1) time and worst-case O(log N) time. #[inline(always)] pub fn prev(&mut self) -> Option { - if !self.is_reversed { - self.prev_impl() - } else { - self.next_impl() - } + // this is needed, so that users can call `.prev()` without importing TwoWayIterator + TwoWayIterator::prev(self) } #[inline] @@ -298,6 +303,16 @@ impl<'a> Iterator for Bytes<'a> { } } +impl<'a> TwoWayIterator for Bytes<'a> { + fn prev(&mut self) -> Option { + if !self.is_reversed { + self.prev_impl() + } else { + self.next_impl() + } + } +} + impl<'a> ExactSizeIterator for Bytes<'a> {} //========================================================== @@ -441,16 +456,20 @@ impl<'a> Chars<'a> { self } + /// Return peekable version of the iterator + #[inline(always)] + pub fn two_way_peekable(self) -> TwoWayPeekable { + // this is needed, so that users can call `.two_way_peekable()` without importing TwoWayIterator + TwoWayIterator::two_way_peekable(self) + } + /// Advances the iterator backwards and returns the previous value. /// /// Runs in amortized O(1) time and worst-case O(log N) time. #[inline(always)] pub fn prev(&mut self) -> Option { - if !self.is_reversed { - self.prev_impl() - } else { - self.next_impl() - } + // this is needed, so that users can call `.prev()` without importing TwoWayIterator + TwoWayIterator::prev(self) } #[inline] @@ -536,6 +555,20 @@ impl<'a> Iterator for Chars<'a> { } } +impl<'a> TwoWayIterator for Chars<'a> { + /// Advances the iterator backwards and returns the previous value. + /// + /// Runs in amortized O(1) time and worst-case O(log N) time. + #[inline(always)] + fn prev(&mut self) -> Option { + if !self.is_reversed { + self.prev_impl() + } else { + self.next_impl() + } + } +} + impl<'a> ExactSizeIterator for Chars<'a> {} //========================================================== @@ -751,17 +784,20 @@ impl<'a> Lines<'a> { self } + /// Return peekable version of the iterator + #[inline(always)] + pub fn two_way_peekable(self) -> TwoWayPeekable { + // this is needed, so that users can call `.two_way_peekable()` without importing TwoWayIterator + TwoWayIterator::two_way_peekable(self) + } + /// Advances the iterator backwards and returns the previous value. /// - /// Runs in O(1) time with respect to rope length and O(N) time with - /// respect to line length. + /// Runs in amortized O(1) time and worst-case O(log N) time. #[inline(always)] pub fn prev(&mut self) -> Option> { - if self.is_reversed { - self.next_impl() - } else { - self.prev_impl() - } + // this is needed, so that users can call `.prev()` without importing TwoWayIterator + TwoWayIterator::prev(self) } fn prev_impl(&mut self) -> Option> { @@ -1244,6 +1280,16 @@ impl<'a> Iterator for Lines<'a> { } } +impl<'a> TwoWayIterator for Lines<'a> { + fn prev(&mut self) -> Option { + if !self.is_reversed { + self.prev_impl() + } else { + self.next_impl() + } + } +} + impl ExactSizeIterator for Lines<'_> {} //========================================================== @@ -1535,16 +1581,20 @@ impl<'a> Chunks<'a> { self } + /// Return peekable version of the iterator + #[inline(always)] + pub fn two_way_peekable(self) -> TwoWayPeekable { + // this is needed, so that users can call `.two_way_peekable()` without importing TwoWayIterator + TwoWayIterator::two_way_peekable(self) + } + /// Advances the iterator backwards and returns the previous value. /// /// Runs in amortized O(1) time and worst-case O(log N) time. #[inline(always)] pub fn prev(&mut self) -> Option<&'a str> { - if !self.is_reversed { - self.prev_impl() - } else { - self.next_impl() - } + // this is needed, so that users can call `.prev()` without importing TwoWayIterator + TwoWayIterator::prev(self) } fn prev_impl(&mut self) -> Option<&'a str> { @@ -1713,6 +1763,16 @@ impl<'a> Iterator for Chunks<'a> { } } +impl<'a> TwoWayIterator for Chunks<'a> { + fn prev(&mut self) -> Option { + if !self.is_reversed { + self.prev_impl() + } else { + self.next_impl() + } + } +} + #[cfg(test)] mod tests { #![allow(clippy::while_let_on_iterator)] diff --git a/src/lib.rs b/src/lib.rs index 3d7d6094..2a32d0ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -178,6 +178,7 @@ mod tree; pub mod iter; pub mod str_utils; +pub mod two_way_peekable; use std::ops::Bound; diff --git a/src/two_way_peekable.rs b/src/two_way_peekable.rs new file mode 100644 index 00000000..21c63521 --- /dev/null +++ b/src/two_way_peekable.rs @@ -0,0 +1,213 @@ +pub trait TwoWayIterator: Iterator { + fn prev(&mut self) -> Option; + + fn two_way_peekable(self) -> TwoWayPeekable + where + Self: Sized, + Self::Item: Copy, + { + TwoWayPeekable { + itr: self, + peeked: Peeked::None, + } + } +} + +#[derive(Debug)] +enum Peeked { + None, + Prev(Option), // remember peeked value even if it was none + Next(Option), +} + +pub struct TwoWayPeekable +where + I: TwoWayIterator, + I::Item: Copy, +{ + itr: I, + peeked: Peeked, +} + +impl Iterator for TwoWayPeekable +where + I: TwoWayIterator, + I::Item: Copy, +{ + type Item = I::Item; + + /// Advances the iterator forward and returns the next value. + #[inline] + fn next(&mut self) -> Option { + match self.peeked { + Peeked::None => self.itr.next(), + Peeked::Next(next) => { + self.peeked = Peeked::None; + next + } + Peeked::Prev(_) => { + self.peeked = Peeked::None; + self.itr.next(); // compensate for prev peeked one + self.itr.next() + } + } + } +} + +impl TwoWayIterator for TwoWayPeekable +where + I: TwoWayIterator, + I::Item: Copy, +{ + /// Advances the iterator backwards and returns the previous value. + #[inline] + fn prev(&mut self) -> Option { + match self.peeked { + Peeked::None => self.itr.prev(), + Peeked::Prev(prev) => { + self.peeked = Peeked::None; + prev + } + Peeked::Next(_) => { + self.peeked = Peeked::None; + self.itr.prev(); // compensate for prev peeked one + self.itr.prev() + } + } + } +} + +impl TwoWayPeekable +where + I: TwoWayIterator, + I::Item: Copy, +{ + /// Return the next value witout advancing the iterator. + #[inline] + pub fn peek_next(&mut self) -> Option { + match self.peeked { + Peeked::Next(next) => next, + _ => { + if let Peeked::Prev(Some(_)) = self.peeked { + // compensate for prev peeked one + self.itr.next(); + } + + let next = self.itr.next(); + self.peeked = Peeked::Next(next); + next + } + } + } + + /// Return the previous value witout advancing the iterator. + #[inline] + pub fn peek_prev(&mut self) -> Option { + match self.peeked { + Peeked::Prev(prev) => prev, + _ => { + if let Peeked::Next(Some(_)) = self.peeked { + // compensate for next peeked one + self.itr.prev(); + } + + let prev = self.itr.prev(); + self.peeked = Peeked::Prev(prev); + prev + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Rope; + + #[test] + #[cfg_attr(miri, ignore)] + fn chars_01() { + let r = Rope::from_str("a"); + let mut i = r.chars().two_way_peekable(); + + assert_eq!(None, i.prev()); + assert_eq!(Some('a'), i.peek_next()); + assert_eq!(Some('a'), i.next()); + } + + #[test] + #[cfg_attr(miri, ignore)] + fn chars_02() { + let r = Rope::from_str("a"); + let mut i = r.chars().two_way_peekable(); + + assert_eq!(Some('a'), i.next()); + assert_eq!(None, i.next()); + assert_eq!(Some('a'), i.peek_prev()); + assert_eq!(Some('a'), i.prev()); + assert_eq!(None, i.peek_prev()); + assert_eq!(Some('a'), i.peek_next()); + } + + #[test] + #[cfg_attr(miri, ignore)] + fn chars_03() { + let r = Rope::from_str("ab"); + let mut i = r.chars().two_way_peekable(); + + assert_eq!(Some('a'), i.next()); + assert_eq!(Some('b'), i.peek_next()); + assert_eq!(Some('a'), i.peek_prev()); + assert_eq!(Some('b'), i.next()); + } + + #[test] + #[cfg_attr(miri, ignore)] + fn chars_04() { + let r = Rope::from_str("ab"); + let mut i = r.chars().two_way_peekable(); + + assert_eq!(Some('a'), i.next()); + assert_eq!(Some('b'), i.next()); + assert_eq!(None, i.peek_next()); + assert_eq!(Some('b'), i.peek_prev()); + assert_eq!(None, i.next()); + assert_eq!(Some('b'), i.peek_prev()); + assert_eq!(Some('b'), i.prev()); + assert_eq!(Some('a'), i.prev()); + } + + #[test] + #[cfg_attr(miri, ignore)] + fn chars_reversed() { + let r = Rope::from_str("ab"); + let mut i = r.chars().reversed().two_way_peekable(); + + assert_eq!(None, i.next()); + assert_eq!(Some('a'), i.prev()); + assert_eq!(Some('b'), i.peek_prev()); + assert_eq!(Some('a'), i.peek_next()); + assert_eq!(Some('b'), i.prev()); + assert_eq!(None, i.prev()); + } + + #[test] + #[cfg_attr(miri, ignore)] + fn lines() { + let r = Rope::from_str( + "\ +Roses are red +Violets are blue +Ropes are brown +or yellow? idk", + ); + let mut i = r.lines().two_way_peekable(); + + assert_eq!(Some("Roses are red\n".into()), i.next()); + assert_eq!(Some("Violets are blue\n".into()), i.next()); + assert_eq!(Some("Ropes are brown\n".into()), i.next()); + assert_eq!(Some("or yellow? idk".into()), i.peek_next()); + assert_eq!(Some("Ropes are brown\n".into()), i.peek_prev()); + assert_eq!(Some("or yellow? idk".into()), i.next()); + } +}