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

allow deriving BitReadSized for enums

This commit is contained in:
Robin Appelman 2019-02-28 20:24:43 +01:00
commit a2b0d4ffb4
4 changed files with 113 additions and 22 deletions

View file

@ -51,12 +51,12 @@
//! //!
//! # Enums //! # Enums
//! //!
//! 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 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` or `BitReadSized`
//!
//! 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.
//! //!
//! For details about setting the input size for fields implementing `BitReadSized` see the block about size in the `Structs` section above.
//!
//! The discriminant for the variants defaults to incrementing by one for every field, starting with `0`. //! The discriminant for the variants defaults to incrementing by one for every field, starting with `0`.
//! You can overwrite the discriminant for a field, which will also change the discriminant for every following field. //! You can overwrite the discriminant for a field, which will also change the discriminant for every following field.
//! //!
@ -80,20 +80,36 @@
//! #[derive(BitRead)] //! #[derive(BitRead)]
//! #[discriminant_bits = 2] //! #[discriminant_bits = 2]
//! enum TestUnnamedFieldEnum { //! enum TestUnnamedFieldEnum {
//! #[size = 5]
//! Foo(i8), //! Foo(i8),
//! Bar(bool), //! Bar(bool),
//! #[discriminant = 3] // since rust only allows setting the discriminant on field-less enums, you can use an attribute instead //! #[discriminant = 3] // since rust only allows setting the discriminant on field-less enums, you can use an attribute instead
//! Asd(u8), //! Asd(u8),
//! } //! }
//! ``` //! ```
//!
//! ```
//! # use bitstream_reader_derive::BitReadSized;
//! #
//! #[derive(BitReadSized, PartialEq, Debug)]
//! #[discriminant_bits = 2]
//! enum TestUnnamedFieldEnumSized {
//! #[size = 5]
//! Foo(i8),
//! Bar(bool),
//! #[discriminant = 3]
//! #[size = "input_size"]
//! Asd(u8),
//! }
//! ```
extern crate proc_macro; 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::spanned::Spanned; use syn::spanned::Spanned;
use syn::{ use syn::{
parse_macro_input, parse_quote, Attribute, Data, DeriveInput, Expr, Field, Fields, parse_macro_input, parse_quote, Attribute, Data, DeriveInput, Expr, Fields, GenericParam,
GenericParam, Generics, Ident, Lit, LitStr, Meta, Generics, Ident, Lit, LitStr, Meta,
}; };
/// See the [crate documentation](index.html) for details /// See the [crate documentation](index.html) for details
@ -181,7 +197,7 @@ fn parse(data: &Data, struct_name: &Ident, attrs: &Vec<Attribute>) -> TokenStrea
let definitions = fields.named.iter().map(|f| { let definitions = fields.named.iter().map(|f| {
let name = &f.ident; let name = &f.ident;
// Get attributes `#[..]` on each field // Get attributes `#[..]` on each field
let size = get_field_size(f); let size = get_field_size(&f.attrs, f.span());
let field_type = &f.ty; let field_type = &f.ty;
let span = f.span(); let span = f.span();
match size { match size {
@ -263,9 +279,24 @@ fn parse(data: &Data, struct_name: &Ident, attrs: &Vec<Attribute>) -> TokenStrea
Fields::Unit => quote_spanned! {span=> Fields::Unit => quote_spanned! {span=>
#struct_name::#variant_name #struct_name::#variant_name
}, },
Fields::Unnamed(_) => quote_spanned! {span=> Fields::Unnamed(f) => {
let size = get_field_size(&variant.attrs, f.span());
match size {
Some(size) => {
quote_spanned! {span=>
#struct_name::#variant_name({
let size:usize = #size;
stream.read_sized(size)?
})
}
}
None => {
quote_spanned! {span=>
#struct_name::#variant_name(stream.read()?) #struct_name::#variant_name(stream.read()?)
}, }
}
}
}
_ => unimplemented!(), _ => unimplemented!(),
}; };
quote_spanned! {span=> quote_spanned! {span=>
@ -290,9 +321,8 @@ fn parse(data: &Data, struct_name: &Ident, attrs: &Vec<Attribute>) -> TokenStrea
} }
} }
fn get_field_size(field: &Field) -> Option<TokenStream> { fn get_field_size(attrs: &Vec<Attribute>, span: Span) -> Option<TokenStream> {
let span = field.span(); get_attr(attrs, "size")
get_attr(&field.attrs, "size")
.map(|size_lit| match size_lit { .map(|size_lit| match size_lit {
Lit::Int(size) => { Lit::Int(size) => {
quote_spanned! {span=> quote_spanned! {span=>
@ -300,7 +330,7 @@ fn get_field_size(field: &Field) -> Option<TokenStream> {
} }
} }
Lit::Str(size_field) => { Lit::Str(size_field) => {
let size = Ident::new(&size_field.value(), Span::call_site()); let size = Ident::new(&size_field.value(), span);
quote_spanned! {span=> quote_spanned! {span=>
#size as usize #size as usize
} }
@ -308,7 +338,7 @@ fn get_field_size(field: &Field) -> Option<TokenStream> {
_ => panic!("Unsupported value for size attribute"), _ => panic!("Unsupported value for size attribute"),
}) })
.or_else(|| { .or_else(|| {
get_attr(&field.attrs, "size_bits").map(|size_bits_lit| { get_attr(attrs, "size_bits").map(|size_bits_lit| {
quote_spanned! {span=> quote_spanned! {span=>
stream.read_int::<usize>(#size_bits_lit)? stream.read_int::<usize>(#size_bits_lit)?
} }

View file

@ -87,6 +87,7 @@ fn test_read_bare_enum() {
#[derive(BitRead, PartialEq, Debug)] #[derive(BitRead, PartialEq, Debug)]
#[discriminant_bits = 2] #[discriminant_bits = 2]
enum TestUnnamedFieldEnum { enum TestUnnamedFieldEnum {
#[size = 5]
Foo(i8), Foo(i8),
Bar(bool), Bar(bool),
#[discriminant = 3] #[discriminant = 3]
@ -113,11 +114,8 @@ fn test_read_unnamed_field_enum() {
); );
assert_eq!(10, stream.pos()); assert_eq!(10, stream.pos());
stream.set_pos(2).unwrap(); stream.set_pos(2).unwrap();
assert_eq!( assert_eq!(TestUnnamedFieldEnum::Foo(0b11_0_1), stream.read().unwrap());
TestUnnamedFieldEnum::Foo(0b11_0_1000), assert_eq!(9, stream.pos());
stream.read().unwrap()
);
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());
@ -148,3 +146,35 @@ fn test_read_struct_sized() {
stream.read_sized(3).unwrap() stream.read_sized(3).unwrap()
); );
} }
#[derive(BitReadSized, PartialEq, Debug)]
#[discriminant_bits = 2]
enum TestUnnamedFieldEnumSized {
#[size = 5]
Foo(i8),
Bar(bool),
#[discriminant = 3]
#[size = "input_size"]
Asd(u8),
}
#[test]
fn test_read_unnamed_field_enum_sized() {
let bytes = vec![
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!(
TestUnnamedFieldEnumSized::Asd(0b_00_0110),
stream.read_sized(6).unwrap()
);
assert_eq!(8, stream.pos());
}

View file

@ -48,7 +48,7 @@ use std::fmt;
use std::fmt::Display; use std::fmt::Display;
pub use std::string::FromUtf8Error; pub use std::string::FromUtf8Error;
pub use bitstream_reader_derive::BitRead; pub use bitstream_reader_derive::{BitRead, BitReadSized};
pub use buffer::BitBuffer; pub use buffer::BitBuffer;
pub use endianness::*; pub use endianness::*;
pub use read::{BitRead, BitReadSized}; pub use read::{BitRead, BitReadSized};

View file

@ -40,10 +40,12 @@ use crate::{BitStream, Endianness, Result};
/// ///
/// # Enums /// # Enums
/// ///
/// 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 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` or [`BitReadSized`]
/// ///
/// 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.
/// ///
/// For details about setting the input size for fields implementing [`BitReadSized`] see the block about size in the `Structs` section above.
///
/// The discriminant for the variants defaults to incrementing by one for every field, starting with `0`. /// The discriminant for the variants defaults to incrementing by one for every field, starting with `0`.
/// You can overwrite the discriminant for a field, which will also change the discriminant for every following field. /// You can overwrite the discriminant for a field, which will also change the discriminant for every following field.
/// ///
@ -67,6 +69,7 @@ use crate::{BitStream, Endianness, Result};
/// #[derive(BitRead)] /// #[derive(BitRead)]
/// #[discriminant_bits = 2] /// #[discriminant_bits = 2]
/// enum TestUnnamedFieldEnum { /// enum TestUnnamedFieldEnum {
/// #[size = 5]
/// Foo(i8), /// Foo(i8),
/// Bar(bool), /// Bar(bool),
/// #[discriminant = 3] // since rust only allows setting the discriminant on field-less enums, you can use an attribute instead /// #[discriminant = 3] // since rust only allows setting the discriminant on field-less enums, you can use an attribute instead
@ -165,6 +168,34 @@ impl<E: Endianness> BitRead<E> for String {
/// } /// }
/// ``` /// ```
/// ///
/// # Enums
///
/// 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`] or `BitReadSized`
///
/// 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.
///
/// For details about setting the input size for fields implementing `BitReadSized` see the block about size in the `Structs` section above.
///
/// The discriminant for the variants defaults to incrementing by one for every field, starting with `0`.
/// You can overwrite the discriminant for a field, which will also change the discriminant for every following field.
///
/// ## Examples
///
/// ```
/// # use bitstream_reader_derive::BitReadSized;
/// #
/// #[derive(BitReadSized)]
/// #[discriminant_bits = 2]
/// enum TestUnnamedFieldEnum {
/// #[size = 5]
/// Foo(i8),
/// Bar(bool),
/// #[discriminant = 3] // since rust only allows setting the discriminant on field-less enums, you can use an attribute instead
/// #[size = "input_size"]
/// Asd(u8),
/// }
/// ```
///
/// [`BitRead`]: trait.BitRead.html /// [`BitRead`]: trait.BitRead.html
/// [read_sized]: struct.BitStream.html#method.read_sized /// [read_sized]: struct.BitStream.html#method.read_sized
/// [read]: struct.BitStream.html#method.read /// [read]: struct.BitStream.html#method.read