diff options
Diffstat (limited to 'mingling_macros/src')
| -rw-r--r-- | mingling_macros/src/chain.rs | 36 | ||||
| -rw-r--r-- | mingling_macros/src/chain_struct.rs | 23 | ||||
| -rw-r--r-- | mingling_macros/src/dispatcher.rs | 72 | ||||
| -rw-r--r-- | mingling_macros/src/dispatcher_chain.rs | 94 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 151 | ||||
| -rw-r--r-- | mingling_macros/src/node.rs | 10 | ||||
| -rw-r--r-- | mingling_macros/src/render.rs | 26 | ||||
| -rw-r--r-- | mingling_macros/src/renderer.rs | 149 |
8 files changed, 255 insertions, 306 deletions
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 } }; |
