use proc_macro2::{TokenStream, TokenTree, Group, Ident};
use quote::quote;
use syn::parse::Parser;
pub fn process_eager(input: TokenStream) -> syn::Result<TokenStream> {
let mut output = TokenStream::new();
let mut tokens = input.into_iter().peekable();
while let Some(token) = tokens.next() {
match token {
TokenTree::Ident(ident) if is_evaluatable_macro(&ident) => {
if let Some(TokenTree::Punct(punct)) = tokens.peek() {
if punct.as_char() == '!' {
tokens.next();
if let Some(TokenTree::Group(group)) = tokens.next() {
let result = evaluate_macro(&ident, group)?;
output.extend(result);
} else {
output.extend(quote! { #ident ! });
}
} else {
output.extend(quote! { #ident });
}
} else {
output.extend(quote! { #ident });
}
}
TokenTree::Group(group) => {
let processed = process_eager(group.stream())?;
let new_group = Group::new(group.delimiter(), processed);
output.extend(std::iter::once(TokenTree::Group(new_group)));
}
other => {
output.extend(std::iter::once(other));
}
}
}
Ok(output)
}
fn is_evaluatable_macro(ident: &Ident) -> bool {
let name = ident.to_string();
name == "tomplate" || name == "concat"
}
fn evaluate_macro(name: &Ident, args: Group) -> syn::Result<TokenStream> {
let macro_name = name.to_string();
match macro_name.as_str() {
"tomplate" => evaluate_tomplate(args.stream()),
"concat" => evaluate_concat(args.stream()),
_ => {
Ok(quote! { #name ! #args })
}
}
}
fn evaluate_tomplate(input: TokenStream) -> syn::Result<TokenStream> {
let tomplate_input = syn::parse2::<crate::TomplateInput>(input)?;
let result = crate::process_template(tomplate_input)?;
Ok(result)
}
fn evaluate_concat(input: TokenStream) -> syn::Result<TokenStream> {
let processed_input = process_eager(input)?;
let parser = |input: syn::parse::ParseStream| -> syn::Result<Vec<String>> {
let mut parts = Vec::new();
while !input.is_empty() {
if let Ok(lit) = input.parse::<syn::LitStr>() {
parts.push(lit.value());
}
else if let Ok(lit) = input.parse::<syn::LitInt>() {
parts.push(lit.to_string());
}
else if let Ok(lit) = input.parse::<syn::LitFloat>() {
parts.push(lit.to_string());
}
else if let Ok(lit) = input.parse::<syn::LitBool>() {
parts.push(lit.value.to_string());
}
else {
input.step(|cursor| {
if let Some((_, rest)) = cursor.token_tree() {
Ok(((), rest))
} else {
Err(cursor.error("Unexpected end of input"))
}
})?;
}
if input.peek(syn::Token![,]) {
input.parse::<syn::Token![,]>()?;
}
}
Ok(parts)
};
let parts = parser.parse2(processed_input)?;
let concatenated = parts.join("");
Ok(quote! { #concatenated })
}