mirror of
https://codeberg.org/icewind/bitbuffer.git
synced 2026-06-04 00:54:07 +02:00
allow deriving BitReadSized for enums
This commit is contained in:
parent
5ec499f42f
commit
a2b0d4ffb4
4 changed files with 113 additions and 22 deletions
|
|
@ -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
|
||||||
|
|
@ -122,7 +138,7 @@ pub fn derive_bitread(input: proc_macro::TokenStream) -> proc_macro::TokenStream
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// panic!("{}", TokenStream::to_string(&expanded));
|
//panic!("{}", TokenStream::to_string(&expanded));
|
||||||
|
|
||||||
proc_macro::TokenStream::from(expanded)
|
proc_macro::TokenStream::from(expanded)
|
||||||
}
|
}
|
||||||
|
|
@ -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) => {
|
||||||
#struct_name::#variant_name(stream.read()?)
|
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()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => 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)?
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
|
||||||
33
src/read.rs
33
src/read.rs
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue