mirror of
https://codeberg.org/icewind/bitbuffer.git
synced 2026-06-04 00:54:07 +02:00
allow BitReadSized to be derived
This commit is contained in:
parent
c145761970
commit
5ec499f42f
4 changed files with 181 additions and 43 deletions
|
|
@ -1,8 +1,8 @@
|
||||||
//! Automatically generate `BitRead` implementations for structs and enums
|
//! Automatically generate `BitRead` and `BitReadSized` implementations for structs and enums
|
||||||
//!
|
//!
|
||||||
//! # Structs
|
//! # Structs
|
||||||
//!
|
//!
|
||||||
//! The implementation can be derived for struct as long as every field in the struct implements `BitRead` or `BitReadSized`
|
//! The implementation can be derived for a struct as long as every field in the struct implements `BitRead` or `BitReadSized`
|
||||||
//!
|
//!
|
||||||
//! The struct is read field by field in the order they are defined in, if the size for a field is set `stream.read_sized()`
|
//! The struct is read field by field in the order they are defined in, if the size for a field is set `stream.read_sized()`
|
||||||
//! will be used, otherwise `stream_read()` will be used.
|
//! will be used, otherwise `stream_read()` will be used.
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
//! - use a previously defined field as the size using the `size` attribute
|
//! - use a previously defined field as the size using the `size` attribute
|
||||||
//! - read a set number of bits as an integer, using the resulting value as size using the `read_bits` attribute
|
//! - read a set number of bits as an integer, using the resulting value as size using the `read_bits` attribute
|
||||||
//!
|
//!
|
||||||
|
//! When deriving `BitReadSized` the input size can be used in the size attribute as the `input_size` field.
|
||||||
|
//!
|
||||||
//! ## Examples
|
//! ## Examples
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
|
|
@ -34,9 +36,24 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use bitstream_reader_derive::BitReadSized;
|
||||||
|
//!
|
||||||
|
//! #[derive(BitReadSized, PartialEq, Debug)]
|
||||||
|
//! struct TestStructSized {
|
||||||
|
//! foo: u8,
|
||||||
|
//! #[size = "input_size"]
|
||||||
|
//! string: String,
|
||||||
|
//! #[size = "input_size"]
|
||||||
|
//! int: u8,
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
//! # Enums
|
//! # Enums
|
||||||
//!
|
//!
|
||||||
//! The implementation can be derived for enums as long as every variant of the enums either has no field, or an unnamed field that implements `BitRead`
|
//! The implementation can be derived for an enum as long as every variant of the enum either has no field, or an unnamed field that implements `BitRead`
|
||||||
|
//!
|
||||||
|
//! Deriving `BitReadSized` for enums is not supported, only `BitRead` can be derived.
|
||||||
//!
|
//!
|
||||||
//! The enum is read by first reading a set number of bits as the discriminant of the enum, then the variant for the read discriminant is read.
|
//! The enum is read by first reading a set number of bits as the discriminant of the enum, then the variant for the read discriminant is read.
|
||||||
//!
|
//!
|
||||||
|
|
@ -73,12 +90,15 @@ extern crate proc_macro;
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{quote, quote_spanned};
|
use quote::{quote, quote_spanned};
|
||||||
use syn::{Attribute, Data, DeriveInput, Expr, Field, Fields, GenericParam, Generics, Ident, Lit, Meta, parse_macro_input, parse_quote, LitStr};
|
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{
|
||||||
|
parse_macro_input, parse_quote, Attribute, Data, DeriveInput, Expr, Field, Fields,
|
||||||
|
GenericParam, Generics, Ident, Lit, LitStr, Meta,
|
||||||
|
};
|
||||||
|
|
||||||
/// See the [crate documentation](index.html) for details
|
/// See the [crate documentation](index.html) for details
|
||||||
#[proc_macro_derive(BitRead, attributes(size, size_bits, discriminant_bits, discriminant))]
|
#[proc_macro_derive(BitRead, attributes(size, size_bits, discriminant_bits, discriminant))]
|
||||||
pub fn derive_helper_attr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn derive_bitread(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
|
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
let name = input.ident;
|
let name = input.ident;
|
||||||
|
|
@ -107,6 +127,40 @@ pub fn derive_helper_attr(input: proc_macro::TokenStream) -> proc_macro::TokenSt
|
||||||
proc_macro::TokenStream::from(expanded)
|
proc_macro::TokenStream::from(expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See the [crate documentation](index.html) for details
|
||||||
|
#[proc_macro_derive(
|
||||||
|
BitReadSized,
|
||||||
|
attributes(size, size_bits, discriminant_bits, discriminant)
|
||||||
|
)]
|
||||||
|
pub fn derive_bitread_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
|
let name = input.ident;
|
||||||
|
|
||||||
|
let generics = add_trait_bounds(input.generics);
|
||||||
|
let mut trait_generics = generics.clone();
|
||||||
|
// we need these separate generics to only add out Endianness param to the 'impl'
|
||||||
|
let (_, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
|
trait_generics
|
||||||
|
.params
|
||||||
|
.push(parse_quote!(_E: ::bitstream_reader::Endianness));
|
||||||
|
let (impl_generics, _, _) = trait_generics.split_for_impl();
|
||||||
|
|
||||||
|
let parse = parse(&input.data, &name, &input.attrs);
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl #impl_generics ::bitstream_reader::BitReadSized<_E> for #name #ty_generics #where_clause {
|
||||||
|
fn read(stream: &mut ::bitstream_reader::BitStream<_E>, input_size: usize) -> ::bitstream_reader::Result<Self> {
|
||||||
|
#parse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// panic!("{}", TokenStream::to_string(&expanded));
|
||||||
|
|
||||||
|
proc_macro::TokenStream::from(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
// Add a bound `T: Read` to every type parameter T.
|
// Add a bound `T: Read` to every type parameter T.
|
||||||
fn add_trait_bounds(mut generics: Generics) -> Generics {
|
fn add_trait_bounds(mut generics: Generics) -> Generics {
|
||||||
for param in &mut generics.params {
|
for param in &mut generics.params {
|
||||||
|
|
@ -166,9 +220,13 @@ fn parse(data: &Data, struct_name: &Ident, attrs: &Vec<Attribute>) -> TokenStrea
|
||||||
let discriminant_bits = match get_attr(attrs, "discriminant_bits") {
|
let discriminant_bits = match get_attr(attrs, "discriminant_bits") {
|
||||||
Some(bits_lit) => match bits_lit {
|
Some(bits_lit) => match bits_lit {
|
||||||
Lit::Int(bits) => bits.value(),
|
Lit::Int(bits) => bits.value(),
|
||||||
_ => panic!("'discriminant_bits' attribute is required to be an integer literal")
|
_ => {
|
||||||
|
panic!("'discriminant_bits' attribute is required to be an integer literal")
|
||||||
|
}
|
||||||
},
|
},
|
||||||
None => panic!("'discriminant_bits' attribute is required when deriving `BinRead` for enums")
|
None => panic!(
|
||||||
|
"'discriminant_bits' attribute is required when deriving `BinRead` for enums"
|
||||||
|
),
|
||||||
};
|
};
|
||||||
let discriminant_read = quote! {
|
let discriminant_read = quote! {
|
||||||
let discriminant:usize = stream.read_int(#discriminant_bits as usize)?;
|
let discriminant:usize = stream.read_int(#discriminant_bits as usize)?;
|
||||||
|
|
@ -177,38 +235,43 @@ fn parse(data: &Data, struct_name: &Ident, attrs: &Vec<Attribute>) -> TokenStrea
|
||||||
let mut last_discriminant = -1;
|
let mut last_discriminant = -1;
|
||||||
let mut discriminants = Vec::with_capacity(data.variants.len());
|
let mut discriminants = Vec::with_capacity(data.variants.len());
|
||||||
for variant in &data.variants {
|
for variant in &data.variants {
|
||||||
let discriminant = variant.discriminant.clone()
|
let discriminant = variant
|
||||||
|
.discriminant
|
||||||
|
.clone()
|
||||||
.map(|(_, expr)| match expr {
|
.map(|(_, expr)| match expr {
|
||||||
Expr::Lit(expr_lit) => expr_lit.lit,
|
Expr::Lit(expr_lit) => expr_lit.lit,
|
||||||
_ => panic!("discriminant is required to be an integer literal")
|
_ => panic!("discriminant is required to be an integer literal"),
|
||||||
})
|
})
|
||||||
.or_else(|| get_attr(&variant.attrs, "discriminant"))
|
.or_else(|| get_attr(&variant.attrs, "discriminant"))
|
||||||
.map(|lit| match lit {
|
.map(|lit| match lit {
|
||||||
Lit::Int(lit) => lit.value(),
|
Lit::Int(lit) => lit.value(),
|
||||||
_ => panic!("discriminant is required to be an integer literal")
|
_ => panic!("discriminant is required to be an integer literal"),
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| (last_discriminant + 1) as u64)
|
||||||
(last_discriminant + 1) as u64
|
as usize;
|
||||||
}) as usize;
|
|
||||||
last_discriminant = discriminant as isize;
|
last_discriminant = discriminant as isize;
|
||||||
discriminants.push(discriminant)
|
discriminants.push(discriminant)
|
||||||
}
|
}
|
||||||
let match_arms = data.variants.iter().zip(discriminants.iter()).map(|(variant, discriminant)| {
|
let match_arms =
|
||||||
let span = variant.span();
|
data.variants
|
||||||
let variant_name = &variant.ident;
|
.iter()
|
||||||
let read_fields = match &variant.fields {
|
.zip(discriminants.iter())
|
||||||
Fields::Unit => quote_spanned! {span=>
|
.map(|(variant, discriminant)| {
|
||||||
#struct_name::#variant_name
|
let span = variant.span();
|
||||||
},
|
let variant_name = &variant.ident;
|
||||||
Fields::Unnamed(_) => quote_spanned! {span=>
|
let read_fields = match &variant.fields {
|
||||||
#struct_name::#variant_name(stream.read()?)
|
Fields::Unit => quote_spanned! {span=>
|
||||||
},
|
#struct_name::#variant_name
|
||||||
_ => unimplemented!()
|
},
|
||||||
};
|
Fields::Unnamed(_) => quote_spanned! {span=>
|
||||||
quote_spanned! {span=>
|
#struct_name::#variant_name(stream.read()?)
|
||||||
#discriminant => #read_fields,
|
},
|
||||||
}
|
_ => unimplemented!(),
|
||||||
});
|
};
|
||||||
|
quote_spanned! {span=>
|
||||||
|
#discriminant => #read_fields,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let span = data.enum_token.span();
|
let span = data.enum_token.span();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use bitstream_reader::{BigEndian, BitBuffer, BitStream, LittleEndian};
|
use bitstream_reader::{BigEndian, BitBuffer, BitStream, LittleEndian};
|
||||||
use bitstream_reader_derive::BitRead;
|
use bitstream_reader_derive::{BitRead, BitReadSized};
|
||||||
|
|
||||||
#[derive(BitRead, PartialEq, Debug)]
|
#[derive(BitRead, PartialEq, Debug)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
|
|
@ -67,8 +67,14 @@ enum TestBareEnum {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_read_bare_enum() {
|
fn test_read_bare_enum() {
|
||||||
let bytes = vec![
|
let bytes = vec![
|
||||||
0b1100_0110, 0b1000_0100, 0b1000_0100, 0b1000_0100,
|
0b1100_0110,
|
||||||
0b1000_0100, 0b1000_0100, 0b1000_0100, 0b1000_0100,
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
];
|
];
|
||||||
let buffer = BitBuffer::new(bytes, BigEndian);
|
let buffer = BitBuffer::new(bytes, BigEndian);
|
||||||
let mut stream = BitStream::from(buffer);
|
let mut stream = BitStream::from(buffer);
|
||||||
|
|
@ -90,17 +96,55 @@ enum TestUnnamedFieldEnum {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_read_unnamed_field_enum() {
|
fn test_read_unnamed_field_enum() {
|
||||||
let bytes = vec![
|
let bytes = vec![
|
||||||
0b1100_0110, 0b1000_0100, 0b1000_0100, 0b1000_0100,
|
0b1100_0110,
|
||||||
0b1000_0100, 0b1000_0100, 0b1000_0100, 0b1000_0100,
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
|
0b1000_0100,
|
||||||
];
|
];
|
||||||
let buffer = BitBuffer::new(bytes, BigEndian);
|
let buffer = BitBuffer::new(bytes, BigEndian);
|
||||||
let mut stream = BitStream::from(buffer);
|
let mut stream = BitStream::from(buffer);
|
||||||
assert_eq!(TestUnnamedFieldEnum::Asd(0b_00_0110_10), stream.read().unwrap());
|
assert_eq!(
|
||||||
|
TestUnnamedFieldEnum::Asd(0b_00_0110_10),
|
||||||
|
stream.read().unwrap()
|
||||||
|
);
|
||||||
assert_eq!(10, stream.pos());
|
assert_eq!(10, stream.pos());
|
||||||
stream.set_pos(2).unwrap();
|
stream.set_pos(2).unwrap();
|
||||||
assert_eq!(TestUnnamedFieldEnum::Foo(0b11_0_1000), stream.read().unwrap());
|
assert_eq!(
|
||||||
|
TestUnnamedFieldEnum::Foo(0b11_0_1000),
|
||||||
|
stream.read().unwrap()
|
||||||
|
);
|
||||||
assert_eq!(12, stream.pos());
|
assert_eq!(12, stream.pos());
|
||||||
stream.set_pos(4).unwrap();
|
stream.set_pos(4).unwrap();
|
||||||
assert_eq!(TestUnnamedFieldEnum::Bar(true), stream.read().unwrap());
|
assert_eq!(TestUnnamedFieldEnum::Bar(true), stream.read().unwrap());
|
||||||
assert_eq!(7, stream.pos());
|
assert_eq!(7, stream.pos());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(BitReadSized, PartialEq, Debug)]
|
||||||
|
struct TestStructSized {
|
||||||
|
foo: u8,
|
||||||
|
#[size = "input_size"]
|
||||||
|
string: String,
|
||||||
|
#[size = "input_size"]
|
||||||
|
int: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_struct_sized() {
|
||||||
|
let bytes = vec![
|
||||||
|
12, 'h' as u8, 'e' as u8, 'l' as u8, 'l' as u8, 'o' as u8, 0, 0, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
let buffer = BitBuffer::new(bytes, LittleEndian);
|
||||||
|
let mut stream = BitStream::from(buffer);
|
||||||
|
assert_eq!(
|
||||||
|
TestStructSized {
|
||||||
|
foo: 12,
|
||||||
|
string: "hel".to_owned(),
|
||||||
|
int: 4,
|
||||||
|
},
|
||||||
|
stream.read_sized(3).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,7 @@
|
||||||
//! - [`BitRead`] is for types that can be read without requiring any size info (e.g. null-terminal strings, floats, whole integers, etc)
|
//! - [`BitRead`] is for types that can be read without requiring any size info (e.g. null-terminal strings, floats, whole integers, etc)
|
||||||
//! - [`BitReadSized`] is for types that require external sizing information to be read (fixed length strings, arbitrary length integers
|
//! - [`BitReadSized`] is for types that require external sizing information to be read (fixed length strings, arbitrary length integers
|
||||||
//!
|
//!
|
||||||
//! The [`BitRead`] trait can be used with `#[derive]` if all fields implement [`BitRead`] or [`BitReadSized`],
|
//! The [`BitRead`] and [`BitReadSized`] traits can be used with `#[derive]` if all fields implement [`BitRead`] or [`BitReadSized`].
|
||||||
//! when `derive`d for structs, it will read all fields in the struct in the order they are defined in.
|
|
||||||
//!
|
//!
|
||||||
//! # Examples
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
|
|
|
||||||
40
src/read.rs
40
src/read.rs
|
|
@ -6,7 +6,7 @@ use crate::{BitStream, Endianness, Result};
|
||||||
///
|
///
|
||||||
/// # Structs
|
/// # Structs
|
||||||
///
|
///
|
||||||
/// The implementation can be derived for struct as long as every field in the struct implements `BitRead` or [`BitReadSized`]
|
/// The implementation can be derived for a struct as long as every field in the struct implements `BitRead` or [`BitReadSized`]
|
||||||
///
|
///
|
||||||
/// The struct is read field by field in the order they are defined in, if the size for a field is set [`stream.read_sized()`][read_sized]
|
/// The struct is read field by field in the order they are defined in, if the size for a field is set [`stream.read_sized()`][read_sized]
|
||||||
/// will be used, otherwise [`stream_read()`][read] will be used.
|
/// will be used, otherwise [`stream_read()`][read] will be used.
|
||||||
|
|
@ -19,8 +19,8 @@ use crate::{BitStream, Endianness, Result};
|
||||||
/// ## Examples
|
/// ## Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use bitstream_reader_derive::BitRead;
|
/// # use bitstream_reader_derive::BitRead;
|
||||||
///
|
/// #
|
||||||
/// #[derive(BitRead)]
|
/// #[derive(BitRead)]
|
||||||
/// struct TestStruct {
|
/// struct TestStruct {
|
||||||
/// foo: u8,
|
/// foo: u8,
|
||||||
|
|
@ -40,7 +40,7 @@ use crate::{BitStream, Endianness, Result};
|
||||||
///
|
///
|
||||||
/// # Enums
|
/// # Enums
|
||||||
///
|
///
|
||||||
/// The implementation can be derived for enums as long as every variant of the enums either has no field, or an unnamed field that implements `BitRead`
|
/// The implementation can be derived for an enum as long as every variant of the enum either has no field, or an unnamed field that implements `BitRead`
|
||||||
///
|
///
|
||||||
/// The enum is read by first reading a set number of bits as the discriminant of the enum, then the variant for the read discriminant is read.
|
/// The enum is read by first reading a set number of bits as the discriminant of the enum, then the variant for the read discriminant is read.
|
||||||
///
|
///
|
||||||
|
|
@ -136,6 +136,38 @@ impl<E: Endianness> BitRead<E> for String {
|
||||||
///
|
///
|
||||||
/// The meaning of the set sized depends on the type being read (e.g, number of bits for integers,
|
/// The meaning of the set sized depends on the type being read (e.g, number of bits for integers,
|
||||||
/// number of bytes for strings, number of items for Vec's, etc)
|
/// number of bytes for strings, number of items for Vec's, etc)
|
||||||
|
///
|
||||||
|
/// The `BitReadSized` trait can be used with `#[derive]` on structs
|
||||||
|
///
|
||||||
|
/// The implementation can be derived for a struct as long as every field in the struct implements [`BitRead`] or `BitReadSized`
|
||||||
|
///
|
||||||
|
/// The struct is read field by field in the order they are defined in, if the size for a field is set [`stream.read_sized()`][read_sized]
|
||||||
|
/// will be used, otherwise [`stream_read()`][read] will be used.
|
||||||
|
///
|
||||||
|
/// The size for a field can be set using 4 different methods
|
||||||
|
/// - set the size as an integer using the `size` attribute,
|
||||||
|
/// - use a previously defined field as the size using the `size` attribute
|
||||||
|
/// - based on the input size by setting `size` attribute to `"input_size"`
|
||||||
|
/// - read a set number of bits as an integer, using the resulting value as size using the `read_bits` attribute
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bitstream_reader_derive::BitReadSized;
|
||||||
|
/// #
|
||||||
|
/// #[derive(BitReadSized, PartialEq, Debug)]
|
||||||
|
/// struct TestStructSized {
|
||||||
|
/// foo: u8,
|
||||||
|
/// #[size = "input_size"]
|
||||||
|
/// string: String,
|
||||||
|
/// #[size = "input_size"]
|
||||||
|
/// int: u8,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`BitRead`]: trait.BitRead.html
|
||||||
|
/// [read_sized]: struct.BitStream.html#method.read_sized
|
||||||
|
/// [read]: struct.BitStream.html#method.read
|
||||||
pub trait BitReadSized<E: Endianness>: Sized {
|
pub trait BitReadSized<E: Endianness>: Sized {
|
||||||
/// Read the type from stream
|
/// Read the type from stream
|
||||||
fn read(stream: &mut BitStream<E>, size: usize) -> Result<Self>;
|
fn read(stream: &mut BitStream<E>, size: usize) -> Result<Self>;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue