use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{Attribute, Field, Item, ItemEnum, ItemStruct, Type};
use crate::shared::{self, unreachable};
pub(crate) mod struct_gen;
struct ItemIr<'a> {
attrs: &'a Vec<Attribute>,
name: &'a Ident,
expanded: TokenStream,
}
pub(super) fn bitsize_internal(args: TokenStream, item: TokenStream) -> TokenStream {
let (item, arb_int) = parse(item, args);
let ir = match item {
Item::Struct(ref item) => {
let expanded = generate_struct(item, &arb_int);
let attrs = &item.attrs;
let name = &item.ident;
ItemIr { attrs, name, expanded }
}
Item::Enum(ref item) => {
let expanded = generate_enum(item);
let attrs = &item.attrs;
let name = &item.ident;
ItemIr { attrs, name, expanded }
}
_ => unreachable(()),
};
generate_common(ir, &arb_int)
}
fn parse(item: TokenStream, args: TokenStream) -> (Item, TokenStream) {
let item = syn::parse2(item).unwrap_or_else(unreachable);
let (_declared_bitsize, arb_int) = shared::bitsize_and_arbitrary_int_from(args);
(item, arb_int)
}
fn generate_struct(struct_data: &ItemStruct, arb_int: &TokenStream) -> TokenStream {
let ItemStruct { vis, ident, fields, .. } = struct_data;
let mut previous_field_sizes = vec![];
type TokenVec = Vec<TokenStream>;
let (accessors, (constructor_args, (constructor_parts, shifted_names))): (TokenVec, (TokenVec, (TokenVec, Vec<Ident>))) = fields
.iter()
.enumerate()
.map(|(i, field)| {
let field_offset = previous_field_sizes
.iter()
.cloned()
.reduce(|acc, next| quote!(#acc + #next))
.unwrap_or_else(|| quote!(0));
let field_size = shared::generate_type_bitsize(&field.ty);
previous_field_sizes.push(field_size);
generate_field(field, &field_offset, i)
})
.unzip();
let const_ = if cfg!(feature = "nightly") { quote!(const) } else { quote!() };
quote! {
#vis struct #ident {
value: #arb_int,
}
impl #ident {
#[allow(clippy::too_many_arguments, clippy::type_complexity, missing_docs, unused_parens)]
pub #const_ fn new(#( #constructor_args )*) -> Self {
type ArbIntOf<T> = <T as Bitsized>::ArbitraryInt;
type BaseIntOf<T> = <ArbIntOf<T> as Integer>::UnderlyingType;
let mut offset = 0;
#( #constructor_parts )*
let raw_value = #( #shifted_names )|*;
let value = #arb_int::new(raw_value);
Self { value }
}
#( #accessors )*
}
}
}
fn generate_field(field: &Field, field_offset: &TokenStream, i: usize) -> (TokenStream, (TokenStream, (TokenStream, Ident))) {
let Field { ident, ty, .. } = field;
let name = if let Some(ident) = ident {
ident.clone()
} else {
let name = format!("val_{i}");
syn::parse_str(&name).unwrap_or_else(unreachable)
};
let name_str = name.to_string();
if name_str.contains("reserved_") || name_str.contains("padding_") {
let getter = generate_getter(field, field_offset, &name);
let size = shared::generate_type_bitsize(ty);
let accessors = quote!(#getter);
let constructor_arg = quote!();
let shifted_name = format!("shifted_{name}");
let shifted_name: Ident = syn::parse_str(&shifted_name).unwrap_or_else(unreachable);
let constructor_part = quote! {
let #shifted_name = {
offset += #size;
0
};
};
return (accessors, (constructor_arg, (constructor_part, shifted_name)));
}
let getter = generate_getter(field, field_offset, &name);
let setter = generate_setter(field, field_offset, &name);
let (constructor_arg, constructor_part, shifted_name) = generate_constructor_stuff(ty, &name);
let accessors = quote! {
#getter
#setter
};
(accessors, (constructor_arg, (constructor_part, shifted_name)))
}
fn generate_getter(field: &Field, offset: &TokenStream, name: &Ident) -> TokenStream {
let Field { attrs, vis, ty, .. } = field;
let getter_value = struct_gen::generate_getter_value(ty, offset, false);
let const_ = if cfg!(feature = "nightly") { quote!(const) } else { quote!() };
let array_at = if let Type::Array(array) = ty {
let elem_ty = &array.elem;
let len_expr = &array.len;
let name: Ident = syn::parse_str(&format!("{name}_at")).unwrap_or_else(unreachable);
let getter_value = struct_gen::generate_getter_value(elem_ty, offset, true);
quote! {
#(#attrs)*
#[allow(clippy::type_complexity, unused_parens)]
#vis #const_ fn #name(&self, index: usize) -> #elem_ty {
::core::assert!(index < #len_expr);
#getter_value
}
}
} else {
quote!()
};
quote! {
#(#attrs)*
#[allow(clippy::type_complexity, unused_parens)]
#vis #const_ fn #name(&self) -> #ty {
#getter_value
}
#array_at
}
}
fn generate_setter(field: &Field, offset: &TokenStream, name: &Ident) -> TokenStream {
let Field { attrs, vis, ty, .. } = field;
let setter_value = struct_gen::generate_setter_value(ty, offset, false);
let name: Ident = syn::parse_str(&format!("set_{name}")).unwrap_or_else(unreachable);
let const_ = if cfg!(feature = "nightly") { quote!(const) } else { quote!() };
let array_at = if let Type::Array(array) = ty {
let elem_ty = &array.elem;
let len_expr = &array.len;
let name: Ident = syn::parse_str(&format!("{name}_at")).unwrap_or_else(unreachable);
let setter_value = struct_gen::generate_setter_value(elem_ty, offset, true);
quote! {
#(#attrs)*
#[allow(clippy::type_complexity, unused_parens)]
#vis #const_ fn #name(&mut self, index: usize, value: #elem_ty) {
::core::assert!(index < #len_expr);
#setter_value
}
}
} else {
quote!()
};
quote! {
#(#attrs)*
#[allow(clippy::type_complexity, unused_parens)]
#vis #const_ fn #name(&mut self, value: #ty) {
#setter_value
}
#array_at
}
}
fn generate_constructor_stuff(ty: &Type, name: &Ident) -> (TokenStream, TokenStream, Ident) {
let name = format!("arg_{name}");
let name: Ident = syn::parse_str(&name).unwrap_or_else(unreachable);
let constructor_arg = quote! {
#name: #ty,
};
let shifted_name = format!("shifted_{name}");
let shifted_name: Ident = syn::parse_str(&shifted_name).unwrap_or_else(unreachable);
let constructor_part = struct_gen::generate_constructor_part(ty, &name, &shifted_name);
(constructor_arg, constructor_part, shifted_name)
}
fn generate_enum(enum_data: &ItemEnum) -> TokenStream {
let ItemEnum { vis, ident, variants, .. } = enum_data;
quote! {
#vis enum #ident {
#variants
}
}
}
fn generate_common(ir: ItemIr, arb_int: &TokenStream) -> TokenStream {
let ItemIr { attrs, name, expanded } = ir;
quote! {
#(#attrs)*
#expanded
impl ::bilge::Bitsized for #name {
type ArbitraryInt = #arb_int;
const BITS: usize = <Self::ArbitraryInt as Bitsized>::BITS;
const MAX: Self::ArbitraryInt = <Self::ArbitraryInt as Bitsized>::MAX;
}
}
}