use std::{collections::BTreeMap, fmt::Write as _};
use proc_macro2::TokenStream;
use syn::{Expr, ExprPath, FieldValue, parse::Parse, punctuated::Punctuated, spanned::Spanned};
use crate::{fmt, props::Props, util::FieldValueKey};
pub fn parse2<A: Parse>(
input: TokenStream,
fn_name: impl Fn(&FieldValue) -> TokenStream,
captured: bool,
) -> Result<(A, Option<Template>, Props), syn::Error> {
let template =
fv_template::Template::parse2(input).map_err(|e| syn::Error::new(e.span(), e))?;
let args = {
let args = template
.before_literal_field_values()
.cloned()
.collect::<Punctuated<FieldValue, Token![,]>>();
syn::parse2(quote!(#args))?
};
let mut extra_field_values: BTreeMap<_, _> = template
.after_literal_field_values()
.map(|fv| Ok((fv.key_name()?, fv)))
.collect::<Result<_, syn::Error>>()?;
let mut props = Props::new();
for fv in template.literal_field_values() {
let k = fv.key_name()?;
match extra_field_values.remove(&k) {
Some(extra_fv) => {
if let Expr::Path(ExprPath { ref path, .. }) = fv.expr {
if !fv.attrs.is_empty() {
return Err(syn::Error::new(
fv.span(),
"keys that exist in the template and extra pairs can only use attributes on the extra pair",
));
}
assert_eq!(
path.get_ident().map(|ident| ident.to_string()).as_ref(),
Some(&k),
"the key name and path don't match"
);
} else {
return Err(syn::Error::new(
extra_fv.span(),
"keys that exist in the template and extra pairs can only use identifiers",
));
}
props.push(extra_fv, fn_name(extra_fv), true, captured)?;
}
None => {
props.push(fv, fn_name(fv), true, captured)?;
}
}
}
for (_, fv) in extra_field_values {
props.push(fv, fn_name(fv), false, captured)?;
}
let (template_parts_tokens, template_literal_tokens) = {
let mut template_visitor = TemplateVisitor {
props: &props,
parts: Ok(Vec::new()),
literal: String::new(),
};
template.visit_literal(&mut template_visitor);
let template_parts = template_visitor.parts?;
let literal = template_visitor.literal;
let parts_tokens = {
quote!({
const __TPL_PARTS: &[emit::template::Part] = &[
#(#template_parts),*
];
__TPL_PARTS
})
};
let literal_tokens = quote!(#literal);
(parts_tokens, literal_tokens)
};
let template = if template.has_literal() {
Some(Template {
template_parts_tokens,
template_literal_tokens,
})
} else {
None
};
Ok((args, template, props))
}
pub struct Template {
template_parts_tokens: TokenStream,
template_literal_tokens: TokenStream,
}
impl Template {
pub fn template_literal_tokens(&self) -> TokenStream {
self.template_literal_tokens.clone()
}
pub fn template_tokens(&self) -> TokenStream {
let template_parts = &self.template_parts_tokens;
quote!(emit::Template::new_ref(#template_parts))
}
}
struct TemplateVisitor<'a> {
props: &'a Props,
parts: syn::Result<Vec<TokenStream>>,
literal: String,
}
impl<'a> fv_template::LiteralVisitor for TemplateVisitor<'a> {
fn visit_hole(&mut self, hole: fv_template::Hole) {
let Ok(ref mut parts) = self.parts else {
return;
};
if let Err(e) = (|| {
let label = hole.get().key_name()?;
let hole = hole.get().key_expr()?;
let field = self.props.get(&label).expect("missing prop");
debug_assert!(field.interpolated);
self.literal.push_str("{");
self.literal.push_str(&label);
self.literal.push_str("}");
let hole_tokens =
fmt::template_hole_with_hook(&field.attrs, &hole, true, field.captured)?;
match field.cfg_attr {
Some(ref cfg_attr) => parts.push(quote!(#cfg_attr { #hole_tokens })),
_ => parts.push(quote!(#hole_tokens)),
}
Ok::<(), syn::Error>(())
})() {
self.parts = Err(e);
}
}
fn visit_text(&mut self, text: fv_template::Text) {
let Ok(ref mut parts) = self.parts else {
return;
};
let needs_escaping = text.needs_escaping();
let raw_text = text.get();
write!(
&mut self.literal,
"{}",
emit_core::template::Part::text_ref(raw_text).with_needs_escaping_raw(needs_escaping)
)
.expect("infallible write");
parts.push(
quote!(emit::template::Part::text(#raw_text).with_needs_escaping_raw(#needs_escaping)),
);
}
}