cellular_raza_core_proc_macro/
testing.rs

1use itertools::Itertools;
2use quote::quote;
3
4use super::simulation_aspects::{SimulationAspect, SimulationAspects};
5
6#[allow(unused)]
7struct Sorted {
8    sorted_kw: syn::Ident,
9    colon: syn::Token![:],
10    sorted: bool,
11}
12
13impl syn::parse::Parse for Sorted {
14    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
15        Ok(Self {
16            sorted_kw: input.parse()?,
17            colon: input.parse()?,
18            sorted: input.parse::<syn::LitBool>()?.value,
19        })
20    }
21}
22
23#[allow(unused)]
24struct MacroParser {
25    test_token: syn::Ident,
26    colon: syn::Token![:],
27    macro_name: syn::Ident,
28    comma: syn::Token![,],
29    aspects: SimulationAspects,
30    min_combinations: Option<core::num::NonZeroUsize>,
31    sorted: Option<bool>,
32}
33
34impl syn::parse::Parse for MacroParser {
35    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
36        let mut res = Self {
37            test_token: input.parse()?,
38            colon: input.parse()?,
39            macro_name: input.parse()?,
40            comma: input.parse()?,
41            aspects: input.parse()?,
42            min_combinations: None,
43            sorted: None,
44        };
45        while !input.is_empty() {
46            let _: syn::Token![,] = input.parse()?;
47            if !input.is_empty() {
48                let keyword: syn::Ident = input.parse()?;
49                let _: syn::Token![:] = input.parse()?;
50                match keyword.to_string().as_ref() {
51                    "min_combinations" => {
52                        res.min_combinations = Some(input.parse::<syn::LitInt>()?.base10_parse()?)
53                    }
54                    "sorted" => res.sorted = Some(input.parse::<syn::LitBool>()?.value),
55                    _ => (),
56                }
57            }
58        }
59        Ok(res)
60    }
61}
62
63impl MacroParser {
64    fn spawn_tests(self) -> proc_macro2::TokenStream {
65        let macro_name = &self.macro_name;
66        let aspects: Vec<_> = self.aspects.to_aspect_list();
67        let min_order = self.min_combinations.map(|x| x.get()).unwrap_or(1);
68        let sorted = self.sorted.map(|x| x).unwrap_or(true);
69
70        let mut stream = quote!();
71        for n in min_order..aspects.len() {
72            let combinations = get_combinations(n, aspects.clone(), sorted);
73
74            for (name, list) in combinations {
75                let list_aspects = list.into_iter().map(|aspect| aspect.to_token_stream());
76                let output = quote!(
77                    #macro_name !(
78                        name:#name,
79                        aspects:[#(#list_aspects),*]
80                    );
81                );
82                stream.extend(output);
83            }
84        }
85        stream
86    }
87}
88
89pub fn run_test_for_aspects(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
90    let macro_parser = syn::parse_macro_input!(input as MacroParser);
91    macro_parser.spawn_tests().into()
92}
93
94fn idents_overlap(id1: &proc_macro2::TokenStream, id2: &proc_macro2::TokenStream) -> bool {
95    let id1_segments = id1.to_string();
96    let id1_segments = id1_segments.split("_").collect::<Vec<_>>();
97    let id2_segments = id2.to_string();
98    let id2_segments = id2_segments.split("_").collect::<Vec<_>>();
99    let l1 = id1_segments.len();
100    let l2 = id2_segments.len();
101    let set = std::collections::HashSet::<&str>::from_iter(
102        id1_segments.into_iter().chain(id2_segments.into_iter()),
103    );
104    set.len() < l1 + l2
105}
106
107fn get_combinations(
108    n: usize,
109    idents: Vec<SimulationAspect>,
110    sorted: bool,
111) -> Vec<(proc_macro2::TokenStream, Vec<SimulationAspect>)> {
112    let idents: Vec<(proc_macro2::TokenStream, Vec<SimulationAspect>)> = idents
113        .into_iter()
114        .map(|s| (s.to_token_stream_lowercase(), vec![s]))
115        .collect();
116
117    if n == 0 {
118        return idents;
119    }
120
121    fn combine_idents(
122        ident1: &(proc_macro2::TokenStream, Vec<SimulationAspect>),
123        ident2: &(proc_macro2::TokenStream, Vec<SimulationAspect>),
124    ) -> Option<(proc_macro2::TokenStream, Vec<SimulationAspect>)> {
125        if idents_overlap(&ident1.0, &ident2.0) {
126            return None;
127        }
128        let i1 = &ident1.0;
129        let i2 = &ident2.0;
130        let name_ident = quote::format_ident!("{}_{}", i1.to_string(), i2.to_string());
131        let name = quote!(#name_ident);
132        let mut list = ident1.1.clone();
133        let list2 = ident2.1.clone();
134        list.extend(list2);
135        let list = list.into_iter().map(|s| s.into()).collect();
136        Some((name, list))
137    }
138
139    if sorted {
140        return idents
141            .iter()
142            .combinations(n)
143            .into_iter()
144            .map(|ids| {
145                ids.into_iter()
146                    .fold((quote::quote!(), vec![]), |mut acc, x| {
147                        if acc.1.is_empty() {
148                            acc = x.clone();
149                            return acc;
150                        }
151                        match combine_idents(&acc, x) {
152                            Some(res) => {
153                                acc = res;
154                                acc
155                            }
156                            None => acc,
157                        }
158                    })
159            })
160            .collect();
161    }
162
163    let combinations: Vec<_> = (1..n).fold(
164        idents
165            .iter()
166            .map(|ident1| {
167                idents
168                    .iter()
169                    .filter_map(move |ident2| combine_idents(ident1, ident2))
170            })
171            .flatten()
172            .collect(),
173        |acc, _| {
174            acc.iter()
175                .map(|ident1| {
176                    idents
177                        .iter()
178                        .filter_map(move |ident2| combine_idents(ident1, ident2))
179                })
180                .flatten()
181                .collect()
182        },
183    );
184    combinations
185}