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#[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(®ular_field_opts);
468 let reg_fields = regular_fields(®ular_field_opts);
469 let reg_buf_fields = regular_buf_fields(®ular_field_opts);
470 let reg_enum_defs = regular_enum_defs(®ular_field_opts);
471 let reg_enum_match = regular_enum_match(®ular_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(®ular_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 ¤t_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(®ular_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(®ular_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}