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

86
Cargo.lock generated
View file

@ -28,10 +28,11 @@ name = "bitbuffer_derive"
version = "0.10.1"
dependencies = [
"bitbuffer",
"merge",
"proc-macro2",
"quote",
"syn",
"syn_util",
"structmeta",
"syn 2.0.22",
]
[[package]]
@ -50,7 +51,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn",
"syn 1.0.92",
"synstructure",
]
@ -78,6 +79,28 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "merge"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9"
dependencies = [
"merge_derive",
"num-traits",
]
[[package]]
name = "merge_derive"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.92",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@ -96,7 +119,7 @@ dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"syn 1.0.92",
"version_check",
]
@ -113,18 +136,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.37"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
dependencies = [
"unicode-xid",
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.18"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
dependencies = [
"proc-macro2",
]
@ -162,7 +185,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn",
"syn 1.0.92",
]
[[package]]
@ -182,7 +205,7 @@ checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.92",
]
[[package]]
@ -193,7 +216,7 @@ checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.92",
]
[[package]]
@ -207,6 +230,29 @@ dependencies = [
"serde",
]
[[package]]
name = "structmeta"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ad9e09554f0456d67a69c1584c9798ba733a5b50349a6c0d0948710523922d"
dependencies = [
"proc-macro2",
"quote",
"structmeta-derive",
"syn 2.0.22",
]
[[package]]
name = "structmeta-derive"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.22",
]
[[package]]
name = "syn"
version = "1.0.92"
@ -219,14 +265,14 @@ dependencies = [
]
[[package]]
name = "syn_util"
version = "0.4.2"
name = "syn"
version = "2.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6754c4559b79657554e9d8a0d56e65e490c76d382b9c23108364ec4125dea23c"
checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-ident",
]
[[package]]
@ -237,10 +283,16 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.92",
"unicode-xid",
]
[[package]]
name = "unicode-ident"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
[[package]]
name = "unicode-xid"
version = "0.2.2"

View file

@ -12,10 +12,11 @@ name = "bitbuffer_derive"
proc-macro = true
[dependencies]
syn = "1.0"
quote = "1.0"
proc-macro2 = "1.0"
syn_util = "0.4"
syn = { version = "2.0.22", features = ["extra-traits"] }
quote = "1.0.29"
proc-macro2 = "1.0.63"
structmeta = "0.2.0"
merge = "0.1.0"
[dev-dependencies]
bitbuffer = { version = "0.10", path = ".." }

View file

@ -1,36 +1,105 @@
use syn::{Expr, Lit, Variant};
use syn_util::get_attribute_value;
use crate::err;
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use std::convert::{TryFrom, TryInto};
use syn::spanned::Spanned;
use syn::{Error, Expr, ExprLit, Lit, LitInt};
#[derive(Copy, Clone)]
pub enum Discriminant {
Int(usize),
Default,
Wildcard,
}
impl From<Lit> for Discriminant {
fn from(lit: Lit) -> Self {
match lit {
Lit::Int(lit) => Discriminant::Int(lit.base10_parse::<usize>().unwrap()),
Lit::Str(lit) => match lit.value().as_str() {
"_" => Discriminant::Wildcard,
_ => panic!("discriminant is required to be an integer literal or \"_\""),
},
_ => panic!("discriminant is required to be an integer literal or \"_\""),
impl TryFrom<Expr> for Discriminant {
type Error = Error;
fn try_from(value: Expr) -> Result<Self, Self::Error> {
match value {
Expr::Lit(ExprLit { lit, .. }) => lit.try_into(),
_ => err("non literal discriminant", value.span())?,
}
}
}
impl From<&Variant> for Discriminant {
fn from(variant: &Variant) -> Self {
variant
.discriminant
.as_ref()
.map(|(_, expr)| match expr {
Expr::Lit(expr_lit) => expr_lit.lit.clone(),
_ => panic!("discriminant is required to be an integer literal"),
})
.or_else(|| get_attribute_value(&variant.attrs, &["discriminant"]))
.map(Discriminant::from)
.unwrap_or(Discriminant::Default)
impl TryFrom<Lit> for Discriminant {
type Error = Error;
fn try_from(value: Lit) -> Result<Self, Self::Error> {
let span = value.span();
match value {
Lit::Int(lit) => Ok(Discriminant::Int(lit.base10_parse()?)),
Lit::Str(lit) => match lit.value().as_str() {
"_" => Ok(Discriminant::Wildcard),
_ => err(
"discriminant is required to be an integer literal or \"_\"",
span,
),
},
_ => err(
"discriminant is required to be an integer literal or \"_\"",
span,
),
}
}
}
impl Discriminant {
pub fn read_token(&self, last_discriminant: &mut isize, span: Span) -> TokenStream {
match self {
Discriminant::Int(discriminant) => {
let lit = LitInt::new(&format!("{}", discriminant), span);
*last_discriminant = *discriminant as isize;
quote! { #lit }
}
Discriminant::Wildcard => quote! { _ },
Discriminant::Default => {
let new_discriminant = (*last_discriminant + 1) as usize;
let lit = LitInt::new(&format!("{}", new_discriminant), span);
*last_discriminant += 1;
quote! { #lit }
}
}
}
pub fn write_token(
&self,
last_discriminant: &mut isize,
max_discriminant: usize,
span: Span,
) -> TokenStream {
match self {
Discriminant::Int(discriminant) => {
let lit = LitInt::new(&format!("{}", discriminant), span);
*last_discriminant = *discriminant as isize;
quote_spanned! { span => #lit }
}
Discriminant::Wildcard => {
let free_discriminant = max_discriminant + 1;
let lit = LitInt::new(&format!("{}", free_discriminant), span);
quote_spanned! { span => #lit }
}
Discriminant::Default => {
let new_discriminant = (*last_discriminant + 1) as usize;
let lit = LitInt::new(&format!("{}", new_discriminant), span);
*last_discriminant += 1;
quote_spanned! { span => #lit }
}
}
}
pub fn max_value(&self, last_discriminant: &mut isize) -> usize {
match self {
Discriminant::Int(discriminant) => {
*last_discriminant = *discriminant as isize;
*discriminant
}
Discriminant::Wildcard => 0,
Discriminant::Default => {
let new_discriminant = (*last_discriminant + 1) as usize;
*last_discriminant += 1;
new_discriminant
}
}
}
}

View file

@ -165,498 +165,110 @@
//! ```
//!
mod discriminant;
mod params;
mod read;
mod size_hint;
mod write;
extern crate proc_macro;
use crate::write::derive_bitwrite_trait;
use discriminant::Discriminant;
use crate::read::{Read, ReadSized};
use crate::write::{Write, WriteSized};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{
parse_macro_input, parse_quote, parse_str, Attribute, Data, DataStruct, DeriveInput, Expr,
Fields, GenericParam, Ident, Lit, LitInt, LitStr, Path,
};
use syn_util::{contains_attribute, get_attribute_value};
use std::fmt::Display;
use syn::{parse_macro_input, DeriveInput, Error, Result};
/// See the [crate documentation](index.html) for details
#[proc_macro_derive(
BitRead,
attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
attributes(
bitbuffer,
size,
size_bits,
discriminant_bits,
discriminant,
endianness,
align
)
)]
pub fn derive_bitread(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_bitread_trait(input, "BitRead".to_owned(), None)
derive_trait::<Read>(input)
}
//
/// See the [crate documentation](index.html) for details
#[proc_macro_derive(
BitReadSized,
attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
attributes(
bitbuffer,
size,
size_bits,
discriminant_bits,
discriminant,
endianness,
align
)
)]
pub fn derive_bitread_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let extra_param = parse_str::<TokenStream>(", input_size: usize").unwrap();
derive_bitread_trait(input, "BitReadSized".to_owned(), Some(extra_param))
derive_trait::<ReadSized>(input)
}
/// See the [crate documentation](index.html) for details
#[proc_macro_derive(
BitWrite,
attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
attributes(
bitbuffer,
size,
size_bits,
discriminant_bits,
discriminant,
endianness,
align
)
)]
pub fn derive_bitwrite(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_bitwrite_trait(input, "BitWrite".into(), "write".into(), None)
derive_trait::<Write>(input)
}
//
/// See the [crate documentation](index.html) for details
#[proc_macro_derive(
BitWriteSized,
attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
attributes(
bitbuffer,
size,
size_bits,
discriminant_bits,
discriminant,
endianness,
align
)
)]
pub fn derive_bitwrite_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let extra_param = parse_str::<TokenStream>(", input_size: usize").unwrap();
derive_bitwrite_trait(
input,
"BitWriteSized".into(),
"write_sized".into(),
Some(extra_param),
)
derive_trait::<WriteSized>(input)
}
fn derive_bitread_trait(
input: proc_macro::TokenStream,
trait_name: String,
extra_param: Option<TokenStream>,
) -> proc_macro::TokenStream {
/// Basic wrapper for error handling
fn derive_trait<Trait: Derivable>(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let endianness = get_attribute_value(&input.attrs, &["endianness"]);
let mut trait_generics = input.generics.clone();
// we need these separate generics to only add out Endianness param to the 'impl'
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
let lifetime: Option<&GenericParam> = trait_generics
.params
.iter()
.find(|param| matches!(param, GenericParam::Lifetime(_)));
let lifetime = match lifetime {
Some(GenericParam::Lifetime(lifetime)) => lifetime.lifetime.clone(),
_ => {
trait_generics.params.push(parse_quote!('a));
parse_quote!('a)
}
};
if endianness.is_none() {
trait_generics
.params
.push(parse_quote!(_E: ::bitbuffer::Endianness));
}
let (impl_generics, _, _) = trait_generics.split_for_impl();
let span = input.span();
let size = size(
input.data.clone(),
name,
&input.attrs,
extra_param.is_some(),
);
let parsed = parse(input.data.clone(), name, &input.attrs, false);
let parsed_unchecked = parse(input.data.clone(), name, &input.attrs, true);
let endianness_placeholder = endianness.unwrap_or_else(|| "_E".to_owned());
let trait_def_str = format!(
"::bitbuffer::{}<{}, {}>",
trait_name, lifetime, &endianness_placeholder
);
let trait_def = parse_str::<Path>(&trait_def_str).unwrap();
let endianness_ident = Ident::new(&endianness_placeholder, span);
let size_extra_param = if extra_param.is_some() {
Some(quote!(input_size: usize))
} else {
None
};
let extra_param_call = if extra_param.is_some() {
Some(quote!(input_size,))
} else {
None
};
let size_method_name = Ident::new(
if extra_param.is_some() {
"bit_size_sized"
} else {
"bit_size"
},
Span::call_site(),
);
let expanded = quote! {
impl #impl_generics #trait_def for #name #ty_generics #where_clause {
#[allow(unused_braces)]
fn read(stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness_ident>#extra_param) -> ::bitbuffer::Result<Self> {
// if the read has a predicable size, we can do the bounds check in one go
match <Self as #trait_def>::#size_method_name(#extra_param_call) {
Some(size) => {
let end = stream.check_read(size)?;
unsafe {
<Self as #trait_def>::read_unchecked(stream, #extra_param_call end)
}
},
None => {
#parsed
}
}
}
#[allow(unused_braces)]
unsafe fn read_unchecked(stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness_ident>#extra_param, end: bool) -> ::bitbuffer::Result<Self> {
#parsed_unchecked
}
fn #size_method_name(#size_extra_param) -> Option<usize> {
#size
}
}
};
// panic!("{}", TokenStream::to_string(&expanded));
proc_macro::TokenStream::from(expanded)
derive_trait_inner::<Trait>(input)
.unwrap_or_else(|err| err.into_compile_error())
.into()
}
fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool) -> TokenStream {
let span = struct_name.span();
let align = get_align(attrs);
match data {
Data::Struct(DataStruct { fields, .. }) => {
let values = fields.iter().map(|f| {
// Get attributes `#[..]` on each field
let size = get_field_size(&f.attrs, f.span());
let align = get_align(attrs);
let field_type = &f.ty;
let span = f.span();
if unchecked {
match size {
Some(size) => {
quote_spanned! { span =>
{
#align;
let _size: usize = #size;
stream.read_sized_unchecked::<#field_type>(_size, end)?
}
}
}
None => {
quote_spanned! { span =>
{
#align;
stream.read_unchecked::<#field_type>(end)?
}
}
}
}
} else {
match size {
Some(size) => {
quote_spanned! { span =>
{
#align;
let _size: usize = #size;
stream.read_sized::<#field_type>(_size)?
}
}
}
None => {
quote_spanned! { span =>
{
#align;
stream.read::<#field_type>()?
}
}
}
}
}
});
match &fields {
Fields::Named(fields) => {
let definitions = fields.named.iter().zip(values).map(|(f, value)| {
let name = &f.ident;
quote_spanned! { f.span() =>
let #name = #value;
}
});
let struct_definition = fields.named.iter().map(|f| {
let name = &f.ident;
quote_spanned! { f.span() =>
#name,
}
});
quote_spanned! { span =>
#align;
#(#definitions)*
Ok(#struct_name {
#(#struct_definition)*
})
}
}
Fields::Unnamed(_) => quote_spanned! { span =>
#align;
Ok(#struct_name(
#(#values ,)*
))
},
Fields::Unit => quote_spanned! { span=>
#align;
Ok(#struct_name)
},
}
}
Data::Enum(data) => {
let discriminant_bits: u64 = match get_attribute_value(attrs, &["discriminant_bits"]) {
Some(attr) => attr,
None => {
return quote_spanned! { span=>
compile_error!("'discriminant_bits' attribute is required when deriving `BinRead` for enums");
}
}
};
let mut last_discriminant = -1;
let match_arms = data.variants.iter().map(|variant| {
let span = variant.span();
let variant_name = &variant.ident;
let read_fields = match &variant.fields {
Fields::Unit => {
if contains_attribute(&variant.attrs, &["align"]) {
return quote_spanned! { span =>
compile_error!("'align' attribute is not allowed on unit variants");
};
}
quote_spanned! { span=>
#struct_name::#variant_name
}
}
Fields::Unnamed(f) => {
let size = get_field_size(&variant.attrs, f.span());
let align = get_align(&variant.attrs);
match size {
Some(size) => {
quote_spanned! { span =>
#struct_name::#variant_name({
#align;
let _size:usize = #size;
stream.read_sized(_size)?
})
}
}
None => {
quote_spanned! { span =>
{
#align;
#struct_name::#variant_name(stream.read()?)
}
}
}
}
}
_ => unimplemented!(),
};
let discriminant_token: TokenStream = match Discriminant::from(variant) {
Discriminant::Int(discriminant) => {
let lit = LitInt::new(&format!("{}", discriminant), span);
last_discriminant = discriminant as isize;
quote_spanned! { span => #lit }
}
Discriminant::Wildcard => quote_spanned! { span => _ },
Discriminant::Default => {
let new_discriminant = (last_discriminant + 1) as usize;
let lit = LitInt::new(&format!("{}", new_discriminant), span);
last_discriminant += 1;
quote_spanned! { span => #lit }
}
};
quote_spanned! {span=>
#discriminant_token => #read_fields,
}
});
let span = data.enum_token.span();
let repr = repr_for_bits(discriminant_bits);
let enum_name = Lit::Str(LitStr::new(&struct_name.to_string(), struct_name.span()));
quote_spanned! {span=>
#align;
#[allow(clippy::unnecessary_cast)]
let discriminant:#repr = stream.read_int(#discriminant_bits as usize)?;
Ok(match discriminant {
#(#match_arms)*
_ => {
#[allow(clippy::unnecessary_cast)]
return Err(::bitbuffer::BitError::UnmatchedDiscriminant{discriminant: discriminant as usize, enum_name: #enum_name.to_string()})
}
})
}
}
_ => unimplemented!(),
}
fn derive_trait_inner<Trait: Derivable>(input: DeriveInput) -> Result<TokenStream> {
let params = Trait::Params::parse(&input)?;
Trait::derive(params)
}
fn size(data: Data, struct_name: &Ident, attrs: &[Attribute], has_input_size: bool) -> TokenStream {
let span = struct_name.span();
trait Derivable {
type Params: DeriveParams;
if contains_attribute(attrs, &["align"]) {
return quote_spanned! { span =>
None
};
}
match data {
Data::Struct(DataStruct { fields, .. }) => {
let sizes = fields.iter().map(|f| {
// Get attributes `#[..]` on each field
if is_const_size(&f.attrs, has_input_size) {
let size = get_field_size(&f.attrs, f.span());
let field_type = &f.ty;
let span = f.span();
match size {
Some(size) => {
quote_spanned! { span =>
<#field_type as ::bitbuffer::BitReadSized<'_, ::bitbuffer::LittleEndian>>::bit_size_sized(#size)
}
}
None => {
quote_spanned! { span =>
<#field_type as ::bitbuffer::BitRead<'_, ::bitbuffer::LittleEndian>>::bit_size()
}
}
}
} else {
quote_spanned! { span =>
None
}
}
});
match &fields {
Fields::Named(_) => quote_spanned! { span =>
Some(0usize)#(.and_then(|sum: usize| #sizes.map(|size: usize| sum + size)))*
},
Fields::Unnamed(_) => quote_spanned! { span =>
Some(0usize)#(.and_then(|sum: usize| #sizes.map(|size: usize| sum + size)))*
},
Fields::Unit => quote_spanned! {span=>
Some(0usize)
},
}
}
Data::Enum(data) => {
let discriminant_bits = match get_attribute_value::<u64>(attrs, &["discriminant_bits"])
{
Some(attr) => attr as usize,
None => {
return quote! {span=>
compile_error!("'discriminant_bits' attribute is required when deriving `BinRead` for enums");
}
}
};
// Unit variants having "align" attributes are not allowed, so we can just check if all variants are unit
let is_unit = data
.variants
.iter()
.all(|variant| matches!(variant.fields, Fields::Unit));
if is_unit {
quote_spanned! {span=>
Some(#discriminant_bits)
}
} else {
quote_spanned! {span=>
None
}
}
}
_ => unimplemented!(),
}
fn derive(params: Self::Params) -> Result<TokenStream>;
}
fn is_const_size(attrs: &[Attribute], has_input_size: bool) -> bool {
if get_attribute_value::<Lit>(attrs, &["size_bits"]).is_some() {
return false;
}
if contains_attribute(attrs, &["align"]) {
return false;
}
get_attribute_value(attrs, &["size"])
.map(|size_lit| match size_lit {
Lit::Int(_) => true,
Lit::Str(size_field) => &size_field.value() == "input_size" && has_input_size,
_ => panic!("Unsupported value for size attribute"),
})
.unwrap_or(true)
trait DeriveParams: Sized {
fn parse(input: &DeriveInput) -> Result<Self>;
}
fn get_field_size(attrs: &[Attribute], span: Span) -> Option<TokenStream> {
get_attribute_value(attrs, &["size"])
.map(|size_lit| match size_lit {
Lit::Int(size) => {
quote_spanned! {span =>
#size
}
}
Lit::Str(size_field) => {
let size = parse_str::<Expr>(&size_field.value()).unwrap();
quote_spanned! {span => {
#[allow(clippy::unnecessary_cast)]
let __size = (#size) as usize;
__size
}
}
}
_ => panic!("Unsupported value for size attribute"),
})
.or_else(|| {
get_attribute_value::<Lit>(attrs, &["size_bits"]).map(|size_bits_lit| {
quote_spanned! {span =>
stream.read_int::<usize> (#size_bits_lit) ?
}
})
})
}
fn repr_for_bits(discriminant_bits: u64) -> TokenStream {
if discriminant_bits <= 8 {
quote!(u8)
} else if discriminant_bits <= 16 {
quote!(u16)
} else if discriminant_bits <= 32 {
quote!(u32)
} else {
quote!(usize)
}
}
fn get_align(attrs: &[Attribute]) -> TokenStream {
if contains_attribute(attrs, &["align"]) {
quote! {
stream.align()?
}
} else {
quote! { () }
}
fn err<R, Msg: Display>(msg: Msg, span: Span) -> Result<R> {
return Err(Error::new(span, msg));
}

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
}
}

View file

@ -0,0 +1,70 @@
use crate::params::{EnumParam, VariantBody};
use crate::read::field::read_struct_or_enum;
use proc_macro2::{Ident, TokenStream};
use quote::quote_spanned;
use syn::Path;
pub fn derive_encode_enum(params: &EnumParam, unchecked: bool) -> TokenStream {
let discriminant_bits = params.discriminant_bits;
let repr = params.discriminant_repr();
let ident = params.ident.clone();
let span = params.span;
let match_arms = params
.variants
.iter()
.zip(params.read_discriminant_tokens())
.map(|(variant, discriminant_token)| {
let span = variant.span();
let variant_name = &variant.variant_name;
let mut variant_path = Path::from(params.ident.clone());
variant_path
.segments
.push(variant.variant_name.clone().into());
let read_variant = match &variant.body {
VariantBody::Unit => quote_spanned! { span =>
Ok(#ident::#variant_name)
},
VariantBody::Fields(fields) => {
read_struct_or_enum(&variant_path, fields, span.clone(), unchecked)
}
};
quote_spanned! {span=>
#discriminant_token => #read_variant,
}
});
let read_fn = Ident::new(
if unchecked {
"read_int_unchecked"
} else {
"read_int"
},
span,
);
let end_param = if unchecked {
Some(quote_spanned!(span => end))
} else {
None
};
let error_handle = if unchecked {
None
} else {
Some(quote_spanned!(span => ?))
};
let name = ident.to_string();
quote_spanned! {span =>
#[allow(clippy::unnecessary_cast)]
let discriminant:#repr = __stream.#read_fn(#discriminant_bits as usize, #end_param)#error_handle;
match discriminant {
#(#match_arms)*
_ => {
#[allow(clippy::unnecessary_cast)]
return Err(::bitbuffer::BitError::UnmatchedDiscriminant{discriminant: discriminant as usize, enum_name: #name.to_string()})
}
}
}
}

View file

@ -0,0 +1,82 @@
use crate::params::FieldParam;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote_spanned;
use syn::Path;
pub fn read_struct_or_enum(
struct_name: &Path,
fields: &[FieldParam],
span: Span,
unchecked: bool,
) -> TokenStream {
let named = fields.iter().any(|f| f.field_name.is_some());
let values = fields.iter().map(|f| {
let align = &f.align;
let field_type = &f.ty;
let span = f.span();
let read_fn = Ident::new(if unchecked { "read_unchecked" } else { "read" }, span);
let read_sized_fn = Ident::new(
if unchecked {
"read_sized_unchecked"
} else {
"read_sized"
},
span,
);
let end_param = if unchecked {
Some(quote_spanned!(span => end))
} else {
None
};
match &f.size {
Some(size) => {
quote_spanned! { span =>
{
#align
let _size: usize = #size;
__stream.#read_sized_fn::<#field_type>(_size, #end_param)?
}
}
}
None => {
quote_spanned! { span =>
{
#align
__stream.#read_fn::<#field_type>(#end_param)?
}
}
}
}
});
if named {
let definitions = fields.iter().zip(values).map(|(f, value)| {
let name = &f.field_name;
quote_spanned! { span =>
let #name = #value;
}
});
let struct_definition = fields.iter().map(|f| {
let name = f
.field_name
.as_ref()
.expect("unnamed field in named struct?");
quote_spanned! { span =>
#name,
}
});
quote_spanned! { span =>
#(#definitions)*
Ok(#struct_name {
#(#struct_definition)*
})
}
} else {
quote_spanned! { span =>
Ok(#struct_name(
#(#values ,)*
))
}
}
}

View file

@ -0,0 +1,118 @@
mod r#enum;
mod field;
mod r#struct;
use self::r#enum::derive_encode_enum;
use self::r#struct::derive_encode_struct;
use crate::params::{InputInnerParams, InputParams};
use crate::size_hint::SizeHint;
use crate::Derivable;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::Result;
fn parse_impl(params: &InputParams, unchecked: bool) -> Result<TokenStream> {
Ok(match &params.inner {
InputInnerParams::Struct(inner) => derive_encode_struct(inner, unchecked),
InputInnerParams::Enum(inner) => derive_encode_enum(inner, unchecked),
})
}
pub struct Read;
impl Derivable for Read {
type Params = InputParams;
fn derive(params: Self::Params) -> Result<TokenStream> {
let (impl_generics, ty_generics, where_clause) = params.generics_for_impl();
let parse = parse_impl(&params, false)?;
let parse_unchecked = parse_impl(&params, true)?;
let size = params.size_hint();
let lifetime = params.lifetime.clone();
let endianness = params.endianness();
let name = params.ident.clone();
let align = params.align;
let span = params.span;
Ok(quote_spanned! {span =>
impl #impl_generics ::bitbuffer::BitRead<#lifetime, #endianness> for #name #ty_generics #where_clause {
#[allow(unused_braces, unused_variables)]
fn read(__stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness>) -> ::bitbuffer::Result<Self> {
// if the read has a predicable size, we can do the bounds check in one go
match <Self as ::bitbuffer::BitRead<#endianness>>::bit_size() {
Some(size) => {
let end = __stream.check_read(size)?;
unsafe {
<Self as ::bitbuffer::BitRead<#endianness>>::read_unchecked(__stream, end)
}
},
None => {
#align
#parse
}
}
}
#[allow(unused_braces, unused_variables)]
unsafe fn read_unchecked(__stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness>, end: bool) -> ::bitbuffer::Result<Self> {
#align
#parse_unchecked
}
fn bit_size() -> Option<usize> {
#size
}
}
})
}
}
pub struct ReadSized;
impl Derivable for ReadSized {
type Params = InputParams;
fn derive(params: Self::Params) -> Result<TokenStream> {
let (impl_generics, ty_generics, where_clause) = params.generics_for_impl();
let parse = parse_impl(&params, false)?;
let parse_unchecked = parse_impl(&params, true)?;
let size = params.size_hint();
let lifetime = params.lifetime.clone();
let endianness = params.endianness();
let name = params.ident.clone();
let align = params.align;
Ok(quote! {
impl #impl_generics ::bitbuffer::BitReadSized<#lifetime, #endianness> for #name #ty_generics #where_clause {
#[allow(unused_braces)]
fn read(__stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness>, input_size: usize) -> ::bitbuffer::Result<Self> {
// if the read has a predicable size, we can do the bounds check in one go
match <Self as ::bitbuffer::BitReadSized<#endianness>>::bit_size_sized(input_size) {
Some(size) => {
let end = __stream.check_read(size)?;
unsafe {
<Self as ::bitbuffer::BitReadSized<#endianness>>::read_unchecked(__stream, input_size, end)
}
},
None => {
#align
#parse
}
}
}
#[allow(unused_braces)]
unsafe fn read_unchecked(__stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness>, input_size: usize, end: bool) -> ::bitbuffer::Result<Self> {
#align
#parse_unchecked
}
fn bit_size_sized(input_size: usize) -> Option<usize> {
#size
}
}
})
}
}

View file

@ -0,0 +1,14 @@
use crate::params::StructParam;
use crate::read::field::read_struct_or_enum;
use proc_macro2::TokenStream;
use quote::quote;
use syn::Path;
pub fn derive_encode_struct(params: &StructParam, unchecked: bool) -> TokenStream {
let path = Path::from(params.ident.clone());
if params.is_unit {
quote!(Ok(#path))
} else {
read_struct_or_enum(&path, &params.fields, params.span(), unchecked)
}
}

View file

@ -0,0 +1,88 @@
use crate::params::{
Alignment, EnumParam, FieldParam, InputInnerParams, InputParams, StructParam, VariantBody,
VariantParam,
};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
pub trait SizeHint {
fn size_hint(&self) -> TokenStream;
}
pub trait SizeHintSized {
fn hint(&self) -> TokenStream;
}
impl SizeHint for FieldParam {
fn size_hint(&self) -> TokenStream {
let span = self.span;
let field_type = &self.ty;
if !self.size_can_be_predicted() {
return quote_spanned! { span => None::<usize>};
}
match &self.size {
Some(size) => {
quote_spanned! { span =>
<#field_type as ::bitbuffer::BitReadSized<'_, ::bitbuffer::LittleEndian>>::bit_size_sized(#size)
}
}
None => quote_spanned! { span =>
<#field_type as ::bitbuffer::BitRead<'_, ::bitbuffer::LittleEndian>>::bit_size()
},
}
}
}
impl SizeHint for VariantParam {
fn size_hint(&self) -> TokenStream {
match &self.body {
VariantBody::Unit => quote!(Some(0)),
VariantBody::Fields(fields) => product_size_hint(&fields, self.span),
}
}
}
impl SizeHint for StructParam {
fn size_hint(&self) -> TokenStream {
product_size_hint(&self.fields, self.span)
}
}
impl SizeHint for EnumParam {
fn size_hint(&self) -> TokenStream {
let fields = sum_size_hint(&self.variants, self.span);
let bits = self.discriminant_bits;
quote_spanned!(self.span => {
Some(#bits + #fields?)
})
}
}
impl SizeHint for InputParams {
fn size_hint(&self) -> TokenStream {
match (self.align, &self.inner) {
(Alignment::Auto, _) => quote!(None),
(_, InputInnerParams::Struct(inner)) => inner.size_hint(),
(_, InputInnerParams::Enum(inner)) => inner.size_hint(),
}
}
}
fn product_size_hint<T: SizeHint>(children: &[T], span: Span) -> TokenStream {
let sizes = children.iter().map(|child| child.size_hint());
quote_spanned!(span => Some(0usize)#(.and_then(|sum: usize| Some(sum + #sizes?)))*)
}
// sum types have a fixed size if all children have the same fixed size
fn sum_size_hint<T: SizeHint>(children: &[T], span: Span) -> TokenStream {
// todo, some actual clever logic that can be const folded away
let mut sizes = children.iter().map(|child| child.size_hint());
let Some(first) = sizes.next() else {
return quote!(Some(0));
};
quote_spanned!(span => #first#(.and_then(|prev: usize| if prev == #sizes? {
Some(prev)
} else {
None
}))*)
}

View file

@ -1,338 +0,0 @@
use crate::discriminant::Discriminant;
use crate::{repr_for_bits, size};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{
parse_macro_input, parse_quote, parse_str, Attribute, Data, DataStruct, DeriveInput, Expr,
Fields, GenericParam, Ident, Index, Lit, LitInt, Member, Path, Type,
};
use syn_util::{contains_attribute, get_attribute_value};
pub fn derive_bitwrite_trait(
input: proc_macro::TokenStream,
trait_name: String,
write_method_name: String,
extra_param: Option<TokenStream>,
) -> proc_macro::TokenStream {
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let endianness = get_attribute_value(&input.attrs, &["endianness"]);
let mut trait_generics = input.generics.clone();
// we need these separate generics to only add out Endianness param to the 'impl'
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
let lifetime: Option<&GenericParam> = trait_generics
.params
.iter()
.find(|param| matches!(param, GenericParam::Lifetime(_)));
let _lifetime = match lifetime {
Some(GenericParam::Lifetime(lifetime)) => lifetime.lifetime.clone(),
_ => {
// trait_generics.params.push(parse_quote!('a));
parse_quote!('a)
}
};
if endianness.is_none() {
trait_generics
.params
.push(parse_quote!(_E: ::bitbuffer::Endianness));
}
let (impl_generics, _, _) = trait_generics.split_for_impl();
let span = input.span();
let _size = size(
input.data.clone(),
name,
&input.attrs,
extra_param.is_some(),
);
let parsed = write(input.data.clone(), name, &input.attrs);
let _parsed_unchecked = write(input.data.clone(), name, &input.attrs);
let endianness_placeholder = endianness.unwrap_or_else(|| "_E".to_owned());
let trait_def_str = format!("::bitbuffer::{}<{}>", trait_name, &endianness_placeholder);
let trait_def = parse_str::<Path>(&trait_def_str).expect("trait");
let endianness_ident = Ident::new(&endianness_placeholder, span);
let _size_extra_param = if extra_param.is_some() {
Some(quote!(input_size: usize))
} else {
None
};
let _extra_param_call = if extra_param.is_some() {
Some(quote!(input_size,))
} else {
None
};
let write_method = Ident::new(&write_method_name, span);
let expanded = quote! {
#[allow(unused_braces)]
impl #impl_generics #trait_def for #name #ty_generics #where_clause {
fn #write_method(&self, __target__stream: &mut ::bitbuffer::BitWriteStream<#endianness_ident>#extra_param) -> ::bitbuffer::Result<()> {
#parsed
}
}
};
// panic!("{}", TokenStream::to_string(&expanded));
proc_macro::TokenStream::from(expanded)
}
fn write(data: Data, struct_name: &Ident, attrs: &[Attribute]) -> TokenStream {
let span = struct_name.span();
let align = get_align(attrs);
match data {
Data::Struct(DataStruct { fields, .. }) => {
let expand = fields.iter().enumerate().map(|(i, field)| {
let name = field
.ident
.clone()
.unwrap_or_else(|| Ident::new(&format!("__{}", i), span));
let member = field.ident.clone().map(Member::Named).unwrap_or_else(|| {
Member::Unnamed(Index {
index: i as u32,
span: field.span(),
})
});
// extract int fields to be used in size expressions
if type_is_int(&field.ty) {
quote_spanned! { field.span() =>
#[allow(unused_variables)]
let #name = self.#member;
}
} else {
quote! {}
}
});
let writes = fields.iter().enumerate().map(|(i, f)| {
// Get attributes `#[..]` on each field
let size = get_field_size(&f.attrs, f.span());
let align = get_align(&f.attrs);
let span = f.span();
let member = f.ident.clone().map(Member::Named).unwrap_or_else(|| {
Member::Unnamed(Index {
index: i as u32,
span,
})
});
match size {
Some(size) => {
quote_spanned! { span =>
{
#align;
let _size: usize = #size;
__target__stream.write_sized(&self.#member, _size)?;
}
}
}
None => {
quote_spanned! { span => {
{
#align;
__target__stream.write(&self.#member)?;
}
}}
}
}
});
quote_spanned! {span=>
#align;
#(#expand)*
#(#writes)*
Ok(())
}
}
Data::Enum(data) => {
let discriminant_bits: u64 = match get_attribute_value(attrs, &["discriminant_bits"]) {
Some(attr) => attr,
None => {
return quote! {span=>
compile_error!("'discriminant_bits' attribute is required when deriving `BinWrite` for enums");
}
}
};
let mut last_discriminant = -1;
let max_discriminant = data
.variants
.iter()
.map(|variant| match Discriminant::from(variant) {
Discriminant::Int(discriminant) => {
last_discriminant = discriminant as isize;
discriminant
}
Discriminant::Wildcard => 0,
Discriminant::Default => {
let new_discriminant = (last_discriminant + 1) as usize;
last_discriminant += 1;
new_discriminant
}
})
.max()
.unwrap_or(0);
let mut last_discriminant = -1;
let discriminant_value = data.variants.iter().map(|variant| {
let span = variant.span();
let variant_name = &variant.ident;
let discriminant_token: TokenStream = match Discriminant::from(variant) {
Discriminant::Int(discriminant) => {
let lit = LitInt::new(&format!("{}", discriminant), span);
last_discriminant = discriminant as isize;
quote_spanned! { span => #lit }
}
Discriminant::Wildcard => {
let free_discriminant = max_discriminant + 1;
let lit = LitInt::new(&format!("{}", free_discriminant), span);
quote_spanned! { span => #lit }
}
Discriminant::Default => {
let new_discriminant = (last_discriminant + 1) as usize;
let lit = LitInt::new(&format!("{}", new_discriminant), span);
last_discriminant += 1;
quote_spanned! { span => #lit }
}
};
match &variant.fields {
Fields::Unit => quote_spanned! {span =>
#struct_name::#variant_name => #discriminant_token
},
Fields::Unnamed(_f) => {
quote_spanned! { span =>
#struct_name::#variant_name(_) => #discriminant_token
}
}
_ => unimplemented!(),
}
});
let write_inner = data.variants.iter().map(|variant| {
let span = variant.span();
let variant_name = &variant.ident;
match &variant.fields {
Fields::Unit => {
if contains_attribute(&variant.attrs, &["align"]) {
return quote_spanned! { span =>
compile_error!("'align' attribute is not allowed on unit variants");
};
}
quote_spanned! {span =>
#struct_name::#variant_name => {},
}
}
Fields::Unnamed(f) => {
let size = get_field_size(&variant.attrs, f.span());
let align = get_align(&variant.attrs);
match size {
Some(size) => {
quote_spanned! { span =>
#struct_name::#variant_name(inner) => {
#align;
let size:usize = #size;
__target__stream.write_sized(inner, size)?;
}
}
}
None => {
quote_spanned! { span =>
#struct_name::#variant_name(inner) => {
#align;
__target__stream.write(inner)?;
}
}
}
}
}
_ => unimplemented!(),
}
});
let span = data.enum_token.span();
let repr = repr_for_bits(discriminant_bits);
quote_spanned! {span=>
#align;
let discriminant:#repr = match &self {
#(#discriminant_value),*
};
#[allow(clippy::unnecessary_cast)]
__target__stream.write_int(discriminant, #discriminant_bits as usize)?;
match &self {
#(#write_inner)*
}
Ok(())
}
}
_ => unimplemented!(),
}
}
fn get_field_size(attrs: &[Attribute], span: Span) -> Option<TokenStream> {
get_attribute_value(attrs, &["size"])
.map(|size_lit| match size_lit {
Lit::Int(size) => {
quote_spanned! {span =>
#size
}
}
Lit::Str(size_field) => {
let size = parse_str::<Expr>(&size_field.value()).expect("size");
quote_spanned! {span => {
#[allow(clippy::unnecessary_cast)]
let __size = (#size) as usize;
__size
}
}
}
_ => panic!("Unsupported value for size attribute"),
})
.or_else(|| {
get_attribute_value::<Lit>(attrs, &["size_bits"]).map(|_| {
quote_spanned! {span =>
compile_error!("#[size_bits] is not supported when deriving BitWrite or BitWriteSized")
}
})
})
}
fn type_is_int(ty: &Type) -> bool {
if let Type::Path(path) = 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
}
}
fn get_align(attrs: &[Attribute]) -> TokenStream {
if contains_attribute(attrs, &["align"]) {
quote! {
__target__stream.align()
}
} else {
quote! { () }
}
}

View file

@ -0,0 +1,63 @@
use crate::params::{EnumParam, VariantBody, VariantBodyType};
use crate::write::field::write_enum_variant;
use proc_macro2::TokenStream;
use quote::quote_spanned;
use syn::Path;
pub fn derive_encode_enum(params: &EnumParam) -> TokenStream {
let discriminant_bits = params.discriminant_bits;
let repr = params.discriminant_repr();
let ident = params.ident.clone();
let span = params.span();
let discriminant_value = params
.variants
.iter()
.zip(params.write_discriminant_tokens())
.map(|(variant, discriminant_token)| {
let span = variant.span();
let variant_name = &variant.variant_name;
match variant.body.body_type() {
VariantBodyType::Unit => quote_spanned! {span =>
#ident::#variant_name => #discriminant_token
},
VariantBodyType::Unnamed => {
quote_spanned! { span =>
#ident::#variant_name(_) => #discriminant_token
}
}
VariantBodyType::Named => {
quote_spanned! { span =>
#ident::#variant_name{..} => #discriminant_token
}
}
}
});
let write_inner = params.variants.iter().map(|variant| {
let span = variant.span();
let mut path = Path::from(ident.clone());
path.segments.push(variant.variant_name.clone().into());
match &variant.body {
VariantBody::Unit => {
quote_spanned! {span =>
#path => {},
}
}
VariantBody::Fields(fields) => write_enum_variant(path, &fields, span),
}
});
quote_spanned! {span=>
let discriminant:#repr = match &self {
#(#discriminant_value),*
};
#[allow(clippy::unnecessary_cast)]
__stream.write_int(discriminant, #discriminant_bits as usize)?;
match &self {
#(#write_inner)*
}
Ok(())
}
}

View file

@ -0,0 +1,81 @@
use crate::params::FieldParam;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote_spanned;
use syn::Path;
pub fn write_struct(fields: &[FieldParam], span: Span) -> TokenStream {
let expand = fields
.iter()
.enumerate()
.zip(names(fields))
.map(|((index, field), name)| {
let member = field.member(index as u32);
let size_field = match (field.is_int(), field.field_name.as_ref()) {
(true, Some(name)) => Some(quote_spanned! { field.span() =>
#[allow(unused_variables)]
let #name = self.#member;
}),
_ => None,
};
quote_spanned! { field.span() =>
#size_field
#[allow(unused_variables)]
let #name = &self.#member;
}
});
let writes = writes(fields);
quote_spanned! {span=>
#(#expand)*
#(#writes)*
}
}
fn names(fields: &[FieldParam]) -> impl Iterator<Item = Ident> + '_ {
fields
.iter()
.enumerate()
.map(|(index, field)| Ident::new(&format!("__field_{}", index), field.span()))
}
fn writes(fields: &[FieldParam]) -> impl Iterator<Item = TokenStream> + '_ {
let names = names(fields);
fields.iter().zip(names).map(|(field, name)| {
let align = &field.align.write();
let span = field.span();
match &field.size {
Some(size) => {
quote_spanned! { span =>
{
#align
let _size: usize = #size;
__stream.write_sized(#name, _size)?;
}
}
}
None => {
quote_spanned! { span =>
{
#align
__stream.write(#name)?;
}
}
}
}
})
}
pub fn write_enum_variant(variant: Path, fields: &[FieldParam], span: Span) -> TokenStream {
let names = names(fields);
let named = fields.iter().any(|f| f.field_name.is_some());
let writes = writes(fields);
if named {
quote_spanned!(span => #variant{#(#names,)*} => {
#(#writes;)*
})
} else {
quote_spanned!(span => #variant(#(#names,)*) => {
#(#writes;)*
})
}
}

View file

@ -0,0 +1,68 @@
pub mod r#enum;
pub mod field;
pub mod r#struct;
use self::r#enum::derive_encode_enum;
use self::r#struct::derive_encode_struct;
use crate::params::{InputInnerParams, InputParams};
use crate::Derivable;
use proc_macro2::TokenStream;
use quote::quote;
use syn::Result;
fn encode_impl(params: &InputParams) -> Result<TokenStream> {
Ok(match &params.inner {
InputInnerParams::Struct(inner) => derive_encode_struct(inner),
InputInnerParams::Enum(inner) => derive_encode_enum(inner),
})
}
pub struct Write;
impl Derivable for Write {
type Params = InputParams;
fn derive(params: Self::Params) -> Result<TokenStream> {
let (impl_generics, ty_generics, where_clause) = params.generics_for_impl();
let encode = encode_impl(&params)?;
let endianness = params.endianness();
let name = params.ident.clone();
let align = params.align.write();
Ok(quote! {
impl #impl_generics ::bitbuffer::BitWrite<#endianness> for #name #ty_generics #where_clause {
#[allow(unused_braces)]
fn write(&self, __stream: &mut ::bitbuffer::BitWriteStream<#endianness>) -> ::bitbuffer::Result<()> {
#align
#encode
}
}
})
}
}
pub struct WriteSized;
impl Derivable for WriteSized {
type Params = InputParams;
fn derive(params: Self::Params) -> Result<TokenStream> {
let (impl_generics, ty_generics, where_clause) = params.generics_for_impl();
let encode = encode_impl(&params)?;
let endianness = params.endianness();
let name = params.ident.clone();
let align = params.align.write();
Ok(quote! {
impl #impl_generics ::bitbuffer::BitWriteSized<#endianness> for #name #ty_generics #where_clause {
#[allow(unused_braces)]
fn write_sized(&self, __stream: &mut ::bitbuffer::BitWriteStream<#endianness>, input_size: usize) -> ::bitbuffer::Result<()> {
#align
#encode
}
}
})
}
}

View file

@ -0,0 +1,13 @@
use crate::params::StructParam;
use crate::write::field::write_struct;
use proc_macro2::TokenStream;
use quote::quote;
pub fn derive_encode_struct(params: &StructParam) -> TokenStream {
let body = write_struct(&params.fields, params.span());
quote!(
#body
Ok(())
)
}

View file

@ -3,13 +3,8 @@
#![allow(unused_imports)]
use bitbuffer::{BitReadStream, Endianness};
use bitbuffer_derive::{BitRead, BitWrite, BitWriteSized};
use bitbuffer_derive::{BitRead, BitReadSized, BitWrite, BitWriteSized};
#[derive(BitWrite)]
#[discriminant_bits = 4]
enum TestEnumRest {
Foo,
Bar,
#[discriminant = "_"]
Asd,
}
#[derive(BitWrite, PartialEq, Debug)]
#[align]
struct AlignStruct(u8);

View file

@ -73,7 +73,19 @@
];
devShells = let
tools = with pkgs; [bacon cargo-edit cargo-outdated];
tools = with pkgs; [
bacon
cargo-edit
cargo-outdated
(writeShellApplication {
name = "cargo-expand";
runtimeInputs = [cargo-expand toolchain];
text = ''
# shellcheck disable=SC2068
RUSTC_BOOTSTRAP=1 cargo-expand $@
'';
})
];
in {
default = mkShell {
nativeBuildInputs = [toolchain] ++ tools;