use crate::bitfield::{
is_int_size_regular_type, try_parse_arbitrary_int_type, ArrayInfo, BaseDataSize, CustomType,
FieldDefinition, BITCOUNT_BOOL,
};
use proc_macro2::{Ident, Literal, Punct, TokenStream as TokenStream2, TokenTree};
use quote::{quote, ToTokens};
use std::ops::{Deref, Range};
use syn::{
parse2, spanned::Spanned, Attribute, Error, ExprArray, Field, Fields, GenericArgument,
MetaList, PathArguments, Result, Type,
};
pub fn parse(fields: &Fields, base_data_size: BaseDataSize) -> Result<Vec<FieldDefinition>> {
let mut field_definitions = Vec::with_capacity(fields.len());
for field in fields {
match parse_field(base_data_size.internal, field) {
Ok(def) => field_definitions.push(def),
Err(ts) => return Err(ts),
}
}
Ok(field_definitions)
}
fn parse_scalar_field(ty: &Type) -> Result<Option<(usize, bool)>> {
match ty {
Type::Path(path) => {
let type_str = path.to_token_stream().to_string();
let result = match type_str.as_str() {
"bool" => Some(Some((BITCOUNT_BOOL, false))),
"u8" => Some(Some((8, false))),
"i8" => Some(Some((8, true))),
"u16" => Some(Some((16, false))),
"i16" => Some(Some((16, true))),
"u32" => Some(Some((32, false))),
"i32" => Some(Some((32, true))),
"u64" => Some(Some((64, false))),
"i64" => Some(Some((64, true))),
"u128" => Some(Some((128, false))),
"i128" => Some(Some((128, true))),
_ => None,
};
if let Some(value) = result {
return Ok(value);
}
if let Some(last_segment) = path.path.segments.last() {
return Ok(try_parse_arbitrary_int_type(&last_segment.ident.to_string(), true));
}
Err(Error::new(path.span(), "invalid path for bitfield field"))
}
_ => Err(Error::new(
ty.span(),
format!(
"bitfield!: Field type {} not valid. Supported types: bool, u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, arbitrary int (e.g., u1, u3, u62, i81). Their arrays are also supported.",
ty.into_token_stream()
),
)),
}
}
fn parse_enumeration(ty: &Type, number_of_bits: usize) -> Result<(CustomType, Type, Type)> {
let (inner_type, result_type) = if let Type::Path(type_path) = ty {
if let Some(last_segment) = type_path.path.segments.last() {
if last_segment.ident == "Option" {
match &last_segment.arguments {
PathArguments::AngleBracketed(args) => {
if args.args.len() != 1 {
return Err(Error::new_spanned(last_segment, "Invalid Option<T> path. Expected exactly one generic type argument"));
}
let option_generic_type = args.args.last().unwrap();
match option_generic_type {
GenericArgument::Type(generic_type) => {
let enum_fallback_value = {
let type_string = if is_int_size_regular_type(number_of_bits) {
format!("u{}", number_of_bits)
} else {
format!("arbitrary_int::u{}", number_of_bits)
};
let Ok(result) = syn::parse_str::<Type>(type_string.as_str())
else {
return Err(Error::new_spanned(
option_generic_type,
"bitfield!: Error parsing unsigned_field_type",
));
};
result
};
let result_type_string = format!(
"Result<{}, {}>",
generic_type.to_token_stream(),
enum_fallback_value.to_token_stream(),
);
let result_type = syn::parse_str::<Type>(&result_type_string)
.expect("bitfield!: Error creating type from Result<,>");
(generic_type, result_type)
}
_ => {
return Err(Error::new_spanned(
option_generic_type,
"Invalid Option binding: Expected generic type",
))
}
}
}
_ => panic!("Expected < after Option"),
}
} else {
(ty, ty.clone())
}
} else {
(ty, ty.clone())
}
} else {
(ty, ty.clone())
};
Ok((
CustomType::Yes(Box::new(inner_type.clone())),
result_type,
inner_type.clone(),
))
}
fn parse_field(base_data_size: usize, field: &Field) -> Result<FieldDefinition> {
let field_name = field.ident.as_ref().unwrap();
let (ty, indexed_count) = {
match &field.ty {
Type::Array(ty) => {
let length = (&ty.len).into_token_stream().to_string();
(
ty.elem.deref(),
Some(
length
.parse::<usize>()
.unwrap_or_else(|_| panic!("{} is not a valid number", length)),
),
)
}
_ => (&field.ty, None),
}
};
let field_type_size_from_data_type = parse_scalar_field(ty)?;
let unsigned_field_type = if let Some((bits, is_signed)) = field_type_size_from_data_type {
if is_signed {
Some(
syn::parse_str::<Type>(format!("u{}", bits).as_str())
.unwrap_or_else(|_| panic!("bitfield!: Error parsing unsigned_field_type")),
)
} else {
None
}
} else {
None
};
let mut ranges: Vec<Range<usize>> = Vec::new();
let mut ranges_token: Option<u32> = None;
let mut provide_getter = false;
let mut provide_setter = false;
let mut indexed_stride: Option<usize> = None;
let mut doc_comment: Vec<Attribute> = Vec::new();
for attr in &field.attrs {
let attr_name = &attr
.path()
.segments
.first()
.unwrap_or_else(|| panic!("bitfield!: Invalid path"))
.ident;
match attr_name.to_string().as_str() {
start @ ("bits" | "bit") => {
let is_range = start == "bits";
let range_span = attr
.meta
.require_list()
.unwrap()
.tokens
.clone()
.into_iter()
.take_while(|t| &t.to_string() != ",")
.collect::<proc_macro2::TokenStream>();
let mut finished_argument = |range_parser: ArgumentParser,
is_in_array: bool,
token_id: u32|
-> Result<()> {
match range_parser {
ArgumentParser::RangeGotBothLimits(_, _)
| ArgumentParser::RangeGotLowerLimit(_) => {
if is_in_array {
if let Some(ranges_token) = ranges_token {
if ranges_token != token_id {
return Err(Error::new_spanned(
&range_span,
"bitfield!: Seen multiple bit-ranges, but only one is allowed",
));
}
}
} else if !ranges.is_empty() {
return Err(Error::new_spanned(
&range_span,
"bitfield!: Seen multiple bit-ranges, but only one is allowed",
));
}
ranges_token = Some(token_id);
}
_ => {}
}
match range_parser {
ArgumentParser::RangeGotBothLimits(lower, upper) => {
if !is_in_array && !is_range {
return Err(Error::new_spanned(
&range_span,
"bitfield!: bit requires an inclusive range, for example bits(10..=19). bit(10) allows specifying a single bit",
));
}
ranges.push(Range {
start: lower,
end: upper + 1,
});
}
ArgumentParser::RangeGotLowerLimit(lower) => {
if is_range && !is_in_array {
return Err(Error::new_spanned(
&range_span,
"bitfield!: bits requires a single bit, for example bit(10). bits(10..=12) can be used to specify multiple bits",
));
}
ranges.push(Range {
start: lower,
end: lower + 1,
});
}
ArgumentParser::ReadWrite => {
provide_getter = true;
provide_setter = true;
}
ArgumentParser::Read => {
provide_getter = true;
}
ArgumentParser::Write => {
provide_setter = true;
}
ArgumentParser::StrideComplete(stride) => {
if indexed_count.is_none() {
return Err(Error::new_spanned(
&attr.meta,
"bitfield!: stride is only supported for indexed properties. Use array type (e.g. [u8; 8]) to indicate"
));
}
indexed_stride = Some(stride);
}
ArgumentParser::Reset => {
}
_ =>
Err(Error::new_spanned(
&range_span,
"bitfield!: Invalid syntax. Supported: bits(5..=6, access, stride = x), where x is an integer and access can be r, w or rw",
))?,
};
Ok(())
};
ArgumentParser::parse_argument_tokens(
parse2::<MetaList>(attr.meta.to_token_stream())
.unwrap()
.tokens,
false,
&mut finished_argument,
None,
)?;
let token_string = attr.meta.to_token_stream().to_string();
assert!(token_string.starts_with("bit"));
let attr_token_string = if token_string.starts_with("bits") {
token_string.trim_start_matches("bits").trim()
} else {
token_string.trim_start_matches("bit").trim()
};
if &attr_token_string[..1] != "(" {
return Err(Error::new_spanned(
&attr.meta,
format!("bitfield!: Expected '(' after '{}'", start),
));
}
if &attr_token_string[attr_token_string.len() - 1..] != ")" {
return Err(Error::new_spanned(
&attr.meta,
format!("bitfield!: Expected ')' to close '{}'", start),
));
}
}
"doc" => {
doc_comment.push(attr.clone());
}
_ => {
return Err(Error::new_spanned(
attr_name,
format!(
"bitfield!: Unhandled attribute '{}'. Only supported attributes are 'bit' or 'bits'",
attr_name)
));
}
}
}
let number_of_bits = ranges.iter().fold(0, |a, b| a + b.end - b.start);
let (field_type_size, primitive_type) = match field_type_size_from_data_type {
None => (number_of_bits, {
if number_of_bits <= 8 {
quote! { u8 }
} else if number_of_bits <= 16 {
quote! { u16 }
} else if number_of_bits <= 32 {
quote! { u32 }
} else if number_of_bits <= 64 {
quote! { u64 }
} else if number_of_bits <= 128 {
quote! { u128 }
} else {
panic!("bitfield!: number_of_bits is too large!")
}
}),
Some((b, _is_signed)) => (b, quote! { #ty }),
};
if field_type_size == BITCOUNT_BOOL {
if number_of_bits != 1 || ranges.len() != 1 {
return Err(Error::new_spanned(
field.attrs.first(),
format!("bitfield!: Field {} is a bool, so it should only use a single bit (use syntax 'bit({})' instead)", field_name, ranges[0].start)
));
}
} else if number_of_bits != field_type_size {
return Err(Error::new_spanned(
&field.ty,
format!("bitfield!: Field {} has type {}, which doesn't match the number of bits ({}) that are being used for it", field_name, ty.to_token_stream(), number_of_bits)
));
}
if let Some(indexed_count) = indexed_count {
verify_bounds_for_array(
field,
field_name,
&ranges,
indexed_count,
&mut indexed_stride,
number_of_bits,
base_data_size,
)?;
} else {
verify_bits_in_range(field, field_name, &ranges, base_data_size)?;
}
let (custom_type, getter_type, setter_type) = if field_type_size_from_data_type.is_none() {
parse_enumeration(ty, number_of_bits)?
} else {
(CustomType::No, ty.clone(), ty.clone())
};
let use_regular_int = match field_type_size_from_data_type {
Some((i, _is_signed)) => is_int_size_regular_type(i),
None => {
number_of_bits != 1 && is_int_size_regular_type(number_of_bits)
}
};
Ok(FieldDefinition {
field_name: field_name.clone(),
ranges,
field_type_size,
getter_type: if provide_getter {
Some(getter_type)
} else {
None
},
setter_type: if provide_setter {
Some(setter_type)
} else {
None
},
use_regular_int,
primitive_type,
custom_type,
doc_comment,
array: indexed_count.map(|count| ArrayInfo {
count,
indexed_stride: indexed_stride.unwrap(),
}),
field_type_size_from_data_type: field_type_size_from_data_type.map(|v| v.0),
is_signed: field_type_size_from_data_type.is_some_and(|v| v.1),
unsigned_field_type,
})
}
fn verify_bits_in_range(
field: &Field,
field_name: &Ident,
ranges: &[Range<usize>],
base_data_size: usize,
) -> syn::Result<()> {
let highest_bit_index_in_ranges = ranges.iter().map(|range| range.end).max().unwrap_or(0);
if highest_bit_index_in_ranges > base_data_size {
return Err(Error::new_spanned(
field.attrs.first(),
format!(
"bitfield!: Field {} requires {} bits, but only has ({})",
field_name, highest_bit_index_in_ranges, base_data_size
),
));
}
Ok(())
}
fn verify_bounds_for_array(
field: &Field,
field_name: &Ident,
ranges: &[Range<usize>],
indexed_count: usize,
indexed_stride: &mut Option<usize>,
number_of_bits: usize,
base_data_size: usize,
) -> syn::Result<()> {
if ranges.len() == 1 {
let indexed_stride = *indexed_stride.get_or_insert(number_of_bits);
if number_of_bits > indexed_stride {
return Err(Error::new_spanned(
field.attrs.first(),
format!(
"bitfield!: Field {} is declared as {} bits, which is larger than the stride {}",
field_name,
number_of_bits,
indexed_stride
),
));
}
} else {
if indexed_stride.is_none() {
return Err(Error::new_spanned(
field,
format!(
"bitfield!: Field {} is declared as non-contiguous and array, so it needs a stride. Specify using \"stride = x\".",
field_name,
),
));
}
}
let highest_bit_index_in_ranges = ranges.iter().map(|range| range.end).max().unwrap_or(0);
let number_of_bits_indexed =
(indexed_count - 1) * indexed_stride.unwrap() + highest_bit_index_in_ranges;
if number_of_bits_indexed > base_data_size {
return Err(Error::new_spanned(
field.attrs.first(),
format!(
"bitfield!: Array-field {} requires {number_of_bits_indexed} bits for the array, but only has ({})",
field_name,
base_data_size
)
));
}
if indexed_count < 2 {
return Err(Error::new_spanned(
&field.ty,
format!(
"bitfield!: Field {} is declared as array, but with fewer than 2 elements.",
field_name
),
));
}
Ok(())
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum ArgumentParser {
Reset,
ResetOnlyRangeAllowed,
RangeGotLowerLimit(usize),
RangeGotFirstPeriod(usize),
RangeGotSecondPeriod(usize),
RangeGotEquals(usize),
RangeGotBothLimits(usize, usize),
StrideStarted,
HasStrideEquals,
StrideComplete(usize),
Read,
Write,
ReadWrite,
}
impl ArgumentParser {
fn parse_literal_number(number: Literal) -> Result<usize> {
number
.to_string()
.parse()
.map_err(|_| Error::new_spanned(&number, "bitfield!: Not a valid number in bitrange."))
}
pub fn take_literal(&self, lit: Literal) -> Result<ArgumentParser> {
match self {
ArgumentParser::Reset | ArgumentParser::ResetOnlyRangeAllowed => Ok(
ArgumentParser::RangeGotLowerLimit(Self::parse_literal_number(lit)?),
),
ArgumentParser::RangeGotEquals(lower) => Ok(ArgumentParser::RangeGotBothLimits(
*lower,
Self::parse_literal_number(lit)?,
)),
ArgumentParser::HasStrideEquals => Ok(ArgumentParser::StrideComplete(
Self::parse_literal_number(lit)?,
)),
_ => Err(Error::new_spanned(
&lit,
"bitfield!: Invalid bit-range. Expected x..=y, for example 6..=10.",
)),
}
}
fn take_punct(&self, punct: Punct) -> Result<ArgumentParser> {
match self {
ArgumentParser::RangeGotLowerLimit(lower) if punct.as_char() == '.' => {
Ok(ArgumentParser::RangeGotFirstPeriod(*lower))
}
ArgumentParser::RangeGotFirstPeriod(lower) if punct.as_char() == '.' => {
Ok(ArgumentParser::RangeGotSecondPeriod(*lower))
}
ArgumentParser::RangeGotSecondPeriod(lower) if punct.as_char() == '=' => {
Ok(ArgumentParser::RangeGotEquals(*lower))
}
ArgumentParser::StrideStarted if punct.as_char() == '=' || punct.as_char() == ':' => {
Ok(ArgumentParser::HasStrideEquals)
}
_ => Err(Error::new_spanned(
&punct,
"bitfield!: Invalid bit-range. Expected x..=y, for example 6..=10.",
)),
}
}
fn take_ident(&self, id: Ident) -> Result<ArgumentParser> {
let s = id.to_string();
match self {
ArgumentParser::Reset if s == "rw" => Ok(ArgumentParser::ReadWrite),
ArgumentParser::Reset if s == "r" => Ok(ArgumentParser::Read),
ArgumentParser::Reset if s == "w" => Ok(ArgumentParser::Write),
ArgumentParser::Reset if s == "stride" => Ok(ArgumentParser::StrideStarted),
_ => Err(Error::new_spanned(
&id,
format!(
"bitfield!: Invalid ident '{}'. Expected r, rw, w or stride",
s
),
)),
}
}
fn parse_argument_tokens<F: FnMut(ArgumentParser, bool, u32) -> Result<()>>(
token_stream: TokenStream2,
is_in_array: bool,
finished_argument: &mut F,
outer_token_id: Option<u32>,
) -> Result<()> {
let reset = if is_in_array {
Self::ResetOnlyRangeAllowed
} else {
Self::Reset
};
let mut token_id = 0;
let mut argument_parser = reset;
for meta in token_stream {
match meta {
TokenTree::Group(group) => {
let range_array = parse2::<ExprArray>(group.to_token_stream()).unwrap();
for range in range_array.elems {
Self::parse_argument_tokens(
range.to_token_stream(),
true,
finished_argument,
Some(token_id),
)?;
}
}
TokenTree::Ident(id) => {
argument_parser = argument_parser.take_ident(id)?;
}
TokenTree::Punct(punct) => match punct.as_char() {
',' => {
finished_argument(
argument_parser,
is_in_array,
outer_token_id.unwrap_or(token_id),
)?;
argument_parser = reset;
}
_ => {
argument_parser = argument_parser.take_punct(punct)?;
}
},
TokenTree::Literal(lit) => {
argument_parser = argument_parser.take_literal(lit)?;
}
}
token_id += 1;
}
finished_argument(
argument_parser,
is_in_array,
outer_token_id.unwrap_or(token_id),
)?;
Ok(())
}
}