1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
use proc_macro::TokenStream;

use quote::quote;
use syn::spanned::Spanned;
use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields};

/*
 See the documentation in the parse-variants crate on how to use the macro.
 Here is a quick example for how the generated code looks.
 We apply the macro to this enum:

     #[derive(crate::Parse)]
     enum EnumWithMixedVariants {
        TwoExpressionsSeparatedByKeyword {
            first: syn::Expr,
            _the_dude: keywords::lebowski,
            second: syn::Expr,
        },
        IdentifierPlusPlus(Ident, syn::token::Add,syn::token::Add),
     }

 Then the generate syn::parse::Parse implementation looks like this:

    impl ::syn::parse::Parse for EnumWithMixedVariants {
        fn parse(input: &::syn::parse::ParseBuffer) -> ::std::result::Result<Self, ::syn::Error> {
            use ::syn::parse::discouraged::Speculative; //needed for input.advance_to(...)
            // 1) fork input for first variant and try if we can parse it
            let fork = input.fork();
            if let Ok(variant) = (|| {   // here we use a closure to return a result or error without returning the error directly from our parse function
                // this is how parsing named fields looks like
                Ok(EnumWithMixedVariants::TwoExpressionsSeparatedByKeyword {
                    first: fork.parse()?,
                    _the_dude: fork.parse()?,
                    second: fork.parse()?,
                }) as ::std::result::Result<EnumWithMixedVariants, ::syn::Error>
            })() {
                // if we can parse the variant, advance the parsebuffer and return immediately
                input.advance_to(&fork);
                return Ok(variant);
            }
            // 2) fork the second variant
            let fork = input.fork();
            if let Ok(variant) = (|| {   // same trick with the closure as above to catch the error returns
                // this is how parsing named fields looks like
                Ok(EnumWithMixedVariants::IdentifierPlusPlus(
                    fork.parse()?,
                    fork.parse()?,
                    fork.parse()?,
                )) as ::std::result::Result<EnumWithMixedVariants, ::syn::Error>
            })() {
                input.advance_to(&fork);
                return Ok(variant);
            }
            // if no variants can be parsed, return an error
            Err(syn::Error::new(
                input.span(),
                ::std::format! {
                    "parse error: tokens cannot be parsed as any variant of {}",
                    ::std::stringify! { EnumWithMixedVariants }
                },
            ))
        }
    }
*/

#[proc_macro_derive(Parse)]
pub fn derive_parse_variants(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let enum_ident = &input.ident;
    let data_enum = match get_data_enum(&input) {
        Ok(data_enum) => data_enum,
        Err(error) => {
            return error;
        }
    };

    // here we generate the code that tries to parse the actual variants by repeatedly forking
    // the input parse buffer and then trying to parse the input as the contents of the respective
    // variant.
    let mut try_parse_variants = proc_macro2::TokenStream::new();

    for variant in data_enum.variants.iter() {
        let variant_name = &variant.ident;
        let try_parse_variant = match variant.fields {
            Fields::Named(ref fields_named) => {
                let fields: Vec<_> = fields_named
                    .named
                    .iter()
                    .map(|field| field.ident.as_ref().unwrap())
                    .collect();
                // generated code looks e.g. like this
                // Ok(MyEnum::StructLikeVariant{field1 : fork.parse()?, field2 : fork.parse()?})
                quote! {
                        Ok(#enum_ident::#variant_name {#(#fields : fork.parse()?),*})
                }
            }
            Fields::Unnamed(ref fields_unnamed) => {
                let fork_parse_questionmark = quote! {fork.parse()?};
                let repeated_input_parsing =
                    std::iter::repeat(fork_parse_questionmark).take(fields_unnamed.unnamed.len());
                // code looks e.g. like this
                // Ok(MyEnum::TupleLikeVariant(fork.parse()?,fork.parse()?))
                // where fork.parse()? is repeated for each field of the tuple like variant
                quote! {
                    Ok(#enum_ident::#variant_name (#(#repeated_input_parsing),*))
                }
            }
            Fields::Unit => {
                // unit like variants (i.e. variants with no fields)
                // cannot be parsed and will return a compile error
                return syn::Error::new(
                    variant.ident.span(),
                    "illegal unit variant: enumeration may not have variants without fields",
                )
                .to_compile_error()
                .into();
            }
        };

        try_parse_variants.extend(quote! {
            let fork = input.fork();
            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
                input.advance_to(&fork);
                return Ok(variant);
            }
        })
    }

    // the implementation of the derive trait
    let parse_impl_tokens = quote! {
        impl ::syn::parse::Parse for #enum_ident {
            fn parse(input : & ::syn::parse::ParseBuffer) -> ::std::result::Result<Self, ::syn::Error> {
                // we have to use this for the advante_to method in the parsing body
                use ::syn::parse::discouraged::Speculative;
                // parsing the variants
                #try_parse_variants
                // if none of the variants can be parsed, return an error
                Err(syn::Error::new(input.span(),::std::format!{"parse error: tokens cannot be parsed as any variant of {}", ::std::stringify!{#enum_ident}}))
            }
        }
    };

    //println!("IMPLEMENTATION = \n{}", parse_impl_tokens.to_string());
    //panic!();

    parse_impl_tokens.into()
}

// helper function to return the DataEnum of the derive input.
// # Returns
// the DataEnum field, if the derive input is an enum and if the enum has at least one variant.
// If the enum has no variants or the derive input is not an enum, a descriptive error is returned. The
// error is returned as TokenStream and can be passed on directly.
fn get_data_enum(input: &DeriveInput) -> Result<&DataEnum, TokenStream> {
    match input.data {
        Data::Enum(ref data_enum) => {
            if !data_enum.variants.is_empty() {
                Ok(data_enum)
            } else {
                Err(syn::Error::new(
                    input
                        .span()
                        .join(input.ident.span())
                        .unwrap_or_else(|| input.ident.span()),
                    "no variants: enumeration must have at least one variant",
                )
                .to_compile_error()
                .into())
            }
        }
        Data::Union(_) | Data::Struct(_) => Err(syn::Error::new(
            input.span(),
            "expected enum: parsing variants only works with enumerations",
        )
        .to_compile_error()
        .into()),
    }
}