diff --git a/Cargo.toml b/Cargo.toml index fba25c0..3ab3153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ authors = ["Dean J. Karstedt "] license-file = "LICENSE" [dependencies] +pmp-macro = { path = "crates/pmp-macro" } pmp-emitter = { path = "crates/pmp-emitter" } clap = { version = "^4", features = ["derive"] } diff --git a/crates/pmp-core/src/functions.rs b/crates/pmp-core/src/functions.rs index c39773e..8aa35b3 100644 --- a/crates/pmp-core/src/functions.rs +++ b/crates/pmp-core/src/functions.rs @@ -24,3 +24,26 @@ pub fn snake_to_title(s: &str) -> String .collect::>() .join(" ") } + +pub fn pascal_to_snake(s: &str) -> String +{ + let mut out = String::with_capacity(s.len()); + let mut prev_lower = false; + + for c in s.chars() + { + if c.is_uppercase() + { + if prev_lower { out.push('_'); } + for lc in c.to_lowercase() { out.push(lc); } + prev_lower = false; + } + else + { + prev_lower = true; + out.push(c); + } + } + + out +} diff --git a/crates/pmp-macro/crates/pmp-macro-core/src/expand.rs b/crates/pmp-macro/crates/pmp-macro-core/src/expand.rs index 4c84d3a..5263ec5 100644 --- a/crates/pmp-macro/crates/pmp-macro-core/src/expand.rs +++ b/crates/pmp-macro/crates/pmp-macro-core/src/expand.rs @@ -3,6 +3,22 @@ use proc_macro2::TokenStream as TokenStream2; use crate::attrs::MacroInput; +fn rewrite_bitor_target_expr(expr: syn::Expr) -> syn::Expr +{ + match expr + { + syn::Expr::Binary(mut bin) if matches!(bin.op, syn::BinOp::BitOr(_)) => { + bin.left = Box::new(rewrite_bitor_target_expr(*bin.left)); + bin.right = Box::new(rewrite_bitor_target_expr(*bin.right)); + syn::Expr::Binary(bin) + } + + other => { + syn::parse_quote! { (#other).bits() } + } + } +} + pub fn expand(parsed: MacroInput) -> syn::Result { let struct_ident = &parsed.ident; @@ -16,8 +32,8 @@ pub fn expand(parsed: MacroInput) -> syn::Result .unwrap_or_else(|| struct_ident.to_string()); let target_expr: syn::Expr = parsed.target - .map(|e| e.0) - .unwrap_or_else(|| syn::parse_quote! { ::pmp_macro_misc::Target::ALL }); + .map(|e| rewrite_bitor_target_expr(e.0)) + .unwrap_or_else(|| syn::parse_quote! { ::pmp_macro::Target::ALL.bits() }); let docs: String = parsed.docs.map(|s| s.0).unwrap_or_default(); @@ -53,7 +69,7 @@ pub fn expand(parsed: MacroInput) -> syn::Result }; param_tokens.push(quote_spanned! { span => - ::pmp_macro_misc::ParamDescriptor { + ::pmp_macro::ParamDescriptor { name: #field_name, php_type: #php_type, required: #required, @@ -64,47 +80,48 @@ pub fn expand(parsed: MacroInput) -> syn::Result if let Some(e) = errors { return Err(e); } - let mod_ident = format_ident!("__pmp_macro_impl_{}", struct_ident); - let handler = quote! { super::#struct_ident }; + let snaked_ident = pmp_core::pascal_to_snake(struct_ident.to_string().as_str()); + let mod_ident = format_ident!("__pmp_macro_impl_{}", snaked_ident); + let handler = quote! { super::#struct_ident }; Ok(quote! { #[doc(hidden)] mod #mod_ident { - use ::pmp_macro_misc::{ - AnalyzeFn, MacroDescriptor, MacroRegistration, ParamDescriptor, - TransformFn, PmpMacro, + use ::pmp_macro::{ + AnalyzeFn, MacroDescriptor, MacroRegistration, + ParamDescriptor, Target, TransformFn, PmpMacro, }; static PARAMS: &[ParamDescriptor] = &[ #(#param_tokens),* ]; static DESCRIPTOR: MacroDescriptor = MacroDescriptor { - name: #attr_name, - target: #target_expr, - docs: #docs, - params: PARAMS, + name: #attr_name, + target_bits: #target_expr, + docs: #docs, + params: PARAMS, }; fn analyze( - ctx: &::pmp_macro_misc::MacroContext, - attr: &::pmp_macro_misc::ResolvedAttr, - node: &::pmp_macro_misc::AttributedNode, - ) -> ::std::vec::Vec<::pmp_macro_misc::Diagnostic> + ctx: &::pmp_macro::MacroContext, + attr: &::pmp_macro::ResolvedAttr, + node: &::pmp_macro::AttributedNode, + ) -> ::std::vec::Vec<::pmp_macro::Diagnostic> { <#handler as PmpMacro>::analyze(&::std::default::Default::default(), ctx, attr, node) } fn transform( - ctx: &::pmp_macro_misc::MacroContext, - attr: &::pmp_macro_misc::ResolvedAttr, - node: &::pmp_macro_misc::AttributedNode, - ) -> ::std::vec::Vec<::pmp_macro_misc::Rewrite> + ctx: &::pmp_macro::MacroContext, + attr: &::pmp_macro::ResolvedAttr, + node: &::pmp_macro::AttributedNode, + ) -> ::std::vec::Vec<::pmp_macro::Rewrite> { <#handler as PmpMacro>::transform(&::std::default::Default::default(), ctx, attr, node) } - ::inventory::submit! { + ::pmp_macro::inventory::submit! { MacroRegistration::new(&DESCRIPTOR, analyze, transform) } } diff --git a/crates/pmp-macro/crates/pmp-macro-misc/src/descriptor.rs b/crates/pmp-macro/crates/pmp-macro-misc/src/descriptor.rs index b84ae8c..cadcaeb 100644 --- a/crates/pmp-macro/crates/pmp-macro-misc/src/descriptor.rs +++ b/crates/pmp-macro/crates/pmp-macro-misc/src/descriptor.rs @@ -1,36 +1,26 @@ use crate::target::Target; -/// Describes a single constructor parameter of a PHP macro attribute. -/// -/// All fields are `&'static str` so the descriptor can be embedded as a -/// `static` and referenced from the inventory without allocation. #[derive(Debug, Clone, Copy)] pub struct ParamDescriptor { - /// The parameter name as it appears in the PHP constructor. pub name: &'static str, - /// A human-readable PHP type hint, e.g. `"class-string[]"` or `"bool"`. pub php_type: &'static str, - /// Whether the parameter must be supplied by the caller. pub required: bool, - /// The PHP source representation of the default value, e.g. `"[]"` or `"false"`. - /// `None` when `required` is `true`. pub default: Option<&'static str>, } -/// Static metadata for a registered PHP macro attribute. -/// -/// Generated by `#[derive(PmpMacro)]` and stored as a `&'static MacroDescriptor` -/// in the inventory. Entirely allocation-free. #[derive(Debug)] pub struct MacroDescriptor { - /// The PHP attribute name, e.g. `"Sealed"`. Used to match against - /// attribute usages in parsed PHP files. - pub name: &'static str, - /// Which PHP constructs this attribute is permitted to annotate. - pub target: Target, - /// Human-readable description of what this macro does. - /// Used for error messages and generated documentation. - pub docs: &'static str, - /// The constructor parameters this attribute accepts. - pub params: &'static [ParamDescriptor], + pub name: &'static str, + /// Raw bitflags value - use `.target()` to get a `Target`. + /// Stored as `u16` so the static initializer remains `const`. + pub target_bits: u16, + pub docs: &'static str, + pub params: &'static [ParamDescriptor], +} + +impl MacroDescriptor +{ + pub fn target(&self) -> Target { + Target::from_bits_truncate(self.target_bits) + } } diff --git a/crates/pmp-macro/crates/pmp-macro-misc/src/lib.rs b/crates/pmp-macro/crates/pmp-macro-misc/src/lib.rs index a6f9f6a..ef566a4 100644 --- a/crates/pmp-macro/crates/pmp-macro-misc/src/lib.rs +++ b/crates/pmp-macro/crates/pmp-macro-misc/src/lib.rs @@ -14,7 +14,10 @@ mod traits; mod context; mod descriptor; -pub use target::Target; -pub use descriptor::{ MacroDescriptor, ParamDescriptor }; -pub use traits::{ PmpMacro, MacroRegistry, MacroRegistration, }; -pub use context::{ Rewrite, ResolvedAttr, Diagnostic, MacroContext, AttributedNode }; +#[doc(hidden)] +pub use inventory; + +pub use target::*; +pub use traits::*; +pub use context::*; +pub use descriptor::*;