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()), } }