summaryrefslogtreecommitdiff
path: root/mingling_macros/src
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-03-28 00:47:46 +0800
committer魏曹先生 <1992414357@qq.com>2026-03-28 00:47:46 +0800
commit7ce68cd11516bd7cf037ecea99a92aee7c31b2c3 (patch)
treea3923ad41c91aa21fe169fd6b4b1bf8898a82589 /mingling_macros/src
Add initial Mingling framework codebase
Diffstat (limited to 'mingling_macros/src')
-rw-r--r--mingling_macros/src/chain.rs164
-rw-r--r--mingling_macros/src/chain_struct.rs185
-rw-r--r--mingling_macros/src/dispatcher.rs72
-rw-r--r--mingling_macros/src/lib.rs182
-rw-r--r--mingling_macros/src/node.rs60
-rw-r--r--mingling_macros/src/render.rs87
-rw-r--r--mingling_macros/src/renderer.rs248
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()
+}