Replace simple_event_shunt macro with proc macro

This macro is pretty complicated and I needed to add the ability to
clean keywords, so it makes more sense to have this be a proc macro.
This commit is contained in:
Shawn Wallace 2024-10-15 02:17:20 -04:00
parent b988762955
commit 73ca9c91f1
6 changed files with 161 additions and 77 deletions

133
macros/src/lib.rs Normal file
View file

@ -0,0 +1,133 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
braced, bracketed, parse::Parse, parse_macro_input, parse_quote, punctuated::Punctuated, Token,
};
enum FieldOrClosure {
Field(syn::Ident),
Closure(syn::Ident, syn::Expr),
}
impl Parse for FieldOrClosure {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse().map(|ident| Self::Field(ident)).or_else(|_| {
input.parse().map(|mut closure: syn::ExprClosure| {
assert_eq!(closure.inputs.len(), 1);
let syn::Pat::Ident(arg) = closure.inputs.pop().unwrap().into_value() else {
panic!("expected ident for closure argument");
};
Self::Closure(arg.ident, *closure.body)
})
})
}
}
struct EventVariant {
name: syn::Ident,
fields: Option<Punctuated<FieldOrClosure, Token![,]>>,
}
impl Parse for EventVariant {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let name = input.parse()?;
let fields = if input.peek(syn::token::Brace) {
let f;
braced!(f in input);
let f = Punctuated::parse_terminated(&f)?;
Some(f)
} else {
None
};
Ok(Self { name, fields })
}
}
struct Input {
object: syn::Expr,
event_object: syn::Ident,
event_type: syn::Type,
events: Punctuated<EventVariant, Token![,]>,
}
impl Parse for Input {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let object = input.parse()?;
input.parse::<Token![,]>()?;
let event_object = input.parse()?;
input.parse::<Token![:]>()?;
let event_type = input.parse()?;
input.parse::<Token![=>]>()?;
let events;
bracketed!(events in input);
let events = Punctuated::parse_terminated(&events)?;
Ok(Self {
object,
event_object,
event_type,
events,
})
}
}
#[proc_macro]
pub fn simple_event_shunt(tokens: TokenStream) -> TokenStream {
let Input {
object,
event_object,
event_type,
events,
} = parse_macro_input!(tokens as Input);
let match_arms = events.into_iter().map(|e| {
let mut field_names = Punctuated::<_, Token![,]>::new();
let mut fn_args = Punctuated::<syn::Expr, Token![,]>::new();
if let Some(fields) = e.fields {
for field in fields {
match field {
FieldOrClosure::Field(name) => {
fn_args.push(parse_quote! { #name });
field_names.push(name);
}
FieldOrClosure::Closure(name, expr) => {
field_names.push(name);
fn_args.push(expr);
}
}
}
}
let name = e.name;
let fn_name = String::from_utf8(
name.to_string()
.bytes()
.enumerate()
.flat_map(|(idx, c)| {
if idx != 0 && c.is_ascii_uppercase() {
vec![b'_', c.to_ascii_lowercase()]
} else {
vec![c.to_ascii_lowercase()]
}
})
.collect::<Vec<_>>(),
)
.unwrap();
let keyword_pfx = if fn_name == "type" { "_" } else { "" };
let fn_name = format_ident!("{keyword_pfx}{fn_name}");
quote! {
#name { #field_names } => { #object.#fn_name(#fn_args); }
}
});
quote! {{
use #event_type::*;
match #event_object {
#(#match_arms)*
_ => log::warn!(concat!("unhandled ", stringify!(#event_type), ": {:?}"), #event_object)
}
}}
.into()
}