structdump_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote, quote_spanned};
3use syn::spanned::Spanned;
4use syn::{Data, Fields, Ident};
5
6#[proc_macro_derive(Codegen)]
7pub fn derive_codegen(input: TokenStream) -> TokenStream {
8	let ast = match syn::parse(input) {
9		Ok(v) => v,
10		Err(e) => return e.to_compile_error().into(),
11	};
12	impl_codegen(&ast)
13}
14fn fields_codegen_match(fields: &Fields) -> proc_macro2::TokenStream {
15	match fields {
16		Fields::Named(ref fields) => {
17			let f = fields.named.iter().map(|f| {
18				let name = &f.ident;
19				quote_spanned! {f.span()=>#name}
20			});
21			quote! {{#(#f, )*}}
22		}
23		Fields::Unnamed(ref fields) => {
24			let f = fields.unnamed.iter().enumerate().map(|(i, f)| {
25				let name = format_ident!("f{i}");
26				quote_spanned! {f.span()=>#name}
27			});
28			quote! {(#(#f, )*)}
29		}
30		Fields::Unit => {
31			quote! {}
32		}
33	}
34}
35fn fields_codegen_body(
36	name: &Ident,
37	variant: Option<&Ident>,
38	fields: &Fields,
39) -> proc_macro2::TokenStream {
40	let name = name.to_string();
41	let variant = variant
42		.map(|v| {
43			let v = v.to_string();
44			quote! {
45				Some(::structdump::format_ident!(#v))
46			}
47		})
48		.unwrap_or_else(|| quote! {None});
49	match fields {
50		Fields::Named(ref fields) => {
51			let f = fields.named.iter().map(|f| {
52				let name = f
53					.ident
54					.clone()
55					.expect("we're iterating over Fields::Named")
56					.to_string();
57				let ident = &f.ident;
58				quote! {
59					.field(res, ::structdump::format_ident!(#name), #ident)
60				}
61			});
62			quote! {<::structdump::StructBuilder<::structdump::Named>>::new(::structdump::format_ident!(#name), #variant, unique)#(#f)*.build(res)}
63		}
64		Fields::Unnamed(ref fields) => {
65			let f = fields.unnamed.iter().enumerate().map(|(i, _)| {
66				let ident = format_ident!("f{i}");
67				quote! {
68					.field(res, #ident)
69				}
70			});
71			quote! {<::structdump::StructBuilder<::structdump::Unnamed>>::new(::structdump::format_ident!(#name), #variant, unique)#(#f)*.build(res)}
72		}
73		Fields::Unit => {
74			quote! {<::structdump::StructBuilder<::structdump::Unit>>::new(::structdump::format_ident!(#name), #variant, unique).build()}
75		}
76	}
77}
78fn impl_codegen(ast: &syn::DeriveInput) -> TokenStream {
79	let name = &ast.ident;
80	let out = match &ast.data {
81		Data::Struct(ref data) => {
82			let head = fields_codegen_match(&data.fields);
83			let body = fields_codegen_body(name, None, &data.fields);
84			quote! {
85				let #name #head = &self;
86				#body
87			}
88		}
89		Data::Enum(data) => {
90			let variants = data.variants.iter().map(|v| {
91				let var_name = &v.ident;
92				let match_q = fields_codegen_match(&v.fields);
93				let match_b = fields_codegen_body(name, Some(var_name), &v.fields);
94				quote_spanned! {v.span()=>
95					#name::#var_name #match_q => {
96						#match_b
97					}
98				}
99			});
100			quote! {
101				match &self {
102					#(#variants ,)*
103				}
104			}
105		}
106		Data::Union(_) => unimplemented!(),
107	};
108	let gen = quote! {
109		impl ::structdump::Codegen for #name {
110			fn gen_code(&self, res: &mut ::structdump::CodegenResult, unique: bool) -> ::structdump::TokenStream {
111				#out
112			}
113		}
114	};
115	gen.into()
116}