parse_variants_derive/lib.rs
1use proc_macro::TokenStream;
2
3use quote::quote;
4use syn::spanned::Spanned;
5use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields};
6
7/*
8 See the documentation in the parse-variants crate on how to use the macro.
9 Here is a quick example for how the generated code looks.
10 We apply the macro to this enum:
11
12 #[derive(crate::Parse)]
13 enum EnumWithMixedVariants {
14 TwoExpressionsSeparatedByKeyword {
15 first: syn::Expr,
16 _the_dude: keywords::lebowski,
17 second: syn::Expr,
18 },
19 IdentifierPlusPlus(Ident, syn::token::Add,syn::token::Add),
20 }
21
22 Then the generate syn::parse::Parse implementation looks like this:
23
24 impl ::syn::parse::Parse for EnumWithMixedVariants {
25 fn parse(input: &::syn::parse::ParseBuffer) -> ::std::result::Result<Self, ::syn::Error> {
26 use ::syn::parse::discouraged::Speculative; //needed for input.advance_to(...)
27 // 1) fork input for first variant and try if we can parse it
28 let fork = input.fork();
29 if let Ok(variant) = (|| { // here we use a closure to return a result or error without returning the error directly from our parse function
30 // this is how parsing named fields looks like
31 Ok(EnumWithMixedVariants::TwoExpressionsSeparatedByKeyword {
32 first: fork.parse()?,
33 _the_dude: fork.parse()?,
34 second: fork.parse()?,
35 }) as ::std::result::Result<EnumWithMixedVariants, ::syn::Error>
36 })() {
37 // if we can parse the variant, advance the parsebuffer and return immediately
38 input.advance_to(&fork);
39 return Ok(variant);
40 }
41 // 2) fork the second variant
42 let fork = input.fork();
43 if let Ok(variant) = (|| { // same trick with the closure as above to catch the error returns
44 // this is how parsing named fields looks like
45 Ok(EnumWithMixedVariants::IdentifierPlusPlus(
46 fork.parse()?,
47 fork.parse()?,
48 fork.parse()?,
49 )) as ::std::result::Result<EnumWithMixedVariants, ::syn::Error>
50 })() {
51 input.advance_to(&fork);
52 return Ok(variant);
53 }
54 // if no variants can be parsed, return an error
55 Err(syn::Error::new(
56 input.span(),
57 ::std::format! {
58 "parse error: tokens cannot be parsed as any variant of {}",
59 ::std::stringify! { EnumWithMixedVariants }
60 },
61 ))
62 }
63 }
64*/
65
66#[proc_macro_derive(Parse)]
67pub fn derive_parse_variants(input: TokenStream) -> TokenStream {
68 let input = parse_macro_input!(input as DeriveInput);
69 let enum_ident = &input.ident;
70 let data_enum = match get_data_enum(&input) {
71 Ok(data_enum) => data_enum,
72 Err(error) => {
73 return error;
74 }
75 };
76
77 // here we generate the code that tries to parse the actual variants by repeatedly forking
78 // the input parse buffer and then trying to parse the input as the contents of the respective
79 // variant.
80 let mut try_parse_variants = proc_macro2::TokenStream::new();
81
82 for variant in data_enum.variants.iter() {
83 let variant_name = &variant.ident;
84 let try_parse_variant = match variant.fields {
85 Fields::Named(ref fields_named) => {
86 let fields: Vec<_> = fields_named
87 .named
88 .iter()
89 .map(|field| field.ident.as_ref().unwrap())
90 .collect();
91 // generated code looks e.g. like this
92 // Ok(MyEnum::StructLikeVariant{field1 : fork.parse()?, field2 : fork.parse()?})
93 quote! {
94 Ok(#enum_ident::#variant_name {#(#fields : fork.parse()?),*})
95 }
96 }
97 Fields::Unnamed(ref fields_unnamed) => {
98 let fork_parse_questionmark = quote! {fork.parse()?};
99 let repeated_input_parsing =
100 std::iter::repeat(fork_parse_questionmark).take(fields_unnamed.unnamed.len());
101 // code looks e.g. like this
102 // Ok(MyEnum::TupleLikeVariant(fork.parse()?,fork.parse()?))
103 // where fork.parse()? is repeated for each field of the tuple like variant
104 quote! {
105 Ok(#enum_ident::#variant_name (#(#repeated_input_parsing),*))
106 }
107 }
108 Fields::Unit => {
109 // unit like variants (i.e. variants with no fields)
110 // cannot be parsed and will return a compile error
111 return syn::Error::new(
112 variant.ident.span(),
113 "illegal unit variant: enumeration may not have variants without fields",
114 )
115 .to_compile_error()
116 .into();
117 }
118 };
119
120 try_parse_variants.extend(quote! {
121 let fork = input.fork();
122 if let Ok(variant) = (||{#try_parse_variant as ::std::result::Result<#enum_ident,::syn::Error>})() { //TODO: document this: less verbose variant than before. So that the error type can be caught at the boundary of this closure and does not propagate outside of the parse function
123 input.advance_to(&fork);
124 return Ok(variant);
125 }
126 })
127 }
128
129 // the implementation of the derive trait
130 let parse_impl_tokens = quote! {
131 impl ::syn::parse::Parse for #enum_ident {
132 fn parse(input : & ::syn::parse::ParseBuffer) -> ::std::result::Result<Self, ::syn::Error> {
133 // we have to use this for the advante_to method in the parsing body
134 use ::syn::parse::discouraged::Speculative;
135 // parsing the variants
136 #try_parse_variants
137 // if none of the variants can be parsed, return an error
138 Err(syn::Error::new(input.span(),::std::format!{"parse error: tokens cannot be parsed as any variant of {}", ::std::stringify!{#enum_ident}}))
139 }
140 }
141 };
142
143 //println!("IMPLEMENTATION = \n{}", parse_impl_tokens.to_string());
144 //panic!();
145
146 parse_impl_tokens.into()
147}
148
149// helper function to return the DataEnum of the derive input.
150// # Returns
151// the DataEnum field, if the derive input is an enum and if the enum has at least one variant.
152// If the enum has no variants or the derive input is not an enum, a descriptive error is returned. The
153// error is returned as TokenStream and can be passed on directly.
154fn get_data_enum(input: &DeriveInput) -> Result<&DataEnum, TokenStream> {
155 match input.data {
156 Data::Enum(ref data_enum) => {
157 if !data_enum.variants.is_empty() {
158 Ok(data_enum)
159 } else {
160 Err(syn::Error::new(
161 input
162 .span()
163 .join(input.ident.span())
164 .unwrap_or_else(|| input.ident.span()),
165 "no variants: enumeration must have at least one variant",
166 )
167 .to_compile_error()
168 .into())
169 }
170 }
171 Data::Union(_) | Data::Struct(_) => Err(syn::Error::new(
172 input.span(),
173 "expected enum: parsing variants only works with enumerations",
174 )
175 .to_compile_error()
176 .into()),
177 }
178}