use darling::FromMeta;
use proc_macro2::Span;
use quote::{ToTokens, TokenStreamExt};
use syn::{
Attribute, Ident, LitInt, Meta, MetaList, Path, parse::Parse, parse2, punctuated::Punctuated,
spanned::Spanned, token::Comma,
};
#[derive(Debug, Clone, Copy)]
pub struct FieldAttrs {
pub bits: Option<usize>,
}
impl TryFrom<&[Attribute]> for FieldAttrs {
type Error = darling::Error;
fn try_from(value: &[Attribute]) -> Result<Self, Self::Error> {
let mut bits = None;
for Attribute {
pound_token: _,
style: _,
bracket_token: _,
meta,
} in value
{
match meta {
Meta::Path(_path) => {}
Meta::List(MetaList {
path,
delimiter: _,
tokens,
}) => {
let ident = match path.get_ident() {
Some(ident) => Ok(ident),
None => Err(syn::Error::new(
path.span(),
format!("Expected ident got {path:?}"),
)),
}?;
let ident_str = ident.to_string();
match ident_str.as_str() {
"bits" => {
let size: LitInt = parse2(tokens.clone())?;
bits = Some(size.base10_parse()?);
}
ident_str => {
return Err(syn::Error::new(
path.span(),
format!("Unknown attribute {ident_str}"),
)
.into());
}
}
}
Meta::NameValue(_meta_name_value) => {}
}
}
Ok(Self { bits })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, FromMeta)]
pub enum BitOrder {
#[default]
Lsb0,
Msb0,
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy, FromMeta, PartialEq, Eq)]
pub enum Repr {
u8,
u16,
u32,
u64,
}
impl ToTokens for Repr {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let str = match self {
Repr::u8 => "u8",
Repr::u16 => "u16",
Repr::u32 => "u32",
Repr::u64 => "u64",
};
tokens.append(Ident::new(str, Span::call_site()));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Impls {
Debug,
}
impl Parse for Impls {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
let ident_str = ident.to_string().to_lowercase();
Ok(match ident_str.as_str() {
"debug" => Self::Debug,
ident_str => {
return Err(syn::Error::new(
ident.span(),
format!("Unkown impl `{ident_str}`"),
));
}
})
}
}
#[derive(Debug, FromMeta)]
#[darling(derive_syn_parse)]
pub struct BitFieldArgs {
#[darling(default)]
pub repr: Option<Repr>,
#[darling(default)]
pub container: List<Path>,
#[darling(default)]
pub derives: List<Path>,
#[darling(default)]
pub impls: List<Impls>,
#[darling(default)]
pub bitorder: BitOrder,
}
#[derive(Debug, Clone)]
pub struct List<T>(Punctuated<T, Comma>);
impl<T> List<T> {
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn first(&self) -> Option<&T> {
self.0.first()
}
}
impl<T: Parse> FromMeta for List<T> {
fn from_meta(item: &Meta) -> darling::Result<Self> {
match item {
Meta::List(meta_list) => {
let tokens = meta_list.clone().tokens;
let list = parse2(tokens).map_err(darling::Error::from)?;
Ok(list)
}
_ => Err(darling::Error::custom("Expected a list of identifiers")),
}
}
}
impl<T: Parse> Parse for List<T> {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self(Punctuated::parse_terminated(input)?))
}
}
impl<T> ToTokens for List<T>
where
T: ToTokens,
{
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.0.to_tokens(tokens);
}
}
impl<T> Default for List<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T> IntoIterator for List<T> {
type Item = T;
type IntoIter = <Punctuated<T, Comma> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use darling::ast::NestedMeta;
use quote::quote;
#[test]
fn parse_repr() {
let tokens = quote! {
repr(u8),
};
let attrs = NestedMeta::parse_meta_list(tokens).unwrap();
let bitfield_args = BitFieldArgs::from_list(&attrs).unwrap();
assert_eq!(bitfield_args.repr, Some(Repr::u8));
}
#[test]
fn parse_repr_invalid() {
let tokens = quote! {
repr(i8),
};
let attrs = NestedMeta::parse_meta_list(tokens).unwrap();
let bitfield_args = BitFieldArgs::from_list(&attrs);
assert!(bitfield_args.is_err());
}
#[test]
fn parse_repr_multiple() {
let tokens = quote! {
repr(u8, u16),
};
let attrs = NestedMeta::parse_meta_list(tokens).unwrap();
let bitfield_args = BitFieldArgs::from_list(&attrs);
assert!(bitfield_args.is_err());
}
#[test]
fn parse_container() {
let tokens = quote! {
container(u8),
};
let attrs = NestedMeta::parse_meta_list(tokens).unwrap();
let bitfield_args = BitFieldArgs::from_list(&attrs).unwrap();
let first = bitfield_args.container.first().unwrap();
let ident_str = first.get_ident().unwrap().to_string();
assert_eq!(ident_str, "u8");
}
#[test]
fn parse_impls() {
let tokens = quote! {
impls(debug),
};
let attrs = NestedMeta::parse_meta_list(tokens).unwrap();
let bitfield_args = BitFieldArgs::from_list(&attrs).unwrap();
assert_eq!(bitfield_args.impls.len(), 1);
assert_eq!(bitfield_args.impls.first(), Some(&Impls::Debug));
}
#[test]
fn parse_bitorder() {
let tokens = quote! {
bitorder(msb0),
};
let attrs = NestedMeta::parse_meta_list(tokens).unwrap();
let bitfield_args = BitFieldArgs::from_list(&attrs).unwrap();
assert_eq!(bitfield_args.bitorder, BitOrder::Msb0);
}
#[test]
fn parse_bitorder_default() {
let tokens = quote! {};
let attrs = NestedMeta::parse_meta_list(tokens).unwrap();
let bitfield_args = BitFieldArgs::from_list(&attrs).unwrap();
assert_eq!(bitfield_args.bitorder, BitOrder::Lsb0);
}
}