'polymorphic!' macro code cleanup

This commit is contained in:
xeon 2024-12-12 20:44:27 +03:00
parent 205fda45dc
commit ec9ddd4ef7
2 changed files with 165 additions and 145 deletions

View file

@ -1,150 +1,8 @@
use polymorphic::implement_polymorphic;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{
braced, bracketed,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
token, Ident, LitInt, Token, Type,
};
struct PolymorphicInput {
name: Ident,
_brace_token: token::Bracket,
common_fields: Punctuated<FieldEntry, Token![,]>,
structs: Punctuated<StructEntry, Token![,]>,
}
struct FieldEntry {
name: Ident,
_colon_token: Token![:],
ty: Type,
}
struct StructEntry {
name: Ident,
_brace_token: token::Brace,
fields: Punctuated<FieldEntry, Token![,]>,
_eq_token: Token![=],
discriminant: LitInt,
}
impl Parse for PolymorphicInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
Ok(Self {
name: input.parse()?,
_brace_token: bracketed!(content in input),
common_fields: content.parse_terminated(FieldEntry::parse, Token![,])?,
structs: input.parse_terminated(StructEntry::parse, Token![,])?,
})
}
}
impl Parse for FieldEntry {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
name: input.parse()?,
_colon_token: input.parse()?,
ty: input.parse()?,
})
}
}
impl Parse for StructEntry {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
Ok(Self {
name: input.parse()?,
_brace_token: braced!(content in input),
fields: content.parse_terminated(FieldEntry::parse, Token![,])?,
_eq_token: input.parse()?,
discriminant: input.parse()?,
})
}
}
mod polymorphic;
#[proc_macro]
pub fn polymorphic(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as PolymorphicInput);
let enum_name = &input.name;
let mut variants = quote!();
for stru in input.structs.iter() {
let variant_name = &stru.name;
let variant_discr = &stru.discriminant;
let mut fields = quote!();
for field in input.common_fields.iter().chain(stru.fields.iter()) {
let field_name = &field.name;
let field_type = &field.ty;
fields.extend(quote! {
#field_name: #field_type,
});
}
variants.extend(quote! {
#variant_name {
#fields
} = #variant_discr,
});
}
let mut getters_and_setters = quote!();
for field in input.common_fields.iter() {
let field_type = &field.ty;
let field_name = &field.name;
let getter_name = Ident::new(&format!("get_{}", field_name), Span::call_site());
let setter_name = Ident::new(&format!("set_{}", field_name), Span::call_site());
let mut getter_match_arms = quote!();
let mut setter_match_arms = quote!();
for stru in input.structs.iter() {
let stru_name = &stru.name;
getter_match_arms.extend(quote! {
Self::#stru_name { #field_name, .. } => #field_name,
});
setter_match_arms.extend(quote! {
Self::#stru_name { #field_name, .. } => { *#field_name = value; },
});
}
getters_and_setters.extend(quote! {
pub fn #getter_name(&self) -> &#field_type {
match self {
#getter_match_arms
}
}
pub fn #setter_name(&mut self, value: #field_type) {
match self {
#setter_match_arms
}
}
});
}
// can't use base_fields_count directly in 'quote!'
// 'quote!' puts suffixed literal, but it's not allowed in attributes
let base_fields_count = input.common_fields.len();
let base_attr = format!("#[base = {base_fields_count}]")
.parse::<proc_macro2::TokenStream>()
.unwrap();
quote! {
#[derive(Debug, Clone, ::qwer::OctData)]
#[repr(u32)]
#base_attr
pub enum #enum_name {
#variants
}
impl #enum_name {
#getters_and_setters
}
}
.into()
implement_polymorphic(input)
}

View file

@ -0,0 +1,162 @@
use proc_macro::TokenStream;
use proc_macro2::{Group, Literal, Punct, Span, TokenTree};
use quote::quote;
use syn::{
braced, bracketed,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
token, Ident, LitInt, Token, Type,
};
struct PolymorphicInput {
name: Ident,
_brace_token: token::Bracket,
common_fields: Punctuated<FieldEntry, Token![,]>,
structs: Punctuated<StructEntry, Token![,]>,
}
struct FieldEntry {
name: Ident,
_colon_token: Token![:],
ty: Type,
}
struct StructEntry {
name: Ident,
_brace_token: token::Brace,
fields: Punctuated<FieldEntry, Token![,]>,
_eq_token: Token![=],
discriminant: LitInt,
}
impl Parse for PolymorphicInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
Ok(Self {
name: input.parse()?,
_brace_token: bracketed!(content in input),
common_fields: content.parse_terminated(FieldEntry::parse, Token![,])?,
structs: input.parse_terminated(StructEntry::parse, Token![,])?,
})
}
}
impl Parse for FieldEntry {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
name: input.parse()?,
_colon_token: input.parse()?,
ty: input.parse()?,
})
}
}
impl Parse for StructEntry {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
Ok(Self {
name: input.parse()?,
_brace_token: braced!(content in input),
fields: content.parse_terminated(FieldEntry::parse, Token![,])?,
_eq_token: input.parse()?,
discriminant: input.parse()?,
})
}
}
pub fn implement_polymorphic(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as PolymorphicInput);
let enum_name = &input.name;
let mut variants = quote!();
for stru in input.structs.iter() {
let variant_name = &stru.name;
let variant_discr = &stru.discriminant;
let mut fields = quote!();
for field in input.common_fields.iter().chain(stru.fields.iter()) {
let field_name = &field.name;
let field_type = &field.ty;
fields.extend(quote! {
#field_name: #field_type,
});
}
variants.extend(quote! {
#variant_name {
#fields
} = #variant_discr,
});
}
let mut getters_and_setters = quote!();
for field in input.common_fields.iter() {
let field_type = &field.ty;
let field_name = &field.name;
let getter_name = Ident::new(&format!("get_{}", field_name), Span::call_site());
let setter_name = Ident::new(&format!("set_{}", field_name), Span::call_site());
let mut getter_match_arms = quote!();
let mut setter_match_arms = quote!();
for stru in input.structs.iter() {
let stru_name = &stru.name;
getter_match_arms.extend(quote! {
Self::#stru_name { #field_name, .. } => #field_name,
});
setter_match_arms.extend(quote! {
Self::#stru_name { #field_name, .. } => { *#field_name = value; },
});
}
getters_and_setters.extend(quote! {
pub fn #getter_name(&self) -> &#field_type {
match self {
#getter_match_arms
}
}
pub fn #setter_name(&mut self, value: #field_type) {
match self {
#setter_match_arms
}
}
});
}
// can't use base_fields_count directly in 'quote!'
// 'quote!' puts suffixed literal, but it's not allowed in attributes
let base_fields_count = input.common_fields.len();
let base_attr = [
TokenTree::Punct(Punct::new('#', proc_macro2::Spacing::Joint)),
TokenTree::Group(Group::new(
proc_macro2::Delimiter::Bracket,
[
TokenTree::Ident(Ident::new("base", Span::call_site())),
TokenTree::Punct(Punct::new('=', proc_macro2::Spacing::Alone)),
TokenTree::Literal(Literal::usize_unsuffixed(base_fields_count)),
]
.into_iter()
.collect::<proc_macro2::TokenStream>(),
)),
]
.into_iter()
.collect::<proc_macro2::TokenStream>();
quote! {
#[derive(Debug, Clone, ::qwer::OctData)]
#[repr(u32)]
#base_attr
pub enum #enum_name {
#variants
}
impl #enum_name {
#getters_and_setters
}
}
.into()
}