diff options
Diffstat (limited to 'macros')
| -rw-r--r-- | macros/cmd_system_macros/Cargo.toml | 12 | ||||
| -rw-r--r-- | macros/cmd_system_macros/src/lib.rs | 92 | ||||
| -rw-r--r-- | macros/render_system_macros/Cargo.toml | 12 | ||||
| -rw-r--r-- | macros/render_system_macros/src/lib.rs | 143 |
4 files changed, 259 insertions, 0 deletions
diff --git a/macros/cmd_system_macros/Cargo.toml b/macros/cmd_system_macros/Cargo.toml new file mode 100644 index 0000000..4a91064 --- /dev/null +++ b/macros/cmd_system_macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cmd_system_macros" +version.workspace = true +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full", "extra-traits", "visit"] } diff --git a/macros/cmd_system_macros/src/lib.rs b/macros/cmd_system_macros/src/lib.rs new file mode 100644 index 0000000..e585782 --- /dev/null +++ b/macros/cmd_system_macros/src/lib.rs @@ -0,0 +1,92 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Expr, ItemFn, Lit, Type, parse_macro_input, parse_quote}; + +#[proc_macro_attribute] +pub fn exec(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input_fn = parse_macro_input!(item as ItemFn); + let fn_block = &input_fn.block; + + let mut output_mappings = Vec::new(); + extract_cmd_output_macros(fn_block, &mut output_mappings); + + let mapping_fn = generate_mapping_function(&output_mappings); + + let expanded = quote! { + #input_fn + + #mapping_fn + }; + + TokenStream::from(expanded) +} + +fn extract_cmd_output_macros(block: &syn::Block, mappings: &mut Vec<(String, syn::Type)>) { + use syn::visit::Visit; + + struct CmdOutputVisitor<'a> { + mappings: &'a mut Vec<(String, syn::Type)>, + } + + impl<'a> syn::visit::Visit<'a> for CmdOutputVisitor<'a> { + fn visit_macro(&mut self, mac: &'a syn::Macro) { + if mac.path.is_ident("cmd_output") { + let nested_result = syn::parse2::<syn::ExprTuple>(mac.tokens.clone()); + if let Ok(nested) = nested_result { + if nested.elems.len() < 2 { + syn::visit::visit_macro(self, mac); + return; + } + + let first_elem = &nested.elems[0]; + let second_elem = &nested.elems[1]; + + let type_path_opt = match first_elem { + Expr::Path(path) => Some(path), + _ => None, + }; + + let lit_str_opt = match second_elem { + Expr::Lit(lit) => match &lit.lit { + Lit::Str(lit_str) => Some(lit_str), + _ => None, + }, + _ => None, + }; + + if let (Some(type_path), Some(lit_str)) = (type_path_opt, lit_str_opt) { + let type_name = lit_str.value(); + if let Some(type_ident) = type_path.path.get_ident() { + let ty: Type = parse_quote!(#type_ident); + self.mappings.push((type_name, ty)); + } + } + } + } + + syn::visit::visit_macro(self, mac); + } + } + + let mut visitor = CmdOutputVisitor { mappings }; + visitor.visit_block(block); +} + +fn generate_mapping_function(mappings: &[(String, syn::Type)]) -> proc_macro2::TokenStream { + let mapping_entries: Vec<_> = mappings + .iter() + .map(|(name, ty)| { + quote! { + map.insert(#name.to_string(), std::any::TypeId::of::<#ty>()); + } + }) + .collect(); + + quote! { + fn get_output_type_mapping() -> std::collections::HashMap<String, std::any::TypeId> { + let mut map = std::collections::HashMap::new(); + #(#mapping_entries)* + map + } + } +} diff --git a/macros/render_system_macros/Cargo.toml b/macros/render_system_macros/Cargo.toml new file mode 100644 index 0000000..df435db --- /dev/null +++ b/macros/render_system_macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "render_system_macros" +version.workspace = true +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full", "extra-traits"] } diff --git a/macros/render_system_macros/src/lib.rs b/macros/render_system_macros/src/lib.rs new file mode 100644 index 0000000..7466b53 --- /dev/null +++ b/macros/render_system_macros/src/lib.rs @@ -0,0 +1,143 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{ItemFn, Type, parse_macro_input, spanned::Spanned}; + +/// Macro for simplifying renderer definitions +/// +/// Expands the `#[result_renderer(Renderer)]` macro into the corresponding struct and trait implementation +/// +/// # Example +/// ```ignore +/// #[result_renderer(MyRenderer)] +/// async fn render(data: &Output) -> Result<JVRenderResult, CmdRenderError> { +/// // Rendering logic +/// } +/// ``` +/// +/// Expands to: +/// ```ignore +/// pub struct MyRenderer; +/// +/// impl JVResultRenderer<Output> for MyRenderer { +/// async fn render(data: &Output) -> Result<JVRenderResult, CmdRenderError> { +/// // Rendering logic +/// } +/// } +/// +/// impl JVResultAutoRenderer<Output> for MyRenderer { +/// fn get_type_id(&self) -> std::any::TypeId { +/// std::any::TypeId::of::<Self>() +/// } +/// +/// fn get_data_type_id(&self) -> std::any::TypeId { +/// std::any::TypeId::of::<Output>() +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn result_renderer(args: TokenStream, input: TokenStream) -> TokenStream { + // Parse macro arguments (renderer struct name) + let renderer_name = parse_macro_input!(args as syn::Ident); + + // Parse the input function + let input_fn = parse_macro_input!(input as ItemFn); + + // Check if the function is async + if input_fn.sig.asyncness.is_none() { + return syn::Error::new(input_fn.sig.ident.span(), "renderer function must be async") + .to_compile_error() + .into(); + } + + // Get the function name + let fn_name = &input_fn.sig.ident; + + // Get function parameters + let fn_inputs = &input_fn.sig.inputs; + + // Check the number of function parameters + if fn_inputs.len() != 1 { + return syn::Error::new( + input_fn.sig.paren_token.span.join(), + "renderer function must have exactly one parameter", + ) + .to_compile_error() + .into(); + } + + // Extract the type of the first parameter + let param_type = match &fn_inputs[0] { + syn::FnArg::Typed(pat_type) => &pat_type.ty, + syn::FnArg::Receiver(_) => { + return syn::Error::new( + fn_inputs[0].span(), + "renderer function cannot have self parameter", + ) + .to_compile_error() + .into(); + } + }; + + // Check if the parameter type is a reference type, and extract the inner type + let inner_type = match &**param_type { + Type::Reference(type_ref) => { + // Ensure it's a reference type + &type_ref.elem + } + _ => { + return syn::Error::new( + param_type.span(), + "renderer function parameter must be a reference type (&Data)", + ) + .to_compile_error() + .into(); + } + }; + + // Extract the parameter pattern (for function calls) + let param_pattern = match &fn_inputs[0] { + syn::FnArg::Typed(pat_type) => &pat_type.pat, + _ => unreachable!(), + }; + + // Extract the function's visibility modifier + let visibility = &input_fn.vis; + + // Extract generic parameters (if any) + let generics = &input_fn.sig.generics; + + // Extract where clause (if any) + let where_clause = &generics.where_clause; + + // Build the output + let expanded = quote! { + #input_fn + + #visibility struct #renderer_name; + + impl #generics crate::systems::render::renderer::JVResultRenderer<#inner_type> for #renderer_name + #where_clause + { + fn render( + #fn_inputs + ) -> impl ::std::future::Future<Output = ::std::result::Result< + crate::systems::render::renderer::JVRenderResult, + crate::systems::cmd::errors::CmdRenderError + >> + ::std::marker::Send + ::std::marker::Sync { + async move { + #fn_name(#param_pattern).await + } + } + + fn get_type_id(&self) -> std::any::TypeId { + std::any::TypeId::of::<Self>() + } + + fn get_data_type_id(&self) -> std::any::TypeId { + std::any::TypeId::of::<#inner_type>() + } + } + }; + + expanded.into() +} |
