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

allow BitReadSized to be derived

This commit is contained in:
Robin Appelman 2019-02-28 19:47:12 +01:00
commit 5ec499f42f
4 changed files with 181 additions and 43 deletions

View file

@ -1,8 +1,8 @@
//! Automatically generate `BitRead` implementations for structs and enums
//! Automatically generate `BitRead` and `BitReadSized` implementations for structs and enums
//!
//! # 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()`
//! 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
//! - 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
//!
//! ```
@ -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
//!
//! 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.
//!
@ -73,12 +90,15 @@ extern crate proc_macro;
use proc_macro2::{Span, TokenStream};
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::{
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
#[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 name = input.ident;
@ -107,6 +127,40 @@ pub fn derive_helper_attr(input: proc_macro::TokenStream) -> proc_macro::TokenSt
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.
fn add_trait_bounds(mut generics: Generics) -> Generics {
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") {
Some(bits_lit) => match bits_lit {
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:usize = stream.read_int(#discriminant_bits as usize)?;
@ -177,23 +235,28 @@ fn parse(data: &Data, struct_name: &Ident, attrs: &Vec<Attribute>) -> TokenStrea
let mut last_discriminant = -1;
let mut discriminants = Vec::with_capacity(data.variants.len());
for variant in &data.variants {
let discriminant = variant.discriminant.clone()
let discriminant = variant
.discriminant
.clone()
.map(|(_, expr)| match expr {
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"))
.map(|lit| match lit {
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(|| {
(last_discriminant + 1) as u64
}) as usize;
.unwrap_or_else(|| (last_discriminant + 1) as u64)
as usize;
last_discriminant = discriminant as isize;
discriminants.push(discriminant)
}
let match_arms = data.variants.iter().zip(discriminants.iter()).map(|(variant, discriminant)| {
let match_arms =
data.variants
.iter()
.zip(discriminants.iter())
.map(|(variant, discriminant)| {
let span = variant.span();
let variant_name = &variant.ident;
let read_fields = match &variant.fields {
@ -203,7 +266,7 @@ fn parse(data: &Data, struct_name: &Ident, attrs: &Vec<Attribute>) -> TokenStrea
Fields::Unnamed(_) => quote_spanned! {span=>
#struct_name::#variant_name(stream.read()?)
},
_ => unimplemented!()
_ => unimplemented!(),
};
quote_spanned! {span=>
#discriminant => #read_fields,

View file

@ -1,5 +1,5 @@
use bitstream_reader::{BigEndian, BitBuffer, BitStream, LittleEndian};
use bitstream_reader_derive::BitRead;
use bitstream_reader_derive::{BitRead, BitReadSized};
#[derive(BitRead, PartialEq, Debug)]
struct TestStruct {
@ -67,8 +67,14 @@ enum TestBareEnum {
#[test]
fn test_read_bare_enum() {
let bytes = vec![
0b1100_0110, 0b1000_0100, 0b1000_0100, 0b1000_0100,
0b1000_0100, 0b1000_0100, 0b1000_0100, 0b1000_0100,
0b1100_0110,
0b1000_0100,
0b1000_0100,
0b1000_0100,
0b1000_0100,
0b1000_0100,
0b1000_0100,
0b1000_0100,
];
let buffer = BitBuffer::new(bytes, BigEndian);
let mut stream = BitStream::from(buffer);
@ -90,17 +96,55 @@ enum TestUnnamedFieldEnum {
#[test]
fn test_read_unnamed_field_enum() {
let bytes = vec![
0b1100_0110, 0b1000_0100, 0b1000_0100, 0b1000_0100,
0b1000_0100, 0b1000_0100, 0b1000_0100, 0b1000_0100,
0b1100_0110,
0b1000_0100,
0b1000_0100,
0b1000_0100,
0b1000_0100,
0b1000_0100,
0b1000_0100,
0b1000_0100,
];
let buffer = BitBuffer::new(bytes, BigEndian);
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());
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());
stream.set_pos(4).unwrap();
assert_eq!(TestUnnamedFieldEnum::Bar(true), stream.read().unwrap());
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()
);
}

View file

@ -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)
//! - [`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`],
//! when `derive`d for structs, it will read all fields in the struct in the order they are defined in.
//! The [`BitRead`] and [`BitReadSized`] traits can be used with `#[derive]` if all fields implement [`BitRead`] or [`BitReadSized`].
//!
//! # Examples
//!

View file

@ -6,7 +6,7 @@ use crate::{BitStream, Endianness, Result};
///
/// # 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]
/// will be used, otherwise [`stream_read()`][read] will be used.
@ -19,8 +19,8 @@ use crate::{BitStream, Endianness, Result};
/// ## Examples
///
/// ```
/// use bitstream_reader_derive::BitRead;
///
/// # use bitstream_reader_derive::BitRead;
/// #
/// #[derive(BitRead)]
/// struct TestStruct {
/// foo: u8,
@ -40,7 +40,7 @@ use crate::{BitStream, Endianness, Result};
///
/// # 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.
///
@ -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,
/// 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 {
/// Read the type from stream
fn read(stream: &mut BitStream<E>, size: usize) -> Result<Self>;