Skip to main content

cfd16_lib_derive/
lib.rs

1//! A proc-macro for automatically generating encoding/decoding functions.
2
3#![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/// Check that the derive is being implemented on a basic enum without
38/// any contents on the variants. Then verify that there are code
39/// attributes on each Variant. From there assign each encode, decode
40/// mapping corresponding to the prior scan's results.
41#[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        // Ensure that the encoding token has correct type.
73        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        // Ensure that encoding and mode have correct type.
101        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}