From b6876f9df6e3119331fac01c0bc954ca9f3c798b Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Sun, 5 Apr 2026 20:33:57 +0800 Subject: Add general renderer support with serialization formats --- mingling_macros/src/chain.rs | 10 +- mingling_macros/src/lib.rs | 35 ++++++ mingling_macros/src/pack.rs | 4 +- mingling_macros/src/program_setup.rs | 199 +++++++++++++++++++++++++++++++++++ mingling_macros/src/renderer.rs | 21 ++++ 5 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 mingling_macros/src/program_setup.rs (limited to 'mingling_macros/src') diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs index f8b1e1c..7b519a1 100644 --- a/mingling_macros/src/chain.rs +++ b/mingling_macros/src/chain.rs @@ -93,11 +93,11 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { Err(e) => return e.to_compile_error().into(), }; - // Ensure the return type is named "GroupProcess" - if return_type.path.segments.last().unwrap().ident != "GroupProcess" { + // Ensure the return type is named "NextProcess" + if return_type.path.segments.last().unwrap().ident != "NextProcess" { return syn::Error::new( return_type.span(), - "Return type must be 'mingling::marker::GroupProcess'", + "Return type must be 'mingling::marker::NextProcess'", ) .to_compile_error() .into(); @@ -134,7 +134,7 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { async fn proc(#prev_param: Self::Previous) -> ::mingling::ChainProcess { - let _ = GroupProcess; + let _ = NextProcess; // Call the original function #fn_name(#prev_param).await } @@ -159,7 +159,7 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { async fn proc(#prev_param: Self::Previous) -> ::mingling::ChainProcess<#group_name> { - let _ = GroupProcess; + let _ = NextProcess; // Call the original function #fn_name(#prev_param).await } diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 5a32075..9513875 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -12,6 +12,7 @@ mod chain; mod dispatcher_chain; mod node; mod pack; +mod program_setup; mod render; mod renderer; @@ -19,6 +20,9 @@ use once_cell::sync::Lazy; use std::sync::Mutex; // Global variable declarations for storing chain and renderer mappings +#[cfg(feature = "general_renderer")] +pub(crate) static GENERAL_RENDERERS: Lazy>> = + Lazy::new(|| Mutex::new(Vec::new())); pub(crate) static PACKED_TYPES: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); pub(crate) static CHAINS: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); pub(crate) static RENDERERS: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); @@ -65,6 +69,11 @@ pub fn renderer(_attr: TokenStream, item: TokenStream) -> TokenStream { renderer::renderer_attr(item) } +#[proc_macro_attribute] +pub fn program_setup(attr: TokenStream, item: TokenStream) -> TokenStream { + program_setup::setup_attr(attr, item) +} + #[proc_macro] pub fn gen_program(input: TokenStream) -> TokenStream { let name = if input.is_empty() { @@ -83,6 +92,9 @@ pub fn gen_program(input: TokenStream) -> TokenStream { let renderer_exist = RENDERERS_EXIST.lock().unwrap().clone(); let chain_exist = CHAINS_EXIST.lock().unwrap().clone(); + #[cfg(feature = "general_renderer")] + let general_renderers = GENERAL_RENDERERS.lock().unwrap().clone(); + let packed_types: Vec = packed_types .iter() .map(|s| syn::parse_str::(s).unwrap()) @@ -108,6 +120,28 @@ pub fn gen_program(input: TokenStream) -> TokenStream { .map(|s| syn::parse_str::(s).unwrap()) .collect(); + #[cfg(feature = "general_renderer")] + let general_renderer_tokens: Vec = general_renderers + .iter() + .map(|s| syn::parse_str::(s).unwrap()) + .collect(); + + #[cfg(feature = "general_renderer")] + let general_render = quote! { + fn general_render( + any: ::mingling::AnyOutput, + setting: &::mingling::GeneralRendererSetting, + ) -> Result<::mingling::RenderResult, ::mingling::error::GeneralRendererSerializeError> { + match any.member_id { + #(#general_renderer_tokens)* + _ => Ok(::mingling::RenderResult::default()), + } + } + }; + + #[cfg(not(feature = "general_renderer"))] + let general_render = quote! {}; + let expanded = quote! { ::mingling::macros::pack!(#name, RendererNotFound = String); ::mingling::macros::pack!(#name, DispatcherNotFound = Vec); @@ -155,6 +189,7 @@ pub fn gen_program(input: TokenStream) -> TokenStream { _ => false } } + #general_render } impl #name { diff --git a/mingling_macros/src/pack.rs b/mingling_macros/src/pack.rs index c6a6c67..a84010e 100644 --- a/mingling_macros/src/pack.rs +++ b/mingling_macros/src/pack.rs @@ -77,7 +77,7 @@ pub fn pack(input: TokenStream) -> TokenStream { }; // Generate the struct definition - #[cfg(not(feature = "serde"))] + #[cfg(not(feature = "general_renderer"))] let struct_def = quote! { #[derive(Debug)] pub struct #type_name { @@ -85,7 +85,7 @@ pub fn pack(input: TokenStream) -> TokenStream { } }; - #[cfg(feature = "serde")] + #[cfg(feature = "general_renderer")] let struct_def = quote! { #[derive(Debug, serde::Serialize)] pub struct #type_name { diff --git a/mingling_macros/src/program_setup.rs b/mingling_macros/src/program_setup.rs new file mode 100644 index 0000000..54da898 --- /dev/null +++ b/mingling_macros/src/program_setup.rs @@ -0,0 +1,199 @@ +//! Setup Attribute Macro Implementation +//! +//! This module provides the `#[setup]` attribute macro for automatically +//! generating structs that implement the `ProgramSetup` trait from functions. + +use proc_macro::TokenStream; +use quote::quote; +use syn::spanned::Spanned; +use syn::{ + FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input, +}; + +/// Extracts the program parameter from function arguments +fn extract_program_param(sig: &Signature) -> syn::Result<(Pat, TypePath)> { + // The function should have exactly one parameter + if sig.inputs.len() != 1 { + return Err(syn::Error::new( + sig.inputs.span(), + "Setup function must have exactly one parameter", + )); + } + + let arg = &sig.inputs[0]; + match arg { + FnArg::Typed(PatType { pat, ty, .. }) => { + // Extract the pattern (parameter name) + let param_pat = (**pat).clone(); + + // Extract the type, handling references like &mut ThisProgram + match &**ty { + Type::Path(type_path) => Ok((param_pat, type_path.clone())), + Type::Reference(type_ref) => { + // Handle reference types like &mut ThisProgram + match &*type_ref.elem { + Type::Path(type_path) => Ok((param_pat, type_path.clone())), + _ => Err(syn::Error::new( + ty.span(), + "Reference parameter must point to a type path", + )), + } + } + _ => Err(syn::Error::new( + ty.span(), + "Parameter type must be a type path or reference to a type path", + )), + } + } + FnArg::Receiver(_) => Err(syn::Error::new( + arg.span(), + "Setup function cannot have self parameter", + )), + } +} + +/// Validates that the parameter type is `ThisProgram` +fn validate_any_program_param(type_path: &TypePath) -> syn::Result<()> { + // Check if the type is `ThisProgram` + let segments = &type_path.path.segments; + if segments.len() == 1 && segments[0].ident == "ThisProgram" { + Ok(()) + } else { + // Check if it's a qualified path like mingling::marker::ThisProgram + let mut is_any_program = false; + if segments.len() > 1 { + // Check if the last segment is "ThisProgram" + if segments.last().unwrap().ident == "ThisProgram" { + is_any_program = true; + } + } + + if is_any_program { + Ok(()) + } else { + Err(syn::Error::new( + type_path.span(), + "Setup function parameter must be `mingling::marker::ThisProgram`", + )) + } + } +} + +/// Extracts and validates the return type +fn extract_return_type(sig: &Signature) -> syn::Result<()> { + // Setup functions should return () or have no return type + match &sig.output { + ReturnType::Type(_, ty) => { + // Check if it's () + match &**ty { + Type::Tuple(tuple) if tuple.elems.is_empty() => Ok(()), + _ => Err(syn::Error::new( + ty.span(), + "Setup function must return () or have no return type", + )), + } + } + ReturnType::Default => Ok(()), + } +} + +pub fn setup_attr(attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse the attribute arguments (e.g., MyProgram from #[setup(MyProgram)]) + // If no argument is provided, use DefaultProgram + let (program_name, use_crate_prefix) = if attr.is_empty() { + ( + Ident::new("DefaultProgram", proc_macro2::Span::call_site()), + true, + ) + } else { + (parse_macro_input!(attr as Ident), false) + }; + + // Parse the function item + let input_fn = parse_macro_input!(item as ItemFn); + + // Validate the function is not async + if input_fn.sig.asyncness.is_some() { + return syn::Error::new(input_fn.sig.span(), "Setup function cannot be async") + .to_compile_error() + .into(); + } + + // Extract the program parameter + let (program_param, program_type) = match extract_program_param(&input_fn.sig) { + Ok(info) => info, + Err(e) => return e.to_compile_error().into(), + }; + + // Validate that the parameter is ThisProgram + if let Err(e) = validate_any_program_param(&program_type) { + return e.to_compile_error().into(); + } + + // Validate return type + if let Err(e) = extract_return_type(&input_fn.sig) { + return e.to_compile_error().into(); + } + + // Get the function body + let fn_body = &input_fn.block; + + // Get function attributes (excluding the setup attribute) + let mut fn_attrs = input_fn.attrs.clone(); + + // Remove any #[setup(...)] attributes to avoid infinite recursion + fn_attrs.retain(|attr| !attr.path().is_ident("setup")); + + // Get function visibility + let vis = &input_fn.vis; + + // Get function name + let fn_name = &input_fn.sig.ident; + + // Generate struct name from function name using pascal_case + let pascal_case_name = just_fmt::pascal_case!(fn_name.to_string()); + let struct_name = Ident::new(&pascal_case_name, fn_name.span()); + + // Generate the struct and implementation + let expanded = if use_crate_prefix { + quote! { + #(#fn_attrs)* + #vis struct #struct_name; + + impl ::mingling::setup::ProgramSetup for #struct_name { + fn setup(&mut self, program: &mut ::mingling::Program) { + let _ = ThisProgram; + // Call the original function with the actual Program type + #fn_name(program); + } + } + + // Keep the original function for internal use + #(#fn_attrs)* + #vis fn #fn_name(#program_param: &mut ::mingling::Program) { + #fn_body + } + } + } else { + quote! { + #(#fn_attrs)* + #vis struct #struct_name; + + impl ::mingling::setup::ProgramSetup<#program_name, #program_name> for #struct_name { + fn setup(&mut self, program: &mut ::mingling::Program<#program_name, #program_name>) { + let _ = ThisProgram; + // Call the original function with the actual Program type + #fn_name(program); + } + } + + // Keep the original function for internal use + #(#fn_attrs)* + #vis fn #fn_name(#program_param: &mut ::mingling::Program<#program_name, #program_name>) { + #fn_body + } + } + }; + + expanded.into() +} diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs index 0e32b40..4edac88 100644 --- a/mingling_macros/src/renderer.rs +++ b/mingling_macros/src/renderer.rs @@ -109,14 +109,30 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream { Self::#previous_type => true, }; + #[cfg(feature = "general_renderer")] + let general_renderer_entry = quote! { + Self::#previous_type => { + let raw = any.restore::<#previous_type>().unwrap(); + let mut r = ::mingling::RenderResult::default(); + ::mingling::GeneralRenderer::render(&raw, setting, &mut r)?; + Ok(r) + } + }; + let mut renderers = crate::RENDERERS.lock().unwrap(); let mut renderer_exist = crate::RENDERERS_EXIST.lock().unwrap(); let mut packed_types = crate::PACKED_TYPES.lock().unwrap(); + #[cfg(feature = "general_renderer")] + let mut general_renderers = crate::GENERAL_RENDERERS.lock().unwrap(); + let renderer_entry_str = renderer_entry.to_string(); let renderer_exist_entry_str = renderer_exist_entry.to_string(); let previous_type_str = previous_type.to_token_stream().to_string(); + #[cfg(feature = "general_renderer")] + let general_renderer_entry_str = general_renderer_entry.to_string(); + if !renderers.contains(&renderer_entry_str) { renderers.push(renderer_entry_str); } @@ -129,6 +145,11 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream { packed_types.push(previous_type_str); } + #[cfg(feature = "general_renderer")] + if !general_renderers.contains(&general_renderer_entry_str) { + general_renderers.push(general_renderer_entry_str); + } + // Generate the struct and implementation // We need to create a wrapper function that adds the r parameter let expanded = quote! { -- cgit