use itertools::Itertools;
use quote::quote;
use super::simulation_aspects::{SimulationAspect, SimulationAspects};
#[allow(unused)]
struct Sorted {
sorted_kw: syn::Ident,
colon: syn::Token![:],
sorted: bool,
}
impl syn::parse::Parse for Sorted {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self {
sorted_kw: input.parse()?,
colon: input.parse()?,
sorted: input.parse::<syn::LitBool>()?.value,
})
}
}
#[allow(unused)]
struct MacroParser {
test_token: syn::Ident,
colon: syn::Token![:],
macro_name: syn::Ident,
comma: syn::Token![,],
aspects: SimulationAspects,
min_combinations: Option<core::num::NonZeroUsize>,
sorted: Option<bool>,
}
impl syn::parse::Parse for MacroParser {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut res = Self {
test_token: input.parse()?,
colon: input.parse()?,
macro_name: input.parse()?,
comma: input.parse()?,
aspects: input.parse()?,
min_combinations: None,
sorted: None,
};
while !input.is_empty() {
let _: syn::Token![,] = input.parse()?;
if !input.is_empty() {
let keyword: syn::Ident = input.parse()?;
let _: syn::Token![:] = input.parse()?;
match keyword.to_string().as_ref() {
"min_combinations" => {
res.min_combinations = Some(input.parse::<syn::LitInt>()?.base10_parse()?)
}
"sorted" => res.sorted = Some(input.parse::<syn::LitBool>()?.value),
_ => (),
}
}
}
Ok(res)
}
}
impl MacroParser {
fn spawn_tests(self) -> proc_macro2::TokenStream {
let macro_name = &self.macro_name;
let aspects: Vec<_> = self.aspects.to_aspect_list();
let min_order = self.min_combinations.map(|x| x.get()).unwrap_or(1);
let sorted = self.sorted.map(|x| x).unwrap_or(true);
let mut stream = quote!();
for n in min_order..aspects.len() {
let combinations = get_combinations(n, aspects.clone(), sorted);
for (name, list) in combinations {
let list_aspects = list.into_iter().map(|aspect| aspect.to_token_stream());
let output = quote!(
#macro_name !(
name:#name,
aspects:[#(#list_aspects),*]
);
);
stream.extend(output);
}
}
stream
}
}
pub fn run_test_for_aspects(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let macro_parser = syn::parse_macro_input!(input as MacroParser);
macro_parser.spawn_tests().into()
}
fn idents_overlap(id1: &proc_macro2::TokenStream, id2: &proc_macro2::TokenStream) -> bool {
let id1_segments = id1.to_string();
let id1_segments = id1_segments.split("_").collect::<Vec<_>>();
let id2_segments = id2.to_string();
let id2_segments = id2_segments.split("_").collect::<Vec<_>>();
let l1 = id1_segments.len();
let l2 = id2_segments.len();
let set = std::collections::HashSet::<&str>::from_iter(
id1_segments.into_iter().chain(id2_segments.into_iter()),
);
set.len() < l1 + l2
}
fn get_combinations(
n: usize,
idents: Vec<SimulationAspect>,
sorted: bool,
) -> Vec<(proc_macro2::TokenStream, Vec<SimulationAspect>)> {
let idents: Vec<(proc_macro2::TokenStream, Vec<SimulationAspect>)> = idents
.into_iter()
.map(|s| (s.to_token_stream_lowercase(), vec![s]))
.collect();
if n == 0 {
return idents;
}
fn combine_idents(
ident1: &(proc_macro2::TokenStream, Vec<SimulationAspect>),
ident2: &(proc_macro2::TokenStream, Vec<SimulationAspect>),
) -> Option<(proc_macro2::TokenStream, Vec<SimulationAspect>)> {
if idents_overlap(&ident1.0, &ident2.0) {
return None;
}
let i1 = &ident1.0;
let i2 = &ident2.0;
let name_ident = quote::format_ident!("{}_{}", i1.to_string(), i2.to_string());
let name = quote!(#name_ident);
let mut list = ident1.1.clone();
let list2 = ident2.1.clone();
list.extend(list2);
let list = list.into_iter().map(|s| s.into()).collect();
Some((name, list))
}
if sorted {
return idents
.iter()
.combinations(n)
.into_iter()
.map(|ids| {
ids.into_iter()
.fold((quote::quote!(), vec![]), |mut acc, x| {
if acc.1.is_empty() {
acc = x.clone();
return acc;
}
match combine_idents(&acc, x) {
Some(res) => {
acc = res;
acc
}
None => acc,
}
})
})
.collect();
}
let combinations: Vec<_> = (1..n).fold(
idents
.iter()
.map(|ident1| {
idents
.iter()
.filter_map(move |ident2| combine_idents(ident1, ident2))
})
.flatten()
.collect(),
|acc, _| {
acc.iter()
.map(|ident1| {
idents
.iter()
.filter_map(move |ident2| combine_idents(ident1, ident2))
})
.flatten()
.collect()
},
);
combinations
}