extern crate proc_macro;
#[macro_use]
extern crate quote;
extern crate syn;
use proc_macro::TokenStream;
use syn::*;
#[proc_macro_derive(EnumAsGetters)]
pub fn enum_as_getters(input: TokenStream) -> TokenStream {
let s = input.to_string();
let ast = parse_derive_input(&s).unwrap();
let getters = impl_enum_as_getters(&ast);
getters.parse().unwrap()
}
#[proc_macro_derive(EnumIntoGetters)]
pub fn enum_into_getters(input: TokenStream) -> TokenStream {
let s = input.to_string();
let ast = parse_derive_input(&s).unwrap();
let getters = impl_enum_into_getters(&ast);
getters.parse().unwrap()
}
#[proc_macro_derive(EnumIsA)]
pub fn enum_is_a(input: TokenStream) -> TokenStream {
let s = input.to_string();
let ast = parse_derive_input(&s).unwrap();
let mut gen = impl_enum_is_a(&ast);
gen.append(&mut impl_struct_enum_is_a(&ast));
gen.append(&mut impl_unit_enum_is_a(&ast));
gen.parse().unwrap()
}
fn to_snake_case<S: AsRef<str>>(ident: &S) -> String {
let mut snake_case = String::new();
for (i, c) in ident.as_ref().chars().enumerate() {
if i > 0 && c.is_uppercase() {
snake_case.push('_');
}
snake_case.push(c.to_lowercase().next().unwrap());
}
snake_case
}
fn impl_enum_as_getters(ast: &DeriveInput) -> quote::Tokens {
let ref name = ast.ident;
let variants =
if let Body::Enum(ref e) = ast.body { e }
else { unreachable!() };
macro_rules! getter_filter {
() => {
variants.iter()
.filter(|v| if let VariantData::Tuple(_) = v.data { true } else { false })
.filter(|v| v.data.fields().len() == 1)
};
}
let variant_names = getter_filter!()
.map(|v| v.ident.clone())
.collect::<Vec<Ident>>();
let function_names = getter_filter!()
.map(|v| format!("as_{}", to_snake_case(&v.ident)).into())
.collect::<Vec<Ident>>();
let function_name_strs = getter_filter!()
.map(|v| v.ident.to_string().to_lowercase())
.collect::<Vec<String>>();
let variant_types = getter_filter!()
.map(|v| &v.data.fields()[0].ty)
.map(|ty| Ty::Rptr(None, Box::new(MutTy { ty: ty.clone(), mutability: Mutability::Immutable })))
.collect::<Vec<Ty>>();
let getter_names = vec!(name.clone(); variant_types.len());
quote! {
#[allow(dead_code)]
impl #name {
#(pub fn #function_names(&self) -> #variant_types {
if let &#getter_names::#variant_names(ref v) = self {
v
}
else {
panic!(concat!("called ", #function_name_strs, "() on {:?}"), self);
}
}
)*
}
}
}
fn impl_enum_into_getters(ast: &DeriveInput) -> quote::Tokens {
let ref name = ast.ident;
let variants =
if let Body::Enum(ref e) = ast.body { e }
else { unreachable!() };
macro_rules! getter_filter {
() => {
variants.iter()
.filter(|v| if let VariantData::Tuple(_) = v.data { true } else { false })
.filter(|v| v.data.fields().len() == 1)
};
}
let variant_names = getter_filter!()
.map(|v| v.ident.clone())
.collect::<Vec<Ident>>();
let function_names = getter_filter!()
.map(|v| format!("into_{}", to_snake_case(&v.ident)).into())
.collect::<Vec<Ident>>();
let function_name_strs = getter_filter!()
.map(|v| v.ident.to_string().to_lowercase())
.collect::<Vec<String>>();
let variant_types = getter_filter!()
.map(|v| v.data.fields()[0].ty.clone())
.collect::<Vec<Ty>>();
let getter_names = vec!(name.clone(); variant_types.len());
quote! {
#[allow(dead_code)]
impl #name {
#(pub fn #function_names(self) -> #variant_types {
if let #getter_names::#variant_names(v) = self {
v
}
else {
panic!(concat!("called ", #function_name_strs, "() on {:?}"), self);
}
}
)*
}
}
}
fn impl_enum_is_a(ast: &DeriveInput) -> quote::Tokens {
let ref name = ast.ident;
let variants =
if let Body::Enum(ref e) = ast.body { e }
else { unreachable!() };
macro_rules! is_a_filter {
() => {
variants.iter()
.filter(|v| if let VariantData::Tuple(_) = v.data { true } else { false })
};
}
let variant_names = is_a_filter!()
.map(|v| v.ident.clone())
.collect::<Vec<Ident>>();
let function_names = is_a_filter!()
.map(|v| format!("is_{}", to_snake_case(&v.ident)).into())
.collect::<Vec<Ident>>();
let variant_counts = is_a_filter!()
.map(|v| vec!(Ident::new("_"); v.data.fields().len()))
.collect::<Vec<_>>();
let getter_names = vec!(name.clone(); variant_names.len());
quote! {
#[allow(dead_code)]
impl #name {
#(pub fn #function_names(&self) -> bool {
if let &#getter_names::#variant_names(#(#variant_counts),*) = self {
true
}
else {
false
}
})*
}
}
}
fn impl_unit_enum_is_a(ast: &DeriveInput) -> quote::Tokens {
let ref name = ast.ident;
let variants =
if let Body::Enum(ref e) = ast.body { e }
else { unreachable!() };
macro_rules! is_a_filter {
() => {
variants.iter()
.filter(|v| if let VariantData::Unit = v.data { true } else { false })
};
}
let variant_names = is_a_filter!()
.map(|v| v.ident.clone())
.collect::<Vec<Ident>>();
let function_names = is_a_filter!()
.map(|v| format!("is_{}", to_snake_case(&v.ident)).into())
.collect::<Vec<Ident>>();
let getter_names = vec!(name.clone(); variant_names.len());
quote! {
#[allow(dead_code)]
impl #name {
#(pub fn #function_names(&self) -> bool {
if let &#getter_names::#variant_names = self {
true
}
else {
false
}
})*
}
}
}
fn impl_struct_enum_is_a(ast: &DeriveInput) -> quote::Tokens {
let ref name = ast.ident;
let variants =
if let Body::Enum(ref e) = ast.body { e }
else { unreachable!() };
macro_rules! is_a_filter {
() => {
variants.iter()
.filter(|v| if let VariantData::Struct(_) = v.data { true } else { false })
};
}
let variant_names = is_a_filter!()
.map(|v| v.ident.clone())
.collect::<Vec<Ident>>();
let function_names = is_a_filter!()
.map(|v| format!("is_{}", to_snake_case(&v.ident)).into())
.collect::<Vec<Ident>>();
let variant_field_names = is_a_filter!()
.map(|v| v.data.fields().iter().map(|ref f| f.ident.as_ref().unwrap()).collect::<Vec<_>>())
.collect::<Vec<_>>();
let variant_counts = is_a_filter!()
.map(|v| vec!(Ident::new("_"); v.data.fields().len()))
.collect::<Vec<_>>();
let getter_names = vec!(name.clone(); variant_names.len());
quote! {
#[allow(dead_code)]
impl #name {
#(pub fn #function_names(&self) -> bool {
if let &#getter_names::#variant_names { #(#variant_field_names: #variant_counts),* } = self {
true
}
else {
false
}
})*
}
}
}