use crate::{attr, format};
use syn;
use proc_macro2::{Span, TokenStream};
const DEFAULT_INT_DISCRIMINANT: &'static str = "u32";
const DEFAULT_FIRST_INT_DISCRIMINATOR: usize = 1;
pub struct Enum {
pub ident: syn::Ident,
pub explicit_format: Option<format::Enum>,
pub repr_attr: Option<syn::Ident>,
pub variants: Vec<EnumVariant>,
}
pub struct EnumVariant {
pub ident: syn::Ident,
pub explicit_discriminator_attr: Option<syn::Lit>,
pub explicit_int_discriminator_equals: Option<syn::Lit>,
pub actual_discriminator: Option<syn::Lit>,
pub fields: syn::Fields,
}
impl Enum {
pub fn new(ast: &syn::DeriveInput,
e: &syn::DataEnum) -> Enum {
let mut plan = Enum {
ident: ast.ident.clone(),
repr_attr: attr::repr(&ast.attrs),
explicit_format: attr::protocol(&ast.attrs).map(|p| match p {
attr::Protocol::DiscriminantFormat(format) => format,
_ => panic!("expected a discriminant format but got {:?}", p),
}),
variants: e.variants.iter().map(|variant| {
let equals_discriminant = match variant.discriminant.clone().map(|a| a.1) {
Some(syn::Expr::Lit(expr_lit)) => Some(expr_lit.lit),
Some(_) => panic!("'VariantName = <expr>' can only be used with literals"),
None => None,
};
EnumVariant {
ident: variant.ident.clone(),
explicit_discriminator_attr: attr::protocol(&variant.attrs).map(|protocol| match protocol {
attr::Protocol::Discriminator(value) => value,
_ => panic!("expected a discriminator but got {:?}", protocol),
}),
explicit_int_discriminator_equals: equals_discriminant,
actual_discriminator: None,
fields: variant.fields.clone(),
}
}).collect(),
};
plan.resolve();
plan
}
pub fn format(&self) -> format::Enum {
if let Some(ref explicit_format) = self.explicit_format {
explicit_format.clone()
} else { format::Enum::default()
}
}
pub fn discriminant(&self) -> syn::Ident {
match self.repr_attr.clone() {
Some(ty) => ty,
None => match self.format() {
format::Enum::StringDiscriminator => {
syn::Ident::new("String", Span::call_site())
},
format::Enum::IntegerDiscriminator => {
syn::Ident::new(DEFAULT_INT_DISCRIMINANT, Span::call_site())
},
},
}
}
pub fn matchable_discriminator_expr(&self,
variable_ident: syn::Ident)
-> TokenStream {
match self.format() {
format::Enum::IntegerDiscriminator => quote!(#variable_ident),
format::Enum::StringDiscriminator => quote!(&#variable_ident[..]),
}
}
pub fn resolve(&mut self) {
let mut current_default_int_discriminator = DEFAULT_FIRST_INT_DISCRIMINATOR;
let format = self.format().clone();
for variant in self.variants.iter_mut() {
let actual_discriminator: syn::Lit = match variant.explicit_discriminator() {
Some(explicit_discriminator) => explicit_discriminator.clone(),
None => match format {
format::Enum::StringDiscriminator => {
syn::LitStr::new(&variant.ident.to_string(), Span::call_site()).into()
},
format::Enum::IntegerDiscriminator => {
syn::LitInt::new(¤t_default_int_discriminator.to_string(),
Span::call_site()).into()
},
},
};
if let syn::Lit::Int(ref discriminator_value) = actual_discriminator {
current_default_int_discriminator = discriminator_value.base10_parse::<usize>().unwrap() + 1;
}
variant.actual_discriminator = Some(actual_discriminator);
}
}
}
impl EnumVariant {
pub fn discriminator_literal(&self) -> &syn::Lit {
self.actual_discriminator.as_ref().expect("discriminator has not been resolved yet")
}
pub fn explicit_discriminator(&self) -> Option<&syn::Lit> {
match (self.explicit_discriminator_attr.as_ref(), self.explicit_int_discriminator_equals.as_ref()) {
(Some(attr), Some(_)) => Some(attr),
(Some(lit), None) | (None, Some(lit)) => Some(lit),
(None, None) => None,
}
}
pub fn discriminator_expr(&self) -> TokenStream {
match self.discriminator_literal() {
s @ syn::Lit::Str(..) => quote!(#s.to_owned()),
i @ syn::Lit::Int(..) => quote!(#i),
_ => unreachable!(),
}
}
pub fn discriminator_ref_expr(&self) -> TokenStream {
match self.discriminator_literal() {
s @ syn::Lit::Str(..) => quote!(&#s.to_owned()),
i @ syn::Lit::Int(..) => quote!(&#i),
_ => unreachable!(),
}
}
pub fn ignore_fields_pattern_expr(&self) -> TokenStream {
match self.fields {
syn::Fields::Named(..) => quote!({ .. }),
syn::Fields::Unnamed(..) => quote!((..)),
syn::Fields::Unit => quote!(),
}
}
}