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))
} });
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)*
}
}