flipflop 0.2.0

Stress-tester for double-ended iterators.
Documentation
//! [`Strategy`] trait and impls.
use core::{fmt, iter, num::NonZero};

use crate::{
    iter::{Alternating, Apply, Indices},
    side::Side,
};

/// Object-safe version of [`Strategy`], which does basically nothing.
pub trait StrategyObj: fmt::Display + fmt::Debug {}
impl<S: ?Sized + Strategy> StrategyObj for S {}

/// Strategy for determining the order to read a double-ended iterator.
pub trait Strategy: fmt::Display + fmt::Debug {
    /// Iterator returned by [`strategy`].
    ///
    /// [`strategy`]: Self::strategy
    type Iter<'a>: Iterator<Item = Side>
    where
        Self: 'a;

    /// Gets the iterator for the strategy.
    ///
    /// Iterators should always be the same if the strategy does not change. This determinism helps
    /// check when problems occur after an entire iterator is accumulated.
    fn strategy(&self) -> Self::Iter<'_>;

    /// Applies a strategy to an iterator.
    ///
    /// The result returns the direction that was chosen, and the item for the direction.
    #[inline]
    fn apply<I: DoubleEndedIterator>(&self, iter: I) -> Apply<I, Self::Iter<'_>> {
        Apply::new(iter, self.strategy())
    }

    /// Converts strategy into indices which can be passed to [`Accum::get_signed`].
    ///
    /// [`Accum::get_signed`]: crate::accum::Accum::get_signed
    #[inline]
    fn indexed_strategy(&self) -> Indices<Self::Iter<'_>> {
        Indices::new(self.strategy())
    }
}

/// Placeholder strategy that panics with [`unimplemented!`].
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Todo;
impl fmt::Display for Todo {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.pad("unimplemented")
    }
}
impl Strategy for Todo {
    type Iter<'a> = crate::iter::Todo;

    fn strategy(&self) -> crate::iter::Todo {
        crate::iter::Todo
    }
}

/// Strategy that picks a particular side every time.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Always(pub Side);
impl fmt::Display for Always {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "always {}", self.0)
    }
}
impl Strategy for Always {
    type Iter<'a> = iter::Repeat<Side>;

    #[inline]
    fn strategy(&self) -> iter::Repeat<Side> {
        iter::repeat(self.0)
    }
}

/// Strategy that alternates sides with a given period.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Alternate {
    /// Starting side.
    pub start: Side,

    /// Period between alternatiions.
    pub period: NonZero<usize>,
}
impl Alternate {
    /// Swaps the side for the strategy.
    #[inline]
    pub fn swap(self) -> Alternate {
        Alternate {
            start: self.start.swap(),
            period: self.period,
        }
    }

    /// Decreases the period for the strategy by one, if possible.
    #[inline]
    pub fn shrink(self) -> Option<Alternate> {
        let period = self.period.get() - 1;
        Some(Alternate {
            start: self.start,
            period: NonZero::new(period)?,
        })
    }
}
impl fmt::Display for Alternate {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "alternating {} to {} every {} iterations",
            self.start,
            self.start.swap(),
            self.period
        )
    }
}
impl Strategy for Alternate {
    type Iter<'a> = Alternating;

    #[inline]
    fn strategy(&self) -> Self::Iter<'_> {
        Alternating::new(self.start, self.period)
    }
}

/// Things that require the [`rand`] crate.
#[cfg_attr(feature = "nightly", doc(cfg(feature = "rand")))]
#[cfg(any(doc, feature = "rand"))]
mod rand {
    use core::fmt;

    use rand::SeedableRng;

    use crate::{iter::Randomly, strategy::Strategy};

    /// Random strategy.
    pub struct Random<
        #[cfg(feature = "rand")] R: rand::SeedableRng,
        #[cfg(not(feature = "rand"))] R,
    > {
        /// Seed for RNG.
        #[cfg(feature = "rand")]
        seed: R::Seed,

        /// Seed for RNG.
        #[cfg(not(feature = "rand"))]
        seed: R,

        /// Probability of choosing forward direction.
        forward_prob: f64,
    }
    impl<#[cfg(feature = "rand")] R: rand::SeedableRng, #[cfg(not(feature = "rand"))] R> Random<R> {
        /// Creates a new random strategy from the chosen seed.
        ///
        /// The given probability affects how likely [`Forward`] is to be chosen.
        ///
        /// [`Forward`]: crate::side::Side::Forward
        pub fn seeded(
            #[cfg(feature = "rand")] seed: R::Seed,
            #[cfg(not(feature = "rand"))] seed: R,
            forward_prob: f64,
        ) -> Random<R> {
            Random { seed, forward_prob }
        }

        /// Creates a new random strategy from a random seed.
        ///
        /// The given probability affects how likely [`Forward`] is to be chosen.
        ///
        /// [`Forward`]: crate::side::Side::Forward
        #[allow(clippy::self_named_constructors)]
        #[cfg(any(doc, feature = "std"))]
        #[cfg_attr(feature = "nightly", doc(cfg(feature = "std")))]
        pub fn random(forward_prob: f64) -> Random<R> {
            #[cfg(feature = "rand")]
            {
                let mut seed = R::Seed::default();
                rand::Rng::fill(&mut rand::rng(), seed.as_mut());
                Random { seed, forward_prob }
            }
            #[cfg(not(feature = "rand"))]
            {
                unimplemented!()
            }
        }
    }
    impl<#[cfg(feature = "rand")] R: rand::SeedableRng, #[cfg(not(feature = "rand"))] R> fmt::Debug
        for Random<R>
    {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            f.debug_struct("Random")
                .field("seed", &self.seed.as_ref())
                .field("forward_prob", &self.forward_prob)
                .finish()
        }
    }
    impl<#[cfg(feature = "rand")] R: rand::SeedableRng, #[cfg(not(feature = "rand"))] R> Clone
        for Random<R>
    {
        #[inline]
        fn clone(&self) -> Self {
            #[cfg(feature = "rand")]
            {
                let mut seed = R::Seed::default();
                seed.as_mut().copy_from_slice(self.seed.as_ref());
                Random {
                    seed,
                    forward_prob: self.forward_prob,
                }
            }
            #[cfg(not(feature = "rand"))]
            {
                unimplemented!()
            }
        }
    }
    impl<R: SeedableRng> fmt::Display for Random<R> {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "random with seed 0x")?;
            for b in self.seed.as_ref() {
                write!(f, "{b:02x}")?;
            }
            Ok(())
        }
    }

    impl<
        #[cfg(feature = "rand")] R: rand::SeedableRng + rand::RngCore,
        #[cfg(not(feature = "rand"))] R,
    > Strategy for Random<R>
    {
        type Iter<'a>
            = Randomly<R>
        where
            R: 'a;

        #[inline]
        fn strategy(&self) -> Self::Iter<'_> {
            #[cfg(feature = "rand")]
            {
                Randomly::new(self.seed.clone(), self.forward_prob)
            }
            #[cfg(not(feature = "rand"))]
            {
                unimplemented!()
            }
        }
    }
}
#[cfg_attr(feature = "nightly", doc(cfg(feature = "rand")))]
#[cfg(any(doc, feature = "rand"))]
pub use self::rand::Random;