cmake_parser_derive/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use proc_macro::TokenStream;
4use proc_macro_error::{abort, proc_macro_error};
5use quote::{format_ident, quote, quote_spanned};
6use syn::{
7    punctuated::Punctuated, DataEnum, DeriveInput, Expr, ExprArray, ExprLit, Lit, Meta,
8    MetaNameValue, Token,
9};
10
11/// A derive macros for parsing CMake tokens to Rust structures and enums.
12///
13/// Requires dependency to `cmake-parser` crate.
14#[proc_macro_derive(CMake, attributes(cmake))]
15#[proc_macro_error]
16pub fn cmake_derive(input: TokenStream) -> TokenStream {
17    let ast: DeriveInput = syn::parse(input).unwrap();
18
19    let cmake_attr = cmake_attribute(&ast.attrs).unwrap_or_default();
20    let cmake_parse_path = if let Some(crate_path) = cmake_attr.pkg.as_ref() {
21        quote! { #crate_path }
22    } else {
23        quote! { ::cmake_parser }
24    };
25
26    let positional = cmake_attr.positional;
27    let cmake_impl = CMakeImpl::new(ast, cmake_parse_path, cmake_attr);
28    let trait_cmake_parse = if positional {
29        cmake_impl.trait_cmake_parse_positional()
30    } else {
31        cmake_impl.trait_cmake_parse_regular()
32    };
33
34    let trait_cmake_positional = cmake_impl.trait_cmake_positional_regular();
35
36    quote! {
37        #trait_cmake_parse
38        #trait_cmake_positional
39    }
40    .into()
41}
42
43fn enum_fields(variants: &[CMakeEnum]) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
44    variants.iter().map(
45        |CMakeEnum {
46             option: CMakeOption {
47                 ident, lit_bstr, ..
48             },
49             ..
50         }| {
51            quote_spanned! { ident.span() => #lit_bstr }
52        },
53    )
54}
55
56fn positional_var_defs(
57    fields: &[CMakeOption],
58    has_keyword: bool,
59) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
60    fields.iter().enumerate().map(
61        move |(index, CMakeOption {
62             ident, lit_bstr, attr: CMakeAttribute { transparent , keyword_after, in_range, last, allow_empty, ..}, ..
63         })| {
64            let def_mut = if index == fields.len() - 1 {
65                quote! { mut }
66            } else {
67                quote! {}
68            };
69            let has_keyword = has_keyword || *transparent;
70            let tokens = if *last {
71                quote! { last }
72            } else {
73                quote! { tokens }
74            };
75            let keyword_after = keyword_after.as_ref().map(|bstr| { quote! { ; let (_, #def_mut #tokens) = Keyword::positional(#bstr, #tokens, false)? } });
76            if *in_range && index != fields.len() - 1 {
77                let allow_empty = *allow_empty;
78                let range_to_keyword = &fields[index + 1].lit_bstr;
79                quote_spanned! { ident.span() => let (#ident, #def_mut #tokens) = CMakePositional::in_range(#lit_bstr, #range_to_keyword, #allow_empty, #tokens, #has_keyword)? #keyword_after }
80            } else {
81                quote_spanned! { ident.span() => let (#ident, #def_mut #tokens) = CMakePositional::positional(#lit_bstr, #tokens, #has_keyword)? #keyword_after }
82            }
83        },
84    )
85}
86
87fn positional_fields(
88    fields: &[CMakeOption],
89) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
90    fields.iter().map(|CMakeOption { ident, .. }| {
91        quote_spanned! { ident.span() => #ident }
92    })
93}
94
95fn regular_var_defs(fields: &[CMakeOption]) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
96    fields.iter().map(|CMakeOption { ident, .. }| {
97        quote_spanned! { ident.span() => let mut #ident = CMakeParse::default_value() }
98    })
99}
100
101fn regular_enum_defs(
102    fields: &[CMakeOption],
103) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
104    fields.iter().map(
105        |CMakeOption {
106             ident, ident_mode, ..
107         }| {
108            quote_spanned! { ident.span() => #ident_mode }
109        },
110    )
111}
112
113fn regular_enum_match(
114    fields: &[CMakeOption],
115) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
116    fields.iter().map(
117        |CMakeOption {
118             ident, ident_mode, ..
119         }| {
120            quote_spanned! { ident.span() => CMakeParserMode::#ident_mode => #ident.push_keyword(&mut buffers.#ident, first) }
121        },
122    )
123}
124
125fn regular_fields(fields: &[CMakeOption]) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
126    fields.iter().map(|CMakeOption { ident, lit_str, .. }| {
127        quote_spanned! { ident.span() => #ident: #ident.end(&buffers.#ident)?.ok_or_else(|| CommandParseError::MissingToken(#lit_str.to_string()))? }
128    })
129}
130
131fn regular_match_fields(
132    fields: &[CMakeOption],
133) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
134    fields.iter().map(
135        |CMakeOption {
136             ident, lit_bstr, ty, ..
137         }| {
138            quote_spanned! { ident.span() => <#ty as CMakeParse>::matches_type(#lit_bstr, keyword, tokens) }
139        },
140    )
141}
142
143fn regular_match_fields_need_update(
144    fields: &[CMakeOption],
145) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
146    fields.iter().map(
147        |CMakeOption {
148             ident, lit_bstr, ty, ..
149         }| {
150            quote_spanned! { ident.span() => <#ty as CMakeParse>::matches_type(#lit_bstr, keyword_bytes, &[]) && buffer.iter().any(|token| <#ty as CMakeParse>::matches_type(#lit_bstr, token.as_bytes(), &[])) }
151        },
152    )
153}
154
155fn regular_buf_fields(
156    fields: &[CMakeOption],
157) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
158    fields.iter().map(|CMakeOption { ident, .. }| {
159        quote_spanned! { ident.span() => #ident: Vec<Token<'b>> }
160    })
161}
162
163fn regular_if_stms(
164    fields: &[CMakeOption],
165    mode_default: proc_macro2::TokenStream,
166) -> impl Iterator<Item = proc_macro2::TokenStream> + '_ {
167    fields.iter().map(
168        move |CMakeOption {
169                  ident,
170                  ident_mode,
171                  lit_bstr,
172                  ..
173              }| {
174            quote_spanned! { ident.span() => if #ident.matches(#lit_bstr, keyword, tokens) {
175                let (update_mode, rest) = #ident.start(#lit_bstr, first, tokens, &mut buffers.#ident)?;
176                tokens = rest;
177                current_mode = if update_mode {
178                    Some(CMakeParserMode::#ident_mode)
179                } else {
180                    #[allow(clippy::no_effect)]
181                    ();
182                    #mode_default
183                };
184            } else }
185        },
186    )
187}
188
189enum CMakeFields {
190    StructNamedFields(Vec<CMakeOption>),
191    EnumVariants(Vec<CMakeEnum>),
192    Unit,
193}
194struct CMakeOption {
195    attr: CMakeAttribute,
196    ident: syn::Ident,
197    ident_mode: syn::Ident,
198    lit_str: proc_macro2::Literal,
199    lit_bstr: proc_macro2::Literal,
200    ty: Option<syn::Type>,
201}
202
203impl CMakeOption {
204    fn from_fields_named(fields_named: &syn::FieldsNamed) -> Vec<Self> {
205        fields_named
206            .named
207            .iter()
208            .filter_map(|f| {
209                f.ident.as_ref().map(|ident| {
210                    (
211                        ident.clone(),
212                        f.ty.clone(),
213                        cmake_attribute(&f.attrs).unwrap_or_default(),
214                    )
215                })
216            })
217            .map(|(ident, ty, attr)| {
218                let id = ident.to_string();
219                use inflections::Inflect;
220                let ident_mode = quote::format_ident!("{}", id.to_pascal_case());
221                let cmake_keyword = attr.rename.clone().unwrap_or_else(|| id.to_uppercase());
222                let lit_str = proc_macro2::Literal::string(&cmake_keyword);
223                let lit_bstr = proc_macro2::Literal::byte_string(cmake_keyword.as_bytes());
224                CMakeOption {
225                    attr,
226                    ident,
227                    ident_mode,
228                    lit_str,
229                    lit_bstr,
230                    ty: Some(ty),
231                }
232            })
233            .collect()
234    }
235}
236
237struct StrBStr {
238    lit_bstr: proc_macro2::Literal,
239}
240
241struct CMakeEnum {
242    option: CMakeOption,
243    renames: Option<Vec<StrBStr>>,
244    unnamed: bool,
245}
246
247impl CMakeEnum {
248    fn from_variants<'a>(variants: impl IntoIterator<Item = &'a syn::Variant>) -> Vec<Self> {
249        variants
250            .into_iter()
251            .map(|f| {
252                (
253                    f.ident.clone(),
254                    cmake_attribute(&f.attrs).unwrap_or_default(),
255                    match &f.fields {
256                        syn::Fields::Unit => false,
257                        syn::Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => true,
258                        _ => abort!(
259                            f,
260                            "only unit enums and unnamed enums with one field supported"
261                        ),
262                    },
263                )
264            })
265            .map(|(ident, attr, unnamed)| {
266                let id = ident.to_string();
267                use inflections::Inflect;
268                let ident_mode = quote::format_ident!("{}", id.to_pascal_case());
269                let cmake_keyword = attr.rename.clone().unwrap_or_else(|| id.to_constant_case());
270                let lit_str = proc_macro2::Literal::string(&cmake_keyword);
271                let lit_bstr = proc_macro2::Literal::byte_string(cmake_keyword.as_bytes());
272                CMakeEnum {
273                    renames: attr.renames.as_deref().map(|keywords| {
274                        keywords
275                            .iter()
276                            .map(|keyword| StrBStr {
277                                lit_bstr: proc_macro2::Literal::byte_string(keyword.as_bytes()),
278                            })
279                            .collect()
280                    }),
281                    option: CMakeOption {
282                        attr,
283                        ident,
284                        ident_mode,
285                        lit_str,
286                        lit_bstr,
287                        ty: None,
288                    },
289                    unnamed,
290                }
291            })
292            .collect()
293    }
294}
295
296struct CMakeImpl {
297    ast: syn::DeriveInput,
298    crate_path: proc_macro2::TokenStream,
299    cmake_attr: CMakeAttribute,
300}
301
302impl CMakeImpl {
303    fn new(
304        ast: syn::DeriveInput,
305        crate_path: proc_macro2::TokenStream,
306        cmake_attr: CMakeAttribute,
307    ) -> Self {
308        Self {
309            ast,
310            crate_path,
311            cmake_attr,
312        }
313    }
314
315    fn trait_cmake_parse(&self, content: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
316        let Self {
317            ast, crate_path, ..
318        } = self;
319
320        let name = &ast.ident;
321        let generics = &ast.generics;
322        let type_params = generics.type_params();
323        let (_, ty_generics, where_clause) = generics.split_for_impl();
324
325        quote! {
326            #[automatically_derived]
327            impl <'t #(, #type_params)*> #crate_path::CMakeParse<'t> for #name #ty_generics #where_clause {
328                #content
329            }
330        }
331    }
332
333    fn trait_cmake_positional(
334        &self,
335        content: proc_macro2::TokenStream,
336    ) -> proc_macro2::TokenStream {
337        let Self {
338            ast, crate_path, ..
339        } = self;
340
341        let name = &ast.ident;
342        let generics = &ast.generics;
343        let type_params = generics.type_params();
344        let (_, ty_generics, where_clause) = generics.split_for_impl();
345
346        quote! {
347            #[automatically_derived]
348            impl <'t #(, #type_params)*> #crate_path::CMakePositional<'t> for #name #ty_generics #where_clause {
349                #content
350            }
351        }
352    }
353
354    fn fn_matches_type(&self, content: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
355        let Self { crate_path, .. } = self;
356        quote! {
357            fn matches_type(_: &[u8], keyword: &[u8], tokens: &[#crate_path::Token<'t>]) -> bool {
358                #content
359            }
360        }
361    }
362
363    fn fn_need_update(&self, content: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
364        let Self { crate_path, .. } = self;
365        quote! {
366            fn need_update(field_keyword: &[u8], keyword: &#crate_path::Token<'t>, buffer: &[#crate_path::Token<'t>]) -> bool {
367                #content
368            }
369        }
370    }
371
372    fn fn_need_push_keyword(&self, content: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
373        let crate_path = &self.crate_path;
374
375        quote! {
376            fn need_push_keyword(#[allow(unused_variables)] keyword: &#crate_path::Token<'t>) -> bool {
377                #content
378            }
379        }
380    }
381
382    fn fn_parse(
383        &self,
384        is_mut: bool,
385        content: proc_macro2::TokenStream,
386    ) -> proc_macro2::TokenStream {
387        let crate_path = &self.crate_path;
388        let def_mut = if is_mut {
389            quote! { mut }
390        } else {
391            quote! {}
392        };
393
394        quote! {
395            fn parse<'tv>(
396                #def_mut tokens: &'tv [#crate_path::Token<'t>],
397            ) -> Result<(Self, &'tv [#crate_path::Token<'t>]), #crate_path::CommandParseError> {
398                #content
399            }
400        }
401    }
402
403    fn fn_positional(&self, content: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
404        let crate_path = &self.crate_path;
405
406        quote! {
407            fn positional<'tv>(
408                default_name: &'static [u8],
409                mut tokens: &'tv [#crate_path::Token<'t>],
410                has_keyword: bool,
411            ) -> Result<(Self, &'tv [#crate_path::Token<'t>]), #crate_path::CommandParseError> {
412                #content
413            }
414        }
415    }
416
417    fn to_cmake_fields(&self) -> CMakeFields {
418        let name = &self.ast.ident;
419
420        match &self.ast.data {
421            syn::Data::Struct(data_struct) => match &data_struct.fields {
422                syn::Fields::Named(fields_named) => {
423                    CMakeFields::StructNamedFields(CMakeOption::from_fields_named(fields_named))
424                }
425                syn::Fields::Unnamed(_) => {
426                    abort!(data_struct.fields, "unnamed fields are not supported")
427                }
428                syn::Fields::Unit => CMakeFields::Unit,
429            },
430            syn::Data::Enum(DataEnum { variants, .. }) => {
431                CMakeFields::EnumVariants(CMakeEnum::from_variants(variants))
432            }
433            syn::Data::Union(_) => {
434                abort!(name, "unions are not supported")
435            }
436        }
437    }
438
439    fn trait_cmake_positional_regular(&self) -> proc_macro2::TokenStream {
440        let crate_path = &self.crate_path;
441        let fn_positional = self.fn_positional(quote! {
442            if has_keyword {
443                let (_, rest) = #crate_path::Keyword::positional(default_name, tokens, has_keyword)?;
444                tokens = rest;
445            }
446            #crate_path::CMakeParse::parse(tokens)
447        });
448
449        self.trait_cmake_positional(quote! {
450            #fn_positional
451        })
452    }
453
454    fn trait_cmake_parse_regular(&self) -> proc_macro2::TokenStream {
455        let crate_path = &self.crate_path;
456        let fns_cmake = match self.to_cmake_fields() {
457            CMakeFields::StructNamedFields(fields) => {
458                let (positional_field_opts, regular_field_opts): (Vec<_>, Vec<_>) =
459                    fields.into_iter().partition(|field| field.attr.positional);
460
461                let has_regular_fields = !regular_field_opts.is_empty();
462
463                let pos_var_defs =
464                    positional_var_defs(&positional_field_opts, self.cmake_attr.transparent);
465                let pos_fields = positional_fields(&positional_field_opts);
466
467                let reg_var_defs = regular_var_defs(&regular_field_opts);
468                let reg_fields = regular_fields(&regular_field_opts);
469                let reg_buf_fields = regular_buf_fields(&regular_field_opts);
470                let reg_enum_defs = regular_enum_defs(&regular_field_opts);
471                let reg_enum_match = regular_enum_match(&regular_field_opts);
472
473                let mode_default = self
474                    .cmake_attr
475                    .default
476                    .as_deref()
477                    .map(|def| {
478                        use inflections::Inflect;
479
480                        let defi = quote::format_ident!("{}", def.to_pascal_case());
481                        quote! { Some(CMakeParserMode::#defi) }
482                    })
483                    .unwrap_or_else(|| {
484                        quote! { None }
485                    });
486
487                let reg_if_stms = regular_if_stms(&regular_field_opts, mode_default.clone());
488                let reg_except_if_stmt = self.regular_except_if_stmt();
489
490                let regular_fields = if has_regular_fields {
491                    Some(quote! {
492                        #[derive(Default)]
493                        struct Buffers<'b> {
494                            #(#reg_buf_fields,)*
495                        }
496                        enum CMakeParserMode {
497                            #(#reg_enum_defs,)*
498                        }
499                        let mut buffers = Buffers::default();
500                        let mut current_mode = #mode_default;
501
502                        #(#reg_var_defs;)*
503
504                        loop {
505                            let Some((first, rest)) = tokens.split_first() else { break; };
506                            tokens = rest;
507                            let keyword = first.as_bytes();
508                            #reg_except_if_stmt #(#reg_if_stms)* {
509                                match &current_mode {
510                                    Some(cmake_active_mode) => {
511                                        if match cmake_active_mode {
512                                            #(#reg_enum_match,)*
513                                        } {
514                                            current_mode = #mode_default;
515                                        }
516                                    },
517                                    None => {
518                                        return Err(CommandParseError::UnknownOption(
519                                            std::string::String::from_utf8_lossy(keyword).to_string(),
520                                        ))
521                                    }
522                                }
523                            }
524                        }
525                    })
526                } else {
527                    None
528                };
529
530                let check_empty = if !self.cmake_attr.allow_empty {
531                    Some(quote! {
532                        if tokens.is_empty() {
533                            return Err(CommandParseError::TokenRequired);
534                        }
535                    })
536                } else {
537                    None
538                };
539                let require_empty = if self.cmake_attr.complete {
540                    Some(quote! {
541                        if !tokens.is_empty() {
542                            return Err(#crate_path::CommandParseError::Incomplete);
543                        }
544                    })
545                } else {
546                    None
547                };
548
549                let fn_parse = self.fn_parse(
550                    positional_field_opts.is_empty(),
551                    quote! {
552                        use #crate_path::{CommandParseError, CMakeParse, CMakePositional, Keyword, Token};
553                        #check_empty
554
555                        #(#pos_var_defs;)*
556
557                        #regular_fields
558
559                        #require_empty
560
561                        Ok((Self {
562                            #(#pos_fields,)*
563                            #(#reg_fields,)*
564                        }, tokens))
565                    },
566                );
567
568                let fns_for_match_fields = if self.cmake_attr.match_fields {
569                    let reg_match_fields = regular_match_fields(&regular_field_opts);
570                    let fn_matches_type = self.fn_matches_type(quote! {
571                        use #crate_path::CMakeParse;
572                        #(#reg_match_fields)||*
573                    });
574
575                    let reg_match_fields_need_update =
576                        regular_match_fields_need_update(&regular_field_opts);
577                    let fn_need_update = self.fn_need_update(quote! {
578                        use #crate_path::CMakeParse;
579                        let keyword_bytes = keyword.as_bytes();
580                        buffer.contains(keyword)
581                        #(|| (#reg_match_fields_need_update))*
582                    });
583
584                    let fn_need_push_keyword = self.fn_need_push_keyword(quote! {
585                        true
586                    });
587
588                    quote! {
589                        #fn_matches_type
590                        #fn_need_update
591                        #fn_need_push_keyword
592                    }
593                } else {
594                    quote! {}
595                };
596
597                quote! {
598                    #fn_parse
599                    #fns_for_match_fields
600                }
601            }
602            CMakeFields::EnumVariants(variants) => self.trait_cmake_parse_enum(&variants),
603            CMakeFields::Unit => {
604                let fn_parse = self.fn_parse(
605                    false,
606                    quote! {
607                        if tokens.is_empty() {
608                            Ok((Self, tokens))
609                        } else {
610                            Err(#crate_path::CommandParseError::NotEmpty)
611                        }
612                    },
613                );
614                quote! {
615                    #fn_parse
616                }
617            }
618        };
619
620        self.trait_cmake_parse(quote! {
621            #fns_cmake
622        })
623    }
624
625    fn trait_cmake_parse_positional(&self) -> proc_macro2::TokenStream {
626        let crate_path = &self.crate_path;
627        let fns_cmake = match self.to_cmake_fields() {
628            CMakeFields::StructNamedFields(struct_named_fields) => {
629                let split_last_count = struct_named_fields.iter().filter(|f| f.attr.last).count();
630                let split_last = if split_last_count > 0 {
631                    Some(quote! {
632                        let Some((tokens, last)) = tokens.split_last_chunk::<#split_last_count>() else {
633                            return Err(#crate_path::CommandParseError::TokenRequired);
634                        };
635                    })
636                } else {
637                    None
638                };
639                let var_defs =
640                    positional_var_defs(&struct_named_fields, self.cmake_attr.transparent);
641
642                let fields = positional_fields(&struct_named_fields);
643
644                let check_empty = if self.cmake_attr.complete {
645                    Some(quote! {
646                        if !tokens.is_empty() {
647                            return Err(#crate_path::CommandParseError::Incomplete);
648                        }
649                    })
650                } else {
651                    None
652                };
653
654                let fn_cmake_parse = self.fn_parse(
655                    false,
656                    quote! {
657                        use #crate_path::{CMakePositional, Keyword};
658                        #split_last
659                        #(#var_defs;)*
660                        #check_empty
661                        Ok((Self {
662                            #(#fields,)*
663                        }, tokens))
664                    },
665                );
666
667                quote! {
668                    #fn_cmake_parse
669                }
670            }
671            CMakeFields::EnumVariants(variants) => self.trait_cmake_parse_enum(&variants),
672            CMakeFields::Unit => abort!(
673                self.ast.ident,
674                "positional top level attribute not allowed for structs with unit fields."
675            ),
676        };
677        self.trait_cmake_parse(quote! {
678            #fns_cmake
679        })
680    }
681
682    fn trait_cmake_parse_enum(&self, variants: &[CMakeEnum]) -> proc_macro2::TokenStream {
683        if self.cmake_attr.untagged {
684            self.trait_cmake_parse_enum_untagged(variants)
685        } else {
686            self.trait_cmake_parse_enum_tagged(variants)
687        }
688    }
689
690    fn trait_cmake_parse_enum_tagged(&self, variants: &[CMakeEnum]) -> proc_macro2::TokenStream {
691        let crate_path = &self.crate_path;
692
693        let fn_matches_type = if !self.cmake_attr.list {
694            let enum_flds = enum_fields(variants);
695            Some(self.fn_matches_type(quote! {
696                const FIELDS: &[&[u8]] = &[#(#enum_flds),*];
697                FIELDS.contains(&keyword)
698            }))
699        } else {
700            None
701        };
702
703        let enum_fld_matches = self.enum_field_matches(variants);
704        let fn_parse = self.fn_parse(
705            false,
706            quote! {
707                use #crate_path::{CommandParseError, CMakeParse, CMakePositional, Token};
708                let Some((enum_member, rest)) = tokens.split_first() else {
709                    return Err(CommandParseError::TokenRequired);
710                };
711
712                match enum_member.as_bytes() {
713                    #(#enum_fld_matches,)*
714                    keyword => Err(CommandParseError::UnknownOption(
715                        std::string::String::from_utf8_lossy(keyword).to_string(),
716                    )),
717                }
718            },
719        );
720
721        let fn_need_update = if self.cmake_attr.complete {
722            Some(self.fn_need_update(quote! { false }))
723        } else {
724            None
725        };
726
727        let fn_need_push_keyword = if !self.cmake_attr.list {
728            Some(self.fn_need_push_keyword(quote! {
729                true
730            }))
731        } else {
732            None
733        };
734
735        quote! {
736            #fn_matches_type
737            #fn_parse
738            #fn_need_update
739            #fn_need_push_keyword
740        }
741    }
742
743    fn trait_cmake_parse_enum_untagged(&self, variants: &[CMakeEnum]) -> proc_macro2::TokenStream {
744        let crate_path = &self.crate_path;
745
746        let enum_fld_parsers = self.enum_field_parsers(variants);
747        let fn_parse = self.fn_parse(
748            false,
749            quote! {
750                use #crate_path::{CMakeParse, CMakePositional, Keyword};
751                Err(#crate_path::CommandParseError::TokenRequired)
752                #(.or_else(|_| #enum_fld_parsers))*
753            },
754        );
755
756        quote! {
757            #fn_parse
758        }
759    }
760
761    fn enum_field_parsers<'v>(
762        &self,
763        variants: &'v [CMakeEnum],
764    ) -> impl Iterator<Item = proc_macro2::TokenStream> + 'v {
765        let attr_transparent = self.cmake_attr.transparent;
766        let attr_complete = self.cmake_attr.complete;
767        variants.iter().map(
768            move |CMakeEnum {
769                 option:
770                     CMakeOption {
771                         ident,
772                         lit_bstr,
773                         attr: CMakeAttribute {
774                            complete,
775                            transparent, ..
776                         },
777                         ..
778                     },
779                 renames,
780                 unnamed,
781             }| {
782                let transparent = *transparent || attr_transparent;
783                let complete = *complete || attr_complete;
784                let lit_bstrs = renames.as_ref().map(|strbstrs| strbstrs.iter().map(|strbstr| &strbstr.lit_bstr).collect()).unwrap_or_else(|| vec![lit_bstr]);
785                let positional = format_ident!("{}", if !complete { "positional" } else { "positional_complete"});
786                if *unnamed {
787                    quote_spanned! { ident.span() => #(CMakePositional::#positional(#lit_bstrs, tokens, #transparent).map(|(parsed, tokens)| (Self::#ident(parsed), tokens))),* }
788                } else {
789                    quote_spanned! { ident.span() => #(Keyword::#positional(#lit_bstrs, tokens, #transparent).map(|(_, tokens)| (Self::#ident, tokens))),* }
790                }
791            },
792        )
793    }
794
795    fn enum_field_matches<'v>(
796        &self,
797        variants: &'v [CMakeEnum],
798    ) -> impl Iterator<Item = proc_macro2::TokenStream> + 'v {
799        let enum_transparent = self.cmake_attr.transparent;
800        let enum_positional = self.cmake_attr.positional;
801        variants.iter().map(
802            move |CMakeEnum {
803                 option:
804                     CMakeOption {
805                         ident,
806                         lit_bstr,
807                         attr: CMakeAttribute {
808                            transparent,
809                            positional,
810                            ..
811                         },
812                         ..
813                     },
814                     renames,
815                     unnamed,
816             }| {
817                let positional = enum_positional || *positional;
818
819
820                let tokens = if enum_transparent || *transparent {
821                    quote! { rest }
822                } else {
823                    quote! { tokens }
824                };
825                let parser = if positional {
826                    quote! { CMakePositional::positional(#lit_bstr, #tokens, false) }
827                } else {
828                    quote! { CMakeParse::parse(#tokens) }
829                };
830                let lit_bstrs = renames.as_ref().map(|strbstrs| strbstrs.iter().map(|strbstr| &strbstr.lit_bstr).collect()).unwrap_or_else(|| vec![lit_bstr]);
831                if *unnamed {
832                    quote_spanned! { ident.span() => #(#lit_bstrs)|* => #parser.map(|(parsed, tokens)| (Self::#ident(parsed), tokens)) }
833                } else {
834                    quote_spanned! { ident.span() => #(#lit_bstrs)|* => Ok((Self::#ident, rest)) }
835                }
836            },
837        )
838    }
839
840    fn regular_except_if_stmt(&self) -> Option<proc_macro2::TokenStream> {
841        self.cmake_attr.except.as_deref().map(|except| {
842            let except = except
843                .iter()
844                .map(|e| proc_macro2::Literal::byte_string(e.as_bytes()));
845            let crate_path = &self.crate_path;
846            quote! {
847                const FIELDS: &[&[u8]] = &[#(#except),*];
848                if FIELDS.contains(&keyword) {
849                    return Err(#crate_path::CommandParseError::Incomplete)
850                } else
851            }
852        })
853    }
854}
855
856#[derive(Default)]
857struct CMakeAttribute {
858    default: Option<String>,
859    keyword_after: Option<proc_macro2::Literal>,
860    list: bool,
861    match_fields: bool,
862    pkg: Option<syn::Path>,
863    positional: bool,
864    rename: Option<String>,
865    renames: Option<Vec<String>>,
866    transparent: bool,
867    untagged: bool,
868    allow_empty: bool,
869    complete: bool,
870    except: Option<Vec<String>>,
871    in_range: bool,
872    last: bool,
873}
874
875fn cmake_attribute(attrs: &[syn::Attribute]) -> Option<CMakeAttribute> {
876    let attr = attrs.iter().find(|attr| attr.path().is_ident("cmake"))?;
877
878    let nested = attr
879        .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
880        .unwrap();
881
882    let mut default = None;
883    let mut keyword_after = None;
884    let mut list = false;
885    let mut match_fields = false;
886    let mut pkg = None;
887    let mut positional = false;
888    let mut rename = None;
889    let mut renames = None;
890    let mut transparent = false;
891    let mut untagged = false;
892    let mut allow_empty = false;
893    let mut complete = false;
894    let mut except = None;
895    let mut in_range = false;
896    let mut last = false;
897
898    for meta in nested {
899        match meta {
900            Meta::Path(p) if p.is_ident("list") => list = true,
901            Meta::Path(p) if p.is_ident("match_fields") => match_fields = true,
902            Meta::Path(p) if p.is_ident("positional") => positional = true,
903            Meta::Path(p) if p.is_ident("transparent") => transparent = true,
904            Meta::Path(p) if p.is_ident("untagged") => untagged = true,
905            Meta::Path(p) if p.is_ident("allow_empty") => allow_empty = true,
906            Meta::Path(p) if p.is_ident("complete") => complete = true,
907            Meta::Path(p) if p.is_ident("in_range") => in_range = true,
908            Meta::Path(p) if p.is_ident("last") => last = true,
909            Meta::NameValue(MetaNameValue {
910                ref path,
911                value: Expr::Array(ExprArray { elems, .. }),
912                ..
913            }) => {
914                if path.is_ident("rename") {
915                    renames = Some(to_vec_string(elems));
916                } else if path.is_ident("except") {
917                    except = Some(to_vec_string(elems));
918                }
919            }
920            Meta::NameValue(MetaNameValue {
921                ref path,
922                value:
923                    Expr::Lit(ExprLit {
924                        lit: Lit::Str(s), ..
925                    }),
926                ..
927            }) => {
928                if path.is_ident("default") {
929                    default = Some(s.value());
930                } else if path.is_ident("keyword_after") {
931                    keyword_after = Some(proc_macro2::Literal::byte_string(s.value().as_bytes()));
932                } else if path.is_ident("pkg") {
933                    pkg = s.parse().ok();
934                } else if path.is_ident("rename") {
935                    rename = Some(s.value());
936                }
937            }
938            _ => (),
939        }
940    }
941
942    Some(CMakeAttribute {
943        default,
944        keyword_after,
945        list,
946        match_fields,
947        pkg,
948        positional,
949        rename,
950        renames,
951        transparent,
952        untagged,
953        allow_empty,
954        complete,
955        except,
956        in_range,
957        last,
958    })
959}
960
961fn to_vec_string(elems: Punctuated<Expr, syn::token::Comma>) -> Vec<String> {
962    elems
963        .iter()
964        .filter_map(|elem| match elem {
965            Expr::Lit(ExprLit {
966                lit: Lit::Str(s), ..
967            }) => Some(s.value()),
968            _ => None,
969        })
970        .collect()
971}
972
973#[cfg(test)]
974mod tests {
975    use syn::{parse_quote, Attribute};
976
977    use super::*;
978
979    #[test]
980    fn enum_ast() {
981        let en: syn::Stmt = parse_quote! {
982            enum Test {
983                Var1,
984                Var2(String),
985                Var3 { value: String }
986            }
987        };
988        dbg!(en);
989    }
990
991    #[test]
992    fn check_def_attr() {
993        let attr: Attribute = parse_quote! {
994            #[cmake(default = "COMMAND",
995                rename = "mmm",
996                pkg = "crate",
997                transparent,
998                positional,
999                match_fields,
1000                list,
1001            )]
1002        };
1003
1004        let cmake_attr = cmake_attribute(&[attr]).expect("attrs");
1005        assert!(cmake_attr.pkg.is_some());
1006        assert_eq!(Some("mmm"), cmake_attr.rename.as_deref());
1007        assert_eq!(Some("COMMAND"), cmake_attr.default.as_deref());
1008        assert!(cmake_attr.positional);
1009        assert!(cmake_attr.transparent);
1010        assert!(cmake_attr.match_fields);
1011        assert!(cmake_attr.list);
1012    }
1013
1014    #[test]
1015    fn check_attr_rename() {
1016        let attr: Attribute = parse_quote! {
1017            #[cmake(
1018                rename = ["aaa", "bb", "c"]
1019            )]
1020        };
1021
1022        let cmake_attr = cmake_attribute(&[attr]).expect("attrs");
1023        assert_eq!(
1024            Some(vec!["aaa".to_string(), "bb".to_string(), "c".to_string()]),
1025            cmake_attr.renames
1026        );
1027    }
1028    #[test]
1029    fn check_attr_except() {
1030        let attr: Attribute = parse_quote! {
1031            #[cmake(
1032                except = ["aaa", "bb", "c"]
1033            )]
1034        };
1035
1036        let cmake_attr = cmake_attribute(&[attr]).expect("attrs");
1037        assert_eq!(
1038            Some(vec!["aaa".to_string(), "bb".to_string(), "c".to_string()]),
1039            cmake_attr.except
1040        );
1041    }
1042}