mcu-comms-macros 0.1.0

An internal procedural macro library for `mcu-comms` that provides the `#[payload]` macro.
Documentation
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Data, DeriveInput, Fields};

#[proc_macro_attribute]
pub fn payload(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input: DeriveInput = syn::parse(item).unwrap();
    let name = &input.ident;

    let field_sizes = field_sizes(&input.data);

    let expanded = quote! {
        #[derive(
            ::mcu_comms::serde::Serialize,
            ::mcu_comms::serde::Deserialize,
        )]
        #[serde(crate = "::mcu_comms::serde")]
        #input

        impl ::mcu_comms::payload_size::MaxSize for #name {
            const MAX_SIZE: usize = 0 #(+ #field_sizes)*;
        }
        impl ::mcu_comms::payload_size::MaxPayloadSize for #name {
            const FRAME_SIZE: usize = <#name as ::mcu_comms::payload_size::MaxSize>::MAX_SIZE
                + ::mcu_comms::aesccm::HEADER_SIZE
                + ::mcu_comms::aesccm::TAG_SIZE;
            type FrameBuf = [u8; Self::FRAME_SIZE];
            fn new_buf() -> Self::FrameBuf {
                [0_u8; Self::FRAME_SIZE]
            }
        }
        impl ::mcu_comms::payload_size::Payload for #name {}
    };
    expanded.into()
}

fn field_sizes(data: &syn::Data) -> Vec<TokenStream2> {
    let field_sizes = match data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(fields) => fields
                .named
                .iter()
                .map(|f| {
                    let ty = &f.ty;
                    quote! { <#ty as ::mcu_comms::payload_size::MaxSize>::MAX_SIZE }
                })
                .collect(),
            Fields::Unnamed(fields) => fields
                .unnamed
                .iter()
                .map(|f| {
                    let ty = &f.ty;
                    quote! {<#ty as ::mcu_comms::payload_size::MaxSize>::MAX_SIZE}
                })
                .collect(),
            Fields::Unit => vec![],
        },
        Data::Enum(data) => {
            let variant_sizes: Vec<TokenStream2> = data
                .variants
                .iter()
                .map(|v| {
                    let field_sizes: Vec<TokenStream2> = v
                        .fields
                        .iter()
                        .map(|f| {
                            let ty = &f.ty;
                            quote! { <#ty as ::mcu_comms::payload_size::MaxSize>::MAX_SIZE }
                        })
                        .collect();
                    quote! { (0 #(+ #field_sizes)*) }
                })
                .collect();

            vec![quote! {{
                const fn max(a: usize, b: usize) -> usize {
                    if a > b { a } else { b }
                }
                let mut m = 0;
                #( m = max(m, #variant_sizes); )*
                m + 5
            }}]
        }
        _ => panic!("Payload does not support this type"),
    };
    field_sizes
}