use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::{
Error, Expr, ExprArray, Ident, Token,
parse::{Parse, ParseStream, Result},
parse_macro_input,
};
use crate::transform::transform_tokens;
pub fn main(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ForEachInput);
let mut generated = TokenStream2::new();
for item in input.items {
let expanded = replace_in_template(&input.template, &input.param, &item);
generated.extend(expanded);
}
TokenStream::from(generated)
}
fn replace_in_template(template: &TokenStream2, param: &str, item: &Item) -> TokenStream2 {
let template_str = template.to_string();
let template_str = template_str.trim();
let template_str = if template_str.starts_with('{') && template_str.ends_with('}') {
template_str[1..template_str.len() - 1].trim()
} else {
template_str
};
let cleaned_template: TokenStream2 = template_str.parse().unwrap_or_else(|_| template.clone());
let params = match item {
Item::Array(values) => {
let mut array_params = vec![];
for (index, val) in values.iter().enumerate() {
array_params.push((format!("{param}[{index}]"), val.clone()));
}
array_params.push((param.to_string(), format!("[{}]", values.join(", "))));
array_params
}
Item::Single(single_value) => {
vec![(param.to_string(), single_value.clone())]
}
};
transform_tokens(cleaned_template, ¶ms)
}
struct ForEachInput {
items: Vec<Item>,
param: String,
template: TokenStream2,
}
#[derive(Debug, Clone)]
enum Item {
Single(String),
Array(Vec<String>),
}
impl Parse for ForEachInput {
fn parse(input: ParseStream) -> Result<Self> {
let array: ExprArray = input.parse().map_err(|_| {
Error::new_spanned(
input.cursor().token_stream(),
"Expected array like [item1, item2, ...] as first argument",
)
})?;
if array.elems.is_empty() {
return Err(Error::new_spanned(&array, "Array cannot be empty"));
}
let mut items = Vec::new();
for elem in array.elems {
items.push(parse_item(elem)?);
}
input.parse::<Token![,]>()?;
input.parse::<Token![|]>()?;
let param_ident: Ident = input.parse()?;
input.parse::<Token![|]>()?;
let template: TokenStream2 = input.parse()?;
let param = param_ident.to_string();
Ok(ForEachInput {
items,
param,
template,
})
}
}
fn parse_single_value(expr: &Expr) -> Result<String> {
match expr {
Expr::Path(path) if path.path.segments.len() == 1 => {
Ok(path.path.segments[0].ident.to_string())
}
Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit_str),
..
}) => Ok(lit_str.value()),
Expr::Lit(syn::ExprLit {
lit: syn::Lit::Int(lit_int),
..
}) => Ok(lit_int.base10_digits().to_string()),
_ => Err(Error::new_spanned(
expr,
"Expected identifier, string, or number",
)),
}
}
fn parse_item(expr: Expr) -> Result<Item> {
match expr {
ref single_expr @ (Expr::Path(_) | Expr::Lit(_)) => {
Ok(Item::Single(parse_single_value(single_expr)?))
}
Expr::Array(array) => {
if array.elems.is_empty() {
return Err(Error::new_spanned(&array, "Nested arrays cannot be empty"));
}
let mut values = Vec::new();
for elem in array.elems {
values.push(parse_single_value(&elem)?);
}
Ok(Item::Array(values))
}
_ => Err(Error::new_spanned(
&expr,
"Items must be identifiers, strings, numbers, or arrays of these",
)),
}
}