1#![warn(missing_docs)]
4
5use quote::quote;
6use syn;
7
8use proc_macro::TokenStream;
9
10macro_rules! get_encoding {
11 ($meta:expr) => {
12 match $meta {
13 syn::Meta::List(syn::MetaList {
14 path: p,
15 delimiter: _,
16 tokens: t,
17 }) => {
18 let i = p.get_ident()?;
19 let mode = if format!("{}", i) == "kencode" {
20 quote! { cfd16_lib_impl::Mode::Kernel }
21 } else if format!("{}", i) == "uencode" {
22 quote! { cfd16_lib_impl::Mode::User }
23 } else if format!("{}", i) == "encode" {
24 quote! { _ }
25 } else {
26 return None;
27 };
28
29 Some((mode.into(), t.clone().into()))
30 }
31
32 _ => None,
33 }
34 };
35}
36
37#[proc_macro_derive(Codable, attributes(encode, uencode, kencode))]
42pub fn codable_derive(input: TokenStream) -> TokenStream {
43 let input = syn::parse_macro_input!(input as syn::ItemEnum);
44 let name = input.ident;
45
46 let mut encode_impl = quote! {};
47
48 for variant in input.variants.iter() {
49 if !matches!(variant.fields, syn::Fields::Unit) {
50 panic!("Cannot derive Codable implementation for enum with non-unit variants.");
51 }
52
53 let relevant: Vec<(TokenStream, TokenStream)> = variant
54 .attrs
55 .iter()
56 .filter_map(|attr| {
57 if !matches!(attr.style, syn::AttrStyle::Outer) {
58 return None;
59 }
60
61 get_encoding!(&attr.meta)
62 })
63 .collect();
64
65 if relevant.len() != 1 {
66 panic!("Must have exactly one valid encoding per variant.")
67 }
68
69 let name = variant.ident.clone();
70 let (_, encoding) = relevant[0].clone();
71
72 let encoding = syn::parse_macro_input!(encoding as syn::LitInt);
74
75 encode_impl.extend(quote! { #name => #encoding, });
76 }
77
78 let mut decode_impl = quote! {};
79
80 for variant in input.variants.iter() {
81 let relevant: Vec<(TokenStream, TokenStream)> = variant
82 .attrs
83 .iter()
84 .filter_map(|attr| {
85 if !matches!(attr.style, syn::AttrStyle::Outer) {
86 return None;
87 }
88
89 get_encoding!(&attr.meta)
90 })
91 .collect();
92
93 if relevant.len() != 1 {
94 panic!("Must have exactly one valid encoding per variant.")
95 }
96
97 let name = variant.ident.clone();
98 let (mode, encoding) = relevant[0].clone();
99
100 let encoding = syn::parse_macro_input!(encoding as syn::LitInt);
102 let mode: proc_macro2::TokenStream = mode.into();
103
104 decode_impl.extend(quote! { (#encoding, #mode) => Some(#name), });
105 }
106
107 let output = quote! {
108 impl cfd16_lib_impl::Codable for #name {
109 fn encode(&self) -> u16 {
110 use #name::*;
111
112 match self {
113 #encode_impl
114 }
115 }
116
117 fn decode(value: u16, mode: cfd16_lib_impl::Mode) -> Option<Self> {
118 use #name::*;
119
120 match (value, mode) {
121 #decode_impl
122 _ => panic!("Value outside allowable range."),
123 }
124 }
125 }
126 };
127
128 TokenStream::from(output)
129}