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

Merge pull request #8 from DCNick3/align

Re-introduce the align function & add #[align] attribute
This commit is contained in:
Robin Appelman 2023-02-07 16:05:03 +01:00 committed by GitHub
commit 80a1c7cc22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 355 additions and 19 deletions

View file

@ -103,6 +103,38 @@
//! } //! }
//! ``` //! ```
//! //!
//! # Alignment
//!
//! You can request alignment for a struct, enum or a field using #[align] attribute.
//!
//! ```
//! # use bitbuffer::BitRead;
//! #
//! #[derive(BitRead)]
//! #[align] // align the reader before starting to read the struct
//! struct TestAlignStruct {
//! #[size = 1]
//! foo: u8,
//! #[align] // align the reader before reading the field
//! bar: u8,
//! }
//! ```
//!
//! It can also be applied to non-unit enum variants:
//!
//! ```
//! # use bitbuffer::BitRead;
//! #
//! #[derive(BitRead)]
//! #[align] // align the reader before starting to read the enum
//! #[discriminant_bits = 2]
//! enum TestAlignEnum {
//! Foo(u8),
//! #[align] // align the reader before reading the variant (but after reading the discriminant)
//! Bar(u8),
//! }
//! ```
//!
//! # Endianness //! # Endianness
//! //!
//! If the struct that `BitRead` or `BitReadSized` is derived for requires a Endianness type parameter, you need to tell the derive macro the name of the type parameter used //! If the struct that `BitRead` or `BitReadSized` is derived for requires a Endianness type parameter, you need to tell the derive macro the name of the type parameter used
@ -146,12 +178,12 @@ use syn::{
parse_macro_input, parse_quote, parse_str, Attribute, Data, DataStruct, DeriveInput, Expr, parse_macro_input, parse_quote, parse_str, Attribute, Data, DataStruct, DeriveInput, Expr,
Fields, GenericParam, Ident, Lit, LitInt, LitStr, Path, Fields, GenericParam, Ident, Lit, LitInt, LitStr, Path,
}; };
use syn_util::get_attribute_value; use syn_util::{contains_attribute, get_attribute_value};
/// See the [crate documentation](index.html) for details /// See the [crate documentation](index.html) for details
#[proc_macro_derive( #[proc_macro_derive(
BitRead, BitRead,
attributes(size, size_bits, discriminant_bits, discriminant, endianness) attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
)] )]
pub fn derive_bitread(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn derive_bitread(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_bitread_trait(input, "BitRead".to_owned(), None) derive_bitread_trait(input, "BitRead".to_owned(), None)
@ -161,7 +193,7 @@ pub fn derive_bitread(input: proc_macro::TokenStream) -> proc_macro::TokenStream
/// See the [crate documentation](index.html) for details /// See the [crate documentation](index.html) for details
#[proc_macro_derive( #[proc_macro_derive(
BitReadSized, BitReadSized,
attributes(size, size_bits, discriminant_bits, discriminant, endianness) attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
)] )]
pub fn derive_bitread_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn derive_bitread_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let extra_param = parse_str::<TokenStream>(", input_size: usize").unwrap(); let extra_param = parse_str::<TokenStream>(", input_size: usize").unwrap();
@ -171,7 +203,7 @@ pub fn derive_bitread_sized(input: proc_macro::TokenStream) -> proc_macro::Token
/// See the [crate documentation](index.html) for details /// See the [crate documentation](index.html) for details
#[proc_macro_derive( #[proc_macro_derive(
BitWrite, BitWrite,
attributes(size, size_bits, discriminant_bits, discriminant, endianness) attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
)] )]
pub fn derive_bitwrite(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn derive_bitwrite(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_bitwrite_trait(input, "BitWrite".into(), "write".into(), None) derive_bitwrite_trait(input, "BitWrite".into(), "write".into(), None)
@ -181,7 +213,7 @@ pub fn derive_bitwrite(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
/// See the [crate documentation](index.html) for details /// See the [crate documentation](index.html) for details
#[proc_macro_derive( #[proc_macro_derive(
BitWriteSized, BitWriteSized,
attributes(size, size_bits, discriminant_bits, discriminant, endianness) attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
)] )]
pub fn derive_bitwrite_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn derive_bitwrite_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let extra_param = parse_str::<TokenStream>(", input_size: usize").unwrap(); let extra_param = parse_str::<TokenStream>(", input_size: usize").unwrap();
@ -266,6 +298,7 @@ fn derive_bitread_trait(
let expanded = quote! { let expanded = quote! {
impl #impl_generics #trait_def for #name #ty_generics #where_clause { 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> { 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 // 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) { match <Self as #trait_def>::#size_method_name(#extra_param_call) {
@ -281,6 +314,7 @@ fn derive_bitread_trait(
} }
} }
#[allow(unused_braces)]
unsafe fn read_unchecked(stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness_ident>#extra_param, end: bool) -> ::bitbuffer::Result<Self> { unsafe fn read_unchecked(stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness_ident>#extra_param, end: bool) -> ::bitbuffer::Result<Self> {
#parsed_unchecked #parsed_unchecked
} }
@ -299,11 +333,14 @@ fn derive_bitread_trait(
fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool) -> TokenStream { fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool) -> TokenStream {
let span = struct_name.span(); let span = struct_name.span();
let align = get_align(attrs);
match data { match data {
Data::Struct(DataStruct { fields, .. }) => { Data::Struct(DataStruct { fields, .. }) => {
let values = fields.iter().map(|f| { let values = fields.iter().map(|f| {
// Get attributes `#[..]` on each field // Get attributes `#[..]` on each field
let size = get_field_size(&f.attrs, f.span()); let size = get_field_size(&f.attrs, f.span());
let align = get_align(attrs);
let field_type = &f.ty; let field_type = &f.ty;
let span = f.span(); let span = f.span();
if unchecked { if unchecked {
@ -311,6 +348,7 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
Some(size) => { Some(size) => {
quote_spanned! { span => quote_spanned! { span =>
{ {
#align;
let _size: usize = #size; let _size: usize = #size;
stream.read_sized_unchecked::<#field_type>(_size, end)? stream.read_sized_unchecked::<#field_type>(_size, end)?
} }
@ -318,7 +356,10 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
} }
None => { None => {
quote_spanned! { span => quote_spanned! { span =>
stream.read_unchecked::<#field_type>(end)? {
#align;
stream.read_unchecked::<#field_type>(end)?
}
} }
} }
} }
@ -327,6 +368,7 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
Some(size) => { Some(size) => {
quote_spanned! { span => quote_spanned! { span =>
{ {
#align;
let _size: usize = #size; let _size: usize = #size;
stream.read_sized::<#field_type>(_size)? stream.read_sized::<#field_type>(_size)?
} }
@ -334,7 +376,10 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
} }
None => { None => {
quote_spanned! { span => quote_spanned! { span =>
stream.read::<#field_type>()? {
#align;
stream.read::<#field_type>()?
}
} }
} }
} }
@ -356,6 +401,8 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
} }
}); });
quote_spanned! { span => quote_spanned! { span =>
#align;
#(#definitions)* #(#definitions)*
Ok(#struct_name { Ok(#struct_name {
@ -364,11 +411,15 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
} }
} }
Fields::Unnamed(_) => quote_spanned! { span => Fields::Unnamed(_) => quote_spanned! { span =>
#align;
Ok(#struct_name( Ok(#struct_name(
#(#values ,)* #(#values ,)*
)) ))
}, },
Fields::Unit => quote_spanned! {span=> Fields::Unit => quote_spanned! { span=>
#align;
Ok(#struct_name) Ok(#struct_name)
}, },
} }
@ -377,7 +428,7 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
let discriminant_bits: u64 = match get_attribute_value(attrs, &["discriminant_bits"]) { let discriminant_bits: u64 = match get_attribute_value(attrs, &["discriminant_bits"]) {
Some(attr) => attr, Some(attr) => attr,
None => { None => {
return quote! {span=> return quote_spanned! { span=>
compile_error!("'discriminant_bits' attribute is required when deriving `BinRead` for enums"); compile_error!("'discriminant_bits' attribute is required when deriving `BinRead` for enums");
} }
} }
@ -388,15 +439,24 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
let span = variant.span(); let span = variant.span();
let variant_name = &variant.ident; let variant_name = &variant.ident;
let read_fields = match &variant.fields { let read_fields = match &variant.fields {
Fields::Unit => quote_spanned! {span=> Fields::Unit => {
#struct_name::#variant_name 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) => { Fields::Unnamed(f) => {
let size = get_field_size(&variant.attrs, f.span()); let size = get_field_size(&variant.attrs, f.span());
let align = get_align(&variant.attrs);
match size { match size {
Some(size) => { Some(size) => {
quote_spanned! { span => quote_spanned! { span =>
#struct_name::#variant_name({ #struct_name::#variant_name({
#align;
let _size:usize = #size; let _size:usize = #size;
stream.read_sized(_size)? stream.read_sized(_size)?
}) })
@ -404,7 +464,10 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
} }
None => { None => {
quote_spanned! { span => quote_spanned! { span =>
#struct_name::#variant_name(stream.read()?) {
#align;
#struct_name::#variant_name(stream.read()?)
}
} }
} }
} }
@ -437,6 +500,7 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
let enum_name = Lit::Str(LitStr::new(&struct_name.to_string(), struct_name.span())); let enum_name = Lit::Str(LitStr::new(&struct_name.to_string(), struct_name.span()));
quote_spanned! {span=> quote_spanned! {span=>
#align;
let discriminant:#repr = stream.read_int(#discriminant_bits as usize)?; let discriminant:#repr = stream.read_int(#discriminant_bits as usize)?;
Ok(match discriminant { Ok(match discriminant {
#(#match_arms)* #(#match_arms)*
@ -453,6 +517,12 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
fn size(data: Data, struct_name: &Ident, attrs: &[Attribute], has_input_size: bool) -> TokenStream { fn size(data: Data, struct_name: &Ident, attrs: &[Attribute], has_input_size: bool) -> TokenStream {
let span = struct_name.span(); let span = struct_name.span();
if contains_attribute(attrs, &["align"]) {
return quote_spanned! { span =>
None
};
}
match data { match data {
Data::Struct(DataStruct { fields, .. }) => { Data::Struct(DataStruct { fields, .. }) => {
let sizes = fields.iter().map(|f| { let sizes = fields.iter().map(|f| {
@ -503,6 +573,7 @@ fn size(data: Data, struct_name: &Ident, attrs: &[Attribute], has_input_size: bo
} }
}; };
// Unit variants having "align" attributes are not allowed, so we can just check if all variants are unit
let is_unit = data let is_unit = data
.variants .variants
.iter() .iter()
@ -526,6 +597,9 @@ fn is_const_size(attrs: &[Attribute], has_input_size: bool) -> bool {
if get_attribute_value::<Lit>(attrs, &["size_bits"]).is_some() { if get_attribute_value::<Lit>(attrs, &["size_bits"]).is_some() {
return false; return false;
} }
if contains_attribute(attrs, &["align"]) {
return false;
}
get_attribute_value(attrs, &["size"]) get_attribute_value(attrs, &["size"])
.map(|size_lit| match size_lit { .map(|size_lit| match size_lit {
Lit::Int(_) => true, Lit::Int(_) => true,
@ -571,3 +645,13 @@ fn repr_for_bits(discriminant_bits: u64) -> TokenStream {
quote!(usize) quote!(usize)
} }
} }
fn get_align(attrs: &[Attribute]) -> TokenStream {
if contains_attribute(attrs, &["align"]) {
quote! {
stream.align()?
}
} else {
quote! { () }
}
}

View file

@ -7,7 +7,7 @@ use syn::{
parse_macro_input, parse_quote, parse_str, Attribute, Data, DataStruct, DeriveInput, Expr, parse_macro_input, parse_quote, parse_str, Attribute, Data, DataStruct, DeriveInput, Expr,
Fields, GenericParam, Ident, Index, Lit, LitInt, Member, Path, Type, Fields, GenericParam, Ident, Index, Lit, LitInt, Member, Path, Type,
}; };
use syn_util::get_attribute_value; use syn_util::{contains_attribute, get_attribute_value};
pub fn derive_bitwrite_trait( pub fn derive_bitwrite_trait(
input: proc_macro::TokenStream, input: proc_macro::TokenStream,
@ -72,6 +72,7 @@ pub fn derive_bitwrite_trait(
let write_method = Ident::new(&write_method_name, span); let write_method = Ident::new(&write_method_name, span);
let expanded = quote! { let expanded = quote! {
#[allow(unused_braces)]
impl #impl_generics #trait_def for #name #ty_generics #where_clause { 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<()> { fn #write_method(&self, __target__stream: &mut ::bitbuffer::BitWriteStream<#endianness_ident>#extra_param) -> ::bitbuffer::Result<()> {
#parsed #parsed
@ -87,6 +88,8 @@ pub fn derive_bitwrite_trait(
fn write(data: Data, struct_name: &Ident, attrs: &[Attribute]) -> TokenStream { fn write(data: Data, struct_name: &Ident, attrs: &[Attribute]) -> TokenStream {
let span = struct_name.span(); let span = struct_name.span();
let align = get_align(attrs);
match data { match data {
Data::Struct(DataStruct { fields, .. }) => { Data::Struct(DataStruct { fields, .. }) => {
let expand = fields.iter().enumerate().map(|(i, field)| { let expand = fields.iter().enumerate().map(|(i, field)| {
@ -114,6 +117,7 @@ fn write(data: Data, struct_name: &Ident, attrs: &[Attribute]) -> TokenStream {
let writes = fields.iter().enumerate().map(|(i, f)| { let writes = fields.iter().enumerate().map(|(i, f)| {
// Get attributes `#[..]` on each field // Get attributes `#[..]` on each field
let size = get_field_size(&f.attrs, f.span()); let size = get_field_size(&f.attrs, f.span());
let align = get_align(&f.attrs);
let span = f.span(); let span = f.span();
let member = f.ident.clone().map(Member::Named).unwrap_or_else(|| { let member = f.ident.clone().map(Member::Named).unwrap_or_else(|| {
Member::Unnamed(Index { Member::Unnamed(Index {
@ -125,6 +129,7 @@ fn write(data: Data, struct_name: &Ident, attrs: &[Attribute]) -> TokenStream {
Some(size) => { Some(size) => {
quote_spanned! { span => quote_spanned! { span =>
{ {
#align;
let _size: usize = #size; let _size: usize = #size;
__target__stream.write_sized(&self.#member, _size)?; __target__stream.write_sized(&self.#member, _size)?;
} }
@ -132,13 +137,17 @@ fn write(data: Data, struct_name: &Ident, attrs: &[Attribute]) -> TokenStream {
} }
None => { None => {
quote_spanned! { span => { quote_spanned! { span => {
__target__stream.write(&self.#member)?; {
#align;
__target__stream.write(&self.#member)?;
}
}} }}
} }
} }
}); });
quote_spanned! {span=> quote_spanned! {span=>
#align;
#(#expand)* #(#expand)*
#(#writes)* #(#writes)*
Ok(()) Ok(())
@ -217,15 +226,24 @@ fn write(data: Data, struct_name: &Ident, attrs: &[Attribute]) -> TokenStream {
let variant_name = &variant.ident; let variant_name = &variant.ident;
match &variant.fields { match &variant.fields {
Fields::Unit => quote_spanned! {span => Fields::Unit => {
#struct_name::#variant_name => {}, 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) => { Fields::Unnamed(f) => {
let size = get_field_size(&variant.attrs, f.span()); let size = get_field_size(&variant.attrs, f.span());
let align = get_align(&variant.attrs);
match size { match size {
Some(size) => { Some(size) => {
quote_spanned! { span => quote_spanned! { span =>
#struct_name::#variant_name(inner) => { #struct_name::#variant_name(inner) => {
#align;
let size:usize = #size; let size:usize = #size;
__target__stream.write_sized(inner, size)?; __target__stream.write_sized(inner, size)?;
} }
@ -233,7 +251,10 @@ fn write(data: Data, struct_name: &Ident, attrs: &[Attribute]) -> TokenStream {
} }
None => { None => {
quote_spanned! { span => quote_spanned! { span =>
#struct_name::#variant_name(inner) => { __target__stream.write(inner)?; } #struct_name::#variant_name(inner) => {
#align;
__target__stream.write(inner)?;
}
} }
} }
} }
@ -246,6 +267,7 @@ fn write(data: Data, struct_name: &Ident, attrs: &[Attribute]) -> TokenStream {
let repr = repr_for_bits(discriminant_bits); let repr = repr_for_bits(discriminant_bits);
quote_spanned! {span=> quote_spanned! {span=>
#align;
let discriminant:#repr = match &self { let discriminant:#repr = match &self {
#(#discriminant_value),* #(#discriminant_value),*
}; };
@ -300,3 +322,13 @@ fn type_is_int(ty: &Type) -> bool {
false false
} }
} }
fn get_align(attrs: &[Attribute]) -> TokenStream {
if contains_attribute(attrs, &["align"]) {
quote! {
__target__stream.align()
}
} else {
quote! { () }
}
}

View file

@ -335,3 +335,79 @@ fn test_bit_size_sized() {
Some(8 + 8 * 16 + 1) Some(8 + 8 * 16 + 1)
); );
} }
#[derive(BitRead, PartialEq, Debug)]
#[align]
struct AlignStruct(u8);
#[test]
fn test_align() {
let bytes = vec![0, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let buffer = BitReadBuffer::new(&bytes, BigEndian);
let mut stream = BitReadStream::from(buffer);
stream.read_bool().unwrap();
assert_eq!(AlignStruct(0x80), stream.read().unwrap());
assert_eq!(16, stream.pos());
assert_eq!(None, bit_size_of::<AlignStruct>());
}
#[derive(BitRead, PartialEq, Debug)]
#[align]
struct AlignFieldStruct {
#[size = 1]
foo: u8,
#[align]
bar: u8,
}
#[test]
fn test_align_field() {
let bytes = vec![0, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let buffer = BitReadBuffer::new(&bytes, BigEndian);
let mut stream = BitReadStream::from(buffer);
assert_eq!(
AlignFieldStruct { foo: 0, bar: 0x80 },
stream.read().unwrap()
);
assert_eq!(16, stream.pos());
assert_eq!(None, bit_size_of::<AlignStruct>());
}
#[derive(BitRead, PartialEq, Debug)]
#[discriminant_bits = 4]
#[align]
enum AlignEnum {
Foo,
Bar(u8),
}
#[test]
fn test_align_enum() {
let bytes = vec![0x00, 0x18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let buffer = BitReadBuffer::new(&bytes, BigEndian);
let mut stream = BitReadStream::from(buffer);
stream.read_bool().unwrap();
assert_eq!(AlignEnum::Bar(0x80), stream.read().unwrap());
assert_eq!(20, stream.pos());
assert_eq!(None, bit_size_of::<AlignEnum>());
}
#[derive(BitRead, PartialEq, Debug)]
#[discriminant_bits = 4]
#[align]
enum AlignEnumField {
Foo,
#[align]
Bar(u8),
}
#[test]
fn test_align_enum_field() {
let bytes = vec![0x00, 0x10, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let buffer = BitReadBuffer::new(&bytes, BigEndian);
let mut stream = BitReadStream::from(buffer);
stream.read_bool().unwrap();
assert_eq!(AlignEnumField::Bar(0x80), stream.read().unwrap());
assert_eq!(24, stream.pos());
assert_eq!(None, bit_size_of::<AlignEnum>());
}

View file

@ -289,3 +289,76 @@ fn test_read_size_expression() {
stream.write(&val).unwrap(); stream.write(&val).unwrap();
assert_eq!(bytes, data); assert_eq!(bytes, data);
} }
#[derive(BitWrite, PartialEq, Debug)]
#[align]
struct AlignStruct(u8);
#[test]
fn test_align() {
let bytes = vec![0, 0x80];
let mut data = Vec::new();
let mut stream = BitWriteStream::new(&mut data, BigEndian);
stream.write_bool(false).unwrap();
let val = AlignStruct(0x80);
stream.write(&val).unwrap();
assert_eq!(bytes, data);
}
#[derive(BitWrite, PartialEq, Debug)]
#[align]
struct AlignFieldStruct {
#[size = 1]
foo: u8,
#[align]
bar: u8,
}
#[test]
fn test_align_field() {
let bytes = vec![0, 0x80];
let mut data = Vec::new();
let mut stream = BitWriteStream::new(&mut data, BigEndian);
let val = AlignFieldStruct { foo: 0, bar: 0x80 };
stream.write(&val).unwrap();
assert_eq!(bytes, data);
}
#[derive(BitWrite, PartialEq, Debug)]
#[discriminant_bits = 4]
#[align]
enum AlignEnum {
Foo,
Bar(u8),
}
#[test]
fn test_align_enum() {
let bytes = vec![0x00, 0x18, 0];
let mut data = Vec::new();
let mut stream = BitWriteStream::new(&mut data, BigEndian);
stream.write_bool(false).unwrap();
let val = AlignEnum::Bar(0x80);
stream.write(&val).unwrap();
assert_eq!(bytes, data);
}
#[derive(BitWrite, PartialEq, Debug)]
#[discriminant_bits = 4]
#[align]
enum AlignEnumField {
Foo,
#[align]
Bar(u8),
}
#[test]
fn test_align_enum_field() {
let bytes = vec![0x00, 0x10, 0x80];
let mut data = Vec::new();
let mut stream = BitWriteStream::new(&mut data, BigEndian);
stream.write_bool(false).unwrap();
let val = AlignEnumField::Bar(0x80);
stream.write(&val).unwrap();
assert_eq!(bytes, data);
}

View file

@ -423,6 +423,45 @@ where
} }
} }
/// Align the stream on the next byte and returns the amount of bits read
///
/// # Errors
///
/// - [`ReadError::NotEnoughData`]: not enough bits available in the stream to skip
///
/// # Examples
///
/// ```
/// # use bitbuffer::{BitReadBuffer, BitReadStream, LittleEndian, Result};
/// #
/// # fn main() -> Result<()> {
/// # let bytes = vec![
/// # 0b1011_0101, 0b0110_1010, 0b1010_1100, 0b1001_1001,
/// # 0b1001_1001, 0b1001_1001, 0b1001_1001, 0b1110_0111
/// # ];
/// # let buffer = BitReadBuffer::new(&bytes, LittleEndian);
/// # let mut stream = BitReadStream::new(buffer);
/// stream.align()?;
/// assert_eq!(stream.pos(), 0);
///
/// stream.skip_bits(3)?;
/// assert_eq!(stream.pos(), 3);
/// stream.align();
/// assert_eq!(stream.pos(), 8);
/// assert_eq!(stream.read_int::<u8>(4)?, 0b1010);
/// #
/// # Ok(())
/// # }
/// ```
///
/// [`ReadError::NotEnoughData`]: enum.ReadError.html#variant.NotEnoughData
pub fn align(&mut self) -> Result<usize> {
match self.pos % 8 {
0 => Ok(0),
n => self.skip_bits(8 - n).map(|_| 8 - n),
}
}
/// Set the position of the stream /// Set the position of the stream
/// ///
/// # Errors /// # Errors

View file

@ -98,6 +98,38 @@ where
} }
} }
/// Align the stream on the next byte by writing zero bits and returns the amount of bits written
///
/// # Examples
///
/// ```
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
/// #
/// # fn main() -> Result<()> {
/// # use bitbuffer::{BitWriteStream, LittleEndian};
///
/// let mut data = Vec::new();
/// let mut stream = BitWriteStream::new(&mut data, LittleEndian);
/// stream.write_bool(true)?;
/// stream.align();
/// assert_eq!(stream.bit_len(), 8);
/// assert_eq!(data, [0b0000_0001]);
/// #
/// # Ok(())
/// # }
/// ```
///
/// [`ReadError::NotEnoughData`]: enum.ReadError.html#variant.NotEnoughData
pub fn align(&mut self) -> usize {
match self.bit_len() % 8 {
0 => 0,
n => {
self.push_bits(0, 8 - n);
8 - n
}
}
}
/// Write a boolean into the buffer /// Write a boolean into the buffer
/// ///
/// # Examples /// # Examples