lang_util_derive/
node_display.rs

1use darling::{FromDeriveInput, FromField, FromVariant};
2use proc_macro2::TokenStream;
3use quote::{format_ident, quote, quote_spanned};
4use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput};
5
6#[derive(Default, FromMeta)]
7struct DisplayFieldOpts {
8    /// Skip formatting this field or variant
9    #[darling(default)]
10    skip: bool,
11    /// Use this field when formatting extra data
12    #[darling(default)]
13    extra: bool,
14}
15
16#[derive(Default, FromMeta)]
17struct DisplayVariantOpts {
18    /// Value to use as extra format instead of the variant name
19    #[darling(default)]
20    extra: Option<String>,
21}
22
23#[derive(FromField)]
24#[darling(attributes(lang_util))]
25struct NodeDisplayField {
26    ident: Option<syn::Ident>,
27    #[darling(default)]
28    display: DisplayFieldOpts,
29}
30
31#[derive(FromVariant)]
32#[darling(attributes(lang_util))]
33struct NodeDisplayVariant {
34    ident: syn::Ident,
35    fields: darling::ast::Fields<NodeDisplayField>,
36    #[darling(default)]
37    display: DisplayVariantOpts,
38}
39
40fn is_unit_enum(en: &syn::DataEnum) -> bool {
41    en.variants.iter().all(|variant| {
42        NodeDisplayVariant::from_variant(variant)
43            .map(|dv| dv.fields.style == darling::ast::Style::Unit)
44            .unwrap_or(false)
45    })
46}
47
48#[derive(Default, FromMeta)]
49#[darling(default)]
50struct NodeDisplay {
51    leaf: bool,
52}
53
54#[derive(FromDeriveInput)]
55#[darling(attributes(lang_util))]
56pub(crate) struct NodeDisplayOpts {
57    ident: syn::Ident,
58    generics: syn::Generics,
59    #[darling(default)]
60    display: NodeDisplay,
61}
62
63pub(crate) fn node_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
64    // Parse the input tokens into a syntax tree
65    let input = parse_macro_input!(input as DeriveInput);
66
67    // Find out struct-level options
68    let opts = NodeDisplayOpts::from_derive_input(&input).expect("failed to parse options");
69
70    // Add anonymous lifetimes as needed
71    let lifetimes: Vec<_> = opts.generics.lifetimes().map(|_| quote! { '_ }).collect();
72
73    // Generate the name of the target for usage in impl targets
74    let base_ident = &opts.ident;
75    let struct_name = if lifetimes.is_empty() {
76        quote! { #base_ident }
77    } else {
78        quote! { #base_ident<#(#lifetimes),*> }
79    };
80
81    // Is this a "Data" node?
82    let raw_name = base_ident
83        .to_string()
84        .strip_suffix("Data")
85        .map(|id| format_ident!("{}", id));
86
87    // The node name for the NodeDisplay impl
88    let node_name = raw_name.unwrap_or_else(|| base_ident.clone()).to_string();
89
90    let display_quoted = {
91        let display_extra_impl = {
92            let mut ts = TokenStream::new();
93
94            // TODO: Support `extra` on variant struct fields
95            match &input.data {
96                Data::Struct(st) => {
97                    for (i, field) in st.fields.iter().enumerate() {
98                        let df = NodeDisplayField::from_field(field)
99                            .expect("failed to parse field attributes");
100
101                        let ident = if let Some(id) = &df.ident {
102                            quote! { #id }
103                        } else {
104                            let i = syn::Index::from(i);
105                            quote! { #i }
106                        };
107
108                        if df.display.extra {
109                            ts.extend(quote_spanned! {
110                                field.span() =>
111                                    write!(f, " `{}`", self.#ident)?;
112                            });
113                        }
114                    }
115                }
116                Data::Enum(en) => {
117                    let mut match_body = TokenStream::new();
118
119                    // Are the variants all units?
120                    for variant in &en.variants {
121                        let dv = NodeDisplayVariant::from_variant(variant)
122                            .expect("failed to parse variant attributes");
123
124                        let name = &dv.ident;
125
126                        // Find out how to display the enum variant
127                        let vs = dv
128                            .display
129                            .extra
130                            .clone()
131                            .unwrap_or_else(|| dv.ident.to_string());
132
133                        // Fields pattern
134                        let fields = match dv.fields.style {
135                            darling::ast::Style::Unit => None,
136                            darling::ast::Style::Tuple => {
137                                let mut v = Vec::with_capacity(dv.fields.fields.len());
138                                let ident = format_ident!("_");
139                                for _ in 0..dv.fields.fields.len() {
140                                    v.push(ident.clone());
141                                }
142                                Some(quote! { (#(#v),*) })
143                            }
144                            darling::ast::Style::Struct => Some(quote! { { .. } }),
145                        };
146
147                        let quoted = quote_spanned! {
148                            variant.span() =>
149                                Self::#name #fields=> {
150                                    write!(f, " `{}`", #vs)?;
151                                }
152                        };
153
154                        match_body.extend(quoted);
155                    }
156
157                    ts.extend(quote_spanned! {
158                        input.span() => match self {
159                            #match_body
160                        };
161                    });
162                }
163                Data::Union(_) => ts.extend(quote_spanned! {
164                    input.span() =>
165                        compile_error!("Unions are not supported");
166                }),
167            };
168
169            ts.extend(quote! { Ok(()) });
170            ts
171        };
172
173        let display_children_impl = if opts.display.leaf {
174            quote! { Ok(()) }
175        } else {
176            let mut ts = TokenStream::new();
177            ts.extend(quote! { use ::lang_util::node::NodeDisplay; });
178
179            match &input.data {
180                Data::Struct(st) => {
181                    for (i, field) in st.fields.iter().enumerate() {
182                        let df = NodeDisplayField::from_field(field)
183                            .expect("failed to parse field attributes");
184
185                        let ident = if let Some(id) = &df.ident {
186                            quote! { #id }
187                        } else {
188                            let i = syn::Index::from(i);
189                            quote! { #i }
190                        };
191
192                        ts.extend(quote_spanned! {
193                            field.span() =>
194                                write!(f, "{}", self.#ident.display().set_level(level))?;
195                        });
196                    }
197                }
198                Data::Enum(en) => {
199                    let mut match_body = TokenStream::new();
200
201                    if !is_unit_enum(en) {
202                        for variant in &en.variants {
203                            let dv = NodeDisplayVariant::from_variant(variant)
204                                .expect("failed to parse variant attributes");
205
206                            let name = &dv.ident;
207
208                            if let darling::ast::Style::Unit = dv.fields.style {
209                                let vs = dv.ident.to_string();
210                                let quoted = quote_spanned! {
211                                    variant.span() =>
212                                        Self::#name => {
213                                            write!(f, "{}", ::lang_util::node::NodeDisplayWrapper::new(#vs, level))?;
214                                        }
215                                };
216
217                                match_body.extend(quoted);
218                            } else if let darling::ast::Style::Tuple = dv.fields.style {
219                                let mut variant_body = TokenStream::new();
220                                let mut field_names = Vec::new();
221
222                                for (i, field) in dv.fields.fields.iter().enumerate() {
223                                    // Add tuple identifier
224                                    let ff = if field.display.skip {
225                                        format_ident!("_")
226                                    } else {
227                                        format_ident!("tuple{}", i)
228                                    };
229
230                                    field_names.push(ff.clone());
231
232                                    // Add code
233                                    if !field.display.skip {
234                                        variant_body.extend(quote! {
235                                            write!(f, "{}", #ff.display().set_level(level))?;
236                                        });
237                                    }
238                                }
239
240                                match_body.extend(quote_spanned! {
241                                    variant.span() =>
242                                        Self::#name(#(#field_names),*) => {
243                                            #variant_body
244                                        }
245                                });
246                            } else {
247                                let mut variant_body = TokenStream::new();
248                                let mut field_names = Vec::new();
249
250                                for field in &dv.fields.fields {
251                                    // Add tuple identifier
252                                    let ff = field.ident.as_ref().unwrap();
253                                    field_names.push(ff.clone());
254
255                                    // Add code
256                                    if !field.display.skip {
257                                        variant_body.extend(quote! {
258                                            write!(f, "{}", #ff.display().set_level(level))?;
259                                        });
260                                    }
261                                }
262
263                                match_body.extend(quote_spanned! {
264                                    variant.span() =>
265                                        Self::#name { #(#field_names),* } => {
266                                            #variant_body
267                                        }
268                                });
269                            }
270                        }
271
272                        ts.extend(quote_spanned! {
273                            input.span() => match self {
274                                #match_body
275                            };
276                        });
277                    }
278                }
279                Data::Union(_) => ts.extend(quote_spanned! {
280                    input.span() =>
281                        compile_error!("Unions are not supported");
282                }),
283            };
284
285            ts.extend(quote! { Ok(()) });
286            ts
287        };
288
289        quote! {
290            #[automatically_derived]
291            impl ::lang_util::node::NodeContentDisplay for #struct_name {
292                fn name() -> Option<&'static str> {
293                   Some(#node_name)
294                }
295
296                fn display_extra(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
297                    #display_extra_impl
298                }
299
300                fn display_children(&self, level: usize, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
301                    #display_children_impl
302                }
303            }
304        }
305    };
306
307    proc_macro::TokenStream::from(display_quoted)
308}