use proc_macro2::TokenStream;
use syn::{FieldValue, parse::Parse, spanned::Spanned};
use crate::{
args, capture,
props::{Props, push_evt_props},
template::{self, Template},
util::{ToOptionTokens, ToRefTokens},
};
pub struct ExpandTokens {
pub input: TokenStream,
}
struct StandaloneProps {
fvs: Vec<FieldValue>,
}
impl Parse for StandaloneProps {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(StandaloneProps {
fvs: input
.parse_terminated(FieldValue::parse, Token![,])?
.iter()
.cloned()
.collect(),
})
}
}
pub fn expand_tokens(opts: ExpandTokens) -> Result<TokenStream, syn::Error> {
let (standalone, template, mut props) =
template::parse2::<StandaloneProps>(opts.input, dbg_capture_fn, true)?;
let template = match template {
None => {
for fv in standalone.fvs {
props.push(&fv, dbg_capture_fn(&fv), false, true)?;
}
check_dbg_props(&props)?;
compute_template(&props)?
}
Some(template) => {
if standalone.fvs.len() > 0 {
return Err(syn::Error::new(
template.template_literal_tokens().span(),
"specify the template string literal before any properties",
));
}
check_dbg_props(&props)?;
template
}
};
push_loc_props(&mut props)?;
push_evt_props(&mut props, Some(quote!(emit::Level::Debug)))?;
let props_match_input_tokens = props.match_input_tokens();
let props_match_binding_tokens = props.match_binding_tokens();
let props_tokens = props.match_bound_tokens().to_ref_tokens();
let rt_tokens = args::RtArg::default().to_tokens()?.to_ref_tokens();
let when_tokens = None::<TokenStream>.to_option_tokens(quote!(&emit::Empty));
let base_props_tokens = quote!(&emit::Empty);
let extent_tokens = quote!(&emit::Empty);
let mdl_tokens = args::MdlArg::default().to_tokens().to_ref_tokens();
let template_tokens = template.template_tokens().to_ref_tokens();
let emit_tokens = quote!(
emit::__private::__private_emit(
#rt_tokens,
#mdl_tokens,
#when_tokens,
#extent_tokens,
#template_tokens,
#base_props_tokens,
#props_tokens,
);
);
Ok(quote!({
match (#(#props_match_input_tokens),*) {
(#(#props_match_binding_tokens),*) => {
#emit_tokens
}
}
}))
}
fn push_loc_props(props: &mut Props) -> Result<(), syn::Error> {
let fv = syn::parse2::<FieldValue>(quote!(file: file!()))?;
props.push(&fv, capture::default_fn_name(&fv), false, true)?;
let fv = syn::parse2::<FieldValue>(quote!(line: line!()))?;
props.push(&fv, capture::default_fn_name(&fv), false, true)?;
Ok(())
}
fn compute_template(props: &Props) -> Result<Template, syn::Error> {
let mut literal = String::new();
let mut first = true;
for (name, key_value) in props.iter() {
if !first {
literal.push_str(", ");
}
first = false;
literal.push_str(name);
literal.push_str(" = {");
literal.push_str(&key_value.hole_tokens().to_string());
literal.push_str("}");
}
if !first {
literal.push_str(" ");
}
literal.push_str("at {file}:{line}");
let (pre, template, _) =
template::parse2::<StandaloneProps>(quote!(#literal), dbg_capture_fn, true)?;
debug_assert_eq!(0, pre.fvs.len());
Ok(template.expect("missing template"))
}
fn dbg_capture_fn(fv: &FieldValue) -> TokenStream {
quote_spanned!(fv.span()=> __private_capture_anon_as_debug)
}
fn check_dbg_props(props: &Props) -> Result<(), syn::Error> {
for (k, v) in props.iter() {
match k {
"line" | "file" => {
return Err(syn::Error::new(
v.span(),
format!("`{k}` can't be used as a key in the `dbg` macro"),
));
}
_ => (),
}
}
Ok(())
}