use quote::{
quote,
ToTokens,
};
use syn::{
Meta,
Data,
Type,
DataEnum,
Attribute,
DeriveInput,
MetaNameValue,
parse_macro_input,
};
use thiserror::Error;
use proc_macro::TokenStream;
#[derive(Error, Debug)]
enum Error {
#[error("`{0}` can only be derived for enums")]
DeriveForNonEnum(String),
#[error("Missing #[armtype = ...] attribute {0}, required for `{1}`-derived enum")]
MissingArmType(String, String),
#[error("Missing #[value = ...] attribute, expected for `{0}`-derived enum")]
MissingValue(String),
#[error("Attemping to parse non-literal attribute for `value`: not yet supported")]
NonLiteralValue,
}
#[proc_macro_derive(Const, attributes(value, armtype))]
pub fn thisenum_const(input: TokenStream) -> TokenStream {
let name = "Const";
let input = parse_macro_input!(input as DeriveInput);
let enum_name = &input.ident;
let variants = match input.data {
Data::Enum(DataEnum { variants, .. }) => variants,
_ => panic!("{}", Error::DeriveForNonEnum(name.into())),
};
let (type_name, deref) = match get_deref_type(&input.attrs) {
Some((type_name, deref)) => (type_name, deref),
None => panic!("{}", Error::MissingArmType("applied to enum".into(), name.into())),
};
let type_name_raw = match get_type(&input.attrs) {
Some(type_name_raw) => type_name_raw,
None => panic!("{}", Error::MissingArmType("applied to enum".into(), name.into())),
};
let variant_match_arms = variants
.iter()
.map(|variant| {
let variant_name = &variant.ident;
let value = match get_val(name.into(), &variant.attrs) {
Ok(value) => value,
Err(e) => panic!("{}", e),
};
match deref {
true => quote! { #enum_name::#variant_name => #value, },
false => quote! { #enum_name::#variant_name => &#value, },
}
}
);
let variant_par_eq = match deref {
true => quote! { &self.value() == other },
false => quote! { self.value() == other },
};
let expanded = quote! {
impl #enum_name {
#[inline]
#[doc = concat!("* [`&'static ", stringify!(#type_name), "`]")]
pub fn value(&self) -> &'static #type_name {
match self {
#( #variant_match_arms )*
}
}
}
impl ::std::cmp::PartialEq<#type_name_raw> for #enum_name {
#[inline]
fn eq(&self, other: &#type_name_raw) -> bool {
#variant_par_eq
}
}
};
TokenStream::from(expanded)
}
#[proc_macro_derive(ConstEach, attributes(value, armtype))]
pub fn thisenum_const_each(input: TokenStream) -> TokenStream {
let name = "ConstEach";
let input = parse_macro_input!(input as DeriveInput);
let enum_name = &input.ident;
let variants = match input.data {
Data::Enum(DataEnum { variants, .. }) => variants,
_ => panic!("{}", Error::DeriveForNonEnum(name.into())),
};
let variant_code = variants.iter().map(|variant| {
let variant_name = &variant.ident;
match (get_type(&variant.attrs), get_val(name.into(), &variant.attrs)) {
(Some(typ), Ok(value)) => quote! {
#enum_name::#variant_name => {
let val: &dyn ::std::any::Any = &(#value as #typ);
val.downcast_ref::<T>()
},
},
(None, Ok(value)) => quote! {
#enum_name::#variant_name => {
let val: &dyn ::std::any::Any = &#value;
val.downcast_ref::<T>()
},
},
(_, Err(_)) => quote! { #enum_name::#variant_name => None, },
}
});
let expanded = quote! {
impl #enum_name {
pub fn value<T: 'static>(&self) -> Option<&'static T> {
match self {
#( #variant_code )*
_ => None,
}
}
}
};
TokenStream::from(expanded)
}
fn get_val(name: String, attrs: &[Attribute]) -> Result<proc_macro2::TokenStream, Error> {
for attr in attrs {
if !attr.path.is_ident("value") { continue; }
match attr.parse_meta() {
Ok(meta) => match meta {
Meta::NameValue(MetaNameValue { lit, .. }) => return Ok(lit.into_token_stream()),
Meta::List(list) => {
let tokens = list.nested.iter().map(|nested_meta| {
match nested_meta {
syn::NestedMeta::Lit(lit) => lit.to_token_stream(),
syn::NestedMeta::Meta(meta) => meta.to_token_stream(),
}
});
return Ok(quote! { #( #tokens )* });
}
Meta::Path(_) => return Ok(meta.into_token_stream())
},
Err(_) => {
return Err(Error::NonLiteralValue);
},
}
}
Err(Error::MissingValue(name))
}
fn get_deref_type(attrs: &[Attribute]) -> Option<(Type, bool)> {
for attr in attrs {
if !attr.path.is_ident("armtype") { continue; }
let tokens = match attr.parse_args::<proc_macro2::TokenStream>() {
Ok(tokens) => tokens,
Err(_) => return None,
};
let deref = tokens
.to_string()
.trim()
.starts_with('&');
let tokens = match deref {
true => {
let mut tokens = tokens.into_iter();
let _ = tokens.next();
tokens.collect::<proc_macro2::TokenStream>()
}
false => tokens,
};
return match syn::parse2::<Type>(tokens).ok() {
Some(type_name) => Some((type_name, deref)),
None => None
}
}
None
}
fn get_type(attrs: &[Attribute]) -> Option<Type> {
for attr in attrs {
if !attr.path.is_ident("armtype") { continue; }
let tokens = match attr.parse_args::<proc_macro2::TokenStream>() {
Ok(tokens) => tokens,
Err(_) => return None,
};
return syn::parse2::<Type>(
tokens
.into_iter()
.collect::<proc_macro2::TokenStream>()
).ok()
}
None
}