lang_util_derive/
node_display.rs1use 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 #[darling(default)]
10 skip: bool,
11 #[darling(default)]
13 extra: bool,
14}
15
16#[derive(Default, FromMeta)]
17struct DisplayVariantOpts {
18 #[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 let input = parse_macro_input!(input as DeriveInput);
66
67 let opts = NodeDisplayOpts::from_derive_input(&input).expect("failed to parse options");
69
70 let lifetimes: Vec<_> = opts.generics.lifetimes().map(|_| quote! { '_ }).collect();
72
73 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 let raw_name = base_ident
83 .to_string()
84 .strip_suffix("Data")
85 .map(|id| format_ident!("{}", id));
86
87 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 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 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 let vs = dv
128 .display
129 .extra
130 .clone()
131 .unwrap_or_else(|| dv.ident.to_string());
132
133 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 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 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 let ff = field.ident.as_ref().unwrap();
253 field_names.push(ff.clone());
254
255 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}