summaryrefslogtreecommitdiff
path: root/mingling_macros
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-03-29 00:52:16 +0800
committer魏曹先生 <1992414357@qq.com>2026-03-29 00:52:28 +0800
commitdb9afa0b06355028eafe3bc29fe0b2429ba8fd0a (patch)
tree60bf74d0853fcc0c1e9363813c26109a6ca38a4f /mingling_macros
parent7ce68cd11516bd7cf037ecea99a92aee7c31b2c3 (diff)
Completed the first preliminary usable version of the Mingling
framework.
Diffstat (limited to 'mingling_macros')
-rw-r--r--mingling_macros/Cargo.lock7
-rw-r--r--mingling_macros/Cargo.toml1
-rw-r--r--mingling_macros/src/chain.rs36
-rw-r--r--mingling_macros/src/chain_struct.rs23
-rw-r--r--mingling_macros/src/dispatcher.rs72
-rw-r--r--mingling_macros/src/dispatcher_chain.rs94
-rw-r--r--mingling_macros/src/lib.rs151
-rw-r--r--mingling_macros/src/node.rs10
-rw-r--r--mingling_macros/src/render.rs26
-rw-r--r--mingling_macros/src/renderer.rs149
10 files changed, 263 insertions, 306 deletions
diff --git a/mingling_macros/Cargo.lock b/mingling_macros/Cargo.lock
index b2fef0e..9d159fe 100644
--- a/mingling_macros/Cargo.lock
+++ b/mingling_macros/Cargo.lock
@@ -13,12 +13,19 @@ name = "mingling_macros"
version = "0.1.0"
dependencies = [
"just_fmt",
+ "once_cell",
"proc-macro2",
"quote",
"syn",
]
[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/mingling_macros/Cargo.toml b/mingling_macros/Cargo.toml
index 683b5b4..39c6d36 100644
--- a/mingling_macros/Cargo.toml
+++ b/mingling_macros/Cargo.toml
@@ -16,3 +16,4 @@ quote = "1.0"
proc-macro2 = "1.0"
just_fmt = "0.1.2"
+once_cell = "1.21"
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs
index 0c21c79..ddedc05 100644
--- a/mingling_macros/src/chain.rs
+++ b/mingling_macros/src/chain.rs
@@ -72,32 +72,6 @@ fn extract_return_type(sig: &Signature) -> syn::Result<TypePath> {
}
}
-/// 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);
@@ -160,5 +134,15 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
}
};
+ // Record the chain mapping
+ let chain_entry = quote! {
+ #struct_name => #previous_type,
+ };
+ let mut chains = crate::CHAINS.lock().unwrap();
+ let entry = chain_entry.to_string();
+ if !chains.contains(&entry) {
+ chains.push(entry);
+ }
+
expanded.into()
}
diff --git a/mingling_macros/src/chain_struct.rs b/mingling_macros/src/chain_struct.rs
index 82a596d..7305a67 100644
--- a/mingling_macros/src/chain_struct.rs
+++ b/mingling_macros/src/chain_struct.rs
@@ -27,29 +27,6 @@ impl Parse for ChainStructInput {
}
}
-/// 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,
diff --git a/mingling_macros/src/dispatcher.rs b/mingling_macros/src/dispatcher.rs
deleted file mode 100644
index a411081..0000000
--- a/mingling_macros/src/dispatcher.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-//! 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/dispatcher_chain.rs b/mingling_macros/src/dispatcher_chain.rs
new file mode 100644
index 0000000..d9d95a5
--- /dev/null
+++ b/mingling_macros/src/dispatcher_chain.rs
@@ -0,0 +1,94 @@
+//! Dispatcher Chain and Dispatcher Render Macros
+//!
+//! This module provides macros for creating dispatcher chain and dispatcher render structs
+//! with automatic implementations of the `DispatcherChain` trait.
+
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::parse::{Parse, ParseStream};
+use syn::{Ident, Result as SynResult, Token};
+
+/// Parses input in the format: `"command_name", CommandStruct => ChainStruct`
+struct DispatcherChainInput {
+ command_name: syn::LitStr,
+ command_struct: Ident,
+ chain_struct: Ident,
+}
+
+impl Parse for DispatcherChainInput {
+ fn parse(input: ParseStream) -> SynResult<Self> {
+ let command_name = input.parse()?;
+ input.parse::<Token![,]>()?;
+ let command_struct = input.parse()?;
+ input.parse::<Token![=>]>()?;
+ let chain_struct = input.parse()?;
+
+ Ok(DispatcherChainInput {
+ command_name,
+ command_struct,
+ chain_struct,
+ })
+ }
+}
+
+pub fn dispatcher_chain(input: TokenStream) -> TokenStream {
+ let DispatcherChainInput {
+ command_name,
+ command_struct,
+ chain_struct,
+ } = syn::parse_macro_input!(input as DispatcherChainInput);
+
+ let command_name_str = command_name.value();
+
+ let expanded = quote! {
+ #[derive(Debug, Default)]
+ pub struct #command_struct;
+
+ ::mingling::macros::chain_struct!(#chain_struct = Vec<String>);
+
+ impl ::mingling::Dispatcher for #command_struct {
+ fn node(&self) -> ::mingling::Node {
+ ::mingling::macros::node!(#command_name_str)
+ }
+ fn begin(&self, args: Vec<String>) -> ::mingling::ChainProcess {
+ #chain_struct::new(args).to_chain()
+ }
+ fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher> {
+ Box::new(#command_struct)
+ }
+ }
+ };
+
+ expanded.into()
+}
+
+pub fn dispatcher_render(input: TokenStream) -> TokenStream {
+ let DispatcherChainInput {
+ command_name,
+ command_struct,
+ chain_struct,
+ } = syn::parse_macro_input!(input as DispatcherChainInput);
+
+ let command_name_str = command_name.value();
+
+ let expanded = quote! {
+ #[derive(Debug, Default)]
+ pub struct #command_struct;
+
+ ::mingling::macros::chain_struct!(#chain_struct = Vec<String>);
+
+ impl ::mingling::Dispatcher for #command_struct {
+ fn node(&self) -> ::mingling::Node {
+ ::mingling::macros::node!(#command_name_str)
+ }
+ fn begin(&self, args: Vec<String>) -> ::mingling::ChainProcess {
+ #chain_struct::new(args).to_render()
+ }
+ fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher> {
+ Box::new(#command_struct)
+ }
+ }
+ };
+
+ expanded.into()
+}
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index 3fdd130..b32534b 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -4,14 +4,24 @@
//! Macros are implemented in separate modules and re-exported here.
use proc_macro::TokenStream;
+use proc_macro2::Ident;
+use quote::quote;
+use syn::parse_macro_input;
mod chain;
mod chain_struct;
-mod dispatcher;
+mod dispatcher_chain;
mod node;
mod render;
mod renderer;
+use once_cell::sync::Lazy;
+use std::sync::Mutex;
+
+// Global variable declarations for storing chain and renderer mappings
+pub(crate) static CHAINS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
+pub(crate) static RENDERERS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
+
/// Creates a command node from a dot-separated path string.
///
/// # Examples
@@ -26,31 +36,6 @@ 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
@@ -80,6 +65,16 @@ pub fn chain_struct(input: TokenStream) -> TokenStream {
chain_struct::chain_struct(input)
}
+#[proc_macro]
+pub fn dispatcher_chain(input: TokenStream) -> TokenStream {
+ dispatcher_chain::dispatcher_chain(input)
+}
+
+#[proc_macro]
+pub fn dispatcher_render(input: TokenStream) -> TokenStream {
+ dispatcher_chain::dispatcher_render(input)
+}
+
/// Macro for printing to a RenderResult without newline.
///
/// This macro expands to a call to `RenderResult::print` with formatted arguments.
@@ -127,8 +122,8 @@ pub fn r_println(input: TokenStream) -> TokenStream {
/// use mingling_macros::chain;
///
/// #[chain(InitEntry)]
-/// pub async fn proc(_: InitBegin) -> mingling::AnyOutput {
-/// AnyOutput::new::<InitResult>("初始化成功!".to_string().into())
+/// pub async fn proc(_: InitBegin) -> mingling::ChainProcess {
+/// AnyOutput::new::<InitResult>("Init!".to_string().into()).route_chain()
/// }
/// ```
///
@@ -137,8 +132,8 @@ pub fn r_println(input: TokenStream) -> TokenStream {
/// pub struct InitEntry;
/// impl Chain for InitEntry {
/// type Previous = InitBegin;
-/// async fn proc(_: Self::Previous) -> mingling::AnyOutput {
-/// AnyOutput::new::<InitResult>("初始化成功!".to_string().into())
+/// async fn proc(_: Self::Previous) -> mingling::ChainProcess {
+/// AnyOutput::new::<InitResult>("Init!".to_string().into()).route_chain()
/// }
/// }
/// ```
@@ -158,7 +153,7 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream {
/// use mingling_macros::renderer;
///
/// #[renderer(InitResultRenderer)]
-/// fn render(p: InitResult, r: &mut RenderResult) {
+/// fn render(p: InitResult) {
/// let str: String = p.into();
/// r_println!("{}", str);
/// }
@@ -180,3 +175,97 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream {
pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream {
renderer::renderer_attr(attr, item)
}
+
+/// Macro for creating a program structure that collects all chains and renderers.
+///
+/// This macro creates a struct that implements the `ProgramCollect` trait,
+/// which collects all chains and renderers registered with `#[chain]` and `#[renderer]`
+/// attribute macros. The program can then be used to execute the command chain.
+///
+/// # Examples
+///
+/// ```ignore
+/// use mingling_macros::program;
+///
+/// program!(MyProgram);
+///
+/// // This generates:
+/// pub struct MyProgram;
+/// impl mingling::ProgramCollect for MyProgram {
+/// mingling::__dispatch_program_renderers!(...);
+/// mingling::__dispatch_program_chains!(...);
+/// }
+/// impl MyProgram {
+/// pub fn new() -> mingling::Program<MyProgram> {
+/// mingling::Program::new()
+/// }
+/// }
+/// ```
+#[proc_macro]
+pub fn program(input: TokenStream) -> TokenStream {
+ let name = parse_macro_input!(input as Ident);
+
+ let renderers = RENDERERS.lock().unwrap().clone();
+ let chains = CHAINS.lock().unwrap().clone();
+
+ let renderer_tokens: Vec<proc_macro2::TokenStream> = renderers
+ .iter()
+ .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
+ .collect();
+
+ let chain_tokens: Vec<proc_macro2::TokenStream> = chains
+ .iter()
+ .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
+ .collect();
+
+ let expanded = quote! {
+ pub struct #name;
+
+ impl ::mingling::ProgramCollect for #name {
+ ::mingling::__dispatch_program_renderers!(
+ #(#renderer_tokens)*
+ );
+ ::mingling::__dispatch_program_chains!(
+ #(#chain_tokens)*
+ );
+ }
+
+ impl #name {
+ pub fn new() -> ::mingling::Program<#name> {
+ ::mingling::Program::new()
+ }
+ }
+ };
+
+ TokenStream::from(expanded)
+}
+
+/// Internal macro for registering chains.
+///
+/// This macro is used internally by the `#[chain]` attribute macro
+/// and should not be used directly.
+#[doc(hidden)]
+#[proc_macro]
+pub fn __register_chain(input: TokenStream) -> TokenStream {
+ let chain_entry = parse_macro_input!(input as syn::LitStr);
+ let entry_str = chain_entry.value();
+
+ CHAINS.lock().unwrap().push(entry_str);
+
+ TokenStream::new()
+}
+
+/// Internal macro for registering renderers.
+///
+/// This macro is used internally by the `#[renderer]` attribute macro
+/// and should not be used directly.
+#[doc(hidden)]
+#[proc_macro]
+pub fn __register_renderer(input: TokenStream) -> TokenStream {
+ let renderer_entry = parse_macro_input!(input as syn::LitStr);
+ let entry_str = renderer_entry.value();
+
+ RENDERERS.lock().unwrap().push(entry_str);
+
+ TokenStream::new()
+}
diff --git a/mingling_macros/src/node.rs b/mingling_macros/src/node.rs
index 3d9473f..e56f14b 100644
--- a/mingling_macros/src/node.rs
+++ b/mingling_macros/src/node.rs
@@ -23,16 +23,6 @@ impl Parse for NodeInput {
}
}
-/// 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);
diff --git a/mingling_macros/src/render.rs b/mingling_macros/src/render.rs
index 8b75f34..3f1bbe8 100644
--- a/mingling_macros/src/render.rs
+++ b/mingling_macros/src/render.rs
@@ -8,19 +8,6 @@ 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;
@@ -47,19 +34,6 @@ pub fn r_print(input: TokenStream) -> TokenStream {
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;
diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs
index 3fd01bd..14c26df 100644
--- a/mingling_macros/src/renderer.rs
+++ b/mingling_macros/src/renderer.rs
@@ -25,15 +25,15 @@ impl Parse for RendererAttribute {
/// 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 {
+ // The function should have exactly one parameter
+ if sig.inputs.len() != 1 {
return Err(syn::Error::new(
sig.inputs.span(),
- "Renderer function must have exactly two parameters",
+ "Renderer function must have exactly one parameter (the previous type)",
));
}
- // First parameter is the previous type
+ // First and only parameter is the previous type
let arg = &sig.inputs[0];
match arg {
FnArg::Typed(PatType { pat, ty, .. }) => {
@@ -45,7 +45,7 @@ fn extract_previous_info(sig: &Signature) -> syn::Result<(Pat, TypePath)> {
Type::Path(type_path) => Ok((param_pat, type_path.clone())),
_ => Err(syn::Error::new(
ty.span(),
- "First parameter type must be a type path",
+ "Parameter type must be a type path",
)),
}
}
@@ -56,81 +56,6 @@ fn extract_previous_info(sig: &Signature) -> syn::Result<(Pat, TypePath)> {
}
}
-/// 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
@@ -149,35 +74,6 @@ fn extract_return_type(sig: &Signature) -> syn::Result<()> {
}
}
-/// 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);
@@ -199,11 +95,6 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
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();
@@ -214,6 +105,7 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
// 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"));
@@ -223,7 +115,19 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
// Get function name
let fn_name = &input_fn.sig.ident;
+ // Register the renderer in the global list
+ let renderer_entry = quote! {
+ #struct_name => #previous_type,
+ };
+
+ let mut renderers = crate::RENDERERS.lock().unwrap();
+ let entry_str = renderer_entry.to_string();
+ if !renderers.contains(&entry_str) {
+ renderers.push(entry_str);
+ }
+
// Generate the struct and implementation
+ // We need to create a wrapper function that adds the r parameter
let expanded = quote! {
#(#fn_attrs)*
#vis struct #struct_name;
@@ -232,14 +136,23 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
type Previous = #previous_type;
fn render(#prev_param: Self::Previous, r: &mut ::mingling::RenderResult) {
- // Call the original function
- #fn_name(#prev_param, r)
+ // Create a local wrapper function that includes r parameter
+ // This allows r_println! to access r
+ #[allow(non_snake_case)]
+ fn render_wrapper(#prev_param: #previous_type, r: &mut ::mingling::RenderResult) {
+ #fn_body
+ }
+
+ // Call the wrapper function
+ render_wrapper(#prev_param, r);
}
}
- // Keep the original function for internal use
+ // Keep the original function for internal use (without r parameter)
#(#fn_attrs)*
- #vis fn #fn_name(#prev_param: #previous_type, r: &mut ::mingling::RenderResult) {
+ #vis fn #fn_name(#prev_param: #previous_type) {
+ let mut dummy_r = ::mingling::RenderResult::default();
+ let r = &mut dummy_r;
#fn_body
}
};