gen_nested_iter_yield/lib.rs
1//! gen-nested-iter-yield exports a helper macro, nested_iter_yield, which can be used to
2//! generate n-nested for loops over identical iterators. This is useful for generating a
3//! stream of permutations with replacement without storing unnecessary intermediary buffers.
4//!
5//! The macro returns a [genawaiter::sync](https://docs.rs/genawaiter/latest/genawaiter/sync/index.html)
6//! generator which, when the feature `futures03` is enabled, implements [futures::stream::Stream](https://docs.rs/futures/0.3.21/futures/stream/index.html).
7
8extern crate proc_macro;
9use proc_macro::TokenStream;
10use std::str::FromStr;
11
12enum PreOrPostFix {
13 Pre(String),
14 Post(String),
15}
16
17/// Creates an n-nested for loop of iterators over the passed input. Assumes that the first
18/// argument can be called multiple times to produce identical iterators.
19///
20/// ## Usage
21/// ### Basic syntax
22/// `nested_iter_yield!(iterable_name, 3)`
23///
24/// ### Dereference values from vector iterable
25/// `nested_iter_yield!(vector_name.iter(), 3, *)`
26///
27/// The third argument is optional. Here `*` is prefixed to each result of the iterator. Any value
28/// passed as the third argument that does not begin with a `.` will be prefixed to each result
29/// of the iterator.
30///
31/// ### Clone values from vector iterable
32///
33/// `nested_iter_yield!(vector_name.iter(), 3, .clone())`
34///
35/// Here `.clone()` is appended to each result of the iterator. Any value that begins with `.` will
36/// be treated as a postfix.
37///
38/// ### Prefix genawaiter exports
39///
40/// When the source library does not have `genawaiter` as a dependency, e.g. because this
41/// helper macro is being used in a library, it is sometimes necessary to prefix the
42/// reference to `genawaiter`. This is done with a fourth argument, as such:
43///
44/// `nested_iter_yield!(vector_name.iter(), 3, .to_owned(), package_name::)`
45#[proc_macro]
46pub fn nested_iter_yield(item: TokenStream) -> TokenStream {
47 // parse input
48 let item_string = item.to_string();
49 let inps = item_string.split(',').collect::<Vec<_>>();
50 let source_iter = inps[0].trim();
51 let n = usize::from_str(inps[1].trim())
52 .expect("nested_iter_yield: could not parse input n as usize.");
53 let value_transformation = match inps.len() {
54 2 => None,
55 l if l > 2 => Some(match inps[2] {
56 m if m.starts_with('.') => PreOrPostFix::Post(inps[2].to_string()),
57 _ => PreOrPostFix::Pre(inps[2].to_string()),
58 }),
59 _ => unreachable!("missing input fields to nested_iter_yield macro"),
60 };
61 let genawaiter_import_prefix = match inps.len() {
62 l if l > 3 => inps[3],
63 _ => "",
64 };
65
66 // generate code
67 let generator_open =
68 genawaiter_import_prefix.to_string() + "genawaiter::sync::Gen::new(|co| async move {";
69 let open_loops = (0..n)
70 .map(|i| format!("for val_{i} in {source_iter} {{"))
71 .collect::<Vec<_>>()
72 .join("\n");
73 let yield_open = "co.yield_(vec![";
74 let yield_args = (0..n)
75 .map(|i| format!("val_{i}"))
76 .map(|variable| match &value_transformation {
77 None => variable,
78 Some(trans) => match trans {
79 PreOrPostFix::Pre(pre) => pre.to_string() + variable.as_str(),
80 PreOrPostFix::Post(post) => variable + post.as_str(),
81 },
82 })
83 .collect::<Vec<_>>()
84 .join(", ");
85 let yield_close = "]).await;";
86 let close_loops = (0..n).map(|_| "}").collect::<Vec<_>>().join("\n");
87 let generator_closed = "})";
88 let generated_code = generator_open
89 + open_loops.as_str()
90 + yield_open
91 + yield_args.as_str()
92 + yield_close
93 + close_loops.as_str()
94 + generator_closed;
95
96 // convert to TokenStream and return
97 generated_code.parse().unwrap()
98}