use proc_macro::TokenStream;
use proc_macro2::{Spacing, TokenStream as TokenStream2, TokenTree};
use quote::{quote, ToTokens};
use syn::{parse2, Expr, ExprAssign, ExprLit, Lit, LitStr};
use crate::formati_args::formati_args;
fn split_top_level(stream: TokenStream2) -> Vec<TokenStream2> {
let mut segs = Vec::<TokenStream2>::new();
let mut cur = TokenStream2::new();
for tt in stream {
match &tt {
TokenTree::Punct(p) if p.as_char() == ',' && p.spacing() == Spacing::Alone => {
segs.push(cur);
cur = TokenStream2::new();
}
_ => cur.extend(std::iter::once(tt)),
}
}
segs.push(cur);
segs
}
pub fn wrap(kind: &str, input: proc_macro::TokenStream) -> TokenStream {
let segments = split_top_level(TokenStream2::from(input));
let split_at = segments
.iter()
.rposition(|seg| {
parse2::<Expr>(seg.clone())
.ok()
.and_then(|e| {
if let Expr::Lit(ExprLit {
lit: Lit::Str(_), ..
}) = e
{
Some(())
} else {
None
}
})
.is_some()
})
.expect("tracing macro needs a string literal message");
let (front, back) = segments.split_at(split_at);
let fmt_seg = &back[0]; let rest = &back[1..];
let lit_expr: Expr = parse2(fmt_seg.clone()).unwrap();
let lit_str: LitStr = match lit_expr {
Expr::Lit(syn::ExprLit {
lit: Lit::Str(s), ..
}) => s,
_ => unreachable!(),
};
let (fmt, expr) = formati_args(&lit_str);
let fmt_str = LitStr::new(&fmt, lit_str.span());
let mut named = Vec::<TokenStream2>::new();
let mut positional = Vec::<TokenStream2>::new();
for seg in rest {
if seg.is_empty() {
continue;
}
let expr: Expr = parse2(seg.clone()).expect("invalid expression after template");
match expr {
Expr::Assign(ExprAssign { .. }) => named.push(expr.to_token_stream()),
_ => positional.push(expr.to_token_stream()),
}
}
let tracing_macro = syn::Ident::new(kind, proc_macro2::Span::call_site());
let front: Vec<&TokenStream2> = front.iter().collect();
quote! {
::tracing::#tracing_macro!(
#(#front ,)*
#fmt_str
#(, #named)*
#(, #expr)*
#(, #positional)*
)
}
.into()
}