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}