use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::spanned::Spanned;
use syn::{ItemFn, ReturnType, Signature, Type, TypePath, parse_macro_input};
use crate::get_global_set;
use crate::res_injection::{extract_args_info, generate_immut_resource_bindings};
fn extract_return_type(sig: &Signature) -> syn::Result<Option<syn::Type>> {
match &sig.output {
ReturnType::Type(_, ty) => {
match &**ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => Ok(None),
custom_ty => Ok(Some((*custom_ty).clone())),
}
}
ReturnType::Default => Ok(None),
}
}
pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
let (program_path, _use_crate_prefix) = parse_renderer_attr_args(attr);
let program_type = &program_path;
let input_fn = parse_macro_input!(item as ItemFn);
if input_fn.sig.asyncness.is_some() {
return syn::Error::new(input_fn.sig.span(), "Renderer function cannot be async")
.to_compile_error()
.into();
}
let (prev_param, previous_type, resources) = match extract_args_info(&input_fn.sig) {
Ok(info) => info,
Err(e) => return e.to_compile_error().into(),
};
if let Some(err_tokens) = crate::check_single_segment_type(&previous_type, "#[renderer]") {
return err_tokens.into();
}
let return_type = match extract_return_type(&input_fn.sig) {
Ok(rt) => rt,
Err(e) => return e.to_compile_error().into(),
};
let fn_body_stmts: Vec<syn::Stmt> = input_fn.block.stmts.clone();
let mut fn_attrs = input_fn.attrs.clone();
fn_attrs.retain(|attr| !attr.path().is_ident("renderer"));
let vis = &input_fn.vis;
let fn_name = &input_fn.sig.ident;
let internal_name = format!(
"__internal_renderer_{}",
just_fmt::snake_case!(fn_name.to_string())
);
let struct_name = syn::Ident::new(&internal_name, fn_name.span());
let has_resources = !resources.is_empty();
let has_mut_resources = resources.iter().any(|r| r.is_mut);
let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), program_type);
let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect();
let (public_return_type, result_return) = match &return_type {
Some(custom_ty) => {
let ret_ty = quote! { #custom_ty };
let expr = quote! { dummy_r.into() };
(ret_ty, expr)
}
None => {
let ret_ty = quote! { () };
let expr = quote! {
if !dummy_r.is_empty() {
::std::println!("{}", &*dummy_r);
}
};
(ret_ty, expr)
}
};
let inner_body_with_resources = if has_mut_resources {
let mut wrapped = quote! { #(#fn_body_stmts)* };
for res in mut_resources.iter().rev() {
let var_name = &res.var_name;
let inner_type = &res.inner_type;
wrapped = quote! {
::mingling::this::<#program_type>().modify_res(|#var_name: &mut #inner_type| {
#wrapped
})
};
}
wrapped
} else {
quote! { #(#fn_body_stmts)* }
};
let render_fn_body = if has_resources {
quote! {
#(#immut_resource_stmts)*
#inner_body_with_resources
}
} else {
quote! { #inner_body_with_resources }
};
let original_fn_body = {
quote! {
let mut dummy_r = ::mingling::RenderResult::default();
{
let __renderer_inner_result = &mut dummy_r;
#(#fn_body_stmts)*
}
#result_return
}
};
let original_inputs = input_fn.sig.inputs.clone();
let expanded = quote! {
#(#fn_attrs)*
#[doc(hidden)]
#[allow(non_camel_case_types)]
#vis struct #struct_name;
::mingling::macros::register_renderer!(#previous_type, #struct_name);
impl ::mingling::Renderer for #struct_name {
type Previous = #previous_type;
fn render(#prev_param: Self::Previous, __renderer_inner_result: &mut ::mingling::RenderResult) {
#render_fn_body
}
}
#(#fn_attrs)*
#vis fn #fn_name(#original_inputs) -> #public_return_type {
#original_fn_body
}
};
expanded.into()
}
fn parse_renderer_attr_args(attr: TokenStream) -> (proc_macro2::TokenStream, bool) {
if attr.is_empty() {
(crate::default_program_path(), true)
} else {
let path: syn::Path =
syn::parse(attr).expect("Expected a path argument for #[renderer(path)]");
(quote! { #path }, false)
}
}
pub fn build_renderer_entry(
struct_name: &syn::Ident,
previous_type: &TypePath,
) -> proc_macro2::TokenStream {
quote! {
#struct_name => #previous_type,
}
}
pub fn build_renderer_exist_entry(previous_type: &TypePath) -> proc_macro2::TokenStream {
quote! {
Self::#previous_type => true,
}
}
#[cfg(feature = "general_renderer")]
pub fn build_general_renderer_entry(previous_type: &TypePath) -> proc_macro2::TokenStream {
quote! {
Self::#previous_type => {
let raw = unsafe { any.restore::<#previous_type>().unwrap_unchecked() };
let mut __renderer_inner_result = ::mingling::RenderResult::default();
::mingling::GeneralRenderer::render(&raw, setting, &mut __renderer_inner_result)?;
Ok(__renderer_inner_result)
}
}
}
pub fn register_renderer(input: TokenStream) -> TokenStream {
let input_parsed = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated);
if input_parsed.len() != 2 {
return syn::Error::new(
input_parsed.span(),
"Expected exactly two comma-separated arguments: `PreviousType, StructName`",
)
.to_compile_error()
.into();
}
let previous_type_expr = &input_parsed[0];
let struct_name_expr = &input_parsed[1];
let previous_type = match syn::parse2::<TypePath>(previous_type_expr.to_token_stream()) {
Ok(ty) => ty,
Err(e) => return e.to_compile_error().into(),
};
let struct_name = match syn::parse2::<syn::Ident>(struct_name_expr.to_token_stream()) {
Ok(ident) => ident,
Err(e) => return e.to_compile_error().into(),
};
let renderer_entry = build_renderer_entry(&struct_name, &previous_type);
let renderer_exist_entry = build_renderer_exist_entry(&previous_type);
#[cfg(feature = "general_renderer")]
let general_renderer_entry = build_general_renderer_entry(&previous_type);
let mut renderers = get_global_set(&crate::RENDERERS).lock().unwrap();
let mut renderer_exist = get_global_set(&crate::RENDERERS_EXIST).lock().unwrap();
#[cfg(feature = "general_renderer")]
let mut general_renderers = get_global_set(&crate::GENERAL_RENDERERS).lock().unwrap();
let renderer_entry_str = renderer_entry.to_string();
let renderer_exist_entry_str = renderer_exist_entry.to_string();
#[cfg(feature = "general_renderer")]
let general_renderer_entry_str = general_renderer_entry.to_string();
renderers.insert(renderer_entry_str);
renderer_exist.insert(renderer_exist_entry_str);
#[cfg(feature = "general_renderer")]
general_renderers.insert(general_renderer_entry_str);
quote! {}.into()
}