#![warn(missing_docs)]
use quote::quote;
use syn;
use proc_macro::TokenStream;
macro_rules! get_encoding {
($meta:expr) => {
match $meta {
syn::Meta::List(syn::MetaList {
path: p,
delimiter: _,
tokens: t,
}) => {
let i = p.get_ident()?;
let mode = if format!("{}", i) == "kencode" {
quote! { cfd16_lib_impl::Mode::Kernel }
} else if format!("{}", i) == "uencode" {
quote! { cfd16_lib_impl::Mode::User }
} else if format!("{}", i) == "encode" {
quote! { _ }
} else {
return None;
};
Some((mode.into(), t.clone().into()))
}
_ => None,
}
};
}
#[proc_macro_derive(Codable, attributes(encode, uencode, kencode))]
pub fn codable_derive(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::ItemEnum);
let name = input.ident;
let mut encode_impl = quote! {};
for variant in input.variants.iter() {
if !matches!(variant.fields, syn::Fields::Unit) {
panic!("Cannot derive Codable implementation for enum with non-unit variants.");
}
let relevant: Vec<(TokenStream, TokenStream)> = variant
.attrs
.iter()
.filter_map(|attr| {
if !matches!(attr.style, syn::AttrStyle::Outer) {
return None;
}
get_encoding!(&attr.meta)
})
.collect();
if relevant.len() != 1 {
panic!("Must have exactly one valid encoding per variant.")
}
let name = variant.ident.clone();
let (_, encoding) = relevant[0].clone();
let encoding = syn::parse_macro_input!(encoding as syn::LitInt);
encode_impl.extend(quote! { #name => #encoding, });
}
let mut decode_impl = quote! {};
for variant in input.variants.iter() {
let relevant: Vec<(TokenStream, TokenStream)> = variant
.attrs
.iter()
.filter_map(|attr| {
if !matches!(attr.style, syn::AttrStyle::Outer) {
return None;
}
get_encoding!(&attr.meta)
})
.collect();
if relevant.len() != 1 {
panic!("Must have exactly one valid encoding per variant.")
}
let name = variant.ident.clone();
let (mode, encoding) = relevant[0].clone();
let encoding = syn::parse_macro_input!(encoding as syn::LitInt);
let mode: proc_macro2::TokenStream = mode.into();
decode_impl.extend(quote! { (#encoding, #mode) => Some(#name), });
}
let output = quote! {
impl cfd16_lib_impl::Codable for #name {
fn encode(&self) -> u16 {
use #name::*;
match self {
#encode_impl
}
}
fn decode(value: u16, mode: cfd16_lib_impl::Mode) -> Option<Self> {
use #name::*;
match (value, mode) {
#decode_impl
_ => panic!("Value outside allowable range."),
}
}
}
};
TokenStream::from(output)
}