diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-03-28 00:47:46 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-03-28 00:47:46 +0800 |
| commit | 7ce68cd11516bd7cf037ecea99a92aee7c31b2c3 (patch) | |
| tree | a3923ad41c91aa21fe169fd6b4b1bf8898a82589 /mingling_macros/src | |
Add initial Mingling framework codebase
Diffstat (limited to 'mingling_macros/src')
| -rw-r--r-- | mingling_macros/src/chain.rs | 164 | ||||
| -rw-r--r-- | mingling_macros/src/chain_struct.rs | 185 | ||||
| -rw-r--r-- | mingling_macros/src/dispatcher.rs | 72 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 182 | ||||
| -rw-r--r-- | mingling_macros/src/node.rs | 60 | ||||
| -rw-r--r-- | mingling_macros/src/render.rs | 87 | ||||
| -rw-r--r-- | mingling_macros/src/renderer.rs | 248 |
7 files changed, 998 insertions, 0 deletions
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs new file mode 100644 index 0000000..0c21c79 --- /dev/null +++ b/mingling_macros/src/chain.rs @@ -0,0 +1,164 @@ +//! Chain Attribute Macro Implementation +//! +//! This module provides the `#[chain]` attribute macro for automatically +//! generating structs that implement the `Chain` trait from async functions. + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; +use syn::{ + FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input, +}; + +/// Parses the chain attribute arguments +struct ChainAttribute { + struct_name: Ident, +} + +impl Parse for ChainAttribute { + fn parse(input: ParseStream) -> syn::Result<Self> { + let struct_name = input.parse()?; + Ok(ChainAttribute { struct_name }) + } +} + +/// Extracts the previous type and parameter name from function arguments +fn extract_previous_info(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(), + "Chain 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 + match &**ty { + Type::Path(type_path) => Ok((param_pat, type_path.clone())), + _ => Err(syn::Error::new( + ty.span(), + "Parameter type must be a type path", + )), + } + } + FnArg::Receiver(_) => Err(syn::Error::new( + arg.span(), + "Chain function cannot have self parameter", + )), + } +} + +/// Extracts the return type from the function signature +fn extract_return_type(sig: &Signature) -> syn::Result<TypePath> { + match &sig.output { + ReturnType::Type(_, ty) => match &**ty { + Type::Path(type_path) => Ok(type_path.clone()), + _ => Err(syn::Error::new( + ty.span(), + "Return type must be a type path", + )), + }, + ReturnType::Default => Err(syn::Error::new( + sig.span(), + "Chain function must have a return type", + )), + } +} + +/// Implementation of the `#[chain]` attribute macro +/// +/// This macro transforms an async function into a struct that implements +/// the `Chain` trait. The struct name is specified in the attribute. +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::chain; +/// +/// #[chain(InitEntry)] +/// pub async fn process(data: InitBegin) -> mingling::AnyOutput { +/// AnyOutput::new::<InitResult>("初始化成功!".to_string().into()) +/// } +/// ``` +/// +/// This generates: +/// ```ignore +/// pub struct InitEntry; +/// impl Chain for InitEntry { +/// type Previous = InitBegin; +/// async fn proc(data: Self::Previous) -> mingling::AnyOutput { +/// AnyOutput::new::<InitResult>("初始化成功!".to_string().into()) +/// } +/// } +/// ``` +pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse the attribute arguments + let chain_attr = parse_macro_input!(attr as ChainAttribute); + let struct_name = chain_attr.struct_name; + + // Parse the function item + let input_fn = parse_macro_input!(item as ItemFn); + + // Validate the function + if !input_fn.sig.asyncness.is_some() { + return syn::Error::new(input_fn.sig.span(), "Chain function must be async") + .to_compile_error() + .into(); + } + + // Extract the previous type and parameter name from function arguments + let (prev_param, previous_type) = match extract_previous_info(&input_fn.sig) { + Ok(info) => info, + Err(e) => return e.to_compile_error().into(), + }; + + // Extract the return type + let return_type = match extract_return_type(&input_fn.sig) { + Ok(ty) => ty, + Err(e) => return e.to_compile_error().into(), + }; + + // Get the function body + let fn_body = &input_fn.block; + + // Get function attributes (excluding the chain attribute) + let mut fn_attrs = input_fn.attrs.clone(); + // Remove any #[chain(...)] attributes to avoid infinite recursion + fn_attrs.retain(|attr| !attr.path().is_ident("chain")); + + // Get function visibility + let vis = &input_fn.vis; + + // Get function name + let fn_name = &input_fn.sig.ident; + + // Generate the struct and implementation + let expanded = quote! { + #(#fn_attrs)* + #vis struct #struct_name; + + impl ::mingling::Chain for #struct_name { + type Previous = #previous_type; + + async fn proc(#prev_param: Self::Previous) -> #return_type { + // Call the original function + #fn_name(#prev_param).await + } + } + + // Keep the original function for internal use + #(#fn_attrs)* + #vis async fn #fn_name(#prev_param: #previous_type) -> #return_type { + #fn_body + } + }; + + expanded.into() +} diff --git a/mingling_macros/src/chain_struct.rs b/mingling_macros/src/chain_struct.rs new file mode 100644 index 0000000..82a596d --- /dev/null +++ b/mingling_macros/src/chain_struct.rs @@ -0,0 +1,185 @@ +//! Chain Struct Macro Implementation +//! +//! This module provides the `chain_struct!` macro for creating wrapper types +//! with automatic implementations of common traits. + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::{Ident, Result as SynResult, Token, Type}; + +/// Parses input in the format: `TypeName = InnerType` +struct ChainStructInput { + type_name: Ident, + inner_type: Type, +} + +impl Parse for ChainStructInput { + fn parse(input: ParseStream) -> SynResult<Self> { + let type_name = input.parse()?; + input.parse::<Token![=]>()?; + let inner_type = input.parse()?; + + Ok(ChainStructInput { + type_name, + inner_type, + }) + } +} + +/// Implementation of the `chain_struct!` macro +/// +/// This macro creates a wrapper struct with automatic implementations of: +/// - `From<InnerType>` and `Into<InnerType>` +/// - `new()` constructor +/// - `Default` (if the inner type implements Default) +/// - `AsRef<InnerType>` and `AsMut<InnerType>` +/// - `Deref` and `DerefMut` to the inner type +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::chain_struct; +/// +/// // Creates a wrapper type around String +/// chain_struct!(NameString = String); +/// +/// // Usage: +/// let name = NameString::new("Hello".to_string()); +/// let inner: String = name.into(); // Into conversion +/// let name2 = NameString::from("World".to_string()); // From conversion +/// let ref_str: &String = name2.as_ref(); // AsRef +/// ``` +pub fn chain_struct(input: TokenStream) -> TokenStream { + let ChainStructInput { + type_name, + inner_type, + } = syn::parse_macro_input!(input as ChainStructInput); + + // Generate the struct definition + #[cfg(not(feature = "serde"))] + let struct_def = quote! { + #[derive(Debug)] + pub struct #type_name { + inner: #inner_type, + } + }; + + #[cfg(feature = "serde")] + let struct_def = quote! { + #[derive(Debug, serde::Serialize)] + pub struct #type_name { + inner: #inner_type, + } + }; + + // Generate the new() method + let new_impl = quote! { + impl #type_name { + /// Creates a new instance of the wrapper type + pub fn new(inner: #inner_type) -> Self { + Self { inner } + } + } + }; + + // Generate From and Into implementations + let from_into_impl = quote! { + impl From<#inner_type> for #type_name { + fn from(inner: #inner_type) -> Self { + Self::new(inner) + } + } + + impl From<#type_name> for #inner_type { + fn from(wrapper: #type_name) -> #inner_type { + wrapper.inner + } + } + }; + + // Generate AsRef and AsMut implementations + let as_ref_impl = quote! { + impl ::std::convert::AsRef<#inner_type> for #type_name { + fn as_ref(&self) -> &#inner_type { + &self.inner + } + } + + impl ::std::convert::AsMut<#inner_type> for #type_name { + fn as_mut(&mut self) -> &mut #inner_type { + &mut self.inner + } + } + }; + + // Generate Deref and DerefMut implementations + let deref_impl = quote! { + impl ::std::ops::Deref for #type_name { + type Target = #inner_type; + + fn deref(&self) -> &Self::Target { + &self.inner + } + } + + impl ::std::ops::DerefMut for #type_name { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } + } + }; + + // Check if the inner type implements Default by generating conditional code + let default_impl = quote! { + impl ::std::default::Default for #type_name + where + #inner_type: ::std::default::Default, + { + fn default() -> Self { + Self::new(::std::default::Default::default()) + } + } + }; + + let any_out_impl = quote! { + impl Into<mingling::AnyOutput> for #type_name { + fn into(self) -> mingling::AnyOutput { + mingling::AnyOutput::new(self) + } + } + + impl Into<mingling::ChainProcess> for #type_name { + fn into(self) -> mingling::ChainProcess { + mingling::AnyOutput::new(self).route_chain() + } + } + + impl #type_name { + /// Converts the wrapper type into a `ChainProcess` for chaining operations. + pub fn to_chain(self) -> mingling::ChainProcess { + mingling::AnyOutput::new(self).route_chain() + } + + /// Converts the wrapper type into a `ChainProcess` for rendering operations. + pub fn to_render(self) -> mingling::ChainProcess { + mingling::AnyOutput::new(self).route_renderer() + } + } + }; + + // Combine all implementations + let expanded = quote! { + #struct_def + + #new_impl + #from_into_impl + #as_ref_impl + #deref_impl + #default_impl + + #any_out_impl + }; + + expanded.into() +} diff --git a/mingling_macros/src/dispatcher.rs b/mingling_macros/src/dispatcher.rs new file mode 100644 index 0000000..a411081 --- /dev/null +++ b/mingling_macros/src/dispatcher.rs @@ -0,0 +1,72 @@ +//! Dispatcher Derive Macro Implementation +//! +//! This module provides the `Dispatcher` derive macro for automatically +//! implementing the `mingling::Dispatcher` trait for structs. + +use just_fmt::dot_case; +use proc_macro::TokenStream; +use quote::quote; +use syn::{Attribute, DeriveInput, Ident, Lit, Meta, MetaNameValue, parse_macro_input}; + +/// Parses the `#[dispatcher("path")]` attribute if present +fn parse_dispatcher_attribute(attrs: &[Attribute]) -> Option<String> { + for attr in attrs { + if attr.path().is_ident("dispatcher") { + match attr.parse_args::<Meta>() { + Ok(Meta::NameValue(MetaNameValue { + value: + syn::Expr::Lit(syn::ExprLit { + lit: Lit::Str(lit_str), + .. + }), + .. + })) => { + return Some(lit_str.value()); + } + Ok(_) => { + // If it's not a string literal, we'll use a default + return None; + } + Err(_) => { + // If parsing fails, we'll use a default + return None; + } + } + } + } + None +} + +/// Generates the command node path from the struct name or attribute +fn generate_command_path(struct_name: &Ident, attr_path: Option<String>) -> String { + if let Some(path) = attr_path { + path + } else { + // Convert struct name to dot_case for default path using the dot_case! macro + dot_case!(struct_name.to_string()) + } +} + +/// Implementation of the `Dispatcher` derive macro +pub fn dispatcher_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let struct_name = &input.ident; + + // Parse the dispatcher attribute if present + let attr_path = parse_dispatcher_attribute(&input.attrs); + + // Generate the command path + let command_path = generate_command_path(struct_name, attr_path); + + // Generate the implementation + let expanded = quote! { + impl ::mingling::Dispatcher for #struct_name { + fn node(&self) -> ::mingling::Node { + ::mingling::macros::node!(#command_path) + } + } + }; + + expanded.into() +} diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs new file mode 100644 index 0000000..3fdd130 --- /dev/null +++ b/mingling_macros/src/lib.rs @@ -0,0 +1,182 @@ +//! Mingling Macros Crate +//! +//! This crate provides procedural macros for the Mingling framework. +//! Macros are implemented in separate modules and re-exported here. + +use proc_macro::TokenStream; + +mod chain; +mod chain_struct; +mod dispatcher; +mod node; +mod render; +mod renderer; + +/// Creates a command node from a dot-separated path string. +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::node; +/// +/// let node = node!("root.subcommand.action"); +/// ``` +#[proc_macro] +pub fn node(input: TokenStream) -> TokenStream { + node::node(input) +} + +/// Derive macro for automatically implementing the `Dispatcher` trait. +/// +/// This macro generates an implementation of `mingling::Dispatcher` for a struct. +/// By default, it uses the struct name converted to snake_case as the command path. +/// You can also specify a custom path using the `#[dispatcher("path")]` attribute. +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::Dispatcher; +/// +/// // Uses default path: "remote.add" +/// #[derive(Dispatcher)] +/// pub struct RemoteAdd; +/// +/// // Uses custom path: "remote.rm" +/// #[derive(Dispatcher)] +/// #[dispatcher("remote.rm")] +/// pub struct MyCommand; +/// ``` +#[proc_macro_derive(Dispatcher, attributes(dispatcher))] +pub fn dispatcher_derive(input: TokenStream) -> TokenStream { + dispatcher::dispatcher_derive(input) +} + +/// Macro for creating wrapper types with automatic trait implementations. +/// +/// This macro creates a new struct that wraps an inner type and automatically +/// implements common traits: +/// - `From<InnerType>` and `Into<InnerType>` +/// - `new()` constructor +/// - `Default` (if inner type implements Default) +/// - `AsRef<InnerType>` and `AsMut<InnerType>` +/// - `Deref` and `DerefMut` to inner type +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::chain_struct; +/// +/// // Creates a wrapper type around String +/// chain_struct!(NameString = String); +/// +/// // Usage: +/// let name = NameString::new("Hello".to_string()); +/// let inner: String = name.into(); // Into conversion +/// let name2 = NameString::from("World".to_string()); // From conversion +/// let ref_str: &String = name2.as_ref(); // AsRef +/// ``` +#[proc_macro] +pub fn chain_struct(input: TokenStream) -> TokenStream { + chain_struct::chain_struct(input) +} + +/// Macro for printing to a RenderResult without newline. +/// +/// This macro expands to a call to `RenderResult::print` with formatted arguments. +/// It expects a mutable reference to a `RenderResult` named `r` to be in scope. +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::r_print; +/// +/// let mut r = RenderResult::default(); +/// r_print!("Hello, {}!", "world"); +/// ``` +#[proc_macro] +pub fn r_print(input: TokenStream) -> TokenStream { + render::r_print(input) +} + +/// Macro for printing to a RenderResult with newline. +/// +/// This macro expands to a call to `RenderResult::println` with formatted arguments. +/// It expects a mutable reference to a `RenderResult` named `r` to be in scope. +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::r_println; +/// +/// let mut r = RenderResult::default(); +/// r_println!("Hello, {}!", "world"); +/// ``` +#[proc_macro] +pub fn r_println(input: TokenStream) -> TokenStream { + render::r_println(input) +} + +/// Attribute macro for automatically generating structs that implement the `Chain` trait. +/// +/// This macro transforms an async function into a struct that implements +/// the `Chain` trait. The struct name is specified in the attribute. +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::chain; +/// +/// #[chain(InitEntry)] +/// pub async fn proc(_: InitBegin) -> mingling::AnyOutput { +/// AnyOutput::new::<InitResult>("初始化成功!".to_string().into()) +/// } +/// ``` +/// +/// This generates: +/// ```ignore +/// pub struct InitEntry; +/// impl Chain for InitEntry { +/// type Previous = InitBegin; +/// async fn proc(_: Self::Previous) -> mingling::AnyOutput { +/// AnyOutput::new::<InitResult>("初始化成功!".to_string().into()) +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream { + chain::chain_attr(attr, item) +} + +/// Attribute macro for automatically generating structs that implement the `Renderer` trait. +/// +/// This macro transforms a function into a struct that implements +/// the `Renderer` trait. The struct name is specified in the attribute. +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::renderer; +/// +/// #[renderer(InitResultRenderer)] +/// fn render(p: InitResult, r: &mut RenderResult) { +/// let str: String = p.into(); +/// r_println!("{}", str); +/// } +/// ``` +/// +/// This generates: +/// ```ignore +/// pub struct InitResultRenderer; +/// impl Renderer for InitResultRenderer { +/// type Previous = InitResult; +/// +/// fn render(p: Self::Previous, r: &mut RenderResult) { +/// let str: String = p.into(); +/// r_println!("{}", str); +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream { + renderer::renderer_attr(attr, item) +} diff --git a/mingling_macros/src/node.rs b/mingling_macros/src/node.rs new file mode 100644 index 0000000..3d9473f --- /dev/null +++ b/mingling_macros/src/node.rs @@ -0,0 +1,60 @@ +//! Command Node Macro Implementation +//! +//! This module provides the `node` procedural macro for creating +//! command nodes from dot-separated path strings. + +use just_fmt::kebab_case; +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::{LitStr, Result as SynResult}; + +/// Parses a string literal input for the node macro +struct NodeInput { + path: LitStr, +} + +impl Parse for NodeInput { + fn parse(input: ParseStream) -> SynResult<Self> { + Ok(NodeInput { + path: input.parse()?, + }) + } +} + +/// Implementation of the `node` procedural macro +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::node; +/// +/// // Creates: Node::default().join("root").join("subcommand").join("action") +/// let node = node!("root.subcommand.action"); +/// ``` +pub fn node(input: TokenStream) -> TokenStream { + // Parse the input as a string literal + let input_parsed = syn::parse_macro_input!(input as NodeInput); + let path_str = input_parsed.path.value(); + + // Split the path by dots + let parts: Vec<String> = path_str + .split('.') + .map(|s| kebab_case!(s).to_string()) + .collect(); + + // Build the expression starting from Node::default() + let mut expr: TokenStream2 = quote! { + mingling::Node::default() + }; + + // Add .join() calls for each part of the path + for part in parts { + expr = quote! { + #expr.join(#part) + }; + } + + expr.into() +} diff --git a/mingling_macros/src/render.rs b/mingling_macros/src/render.rs new file mode 100644 index 0000000..8b75f34 --- /dev/null +++ b/mingling_macros/src/render.rs @@ -0,0 +1,87 @@ +//! Render Macros Module +//! +//! This module provides procedural macros for rendering operations. +//! These macros expect a mutable reference to a `RenderResult` named `r` to be in scope. + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::Parser; +use syn::{Expr, Token}; + +/// Implementation of the `r_print!` procedural macro +/// +/// This macro expands to a call to `RenderResult::print` with formatted arguments. +/// It expects a mutable reference to a `RenderResult` named `r` to be in scope. +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::r_print; +/// +/// let mut r = RenderResult::default(); +/// r_print!("Hello, {}!", "world"); +/// ``` +pub fn r_print(input: TokenStream) -> TokenStream { + // Parse the input as format arguments + let parser = syn::punctuated::Punctuated::<Expr, Token![,]>::parse_terminated; + let format_args = match parser.parse(input) { + Ok(args) => args, + Err(e) => return e.to_compile_error().into(), + }; + + // Build the format macro call + let format_call = if format_args.is_empty() { + quote! { ::std::format!("") } + } else { + let args_iter = format_args.iter(); + quote! { ::std::format!(#(#args_iter),*) } + }; + + let expanded = quote! { + { + let formatted = #format_call; + ::mingling::RenderResult::print(r, &formatted) + } + }; + + expanded.into() +} + +/// Implementation of the `r_println!` procedural macro +/// +/// This macro expands to a call to `RenderResult::println` with formatted arguments. +/// It expects a mutable reference to a `RenderResult` named `r` to be in scope. +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::r_println; +/// +/// let mut r = RenderResult::default(); +/// r_println!("Hello, {}!", "world"); +/// ``` +pub fn r_println(input: TokenStream) -> TokenStream { + // Parse the input as format arguments + let parser = syn::punctuated::Punctuated::<Expr, Token![,]>::parse_terminated; + let format_args = match parser.parse(input) { + Ok(args) => args, + Err(e) => return e.to_compile_error().into(), + }; + + // Build the format macro call + let format_call = if format_args.is_empty() { + quote! { ::std::format!("") } + } else { + let args_iter = format_args.iter(); + quote! { ::std::format!(#(#args_iter),*) } + }; + + let expanded = quote! { + { + let formatted = #format_call; + ::mingling::RenderResult::println(r, &formatted) + } + }; + + expanded.into() +} diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs new file mode 100644 index 0000000..3fd01bd --- /dev/null +++ b/mingling_macros/src/renderer.rs @@ -0,0 +1,248 @@ +//! Renderer Attribute Macro Implementation +//! +//! This module provides the `#[renderer]` attribute macro for automatically +//! generating structs that implement the `Renderer` trait from functions. + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; +use syn::{ + FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input, +}; + +/// Parses the renderer attribute arguments +struct RendererAttribute { + struct_name: Ident, +} + +impl Parse for RendererAttribute { + fn parse(input: ParseStream) -> syn::Result<Self> { + let struct_name = input.parse()?; + Ok(RendererAttribute { struct_name }) + } +} + +/// Extracts the previous type and parameter name from function arguments +fn extract_previous_info(sig: &Signature) -> syn::Result<(Pat, TypePath)> { + // The function should have exactly two parameters + if sig.inputs.len() != 2 { + return Err(syn::Error::new( + sig.inputs.span(), + "Renderer function must have exactly two parameters", + )); + } + + // First parameter is the previous type + 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 + match &**ty { + Type::Path(type_path) => Ok((param_pat, type_path.clone())), + _ => Err(syn::Error::new( + ty.span(), + "First parameter type must be a type path", + )), + } + } + FnArg::Receiver(_) => Err(syn::Error::new( + arg.span(), + "Renderer function cannot have self parameter", + )), + } +} + +/// Validates that the second parameter is r: &mut RenderResult +fn validate_render_result_param(sig: &Signature) -> syn::Result<()> { + // Second parameter should be &mut RenderResult + let arg = &sig.inputs[1]; + + match arg { + FnArg::Typed(PatType { pat, ty, .. }) => { + // Check parameter name is "r" + let param_name = match &**pat { + Pat::Ident(pat_ident) => pat_ident.ident.to_string(), + _ => { + return Err(syn::Error::new( + pat.span(), + "Second parameter must be named 'r'", + )); + } + }; + + if param_name != "r" { + return Err(syn::Error::new( + pat.span(), + "Second parameter must be named 'r'", + )); + } + + // Check type is &mut RenderResult + match &**ty { + Type::Reference(type_ref) => { + // Check mutability + if !type_ref.mutability.is_some() { + return Err(syn::Error::new( + ty.span(), + "Second parameter must be mutable reference: &mut RenderResult", + )); + } + + // Check inner type is RenderResult + match &*type_ref.elem { + Type::Path(type_path) => { + let type_name = + type_path.path.segments.last().unwrap().ident.to_string(); + if type_name != "RenderResult" { + return Err(syn::Error::new( + ty.span(), + "Second parameter must be &mut RenderResult", + )); + } + } + _ => { + return Err(syn::Error::new( + ty.span(), + "Second parameter must be &mut RenderResult", + )); + } + } + } + _ => { + return Err(syn::Error::new( + ty.span(), + "Second parameter must be &mut RenderResult", + )); + } + } + } + FnArg::Receiver(_) => { + return Err(syn::Error::new( + arg.span(), + "Renderer function cannot have self parameter", + )); + } + } + + Ok(()) +} + +/// Extracts the return type from the function signature +fn extract_return_type(sig: &Signature) -> syn::Result<()> { + // Renderer 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(), + "Renderer function must return () or have no return type", + )), + } + } + ReturnType::Default => Ok(()), + } +} + +/// Implementation of the `#[renderer]` attribute macro +/// +/// This macro transforms a function into a struct that implements +/// the `Renderer` trait. The struct name is specified in the attribute. +/// +/// # Examples +/// +/// ```ignore +/// use mingling_macros::renderer; +/// +/// #[renderer(InitResultRenderer)] +/// fn render(data: InitResult, r: &mut RenderResult) { +/// let str: String = data.into(); +/// r_println!("{}", str); +/// } +/// ``` +/// +/// This generates: +/// ```ignore +/// pub struct InitResultRenderer; +/// impl Renderer for InitResultRenderer { +/// type Previous = InitResult; +/// +/// fn render(data: Self::Previous, r: &mut RenderResult) { +/// let str: String = data.into(); +/// r_println!("{}", str); +/// } +/// } +/// ``` +pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse the attribute arguments + let renderer_attr = parse_macro_input!(attr as RendererAttribute); + let struct_name = renderer_attr.struct_name; + + // 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(), "Renderer function cannot be async") + .to_compile_error() + .into(); + } + + // Extract the previous type and parameter name from function arguments + let (prev_param, previous_type) = match extract_previous_info(&input_fn.sig) { + Ok(info) => info, + Err(e) => return e.to_compile_error().into(), + }; + + // Validate second parameter is r: &mut RenderResult + if let Err(e) = validate_render_result_param(&input_fn.sig) { + 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 renderer attribute) + let mut fn_attrs = input_fn.attrs.clone(); + // Remove any #[renderer(...)] attributes to avoid infinite recursion + fn_attrs.retain(|attr| !attr.path().is_ident("renderer")); + + // Get function visibility + let vis = &input_fn.vis; + + // Get function name + let fn_name = &input_fn.sig.ident; + + // Generate the struct and implementation + let expanded = quote! { + #(#fn_attrs)* + #vis struct #struct_name; + + impl ::mingling::Renderer for #struct_name { + type Previous = #previous_type; + + fn render(#prev_param: Self::Previous, r: &mut ::mingling::RenderResult) { + // Call the original function + #fn_name(#prev_param, r) + } + } + + // Keep the original function for internal use + #(#fn_attrs)* + #vis fn #fn_name(#prev_param: #previous_type, r: &mut ::mingling::RenderResult) { + #fn_body + } + }; + + expanded.into() +} |
