1
0
Fork 0
mirror of https://codeberg.org/icewind/bitbuffer.git synced 2026-06-03 16:44:06 +02:00

rewrite derive macro

This commit is contained in:
Robin Appelman 2023-09-12 22:30:45 +02:00
commit 3852f09dd5
21 changed files with 1548 additions and 840 deletions

View file

@ -0,0 +1,135 @@
use crate::params::parse_attrs;
use crate::params::variant::VariantParam;
use merge::Merge;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use structmeta::StructMeta;
use syn::{Attribute, DataEnum, Error, LitInt, Result};
#[derive(Default, StructMeta, Merge, Debug)]
struct EnumAttrs {
discriminant_bits: Option<LitInt>,
}
pub struct EnumParam {
pub span: Span,
pub ident: Ident,
pub variants: Vec<VariantParam>,
pub discriminant_bits: usize,
}
impl EnumParam {
pub fn size_can_be_predicted(&self) -> bool {
self.variants
.iter()
.all(|field| field.size_can_be_predicted())
}
pub fn parse(
data: &DataEnum,
ident: Ident,
attrs: &[Attribute],
span: Span,
) -> Result<EnumParam> {
let attrs: EnumAttrs = parse_attrs(attrs)?;
let variants = data
.variants
.iter()
.map(VariantParam::parse)
.collect::<Result<Vec<VariantParam>>>()?;
let discriminant_bits = attrs
.discriminant_bits
.ok_or_else(|| {
Error::new(
span.clone(),
"'discriminant_bits' attribute is required when deriving `BinRead` for enums",
)
})?
.base10_parse()?;
Ok(EnumParam {
span,
ident,
variants,
discriminant_bits,
})
}
pub fn span(&self) -> Span {
self.span
}
pub fn read_discriminant_tokens(&self) -> impl Iterator<Item = TokenStream> + '_ {
ReadDiscriminantTokenIter {
last: -1,
variants: self.variants.iter(),
}
}
pub fn write_discriminant_tokens(&self) -> impl Iterator<Item = TokenStream> + '_ {
WriteDiscriminantTokenIter {
last: -1,
max: self.max_discriminant(),
variants: self.variants.iter(),
}
}
pub fn max_discriminant(&self) -> usize {
let mut last_discriminant = -1;
self.variants
.iter()
.map(|variant| variant.discriminant.max_value(&mut last_discriminant))
.max()
.unwrap_or(0)
}
pub fn discriminant_repr(&self) -> TokenStream {
if self.discriminant_bits <= 8 {
quote!(u8)
} else if self.discriminant_bits <= 16 {
quote!(u16)
} else if self.discriminant_bits <= 32 {
quote!(u32)
} else {
quote!(u64)
}
}
}
pub struct ReadDiscriminantTokenIter<'a> {
last: isize,
variants: std::slice::Iter<'a, VariantParam>,
}
impl Iterator for ReadDiscriminantTokenIter<'_> {
type Item = TokenStream;
fn next(&mut self) -> Option<Self::Item> {
let variant = self.variants.next()?;
Some(
variant
.discriminant
.read_token(&mut self.last, variant.span()),
)
}
}
pub struct WriteDiscriminantTokenIter<'a> {
last: isize,
max: usize,
variants: std::slice::Iter<'a, VariantParam>,
}
impl Iterator for WriteDiscriminantTokenIter<'_> {
type Item = TokenStream;
fn next(&mut self) -> Option<Self::Item> {
let variant = self.variants.next()?;
Some(
variant
.discriminant
.write_token(&mut self.last, self.max, variant.span()),
)
}
}

View file

@ -0,0 +1,81 @@
use crate::params::{parse_attrs, Alignment, Size};
use merge::Merge;
use proc_macro2::{Ident, Span};
use structmeta::StructMeta;
use syn::spanned::Spanned;
use syn::{Expr, Field, Index, LitInt, Member, Result, Type};
#[derive(Default, StructMeta, Merge)]
struct FieldAttrs {
size: Option<Expr>,
size_bits: Option<LitInt>,
#[merge(strategy = merge::bool::overwrite_false)]
align: bool,
}
pub struct FieldParam {
pub span: Span,
pub field_name: Option<Ident>,
pub size: Option<Size>,
pub align: Alignment,
pub ty: Type,
}
impl FieldParam {
/// Whether the size of the field can be determined without having to read further bits
pub fn size_can_be_predicted(&self) -> bool {
if self.align == Alignment::Auto {
return false;
}
match &self.size {
Some(size) => size.is_const(),
None => true,
}
}
pub fn parse(input: &Field) -> Result<FieldParam> {
let attrs: FieldAttrs = parse_attrs(&input.attrs)?;
let field_name = input.ident.clone();
let align = attrs.align.into();
let size = Size::from_attrs(attrs.size, attrs.size_bits, input.span())?;
let ty = input.ty.clone();
Ok(FieldParam {
span: input.span(),
field_name,
size,
align,
ty,
})
}
pub fn span(&self) -> Span {
self.span
}
pub fn member(&self, index: u32) -> Member {
match self.field_name.as_ref() {
Some(name) => Member::Named(name.clone()),
None => Member::Unnamed(Index {
index,
span: self.span(),
}),
}
}
pub fn is_int(&self) -> bool {
if let Type::Path(path) = &self.ty {
if let Some(ident) = path.path.get_ident() {
let name = ident.to_string();
matches!(
name.as_str(),
"u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" | "i64" | "isize"
)
} else {
false
}
} else {
false
}
}
}

View file

@ -0,0 +1,305 @@
mod r#enum;
mod field;
mod r#struct;
mod variant;
pub use crate::params::field::FieldParam;
pub use crate::params::r#enum::EnumParam;
pub use crate::params::r#struct::StructParam;
pub use crate::params::variant::{VariantBody, VariantBodyType, VariantParam};
use crate::{err, DeriveParams};
use merge::Merge;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
use std::any::type_name;
use std::fmt::Debug;
use structmeta::StructMeta;
use syn::__private::{bool, IntoSpans};
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::token::Paren;
use syn::{
parse_quote, parse_str, Attribute, Data, DeriveInput, Expr, ExprLit, ExprPath, GenericParam,
Generics, ImplGenerics, Lifetime, Lit, LitBool, LitInt, LitStr, MacroDelimiter, Meta, MetaList,
Result, TypeGenerics, WhereClause,
};
pub enum Size {
Expression(Expr, Span),
Bits(usize, Span),
}
impl Size {
pub fn is_const(&self) -> bool {
match self {
Size::Expression(
Expr::Lit(ExprLit {
lit: Lit::Int(_), ..
}),
_,
) => true,
Size::Expression(Expr::Path(ExprPath { path, .. }), _) => path.is_ident("input_size"),
_ => false,
}
}
pub fn from_attrs(
size: Option<Expr>,
size_bits: Option<LitInt>,
span: Span,
) -> Result<Option<Self>> {
Ok(match (size, size_bits) {
(
Some(Expr::Lit(ExprLit {
lit: Lit::Str(field),
..
})),
None,
) => Some(Size::Expression(parse_str(&field.value())?, span)),
(Some(size), None) => Some(Size::Expression(size, span)),
(None, Some(bits)) => Some(Size::Bits(bits.base10_parse()?, span)),
(Some(_), Some(_)) => err("#[size] and #[size_bits] are mutually exclusive", span)?,
(None, None) => None,
})
}
}
impl ToTokens for Size {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Size::Expression(expr, span) => {
let span = span.clone();
tokens.append_all(quote_spanned! {span => {
#[allow(clippy::unnecessary_cast)]
let __size = (#expr) as usize;
__size
}
});
}
Size::Bits(bits, span) => {
let span = span.clone();
tokens.append_all(quote_spanned! {span => {
__stream.read_int::<usize>(#bits)?
}
});
}
}
}
}
#[derive(Default, PartialOrd, PartialEq, Copy, Clone, Debug)]
pub enum Alignment {
#[default]
None,
Auto,
}
impl Alignment {
pub fn write(&self) -> TokenStream {
match self {
Alignment::Auto => quote! {
__stream.align();
},
Alignment::None => quote!(),
}
}
}
impl From<bool> for Alignment {
fn from(value: bool) -> Self {
match value {
true => Alignment::Auto,
false => Alignment::None,
}
}
}
impl Parse for Alignment {
fn parse(input: ParseStream) -> Result<Self> {
Ok(LitBool::parse(input)?.value.into())
}
}
impl Merge for Alignment {
fn merge(&mut self, other: Self) {
if other == Alignment::Auto {
*self = Alignment::Auto
}
}
}
impl ToTokens for Alignment {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Alignment::Auto => tokens.append_all(quote! {
__stream.align()?;
}),
Alignment::None => {}
}
}
}
#[derive(Default, StructMeta, Merge, Debug)]
struct InputAttrs {
endianness: Option<LitStr>,
#[merge(strategy = merge::bool::overwrite_false)]
align: bool,
}
pub struct InputParams {
pub ident: Ident,
pub span: Span,
endianness: Option<String>,
pub align: Alignment,
pub generics: Generics,
pub generics_with_endianness: Generics,
pub inner: InputInnerParams,
pub lifetime: Lifetime,
}
pub enum InputInnerParams {
Struct(StructParam),
Enum(EnumParam),
}
impl DeriveParams for InputParams {
fn parse(input: &DeriveInput) -> Result<Self> {
let attrs: InputAttrs = parse_attrs(&input.attrs)?;
let inner = match &input.data {
Data::Struct(data) => InputInnerParams::Struct(StructParam::parse(
data,
input.ident.clone(),
&input.attrs,
input.span(),
)?),
Data::Enum(data) => InputInnerParams::Enum(EnumParam::parse(
data,
input.ident.clone(),
&input.attrs,
input.span(),
)?),
_ => return err("Only structs and enums are supported", input.span()),
};
let endianness = attrs.endianness.map(|lit| lit.value());
let align = attrs.align.into();
let generics = input.generics.clone();
let mut generics_with_endianness = generics.clone();
let mut lifetimes = input
.generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Lifetime(lifetime) => Some(lifetime),
_ => None,
});
let lifetime = match (lifetimes.next(), lifetimes.next()) {
(_, Some(_)) => {
return err("Only a single lifetime generic is supported", input.span())
}
(Some(param), None) => param.lifetime.clone(),
(None, None) => {
let lifetime = Lifetime::new("'a", input.span());
generics_with_endianness
.params
.push(GenericParam::Lifetime(parse_str("'a").unwrap()));
lifetime
}
};
if endianness.is_none() {
generics_with_endianness
.params
.push(parse_quote!(_E: ::bitbuffer::Endianness));
}
Ok(InputParams {
ident: input.ident.clone(),
span: input.span(),
endianness,
align,
generics,
generics_with_endianness,
lifetime,
inner,
})
}
}
impl InputParams {
#[allow(dead_code)]
pub fn size_can_be_predicted(&self) -> bool {
match &self.inner {
InputInnerParams::Struct(inner) => inner.size_can_be_predicted(),
InputInnerParams::Enum(inner) => inner.size_can_be_predicted(),
}
}
pub fn generics_for_impl<'a>(
&'a self,
) -> (ImplGenerics<'a>, TypeGenerics<'a>, Option<&'a WhereClause>) {
// we need these separate generics to only add out Endianness param to the 'impl'
let (_, ty_generics, where_clause) = self.generics.split_for_impl();
let (impl_generics, _, _) = self.generics_with_endianness.split_for_impl();
(impl_generics, ty_generics, where_clause)
}
pub fn endianness(&self) -> Ident {
Ident::new(
self.endianness.as_deref().unwrap_or_else(|| "_E"),
self.span,
)
}
}
const BARE_ATTRS: &'static [&'static str] = &[
"size",
"size_bits",
"discriminant_bits",
"discriminant",
"endianness",
"align",
];
fn parse_attrs<T: Parse + Default + Merge>(attrs: &[Attribute]) -> Result<T> {
let mut result = T::default();
for attr in attrs {
let parsed = if BARE_ATTRS
.iter()
.any(|name| attr.meta.path().is_ident(name))
{
let wrapped_meta = Meta::List(MetaList {
path: parse_str("bitbuffer").unwrap(),
delimiter: MacroDelimiter::Paren(Paren {
span: attr.span().into_spans(),
}),
tokens: attr.meta.clone().into_token_stream(),
});
let wrapped = Attribute {
pound_token: attr.pound_token,
style: attr.style.clone(),
bracket_token: attr.bracket_token.clone(),
meta: wrapped_meta,
};
wrapped.parse_args()
} else {
attr.parse_args()
};
match parsed {
Ok(parsed) => {
result.merge(parsed);
}
Err(e) => {
// since we first parse our attrs as InputAttrs, and then the same attrs as either an Struct or EnumAttrs
// when doing the first pass we expect a bunch of extra parameters
let is_first_pass = type_name::<T>() == type_name::<InputAttrs>();
if !e.to_string().starts_with("cannot find parameter") && !is_first_pass {
return Err(e);
}
}
}
}
Ok(result)
}

View file

@ -0,0 +1,44 @@
use crate::params::field::FieldParam;
use proc_macro2::{Ident, Span};
use syn::{Attribute, DataStruct, Fields, Result};
pub struct StructParam {
pub span: Span,
pub ident: Ident,
pub fields: Vec<FieldParam>,
pub is_unit: bool,
}
impl StructParam {
pub fn size_can_be_predicted(&self) -> bool {
self.fields
.iter()
.all(|field| field.size_can_be_predicted())
}
pub fn parse(
data: &DataStruct,
ident: Ident,
_attrs: &[Attribute],
span: Span,
) -> Result<StructParam> {
let fields = data
.fields
.iter()
.map(FieldParam::parse)
.collect::<Result<Vec<FieldParam>>>()?;
let is_unit = matches!(data.fields, Fields::Unit);
Ok(StructParam {
span,
ident,
fields,
is_unit,
})
}
pub fn span(&self) -> Span {
self.span
}
}

View file

@ -0,0 +1,143 @@
use crate::discriminant::Discriminant;
use crate::err;
use crate::params::field::FieldParam;
use crate::params::{parse_attrs, Alignment, Size};
use merge::Merge;
use proc_macro2::{Ident, Span};
use std::convert::TryFrom;
use structmeta::StructMeta;
use syn::spanned::Spanned;
use syn::{Expr, ExprLit, Fields, Lit, LitInt, Result, Variant};
#[derive(Default, StructMeta, Merge)]
struct VariantAttrs {
size: Option<Expr>,
size_bits: Option<LitInt>,
#[merge(strategy = merge::bool::overwrite_false)]
align: bool,
discriminant: Option<Lit>,
}
pub struct VariantParam {
pub span: Span,
pub variant_name: Ident,
pub body: VariantBody,
pub discriminant: Discriminant,
}
pub enum VariantBodyType {
Unit,
Unnamed,
Named,
}
pub enum VariantBody {
Unit,
Fields(Vec<FieldParam>),
}
impl VariantBody {
pub fn body_type(&self) -> VariantBodyType {
match self {
VariantBody::Unit => VariantBodyType::Unit,
VariantBody::Fields(fields) => {
let named = fields.iter().any(|f| f.field_name.is_some());
if named {
VariantBodyType::Named
} else {
VariantBodyType::Unnamed
}
}
}
}
}
impl VariantParam {
/// Whether the size of the variant can be determined without having to read further bits
pub fn size_can_be_predicted(&self) -> bool {
match &self.body {
VariantBody::Fields(fields) => fields.iter().all(|field| field.size_can_be_predicted()),
VariantBody::Unit => true,
}
}
pub fn parse(input: &Variant) -> Result<VariantParam> {
let attrs: VariantAttrs = parse_attrs(&input.attrs)?;
let variant_name = input.ident.clone();
let align = attrs.align.into();
let size = Size::from_attrs(attrs.size, attrs.size_bits, input.span())?;
if attrs.discriminant.is_some() && input.discriminant.is_some() {
err(
"variant has both discriminant and discriminant attribute set",
input.span(),
)?;
}
let discriminant = attrs
.discriminant
.map(|lit| {
Expr::Lit(ExprLit {
attrs: Vec::new(),
lit,
})
})
.or_else(|| {
input
.discriminant
.clone()
.map(|(_, discriminant)| discriminant)
})
.map(Discriminant::try_from)
.transpose()?
.unwrap_or(Discriminant::Default);
let body = if matches!(input.fields, Fields::Unit) {
if align == Alignment::Auto {
err(
"'align' attribute is not allowed on unit variants",
input.span(),
)?;
}
if size.is_some() {
err(
"'size' attribute is not allowed on unit variants",
input.span(),
)?;
}
VariantBody::Unit
} else {
let mut fields = input
.fields
.iter()
.map(FieldParam::parse)
.collect::<Result<Vec<FieldParam>>>()?;
// align and size attributes on the variant go to the first field
match (fields.first_mut(), align) {
(Some(field), Alignment::Auto) => {
field.align = align;
}
_ => {}
}
match (fields.first_mut(), size) {
(Some(field), Some(size)) => {
field.size = Some(size);
}
_ => {}
}
VariantBody::Fields(fields)
};
Ok(VariantParam {
span: input.span(),
variant_name,
discriminant,
body,
})
}
pub fn span(&self) -> Span {
self.span
}
}