cfd16-lib-derive 0.5.0

A proc-macro for automatically generating encoding/decoding functions.
Documentation
//! A proc-macro for automatically generating encoding/decoding functions.

#![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,
        }
    };
}

/// Check that the derive is being implemented on a basic enum without
/// any contents on the variants. Then verify that there are code
/// attributes on each Variant. From there assign each encode, decode
/// mapping corresponding to the prior scan's results.
#[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();

        // Ensure that the encoding token has correct type.
        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();

        // Ensure that encoding and mode have correct type.
        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)
}