#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![deny(unsafe_code)]
#![cfg_attr(not(any(feature = "std", test, doc)), no_std)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{
collections::{LinkedList, VecDeque},
string::String,
vec::Vec,
};
use core::fmt;
pub mod prelude {
pub use crate::{Contract, Instruction, Operation, StackingIteratorExt};
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum Instruction<T> {
Mutate(Operation<T>),
Yield,
}
impl<T> Instruction<T> {
#[allow(non_upper_case_globals)]
pub const Pop: Instruction<T> = Instruction::Mutate(Operation::Pop);
#[allow(non_snake_case)]
pub const fn Push(val: T) -> Instruction<T> {
Instruction::Mutate(Operation::Push(val))
}
#[inline]
pub fn into_operation(self) -> Option<Operation<T>> {
match self {
Instruction::Mutate(operation) => Some(operation),
Instruction::Yield => None,
}
}
#[inline]
pub fn as_operation(&self) -> Option<&Operation<T>> {
match self {
Instruction::Mutate(operation) => Some(operation),
Instruction::Yield => None,
}
}
}
impl<T: fmt::Debug> fmt::Debug for Instruction<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Instruction::Mutate(operation) => fmt::Debug::fmt(operation, f),
Instruction::Yield => f.pad("Yield"),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Operation<T> {
Push(T),
Pop,
}
impl<T> Operation<T> {
#[inline]
pub fn into_item(self) -> Option<T> {
match self {
Operation::Push(item) => Some(item),
Operation::Pop => None,
}
}
#[inline]
pub fn as_item(&self) -> Option<&T> {
match self {
Operation::Push(item) => Some(item),
Operation::Pop => None,
}
}
}
pub trait Contract {
fn contract(&mut self, count: usize);
}
fn stack_extend<T>(
stack: &mut (impl Extend<T> + Contract),
mut iter: impl Iterator<Item = Operation<T>>,
) {
loop {
let mut pop_count = 0;
stack.extend(iter.by_ref().map_while(|op| match op {
Operation::Push(val) => Some(val),
Operation::Pop => {
pop_count = 1;
None
}
}));
if pop_count == 0 {
return;
}
let mut next = None;
pop_count += iter
.by_ref()
.map_while(|op| match op {
Operation::Pop => Some(()),
Operation::Push(val) => {
next = Some(val);
None
}
})
.count();
stack.contract(pop_count);
if let Some(val) = next {
stack.extend(Some(val));
} else {
return;
}
}
}
#[cold]
#[track_caller]
fn panic_imploded(count: usize, len: usize) -> ! {
if len == 1 {
panic!("cannot contract by {count} when collection has 1 element");
} else {
panic!("cannot contract by {count} when collection has {len} elements")
}
}
#[cfg(feature = "alloc")]
impl<T> Contract for Vec<T> {
#[track_caller]
fn contract(&mut self, count: usize) {
if count > self.len() {
panic_imploded(count, self.len())
}
self.truncate(self.len() - count);
}
}
#[cfg(feature = "alloc")]
impl<T> Contract for VecDeque<T> {
#[track_caller]
fn contract(&mut self, count: usize) {
if count > self.len() {
panic_imploded(count, self.len())
}
self.truncate(self.len() - count);
}
}
#[cfg(feature = "alloc")]
impl<T> Contract for LinkedList<T> {
#[track_caller]
fn contract(&mut self, count: usize) {
if count > self.len() {
panic_imploded(count, self.len())
}
for _ in 0..count {
self.pop_back();
}
}
}
#[cfg(feature = "alloc")]
impl Contract for String {
#[track_caller]
fn contract(&mut self, count: usize) {
let mut chars = self.chars();
if cfg!(debug_assertions) {
let mut excess = 0;
let mut amount = 0;
for _ in 0..count {
if chars.next_back().is_none() {
excess += 1;
} else {
amount += 1;
}
}
if excess > 0 {
panic_imploded(count, amount);
}
} else {
chars.nth_back(count);
}
let len = chars.as_str().len();
self.truncate(len);
}
}
#[cfg(feature = "alloc")]
impl<T> Extend<Operation<T>> for Vec<T> {
#[inline]
fn extend<I: IntoIterator<Item = Operation<T>>>(&mut self, iter: I) {
stack_extend(self, iter.into_iter())
}
}
#[cfg(feature = "alloc")]
impl<T> Extend<Operation<T>> for VecDeque<T> {
#[inline]
fn extend<I: IntoIterator<Item = Operation<T>>>(&mut self, iter: I) {
stack_extend(self, iter.into_iter())
}
}
#[cfg(feature = "alloc")]
impl<T> Extend<Operation<T>> for LinkedList<T> {
#[inline]
fn extend<I: IntoIterator<Item = Operation<T>>>(&mut self, iter: I) {
stack_extend(self, iter.into_iter())
}
}
pub trait StackingIteratorExt<T>: Iterator<Item = Instruction<T>> {
#[inline]
fn collecting<C: Default + Clone + Extend<T> + Contract>(self) -> Collecting<Self, C>
where
Self: Sized,
{
Collecting {
iter: self,
collection: Default::default(),
}
}
#[inline]
fn operate<'c, C: Extend<T> + Contract>(&mut self, collection: &'c mut C) -> Option<&'c C> {
let mut yielding = false;
stack_extend(
collection,
self.map_while(|inst| match inst {
Instruction::Mutate(operation) => Some(operation),
Instruction::Yield => {
yielding = true;
None
}
}),
);
if yielding {
Some(collection)
} else {
None
}
}
}
impl<T, I: Iterator<Item = Instruction<T>>> StackingIteratorExt<T> for I {}
#[derive(Clone, Debug)]
pub struct Collecting<I, C> {
iter: I,
collection: C,
}
impl<T, I: Iterator<Item = Instruction<T>>, C: Clone + Extend<T> + Contract> Iterator
for Collecting<I, C>
{
type Item = C;
#[inline]
fn next(&mut self) -> Option<C> {
self.iter.operate(&mut self.collection).cloned()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let (_, hi) = self.iter.size_hint();
(0, hi)
}
}
#[cfg(test)]
mod tests {
use core::iter::repeat;
use super::{Contract, Instruction, Operation, StackingIteratorExt};
use alloc::collections::{LinkedList, VecDeque};
macro_rules! seq {
(@ $stack:expr; - $($item:tt)*) => {
$stack.push(Instruction::Mutate(Operation::Pop));
seq!(@ $stack; $($item)*);
};
(@ $stack:expr; + $($item:tt)*) => {
$stack.push(Instruction::Yield);
seq!(@ $stack; $($item)*);
};
(@ $stack:expr; $val:ident $($item:tt)*) => {
$stack.push(Instruction::Mutate(Operation::Push(stringify!($val))));
seq!(@ $stack; $($item)*);
};
(@ $stack:expr;) => {
};
($($item:tt)*) => {
{
#[allow(unused_mut)]
let mut stack: Vec<Instruction<&'static str>> = Vec::new();
seq!(@ stack; $($item)*);
stack.into_iter()
}
};
}
#[test]
fn contract_vec() {
let mut v = vec![1, 2, 3, 4, 5];
assert_eq!(v, [1, 2, 3, 4, 5]);
v.contract(0);
assert_eq!(v, [1, 2, 3, 4, 5]);
v.contract(2);
assert_eq!(v, [1, 2, 3]);
v.contract(3);
assert_eq!(v, []);
v.contract(0);
}
#[test]
fn contract_vec_deque() {
let mut v = VecDeque::from(vec![1, 2, 3, 4, 5]);
assert_eq!(v, [1, 2, 3, 4, 5]);
v.contract(0);
assert_eq!(v, [1, 2, 3, 4, 5]);
v.contract(2);
assert_eq!(v, [1, 2, 3]);
v.contract(3);
assert_eq!(v, []);
v.contract(0);
}
#[test]
fn contract_linked_list() {
let mut l = LinkedList::from([1, 2, 3, 4, 5]);
assert_eq!(l.iter().copied().collect::<Vec<_>>(), [1, 2, 3, 4, 5]);
l.contract(0);
assert_eq!(l.iter().copied().collect::<Vec<_>>(), [1, 2, 3, 4, 5]);
l.contract(2);
assert_eq!(l.iter().copied().collect::<Vec<_>>(), [1, 2, 3]);
l.contract(3);
assert_eq!(l.iter().copied().collect::<Vec<_>>(), []);
l.contract(0);
}
#[test]
fn contract_string() {
let mut s = "世界こんにちは".to_owned();
assert_eq!(s, "世界こんにちは");
s.contract(0);
assert_eq!(s, "世界こんにちは");
s.contract(5);
assert_eq!(s, "世界");
s.contract(1);
assert_eq!(s, "世");
s.contract(1);
assert_eq!(s, "");
s.contract(0);
}
#[test]
#[should_panic = "cannot contract by 1 when collection has 0 elements"]
fn implode_empty_vec() {
let mut v = <Vec<()>>::new();
v.contract(1);
}
#[test]
#[should_panic = "cannot contract by 2 when collection has 1 element"]
fn implode_vec() {
let mut v = vec![1];
v.contract(2);
}
#[test]
#[should_panic = "cannot contract by 1 when collection has 0 elements"]
fn implode_empty_vec_deque() {
let mut v = <VecDeque<()>>::new();
v.contract(1);
}
#[test]
#[should_panic = "cannot contract by 2 when collection has 1 element"]
fn implode_vec_deque() {
let mut v = VecDeque::from([1]);
v.contract(2);
}
#[test]
#[should_panic = "cannot contract by 1 when collection has 0 elements"]
fn implode_empty_linked_list() {
let mut l = <LinkedList<()>>::new();
l.contract(1);
}
#[test]
#[should_panic = "cannot contract by 2 when collection has 1 element"]
fn implode_linked_list() {
let mut l = LinkedList::from([1]);
l.contract(2);
}
#[test]
#[should_panic = "cannot contract by 1 when collection has 0 elements"]
fn implode_empty_string() {
let mut s = String::new();
s.contract(1);
}
#[test]
#[should_panic = "cannot contract by 2 when collection has 1 element"]
fn implode_string() {
let mut l = "あ".to_owned();
l.contract(2);
}
#[test]
fn extend_push() {
let mut v = vec![1, 2, 3];
v.extend([4, 5, 6].into_iter().map(Operation::Push));
assert_eq!(v, [1, 2, 3, 4, 5, 6]);
}
#[test]
fn extend_pop() {
let mut v = vec![1, 2, 3];
v.extend(repeat(Operation::Pop).take(2));
assert_eq!(v, [1]);
}
#[test]
fn empty() {
let seq = seq!();
assert_eq!(seq.collecting::<String>().next(), None);
}
#[test]
fn sneaky_empty() {
let seq = seq!(h e l l o);
let mut iter = seq.collecting::<String>();
assert_eq!(iter.next(), None);
}
#[test]
fn hello() {
let seq = seq!(h e l l o +);
let mut iter = seq.collecting::<String>();
assert_eq!(iter.next().as_deref(), Some("hello"));
assert_eq!(iter.next(), None);
}
#[test]
fn hello_hell() {
let seq = seq!(h e l l o + - +);
let collected: Vec<String> = seq.collecting().collect();
assert_eq!(collected, ["hello", "hell"]);
}
#[test]
fn hello_world() {
let seq = seq!(h e l l o + - + - - - - w o r l d +);
let collected: Vec<String> = seq.collecting().collect();
assert_eq!(collected, ["hello", "hell", "world"]);
}
}