Skip to main content

yuuka_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use std::{cell::RefCell, rc::Rc};
4use syn::{parse_macro_input, Ident};
5
6mod template;
7mod tools;
8mod utils;
9
10use template::{
11    generate_enums_auto_macros, generate_enums_quote, generate_structs_auto_macros,
12    generate_structs_quote,
13};
14use tools::{
15    auto_macros::AutoMacrosType, AutoMacros, DeriveBox, DeriveEnum, DeriveStruct, DeriveVisibility,
16    StructName,
17};
18use utils::flatten;
19
20/// Generate nested structs from a concise DSL-like syntax.
21///
22/// Supports inline struct definitions, arrays, enums, Option-wrapped fields,
23/// custom derives, visibility control, and default values. See the crate-level
24/// documentation for full syntax and examples.
25#[proc_macro]
26pub fn derive_struct(input: TokenStream) -> TokenStream {
27    let input = parse_macro_input!(input as DeriveStruct);
28
29    let is_public = input.visibility == DeriveVisibility::Public;
30    let macro_visibility = input.extra_macros.macros_visibility;
31    let root_ident = match input.ident.clone() {
32        StructName::Named(v) => v,
33        StructName::Unnamed(_) => {
34            return syn::Error::new(
35                proc_macro2::Span::call_site(),
36                "unnamed root struct is not supported; provide a name for the struct",
37            )
38            .to_compile_error()
39            .into();
40        }
41    };
42    let mod_ident = syn::Ident::new(&format!("__{}", root_ident), root_ident.span());
43    let (structs, enums) = match flatten(
44        root_ident.to_string(),
45        Rc::new(RefCell::new(0)),
46        DeriveBox::Struct(Box::new(input.clone())),
47    ) {
48        Ok(v) => v,
49        Err(e) => {
50            return syn::Error::new(root_ident.span(), e.to_string())
51                .to_compile_error()
52                .into();
53        }
54    };
55
56    let structs_auto_macros = generate_structs_auto_macros(structs.clone(), macro_visibility);
57    let enums_auto_macros = generate_enums_auto_macros(enums.clone(), macro_visibility);
58
59    let structs = generate_structs_quote(structs);
60    let enums = generate_enums_quote(enums);
61
62    let ret = if is_public {
63        quote! {
64            #[macro_use]
65            #[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)]
66            pub mod #mod_ident {
67                use super::*;
68
69                #( #structs )*
70                #( #enums )*
71
72                #( #structs_auto_macros )*
73                #( #enums_auto_macros )*
74            }
75
76            pub use #mod_ident::*;
77        }
78    } else {
79        quote! {
80            #[macro_use]
81            #[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)]
82            pub(crate) mod #mod_ident {
83                use super::*;
84
85                #( #structs )*
86                #( #enums )*
87
88                #( #structs_auto_macros )*
89                #( #enums_auto_macros )*
90            }
91
92            pub(crate) use #mod_ident::*;
93        }
94    };
95
96    ret.into()
97}
98
99/// Generate enums (and associated structs) from a concise DSL-like syntax.
100///
101/// Supports unit variants, tuple variants, and struct variants with inline
102/// member definitions. Default values, custom derives, and visibility are
103/// controlled via the same syntax as `derive_struct`.
104#[proc_macro]
105pub fn derive_enum(input: TokenStream) -> TokenStream {
106    let input = parse_macro_input!(input as DeriveEnum);
107
108    let is_public = input.visibility == DeriveVisibility::Public;
109    let macro_visibility = input.extra_macros.macros_visibility;
110    let root_ident = match input.ident.clone() {
111        StructName::Named(v) => v,
112        StructName::Unnamed(_) => {
113            return syn::Error::new(
114                proc_macro2::Span::call_site(),
115                "unnamed root enum is not supported; provide a name for the enum",
116            )
117            .to_compile_error()
118            .into();
119        }
120    };
121    let mod_ident = syn::Ident::new(&format!("__{}", root_ident), root_ident.span());
122    let (structs, enums) = match flatten(
123        root_ident.to_string(),
124        Rc::new(RefCell::new(0)),
125        DeriveBox::Enum(Box::new(input.clone())),
126    ) {
127        Ok(v) => v,
128        Err(e) => {
129            return syn::Error::new(root_ident.span(), e.to_string())
130                .to_compile_error()
131                .into();
132        }
133    };
134
135    let structs_auto_macros = generate_structs_auto_macros(structs.clone(), macro_visibility);
136    let enums_auto_macros = generate_enums_auto_macros(enums.clone(), macro_visibility);
137
138    let structs = generate_structs_quote(structs);
139    let enums = generate_enums_quote(enums);
140
141    let ret = if is_public {
142        quote! {
143            #[macro_use]
144            #[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)]
145            pub mod #mod_ident {
146                use super::*;
147
148                #( #structs )*
149                #( #enums )*
150
151                #( #structs_auto_macros )*
152                #( #enums_auto_macros )*
153            }
154
155            pub use #mod_ident::*;
156        }
157    } else {
158        quote! {
159            #[macro_use]
160            #[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)]
161            pub(crate) mod #mod_ident {
162                use super::*;
163
164                #( #structs )*
165                #( #enums )*
166
167                #( #structs_auto_macros )*
168                #( #enums_auto_macros )*
169            }
170
171            pub(crate) use #mod_ident::*;
172        }
173    };
174
175    ret.into()
176}
177
178/// Construct an instance of a type generated by `derive_struct` or
179/// `derive_enum` using a minimal value-only syntax.
180///
181/// Each field value is automatically routed to the correct generated
182/// `__auto_*` helper macro so that nested inline types are constructed
183/// transparently.
184#[proc_macro]
185pub fn auto(input: TokenStream) -> TokenStream {
186    let input = parse_macro_input!(input as AutoMacros);
187    let ident = input.ident.clone();
188    let body = input.body;
189
190    let macro_ident = Ident::new(&format!("__auto_{}", input.ident), input.ident.span());
191    match body {
192        AutoMacrosType::Struct {
193            items,
194            expand_exprs,
195        } => {
196            let list = items
197                .iter()
198                .map(|(key, value)| {
199                    quote! {
200                        #key: #macro_ident!(#key #value)
201                    }
202                })
203                .collect::<Vec<_>>();
204
205            if let Some(expand_exprs) = expand_exprs {
206                quote! {
207                    #ident {
208                        #( #list ),*,
209                        ..#expand_exprs
210                    }
211                }
212                .into()
213            } else {
214                quote! {
215                    #ident {
216                        #( #list ),*
217                    }
218                }
219                .into()
220            }
221        }
222
223        AutoMacrosType::EnumEmpty { key } => quote! {
224            #ident::#key
225        }
226        .into(),
227        AutoMacrosType::EnumStruct {
228            key,
229            items,
230            expand_exprs,
231        } => {
232            let list = items
233                .iter()
234                .map(|(item_key, value)| {
235                    quote! {
236                        #item_key: #macro_ident!(#key #item_key #value)
237                    }
238                })
239                .collect::<Vec<_>>();
240
241            if let Some(expand_exprs) = expand_exprs {
242                quote! {
243                    #ident::#key {
244                        #( #list ),*,
245                        ..#expand_exprs
246                    }
247                }
248                .into()
249            } else {
250                quote! {
251                    #ident::#key {
252                        #( #list ),*
253                    }
254                }
255                .into()
256            }
257        }
258        AutoMacrosType::EnumTuple { key, items } => {
259            if let [first_item] = items.as_slice() {
260                quote! {
261                    #ident::#key(#macro_ident!(#key #first_item))
262                }
263                .into()
264            } else {
265                let list = items
266                    .iter()
267                    .enumerate()
268                    .map(|(index, item)| {
269                        quote! {
270                            #macro_ident!(#key #index #item)
271                        }
272                    })
273                    .collect::<Vec<_>>();
274
275                quote! {
276                    #ident::#key(#( #list ),*)
277                }
278                .into()
279            }
280        }
281        AutoMacrosType::EnumSinglePath { key, next_key } => quote! {
282            #ident::#key(#macro_ident!(#key 0 #next_key))
283        }
284        .into(),
285
286        AutoMacrosType::Value { items } => {
287            if let [first_item] = items.as_slice() {
288                quote! {
289                    #first_item
290                }
291                .into()
292            } else {
293                let list = items
294                    .iter()
295                    .enumerate()
296                    .map(|(index, item)| {
297                        quote! {
298                            #macro_ident!(#index #item)
299                        }
300                    })
301                    .collect::<Vec<_>>();
302
303                quote! {
304                    (#( #list ),*)
305                }
306                .into()
307            }
308        }
309    }
310}