From 99f0c11860c55d1fa82cdc2f9df211b6e728c107 Mon Sep 17 00:00:00 2001 From: Ross Williams Date: Sat, 29 Jul 2023 13:00:11 -0400 Subject: [PATCH] Replace itertools dependency with copied module Copied multi_product module from itertools 0.11.0 under MIT license. Eliminates dependency on building all of itertools. --- crates/test-case-core/Cargo.toml | 1 - .../src/test_matrix/matrix_product.rs | 271 ++++++++++++++++++ .../{test_matrix.rs => test_matrix/mod.rs} | 17 +- 3 files changed, 278 insertions(+), 11 deletions(-) create mode 100644 crates/test-case-core/src/test_matrix/matrix_product.rs rename crates/test-case-core/src/{test_matrix.rs => test_matrix/mod.rs} (92%) diff --git a/crates/test-case-core/Cargo.toml b/crates/test-case-core/Cargo.toml index 9d487ef..c7e5415 100644 --- a/crates/test-case-core/Cargo.toml +++ b/crates/test-case-core/Cargo.toml @@ -27,4 +27,3 @@ proc-macro2 = { version = "1.0", features = [] } proc-macro-error = "1.0" quote = "1.0" syn = { version = "1.0", features = ["full", "extra-traits"] } -itertools = "0.11.0" diff --git a/crates/test-case-core/src/test_matrix/matrix_product.rs b/crates/test-case-core/src/test_matrix/matrix_product.rs new file mode 100644 index 0000000..78de3ef --- /dev/null +++ b/crates/test-case-core/src/test_matrix/matrix_product.rs @@ -0,0 +1,271 @@ +//! Copied with minor modifications from itertools v0.11.0 +//! under MIT License +//! +//! Source file and commit hash: +//! https://github.com/rust-itertools/itertools/blob/v0.11.0/src/adaptors/multi_product.rs +//! ed6fbda086a913a787450a642acfd4d36dc07c3b + +#[derive(Clone)] +/// An iterator adaptor that iterates over the cartesian product of +/// multiple iterators of type `I`. +/// +/// An iterator element type is `Vec`. +/// +/// See [`.multi_cartesian_product()`](crate::Itertools::multi_cartesian_product) +/// for more information. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct MultiProduct(Vec>) +where + I: Iterator + Clone, + I::Item: Clone; + +/// Create a new cartesian product iterator over an arbitrary number +/// of iterators of the same type. +/// +/// Iterator element is of type `Vec`. +pub fn multi_cartesian_product(iters: H) -> MultiProduct<::IntoIter> +where + H: Iterator, + H::Item: IntoIterator, + ::IntoIter: Clone, + ::Item: Clone, +{ + MultiProduct( + iters + .map(|i| MultiProductIter::new(i.into_iter())) + .collect(), + ) +} + +#[derive(Clone, Debug)] +/// Holds the state of a single iterator within a `MultiProduct`. +struct MultiProductIter +where + I: Iterator + Clone, + I::Item: Clone, +{ + cur: Option, + iter: I, + iter_orig: I, +} + +/// Holds the current state during an iteration of a `MultiProduct`. +#[derive(Debug)] +enum MultiProductIterState { + StartOfIter, + MidIter { on_first_iter: bool }, +} + +impl MultiProduct +where + I: Iterator + Clone, + I::Item: Clone, +{ + /// Iterates the rightmost iterator, then recursively iterates iterators + /// to the left if necessary. + /// + /// Returns true if the iteration succeeded, else false. + fn iterate_last( + multi_iters: &mut [MultiProductIter], + mut state: MultiProductIterState, + ) -> bool { + use self::MultiProductIterState::*; + + if let Some((last, rest)) = multi_iters.split_last_mut() { + let on_first_iter = match state { + StartOfIter => { + let on_first_iter = !last.in_progress(); + state = MidIter { on_first_iter }; + on_first_iter + } + MidIter { on_first_iter } => on_first_iter, + }; + + if !on_first_iter { + last.iterate(); + } + + if last.in_progress() { + true + } else if MultiProduct::iterate_last(rest, state) { + last.reset(); + last.iterate(); + // If iterator is None twice consecutively, then iterator is + // empty; whole product is empty. + last.in_progress() + } else { + false + } + } else { + // Reached end of iterator list. On initialisation, return true. + // At end of iteration (final iterator finishes), finish. + match state { + StartOfIter => false, + MidIter { on_first_iter } => on_first_iter, + } + } + } + + /// Returns the unwrapped value of the next iteration. + fn curr_iterator(&self) -> Vec { + self.0 + .iter() + .map(|multi_iter| multi_iter.cur.clone().unwrap()) + .collect() + } + + /// Returns true if iteration has started and has not yet finished; false + /// otherwise. + fn in_progress(&self) -> bool { + if let Some(last) = self.0.last() { + last.in_progress() + } else { + false + } + } +} + +impl MultiProductIter +where + I: Iterator + Clone, + I::Item: Clone, +{ + fn new(iter: I) -> Self { + MultiProductIter { + cur: None, + iter: iter.clone(), + iter_orig: iter, + } + } + + /// Iterate the managed iterator. + fn iterate(&mut self) { + self.cur = self.iter.next(); + } + + /// Reset the managed iterator. + fn reset(&mut self) { + self.iter = self.iter_orig.clone(); + } + + /// Returns true if the current iterator has been started and has not yet + /// finished; false otherwise. + fn in_progress(&self) -> bool { + self.cur.is_some() + } +} + +impl Iterator for MultiProduct +where + I: Iterator + Clone, + I::Item: Clone, +{ + type Item = Vec; + + fn next(&mut self) -> Option { + if MultiProduct::iterate_last(&mut self.0, MultiProductIterState::StartOfIter) { + Some(self.curr_iterator()) + } else { + None + } + } + + fn count(self) -> usize { + if self.0.is_empty() { + return 0; + } + + if !self.in_progress() { + return self + .0 + .into_iter() + .fold(1, |acc, multi_iter| acc * multi_iter.iter.count()); + } + + self.0.into_iter().fold( + 0, + |acc, + MultiProductIter { + iter, + iter_orig, + cur: _, + }| { + let total_count = iter_orig.count(); + let cur_count = iter.count(); + acc * total_count + cur_count + }, + ) + } + + fn size_hint(&self) -> (usize, Option) { + // Not ExactSizeIterator because size may be larger than usize + if self.0.is_empty() { + return (0, Some(0)); + } + + if !self.in_progress() { + return self.0.iter().fold((1, Some(1)), |acc, multi_iter| { + size_hint::mul(acc, multi_iter.iter.size_hint()) + }); + } + + self.0.iter().fold( + (0, Some(0)), + |acc, + MultiProductIter { + iter, + iter_orig, + cur: _, + }| { + let cur_size = iter.size_hint(); + let total_size = iter_orig.size_hint(); + size_hint::add(size_hint::mul(acc, total_size), cur_size) + }, + ) + } + + fn last(self) -> Option { + let iter_count = self.0.len(); + + let lasts: Self::Item = self + .0 + .into_iter() + .filter_map(|multi_iter| multi_iter.iter.last()) + .collect(); + + if lasts.len() == iter_count { + Some(lasts) + } else { + None + } + } +} + +mod size_hint { + /// `SizeHint` is the return type of `Iterator::size_hint()`. + pub type SizeHint = (usize, Option); + + /// Add `SizeHint` correctly. + #[inline] + pub fn add(a: SizeHint, b: SizeHint) -> SizeHint { + let min = a.0.saturating_add(b.0); + let max = match (a.1, b.1) { + (Some(x), Some(y)) => x.checked_add(y), + _ => None, + }; + + (min, max) + } + + /// Multiply `SizeHint` correctly + #[inline] + pub fn mul(a: SizeHint, b: SizeHint) -> SizeHint { + let low = a.0.saturating_mul(b.0); + let hi = match (a.1, b.1) { + (Some(x), Some(y)) => x.checked_mul(y), + (Some(0), None) | (None, Some(0)) => Some(0), + _ => None, + }; + (low, hi) + } +} diff --git a/crates/test-case-core/src/test_matrix.rs b/crates/test-case-core/src/test_matrix/mod.rs similarity index 92% rename from crates/test-case-core/src/test_matrix.rs rename to crates/test-case-core/src/test_matrix/mod.rs index 0a04483..7fdd101 100644 --- a/crates/test-case-core/src/test_matrix.rs +++ b/crates/test-case-core/src/test_matrix/mod.rs @@ -1,6 +1,5 @@ use std::{iter, mem}; -use itertools::Itertools; use proc_macro2::{Literal, Span}; use syn::{ parse::{Parse, ParseStream}, @@ -11,6 +10,8 @@ use syn::{ use crate::{comment::TestCaseComment, expr::TestCaseExpression, TestCase}; +mod matrix_product; + #[derive(Debug, Default)] pub struct TestMatrix { variables: Vec>, @@ -25,15 +26,11 @@ impl TestMatrix { pub fn cases(&self) -> impl Iterator { let expression = self.expression.clone(); - self.variables - .iter() - .cloned() - .multi_cartesian_product() - .map(move |v| { - let mut case = TestCase::from(v); - case.expression = expression.clone(); - case - }) + matrix_product::multi_cartesian_product(self.variables.iter().cloned()).map(move |v| { + let mut case = TestCase::from(v); + case.expression = expression.clone(); + case + }) } }