stacking_iterator/
lib.rs

1//! Iterator utilities for manipulating stacks.
2//!
3//! This library is intended as an alternative to "borrowing" iterators for cases where collections
4//! are formed using a series of stack operations.
5//!
6//! For example, when enumerating the accepted sequences in a finite-state automation, entering a
7//! state takes the form of a push, whereas exiting a state takes the form of a pop. If you reach an
8//! ending state at any point, the stack of transition keys can be read out as an accepted sequence.
9//!
10//! While "pushing" to sequences takes the form of the built-in [`Extend`], "popping" from sequences
11//! is done via the [`Contract`] trait provided by this crate. If you offer an iterator over
12//! [`Instruction`] items, you can then take advantage of the crate's functionality using the
13//! [`StackingIteratorExt`] extension trait.
14#![warn(missing_docs)]
15#![warn(missing_debug_implementations)]
16#![deny(unsafe_code)]
17#![cfg_attr(not(any(feature = "std", test, doc)), no_std)]
18
19#[cfg(feature = "alloc")]
20extern crate alloc;
21
22#[cfg(feature = "alloc")]
23use alloc::{
24    collections::{LinkedList, VecDeque},
25    string::String,
26    vec::Vec,
27};
28use core::fmt;
29
30/// Prelude for this crate, to be glob-imported in relevant code.
31///
32/// This re-exports the [`Contract`] and [`StackingIteratorExt`] traits in addition to the
33/// [`Instruction`] and [`Operation`] enums.
34pub mod prelude {
35    pub use crate::{Contract, Instruction, Operation, StackingIteratorExt};
36}
37
38/// Control flow involving a stack.
39///
40/// Instructions are used to convert an iterator of [`Operation`]s into an iterator of stack contents.
41#[derive(Copy, Clone, PartialEq, Eq, Hash)]
42pub enum Instruction<T> {
43    /// Mutate the stack with the given operation.
44    Mutate(Operation<T>),
45
46    /// Yield the contents of the stack.
47    Yield,
48}
49impl<T> Instruction<T> {
50    /// Shorthand for wrapping a [`Pop`] operation in [`Mutate`].
51    ///
52    /// [`Pop`]: Operation::Pop
53    /// [`Mutate`]: Instruction::Mutate
54    #[allow(non_upper_case_globals)]
55    pub const Pop: Instruction<T> = Instruction::Mutate(Operation::Pop);
56
57    /// Shorthand for wrapping a [`Push`] operation in [`Mutate`].
58    ///
59    /// [`Push`]: Operation::Push
60    /// [`Mutate`]: Instruction::Mutate
61    #[allow(non_snake_case)]
62    pub const fn Push(val: T) -> Instruction<T> {
63        Instruction::Mutate(Operation::Push(val))
64    }
65
66    /// Returns the operation, if mutating.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use stacking_iterator::prelude::*;
72    ///
73    /// assert_eq!(Instruction::Push(1).into_operation(), Some(Operation::Push(1)));
74    /// assert_eq!(<Instruction<()>>::Pop.into_operation(), Some(Operation::Pop));
75    /// assert_eq!(<Instruction<()>>::Yield.into_operation(), None);
76    /// ```
77    #[inline]
78    pub fn into_operation(self) -> Option<Operation<T>> {
79        match self {
80            Instruction::Mutate(operation) => Some(operation),
81            Instruction::Yield => None,
82        }
83    }
84
85    /// Returns a reference to the operation, if mutating.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use stacking_iterator::prelude::*;
91    ///
92    /// assert_eq!(Instruction::Push(1).as_operation(), Some(&Operation::Push(1)));
93    /// assert_eq!(<Instruction<()>>::Pop.as_operation(), Some(&Operation::Pop));
94    /// assert_eq!(<Instruction<()>>::Yield.as_operation(), None);
95    /// ```
96    #[inline]
97    pub fn as_operation(&self) -> Option<&Operation<T>> {
98        match self {
99            Instruction::Mutate(operation) => Some(operation),
100            Instruction::Yield => None,
101        }
102    }
103}
104impl<T: fmt::Debug> fmt::Debug for Instruction<T> {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        match self {
107            Instruction::Mutate(operation) => fmt::Debug::fmt(operation, f),
108            Instruction::Yield => f.pad("Yield"),
109        }
110    }
111}
112
113/// Stack operation.
114#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
115pub enum Operation<T> {
116    /// Push an item onto the stack.
117    Push(T),
118
119    /// Pop an item from the stack.
120    Pop,
121}
122impl<T> Operation<T> {
123    /// Returns the pushed item, if pushing.
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// use stacking_iterator::prelude::*;
129    ///
130    /// assert_eq!(Operation::Push(1).into_item(), Some(1));
131    /// assert_eq!(<Operation<()>>::Pop.into_item(), None);
132    /// ```
133    #[inline]
134    pub fn into_item(self) -> Option<T> {
135        match self {
136            Operation::Push(item) => Some(item),
137            Operation::Pop => None,
138        }
139    }
140
141    /// Returns a reference to the pushed item, if pushing.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use stacking_iterator::prelude::*;
147    ///
148    /// assert_eq!(Operation::Push(1).as_item(), Some(&1));
149    /// assert_eq!(<Operation<()>>::Pop.as_item(), None);
150    /// ```
151    #[inline]
152    pub fn as_item(&self) -> Option<&T> {
153        match self {
154            Operation::Push(item) => Some(item),
155            Operation::Pop => None,
156        }
157    }
158}
159
160/// Opposite of [`Extend`].
161pub trait Contract {
162    /// Contracts the collection, removing the given number of items from its end.
163    ///
164    /// # Overflow Behavior
165    ///
166    /// This method *may* panic if `count` is larger than the size of the collection.
167    /// Since this check may incur performance penalities, it is only guaranteed if
168    /// debug assertions are enabled.
169    ///
170    /// In these cases, the collection must still correctly remove the elements, but
171    /// may decide to not notify the user that less than the count of items were removed.
172    fn contract(&mut self, count: usize);
173}
174
175fn stack_extend<T>(
176    stack: &mut (impl Extend<T> + Contract),
177    mut iter: impl Iterator<Item = Operation<T>>,
178) {
179    // the iterator will be a series of pushes and pops
180    loop {
181        // use native extend for pushes;
182        // keep track of whether we end on a pop, or `None`
183        let mut pop_count = 0;
184        stack.extend(iter.by_ref().map_while(|op| match op {
185            Operation::Push(val) => Some(val),
186            Operation::Pop => {
187                pop_count = 1;
188                None
189            }
190        }));
191
192        // if we end on None, break out of loop
193        if pop_count == 0 {
194            return;
195        }
196
197        // use native count for pops;
198        // keep track of whether we end on a push, or `None`
199        let mut next = None;
200
201        // we have to use filter_map here to ensure we take the values by-value
202        pop_count += iter
203            .by_ref()
204            .map_while(|op| match op {
205                Operation::Pop => Some(()),
206                Operation::Push(val) => {
207                    next = Some(val);
208                    None
209                }
210            })
211            .count();
212        stack.contract(pop_count);
213
214        // if we end on None, break out of loop
215        if let Some(val) = next {
216            stack.extend(Some(val));
217        } else {
218            return;
219        }
220    }
221}
222
223/// Panic when contracting more than possible.
224#[cold]
225#[track_caller]
226fn panic_imploded(count: usize, len: usize) -> ! {
227    if len == 1 {
228        panic!("cannot contract by {count} when collection has 1 element");
229    } else {
230        panic!("cannot contract by {count} when collection has {len} elements")
231    }
232}
233
234/// Pops items off the [`Vec`].
235#[cfg(feature = "alloc")]
236impl<T> Contract for Vec<T> {
237    #[track_caller]
238    fn contract(&mut self, count: usize) {
239        if count > self.len() {
240            panic_imploded(count, self.len())
241        }
242        self.truncate(self.len() - count);
243    }
244}
245/// Pops items off the back of the [`VecDeque`].
246#[cfg(feature = "alloc")]
247impl<T> Contract for VecDeque<T> {
248    #[track_caller]
249    fn contract(&mut self, count: usize) {
250        if count > self.len() {
251            panic_imploded(count, self.len())
252        }
253        self.truncate(self.len() - count);
254    }
255}
256/// Pops items off the back of the [`LinkedList`].
257#[cfg(feature = "alloc")]
258impl<T> Contract for LinkedList<T> {
259    #[track_caller]
260    fn contract(&mut self, count: usize) {
261        // this is still useful here, since otherwise an excessively large `count` could incur performance penalties
262        if count > self.len() {
263            panic_imploded(count, self.len())
264        }
265        // we don't optimize for count == self.len() since we need to drop all the items anyway
266        for _ in 0..count {
267            self.pop_back();
268        }
269    }
270}
271
272/// Pops some number of characters off the end of the string.
273#[cfg(feature = "alloc")]
274impl Contract for String {
275    #[track_caller]
276    fn contract(&mut self, count: usize) {
277        let mut chars = self.chars();
278
279        // it's slower, but manually counting the number of characters excess is nice to have in debug mode
280        if cfg!(debug_assertions) {
281            let mut excess = 0;
282            let mut amount = 0;
283            for _ in 0..count {
284                if chars.next_back().is_none() {
285                    excess += 1;
286                } else {
287                    amount += 1;
288                }
289            }
290            if excess > 0 {
291                panic_imploded(count, amount);
292            }
293        } else {
294            chars.nth_back(count);
295        }
296
297        let len = chars.as_str().len();
298        self.truncate(len);
299    }
300}
301
302#[cfg(feature = "alloc")]
303impl<T> Extend<Operation<T>> for Vec<T> {
304    #[inline]
305    fn extend<I: IntoIterator<Item = Operation<T>>>(&mut self, iter: I) {
306        stack_extend(self, iter.into_iter())
307    }
308}
309#[cfg(feature = "alloc")]
310impl<T> Extend<Operation<T>> for VecDeque<T> {
311    #[inline]
312    fn extend<I: IntoIterator<Item = Operation<T>>>(&mut self, iter: I) {
313        stack_extend(self, iter.into_iter())
314    }
315}
316#[cfg(feature = "alloc")]
317impl<T> Extend<Operation<T>> for LinkedList<T> {
318    #[inline]
319    fn extend<I: IntoIterator<Item = Operation<T>>>(&mut self, iter: I) {
320        stack_extend(self, iter.into_iter())
321    }
322}
323
324/// Extra methods for iterators of [`Instruction`]s.
325pub trait StackingIteratorExt<T>: Iterator<Item = Instruction<T>> {
326    /// Converts an iterator of [`Instruction`]s into an iterator of collections.
327    ///
328    /// # Example
329    ///
330    /// ```
331    /// use stacking_iterator::prelude::*;
332    ///
333    /// let mut inst = vec![
334    ///     Instruction::Push('h'),
335    ///     Instruction::Push('e'),
336    ///     Instruction::Push('l'),
337    ///     Instruction::Push('l'),
338    ///     Instruction::Push('o'),
339    ///     Instruction::Yield,
340    ///     Instruction::Pop,
341    ///     Instruction::Yield,
342    /// ].into_iter();
343    /// assert_eq!(inst.collecting().collect::<Vec<String>>(), ["hello", "hell"]);
344    /// ```
345    #[inline]
346    fn collecting<C: Default + Clone + Extend<T> + Contract>(self) -> Collecting<Self, C>
347    where
348        Self: Sized,
349    {
350        Collecting {
351            iter: self,
352            collection: Default::default(),
353        }
354    }
355
356    /// Performs the operations of the iterator until the next yield.
357    ///
358    /// If the iterator ends before a yield is encountered, this returns `None` but still mutates
359    /// the original collection.
360    ///
361    /// # Example
362    ///
363    /// ```
364    /// use stacking_iterator::prelude::*;
365    ///
366    /// let mut inst = vec![
367    ///     Instruction::Push('h'),
368    ///     Instruction::Push('e'),
369    ///     Instruction::Push('l'),
370    ///     Instruction::Push('l'),
371    ///     Instruction::Push('o'),
372    ///     Instruction::Yield,
373    ///     Instruction::Pop,
374    ///     Instruction::Yield,
375    ///     Instruction::Pop,
376    ///     Instruction::Pop,
377    ///     Instruction::Pop,
378    ///     Instruction::Pop,
379    /// ].into_iter();
380    /// let mut coll = String::new();
381    /// assert_eq!(inst.operate(&mut coll).map(|x| &**x), Some("hello"));
382    /// assert_eq!(coll, "hello");
383    /// assert_eq!(inst.operate(&mut coll).map(|x| &**x), Some("hell"));
384    /// assert_eq!(coll, "hell");
385    /// assert_eq!(inst.operate(&mut coll), None);
386    /// assert_eq!(coll, "");
387    /// ```
388    #[inline]
389    fn operate<'c, C: Extend<T> + Contract>(&mut self, collection: &'c mut C) -> Option<&'c C> {
390        let mut yielding = false;
391        stack_extend(
392            collection,
393            self.map_while(|inst| match inst {
394                Instruction::Mutate(operation) => Some(operation),
395                Instruction::Yield => {
396                    yielding = true;
397                    None
398                }
399            }),
400        );
401        if yielding {
402            Some(collection)
403        } else {
404            None
405        }
406    }
407}
408impl<T, I: Iterator<Item = Instruction<T>>> StackingIteratorExt<T> for I {}
409
410/// Iterator returned by [`StackingIteratorExt::collecting`].
411#[derive(Clone, Debug)]
412pub struct Collecting<I, C> {
413    iter: I,
414    collection: C,
415}
416impl<T, I: Iterator<Item = Instruction<T>>, C: Clone + Extend<T> + Contract> Iterator
417    for Collecting<I, C>
418{
419    type Item = C;
420
421    #[inline]
422    fn next(&mut self) -> Option<C> {
423        self.iter.operate(&mut self.collection).cloned()
424    }
425
426    #[inline]
427    fn size_hint(&self) -> (usize, Option<usize>) {
428        let (_, hi) = self.iter.size_hint();
429
430        // we could never yield a sequence, or we could yield every time
431        (0, hi)
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    use core::iter::repeat;
438
439    use super::{Contract, Instruction, Operation, StackingIteratorExt};
440    use alloc::collections::{LinkedList, VecDeque};
441
442    macro_rules! seq {
443        (@ $stack:expr; - $($item:tt)*) => {
444            $stack.push(Instruction::Mutate(Operation::Pop));
445            seq!(@ $stack; $($item)*);
446        };
447        (@ $stack:expr; + $($item:tt)*) => {
448            $stack.push(Instruction::Yield);
449            seq!(@ $stack; $($item)*);
450        };
451        (@ $stack:expr; $val:ident $($item:tt)*) => {
452            $stack.push(Instruction::Mutate(Operation::Push(stringify!($val))));
453            seq!(@ $stack; $($item)*);
454        };
455        (@ $stack:expr;) => {
456        };
457        ($($item:tt)*) => {
458            {
459                #[allow(unused_mut)]
460                let mut stack: Vec<Instruction<&'static str>> = Vec::new();
461                seq!(@ stack; $($item)*);
462                stack.into_iter()
463            }
464        };
465    }
466
467    #[test]
468    fn contract_vec() {
469        let mut v = vec![1, 2, 3, 4, 5];
470        assert_eq!(v, [1, 2, 3, 4, 5]);
471        v.contract(0);
472        assert_eq!(v, [1, 2, 3, 4, 5]);
473        v.contract(2);
474        assert_eq!(v, [1, 2, 3]);
475        v.contract(3);
476        assert_eq!(v, []);
477        v.contract(0);
478    }
479
480    #[test]
481    fn contract_vec_deque() {
482        let mut v = VecDeque::from(vec![1, 2, 3, 4, 5]);
483        assert_eq!(v, [1, 2, 3, 4, 5]);
484        v.contract(0);
485        assert_eq!(v, [1, 2, 3, 4, 5]);
486        v.contract(2);
487        assert_eq!(v, [1, 2, 3]);
488        v.contract(3);
489        assert_eq!(v, []);
490        v.contract(0);
491    }
492
493    #[test]
494    fn contract_linked_list() {
495        let mut l = LinkedList::from([1, 2, 3, 4, 5]);
496        assert_eq!(l.iter().copied().collect::<Vec<_>>(), [1, 2, 3, 4, 5]);
497        l.contract(0);
498        assert_eq!(l.iter().copied().collect::<Vec<_>>(), [1, 2, 3, 4, 5]);
499        l.contract(2);
500        assert_eq!(l.iter().copied().collect::<Vec<_>>(), [1, 2, 3]);
501        l.contract(3);
502        assert_eq!(l.iter().copied().collect::<Vec<_>>(), []);
503        l.contract(0);
504    }
505
506    #[test]
507    fn contract_string() {
508        let mut s = "世界こんにちは".to_owned();
509        assert_eq!(s, "世界こんにちは");
510        s.contract(0);
511        assert_eq!(s, "世界こんにちは");
512        s.contract(5);
513        assert_eq!(s, "世界");
514        s.contract(1);
515        assert_eq!(s, "世");
516        s.contract(1);
517        assert_eq!(s, "");
518        s.contract(0);
519    }
520
521    #[test]
522    #[should_panic = "cannot contract by 1 when collection has 0 elements"]
523    fn implode_empty_vec() {
524        let mut v = <Vec<()>>::new();
525        v.contract(1);
526    }
527
528    #[test]
529    #[should_panic = "cannot contract by 2 when collection has 1 element"]
530    fn implode_vec() {
531        let mut v = vec![1];
532        v.contract(2);
533    }
534
535    #[test]
536    #[should_panic = "cannot contract by 1 when collection has 0 elements"]
537    fn implode_empty_vec_deque() {
538        let mut v = <VecDeque<()>>::new();
539        v.contract(1);
540    }
541
542    #[test]
543    #[should_panic = "cannot contract by 2 when collection has 1 element"]
544    fn implode_vec_deque() {
545        let mut v = VecDeque::from([1]);
546        v.contract(2);
547    }
548
549    #[test]
550    #[should_panic = "cannot contract by 1 when collection has 0 elements"]
551    fn implode_empty_linked_list() {
552        let mut l = <LinkedList<()>>::new();
553        l.contract(1);
554    }
555
556    #[test]
557    #[should_panic = "cannot contract by 2 when collection has 1 element"]
558    fn implode_linked_list() {
559        let mut l = LinkedList::from([1]);
560        l.contract(2);
561    }
562
563    #[test]
564    #[should_panic = "cannot contract by 1 when collection has 0 elements"]
565    fn implode_empty_string() {
566        let mut s = String::new();
567        s.contract(1);
568    }
569
570    #[test]
571    #[should_panic = "cannot contract by 2 when collection has 1 element"]
572    fn implode_string() {
573        let mut l = "あ".to_owned();
574        l.contract(2);
575    }
576
577    #[test]
578    fn extend_push() {
579        let mut v = vec![1, 2, 3];
580        v.extend([4, 5, 6].into_iter().map(Operation::Push));
581        assert_eq!(v, [1, 2, 3, 4, 5, 6]);
582    }
583
584    #[test]
585    fn extend_pop() {
586        let mut v = vec![1, 2, 3];
587        v.extend(repeat(Operation::Pop).take(2));
588        assert_eq!(v, [1]);
589    }
590
591    #[test]
592    fn empty() {
593        let seq = seq!();
594        assert_eq!(seq.collecting::<String>().next(), None);
595    }
596
597    #[test]
598    fn sneaky_empty() {
599        let seq = seq!(h e l l o);
600        let mut iter = seq.collecting::<String>();
601        assert_eq!(iter.next(), None);
602    }
603
604    #[test]
605    fn hello() {
606        let seq = seq!(h e l l o +);
607        let mut iter = seq.collecting::<String>();
608        assert_eq!(iter.next().as_deref(), Some("hello"));
609        assert_eq!(iter.next(), None);
610    }
611
612    #[test]
613    fn hello_hell() {
614        let seq = seq!(h e l l o + - +);
615        let collected: Vec<String> = seq.collecting().collect();
616        assert_eq!(collected, ["hello", "hell"]);
617    }
618
619    #[test]
620    fn hello_world() {
621        let seq = seq!(h e l l o + - + - - - - w o r l d +);
622        let collected: Vec<String> = seq.collecting().collect();
623        assert_eq!(collected, ["hello", "hell", "world"]);
624    }
625}