use proc_macro::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields};
#[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;
}
};
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();
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());
quote! {
Ok(#enum_ident::#variant_name (#(#repeated_input_parsing),*))
}
}
Fields::Unit => {
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>})() { input.advance_to(&fork);
return Ok(variant);
}
})
}
let parse_impl_tokens = quote! {
impl ::syn::parse::Parse for #enum_ident {
fn parse(input : & ::syn::parse::ParseBuffer) -> ::std::result::Result<Self, ::syn::Error> {
use ::syn::parse::discouraged::Speculative;
#try_parse_variants
Err(syn::Error::new(input.span(),::std::format!{"parse error: tokens cannot be parsed as any variant of {}", ::std::stringify!{#enum_ident}}))
}
}
};
parse_impl_tokens.into()
}
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()),
}
}