fray-macro 0.1.2

Macros to generate bitfield structs for fray
Documentation
use crate::args::FieldAttrs;

use super::args::BitFieldArgs;
use super::impls;
use darling::{FromMeta, ast::NestedMeta};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Field, Ident, ItemStruct, Path, Visibility, parse2};

#[derive(Debug, Clone)]
pub enum SynField<'a> {
    Field(&'a Field),
    Unused(&'a Field),
}

impl<'a> SynField<'a> {
    pub fn is_used(&self) -> bool {
        match self {
            SynField::Field(_field) => true,
            SynField::Unused(_field) => false,
        }
    }

    pub fn used_field(self) -> Option<&'a Field> {
        match self {
            SynField::Field(field) => Some(field),
            SynField::Unused(_field) => None,
        }
    }
}

impl<'a> From<&'a Field> for SynField<'a> {
    fn from(value: &'a Field) -> Self {
        let is_unused = value
            .ident
            .as_ref()
            .is_some_and(|field| field.to_string().starts_with("_"));
        match is_unused {
            true => Self::Unused(value),
            false => Self::Field(value),
        }
    }
}

impl<'a> From<SynField<'a>> for &'a Field {
    fn from(value: SynField<'a>) -> Self {
        match value {
            SynField::Field(field) => field,
            SynField::Unused(field) => field,
        }
    }
}

pub fn bitfield_impl(
    attrs: proc_macro2::TokenStream,
    input: proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream, darling::Error> {
    let item_struct = parse2::<ItemStruct>(input)?;
    let attr_args = NestedMeta::parse_meta_list(attrs)?;
    let args = BitFieldArgs::from_list(&attr_args)?;

    let ItemStruct {
        attrs: _,
        vis: struct_vis,
        struct_token: _,
        ident: struct_ident,
        generics: _,
        fields: struct_fields,
        semi_token: _,
    } = item_struct;

    let struct_fields = match struct_fields {
        syn::Fields::Named(fields_named) => fields_named,
        _ => {
            return Err(darling::Error::custom(
                "This macro only supports structs with named fields.",
            ));
        }
    };

    let container_type = container_type(&args.repr, args.container)?;

    let fields = (&struct_fields.named).into_iter();

    let struct_def = struct_def(&struct_ident, &struct_vis, &container_type, &args.derives);
    let struct_impls = struct_impls(&struct_ident, &container_type, args.bitorder);

    let field_defs = field_defs(fields.clone().map(SynField::from));
    let field_impls = field_impls(
        &struct_ident,
        fields.clone().map(SynField::from),
        args.bitorder,
    );

    let impls = args.impls.into_iter().map(|imp| match imp {
        crate::args::Impls::Debug => {
            impls::debug_impl(&struct_ident, fields.clone().map(SynField::from))
        } // crate::args::Impls::IntoInner => impls::into_inner(&struct_ident),
    });

    Ok(quote! {
        #struct_def

        #struct_impls

        #field_defs

        #field_impls

        #(#impls)*
    })
}

fn container_type(
    repr: &Option<crate::args::Repr>,
    container: crate::args::List<Path>,
) -> Result<TokenStream, darling::Error> {
    if container.len() > 1 {
        return Err(darling::Error::custom(
            "Only one BitContainer must be specified in `container`.",
        ));
    }
    Ok(match (repr, container.first()) {
        (None, None) => {
            return Err(darling::Error::custom(
                "Either `repr` or `container` must be specified.",
            ));
        }
        (Some(_), Some(_)) => {
            return Err(darling::Error::custom(
                "Only one of `repr` or `container` must be specified.",
            ));
        }
        (None, Some(container)) => quote! {
            #container
        },
        (Some(repr), None) => quote! {
            ::fray::iterable::BitIterableContainer<::core::primitive::#repr>
        },
    })
}

fn struct_def(
    struct_ident: &Ident,
    struct_vis: &Visibility,
    container_type: &TokenStream,
    derives: &super::args::List<Path>,
) -> TokenStream {
    quote! {
        #[derive(#derives)]
        #struct_vis struct #struct_ident(#container_type);
    }
}

fn struct_impls(
    struct_ident: &Ident,
    container_type: &TokenStream,
    bitorder: crate::args::BitOrder,
) -> TokenStream {
    let bitorder = match bitorder {
        crate::args::BitOrder::Lsb0 => quote! {LSB0},
        crate::args::BitOrder::Msb0 => quote! {MSB0},
    };
    quote! {
        impl ::fray::BitFieldImpl for #struct_ident {
            type Container = #container_type;
            type BitOrder = ::fray::bitorder::#bitorder;
        }

        impl ::core::convert::From<#container_type> for #struct_ident {
            fn from(container: #container_type) -> Self {
                Self(container)
            }
        }

        impl ::core::convert::From<#struct_ident> for #container_type {
            fn from(value: #struct_ident) -> Self {
                value.0
            }
        }

        impl ::core::convert::AsRef<#container_type> for #struct_ident {
            fn as_ref(&self) -> &#container_type {
                &self.0
            }
        }

        impl ::core::convert::AsMut<#container_type> for #struct_ident {
            fn as_mut(&mut self) -> &mut #container_type {
                &mut self.0
            }
        }
    }
}

fn field_defs<'a, I>(fields: I) -> TokenStream
where
    I: Iterator<Item = SynField<'a>>,
{
    let field_defs = fields
        .filter_map(SynField::used_field)
        .map(|field| field.ident.as_ref().unwrap())
        .map(|ident| {
            quote! {
                pub enum #ident {}
            }
        });
    quote! {
        #(#field_defs)*
    }
}

fn field_impls<'a, I>(
    struct_ident: &Ident,
    fields: I,
    bitorder: crate::args::BitOrder,
) -> TokenStream
where
    I: Iterator<Item = SynField<'a>>,
{
    let field_path = quote! {::fray::Field<#struct_ident>};
    let mut unused_field_sizes = vec![];
    let mut field_impls = vec![];
    let mut last_ident = None;
    for field in fields {
        let is_used = field.is_used();
        let field: &Field = field.into();
        let syn::Field {
            attrs,
            vis: _,
            mutability: _,
            ident,
            colon_token: _,
            ty,
        } = field;
        let field_attrs = FieldAttrs::try_from(attrs.as_slice()).unwrap();
        let container_size = quote! {<<#struct_ident as ::fray::BitFieldImpl>::Container as ::fray::BitContainer>::SIZE};
        let size = match field_attrs.bits {
            Some(size) => quote! {#size},
            None => quote! {<#ty as ::fray::FieldType>::SIZE},
        };
        if !is_used {
            unused_field_sizes.push(size);
            continue;
        }
        let offset = last_ident
            .map(|last_ident| {
                match bitorder {
                    crate::args::BitOrder::Lsb0 => {
                        quote! { <#last_ident as #field_path>::OFFSET #(+#unused_field_sizes)* + <#last_ident as #field_path>::SIZE }
                    }
                    crate::args::BitOrder::Msb0 => {
                        quote! { <#last_ident as #field_path>::OFFSET - (0 #(+#unused_field_sizes)*) - <Self as #field_path>::SIZE }
                    },
                }
            })
            .unwrap_or(match bitorder {
                crate::args::BitOrder::Lsb0 => quote! {0 #(+#unused_field_sizes)* },
                crate::args::BitOrder::Msb0 => quote! {#container_size - (0 #(+#unused_field_sizes)*) - <Self as #field_path>::SIZE},
            });
        unused_field_sizes.clear();
        field_impls.push(quote! {
            const _: () = {
               assert!(
                   <#ident as #field_path>::SIZE + <#ident as #field_path>::OFFSET <= #container_size,
                   ::core::concat!("field ", ::core::stringify!(#ident), " overflow its container")
               );
            };

            impl #field_path for #ident {
                type Type = #ty;
                type BitsType = <#ty as ::fray::FieldType>::BitsType;
                const OFFSET: usize = #offset;
                const SIZE: usize = #size;
            }
        });
        last_ident = Some(ident);
    }
    quote! {
        #(#field_impls)*
    }
}