use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, format_ident, quote};
use syn::{
Attribute,
Error,
Field,
Fields,
FieldsNamed,
FieldsUnnamed,
Generics,
Ident,
ItemEnum,
Token,
Type,
Variant,
Visibility,
token::Brace,
};
use crate::{
ext_traits::cfg_attrs,
preprocessor::Preprocessor,
processed_fields::{ProcessedFields, ProcessedNamed, ProcessedUnnamed},
};
pub struct ParsedEnum {
attrs: Vec<Attribute>,
vis: Visibility,
enum_token: Token![enum],
ident: Ident,
generics: Generics,
#[allow(dead_code)]
brace_token: Brace,
variants: Vec<ProcessedVariant>,
global: Vec<Preprocessor>,
}
pub struct ProcessedVariant {
attrs: Vec<Attribute>,
ident: Ident,
fields: ProcessedFields,
}
impl ToTokens for ProcessedVariant {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let attrs = &self.attrs;
let ident = &self.ident.clone();
let fields = self.fields.to_token_stream();
tokens.extend(quote! {
#(#attrs) *
#ident #fields
});
}
}
impl TryFrom<ItemEnum> for ParsedEnum {
type Error = Error;
fn try_from(item: ItemEnum) -> Result<Self, Self::Error> {
let ItemEnum {
attrs,
vis,
enum_token,
ident,
generics,
brace_token,
variants,
} = item;
let variants = variants
.into_iter()
.map(|variant| {
let Variant {
attrs,
ident,
fields,
discriminant,
} = variant;
if let Some(discriminant) = discriminant {
return Err(Error::new_spanned(
&discriminant.1,
"Preprocess does not support discriminants.",
));
}
Ok(ProcessedVariant {
attrs,
ident,
fields: fields.try_into()?,
})
})
.collect::<Result<_, Error>>()?;
let global = attrs
.iter()
.filter(|attr| attr.path().is_ident("preprocess"))
.map(|attr| Preprocessor::from_attr(attr, true))
.collect::<Result<Vec<_>, Error>>()?
.into_iter()
.flatten()
.collect::<Vec<_>>();
Ok(Self {
attrs: attrs
.into_iter()
.filter(|attr| !attr.path().is_ident("preprocess"))
.collect(),
vis,
enum_token,
ident,
generics,
brace_token,
variants,
global,
})
}
}
pub fn into_processed(
item: ItemEnum,
strict_mode: bool,
) -> Result<TokenStream, Error> {
let parsed: ParsedEnum = item.try_into()?;
let ParsedEnum {
attrs,
vis,
enum_token,
ident,
generics,
brace_token: _,
variants,
global,
} = parsed;
let processed_ident = format_ident!("{}Processed", ident);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let new_variants = variants
.iter()
.map(|variant| {
let fields = match &variant.fields {
ProcessedFields::Unit => Fields::Unit,
ProcessedFields::Named(ProcessedNamed {
named,
brace_token,
}) => Fields::Named(FieldsNamed {
brace_token: *brace_token,
named: named
.iter()
.map(|(field, preprocessors)| {
if strict_mode && preprocessors.is_empty() {
return Err(Error::new_spanned(
field,
"every field must have at least one preprocessor in strict mode",
));
}
let new_type = preprocessors
.iter()
.fold(
field.ty.to_token_stream(),
|acc, preprocessor| {
preprocessor.get_new_type(&acc)
},
)
.to_string();
let ty: Type = syn::parse_str(&new_type)?;
Ok(Field {
attrs: field.attrs.clone(),
vis: field.vis.clone(),
mutability: field.mutability.clone(),
ident: field.ident.clone(),
colon_token: field.colon_token,
ty,
})
})
.collect::<Result<_, Error>>()?,
}),
ProcessedFields::Unnamed(ProcessedUnnamed {
unnamed,
paren_token,
}) => Fields::Unnamed(FieldsUnnamed {
paren_token: *paren_token,
unnamed: unnamed
.iter()
.map(|(field, preprocessors)| {
if strict_mode && preprocessors.is_empty() {
return Err(Error::new_spanned(
field,
"every field must have at least one preprocessor in strict mode",
));
}
let new_type = preprocessors
.iter()
.fold(
field.ty.to_token_stream(),
|acc, preprocessor| {
preprocessor.get_new_type(&acc)
},
)
.to_string();
let ty: Type = syn::parse_str(&new_type)?;
Ok(Field {
attrs: field.attrs.clone(),
vis: field.vis.clone(),
mutability: field.mutability.clone(),
ident: field.ident.clone(),
colon_token: field.colon_token,
ty,
})
})
.collect::<Result<_, Error>>()?,
}),
};
Ok(Variant {
attrs: variant.attrs.clone(),
ident: variant.ident.clone(),
fields,
discriminant: None,
})
})
.collect::<Result<Vec<_>, Error>>()?;
let global_preprocessors = global.into_iter().map(|preprocessor| {
preprocessor.as_processor_token_stream(
&format_ident!("value"),
&ident.to_token_stream(),
)
});
let variants_destructed = variants.iter().map(|variant| {
let ProcessedVariant {
attrs,
ident,
fields,
} = variant;
let field_names_destructured = match &fields {
ProcessedFields::Unit => TokenStream2::new(),
ProcessedFields::Named(ProcessedNamed { named, .. }) => {
let named = named.iter().map(|(field, _)| {
let cfgs = cfg_attrs(&field.attrs);
let ident = field.ident.as_ref().unwrap();
quote! {
#(#cfgs)*
#ident
}
});
quote! {
{
#(#named),*
}
}
}
ProcessedFields::Unnamed(ProcessedUnnamed { unnamed, .. }) => {
let unnamed =
unnamed.iter().enumerate().map(|(index, (field, _))| {
let cfgs = cfg_attrs(&field.attrs);
let ident = format_ident!("field_{}", index);
quote! {
#(#cfgs)*
#ident
}
});
quote! {
(
#(#unnamed),*
)
}
}
};
let field_preprocessors: Vec<TokenStream2> = match &fields {
ProcessedFields::Unit => vec![],
ProcessedFields::Named(ProcessedNamed { named, .. }) => named
.iter()
.flat_map(|(field, preprocessors)| {
let cfgs = cfg_attrs(&field.attrs);
preprocessors
.iter()
.fold(
(Vec::new(), field.ty.to_token_stream()),
|(mut acc, new_ty), preprocessor| {
let new_ty = preprocessor.get_new_type(&new_ty);
let stmt = preprocessor
.as_processor_token_stream(
field.ident.as_ref().unwrap(),
&new_ty,
);
acc.push(quote! {
#(#cfgs)*
#stmt
});
(acc, new_ty)
},
)
.0
})
.collect(),
ProcessedFields::Unnamed(ProcessedUnnamed { unnamed, .. }) => {
unnamed
.iter()
.enumerate()
.flat_map(|(index, (field, preprocessors))| {
let cfgs = cfg_attrs(&field.attrs);
preprocessors
.iter()
.fold(
(Vec::new(), field.ty.to_token_stream()),
|(mut acc, new_ty), preprocessor| {
let stmt = preprocessor
.as_processor_token_stream(
&format_ident!("field_{}", index),
&new_ty,
);
acc.push(quote! {
#(#cfgs)*
#stmt
});
(acc, preprocessor.get_new_type(&new_ty))
},
)
.0
})
.collect()
}
};
let attrs = attrs
.iter()
.filter(|attr| !attr.path().is_ident("doc"))
.cloned();
quote! {
#(#attrs) *
Self:: #ident #field_names_destructured => {
#(#field_preprocessors
)*
Ok(#processed_ident :: #ident
#field_names_destructured
)
}
}
});
Ok(quote! {
#(#attrs)*
#vis #enum_token #ident #generics {
#(#variants,)*
}
#(#attrs)*
#vis #enum_token #processed_ident #generics {
#(#new_variants,)*
}
impl #impl_generics ::preprocess::Preprocessable for #ident #ty_generics #where_clause {
type Processed = #processed_ident #ty_generics;
fn preprocess(self) -> ::std::result::Result<#processed_ident #ty_generics, ::preprocess::Error> {
let value = self;
#(#global_preprocessors
)*
match value {
#(#variants_destructed) *
}
}
}
}
.into())
}