Refactor macro handling: add pascal_to_snake utility, rewrite Target handling, and update descriptor structure for bits-based targets.

This commit is contained in:
2026-03-05 21:16:45 +01:00
parent 36b065ddb2
commit a238b63e51
5 changed files with 82 additions and 48 deletions

View File

@@ -15,6 +15,7 @@ authors = ["Dean J. Karstedt <dk@cl.team>"]
license-file = "LICENSE" license-file = "LICENSE"
[dependencies] [dependencies]
pmp-macro = { path = "crates/pmp-macro" }
pmp-emitter = { path = "crates/pmp-emitter" } pmp-emitter = { path = "crates/pmp-emitter" }
clap = { version = "^4", features = ["derive"] } clap = { version = "^4", features = ["derive"] }

View File

@@ -24,3 +24,26 @@ pub fn snake_to_title(s: &str) -> String
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" ") .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
}

View File

@@ -3,6 +3,22 @@ use proc_macro2::TokenStream as TokenStream2;
use crate::attrs::MacroInput; 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<TokenStream2> pub fn expand(parsed: MacroInput) -> syn::Result<TokenStream2>
{ {
let struct_ident = &parsed.ident; let struct_ident = &parsed.ident;
@@ -16,8 +32,8 @@ pub fn expand(parsed: MacroInput) -> syn::Result<TokenStream2>
.unwrap_or_else(|| struct_ident.to_string()); .unwrap_or_else(|| struct_ident.to_string());
let target_expr: syn::Expr = parsed.target let target_expr: syn::Expr = parsed.target
.map(|e| e.0) .map(|e| rewrite_bitor_target_expr(e.0))
.unwrap_or_else(|| syn::parse_quote! { ::pmp_macro_misc::Target::ALL }); .unwrap_or_else(|| syn::parse_quote! { ::pmp_macro::Target::ALL.bits() });
let docs: String = parsed.docs.map(|s| s.0).unwrap_or_default(); let docs: String = parsed.docs.map(|s| s.0).unwrap_or_default();
@@ -53,7 +69,7 @@ pub fn expand(parsed: MacroInput) -> syn::Result<TokenStream2>
}; };
param_tokens.push(quote_spanned! { span => param_tokens.push(quote_spanned! { span =>
::pmp_macro_misc::ParamDescriptor { ::pmp_macro::ParamDescriptor {
name: #field_name, name: #field_name,
php_type: #php_type, php_type: #php_type,
required: #required, required: #required,
@@ -64,7 +80,8 @@ pub fn expand(parsed: MacroInput) -> syn::Result<TokenStream2>
if let Some(e) = errors { return Err(e); } if let Some(e) = errors { return Err(e); }
let mod_ident = format_ident!("__pmp_macro_impl_{}", 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 }; let handler = quote! { super::#struct_ident };
Ok(quote! Ok(quote!
@@ -72,39 +89,39 @@ pub fn expand(parsed: MacroInput) -> syn::Result<TokenStream2>
#[doc(hidden)] #[doc(hidden)]
mod #mod_ident mod #mod_ident
{ {
use ::pmp_macro_misc::{ use ::pmp_macro::{
AnalyzeFn, MacroDescriptor, MacroRegistration, ParamDescriptor, AnalyzeFn, MacroDescriptor, MacroRegistration,
TransformFn, PmpMacro, ParamDescriptor, Target, TransformFn, PmpMacro,
}; };
static PARAMS: &[ParamDescriptor] = &[ #(#param_tokens),* ]; static PARAMS: &[ParamDescriptor] = &[ #(#param_tokens),* ];
static DESCRIPTOR: MacroDescriptor = MacroDescriptor { static DESCRIPTOR: MacroDescriptor = MacroDescriptor {
name: #attr_name, name: #attr_name,
target: #target_expr, target_bits: #target_expr,
docs: #docs, docs: #docs,
params: PARAMS, params: PARAMS,
}; };
fn analyze( fn analyze(
ctx: &::pmp_macro_misc::MacroContext, ctx: &::pmp_macro::MacroContext,
attr: &::pmp_macro_misc::ResolvedAttr, attr: &::pmp_macro::ResolvedAttr,
node: &::pmp_macro_misc::AttributedNode, node: &::pmp_macro::AttributedNode,
) -> ::std::vec::Vec<::pmp_macro_misc::Diagnostic> ) -> ::std::vec::Vec<::pmp_macro::Diagnostic>
{ {
<#handler as PmpMacro>::analyze(&::std::default::Default::default(), ctx, attr, node) <#handler as PmpMacro>::analyze(&::std::default::Default::default(), ctx, attr, node)
} }
fn transform( fn transform(
ctx: &::pmp_macro_misc::MacroContext, ctx: &::pmp_macro::MacroContext,
attr: &::pmp_macro_misc::ResolvedAttr, attr: &::pmp_macro::ResolvedAttr,
node: &::pmp_macro_misc::AttributedNode, node: &::pmp_macro::AttributedNode,
) -> ::std::vec::Vec<::pmp_macro_misc::Rewrite> ) -> ::std::vec::Vec<::pmp_macro::Rewrite>
{ {
<#handler as PmpMacro>::transform(&::std::default::Default::default(), ctx, attr, node) <#handler as PmpMacro>::transform(&::std::default::Default::default(), ctx, attr, node)
} }
::inventory::submit! { ::pmp_macro::inventory::submit! {
MacroRegistration::new(&DESCRIPTOR, analyze, transform) MacroRegistration::new(&DESCRIPTOR, analyze, transform)
} }
} }

View File

@@ -1,36 +1,26 @@
use crate::target::Target; 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)] #[derive(Debug, Clone, Copy)]
pub struct ParamDescriptor { pub struct ParamDescriptor {
/// The parameter name as it appears in the PHP constructor.
pub name: &'static str, pub name: &'static str,
/// A human-readable PHP type hint, e.g. `"class-string[]"` or `"bool"`.
pub php_type: &'static str, pub php_type: &'static str,
/// Whether the parameter must be supplied by the caller.
pub required: bool, 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>, 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)] #[derive(Debug)]
pub struct MacroDescriptor { pub struct MacroDescriptor {
/// The PHP attribute name, e.g. `"Sealed"`. Used to match against
/// attribute usages in parsed PHP files.
pub name: &'static str, pub name: &'static str,
/// Which PHP constructs this attribute is permitted to annotate. /// Raw bitflags value - use `.target()` to get a `Target`.
pub target: Target, /// Stored as `u16` so the static initializer remains `const`.
/// Human-readable description of what this macro does. pub target_bits: u16,
/// Used for error messages and generated documentation.
pub docs: &'static str, pub docs: &'static str,
/// The constructor parameters this attribute accepts.
pub params: &'static [ParamDescriptor], pub params: &'static [ParamDescriptor],
} }
impl MacroDescriptor
{
pub fn target(&self) -> Target {
Target::from_bits_truncate(self.target_bits)
}
}

View File

@@ -14,7 +14,10 @@ mod traits;
mod context; mod context;
mod descriptor; mod descriptor;
pub use target::Target; #[doc(hidden)]
pub use descriptor::{ MacroDescriptor, ParamDescriptor }; pub use inventory;
pub use traits::{ PmpMacro, MacroRegistry, MacroRegistration, };
pub use context::{ Rewrite, ResolvedAttr, Diagnostic, MacroContext, AttributedNode }; pub use target::*;
pub use traits::*;
pub use context::*;
pub use descriptor::*;