mirror of
https://codeberg.org/icewind/bitbuffer.git
synced 2026-06-04 09:04:05 +02:00
start of BitWrite derive macro
This commit is contained in:
parent
e43b0b6bb2
commit
ab86b164aa
7 changed files with 653 additions and 39 deletions
|
|
@ -138,7 +138,7 @@ use quote::{quote, quote_spanned};
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{
|
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, Ident, Lit, LitStr, Path, Variant,
|
Field, Fields, Ident, Lit, LitStr, Path, Variant,
|
||||||
};
|
};
|
||||||
use syn_util::get_attribute_value;
|
use syn_util::get_attribute_value;
|
||||||
|
|
||||||
|
|
@ -162,6 +162,15 @@ pub fn derive_bitread_sized(input: proc_macro::TokenStream) -> proc_macro::Token
|
||||||
derive_bitread_trait(input, "BitReadSized".to_owned(), Some(extra_param))
|
derive_bitread_trait(input, "BitReadSized".to_owned(), Some(extra_param))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See the [crate documentation](index.html) for details
|
||||||
|
#[proc_macro_derive(
|
||||||
|
BitWrite,
|
||||||
|
attributes(size, size_bits, discriminant_bits, discriminant, endianness)
|
||||||
|
)]
|
||||||
|
pub fn derive_bitwrite(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
derive_bitwrite_trait(input, "BitWrite".to_owned(), None)
|
||||||
|
}
|
||||||
|
|
||||||
fn derive_bitread_trait(
|
fn derive_bitread_trait(
|
||||||
input: proc_macro::TokenStream,
|
input: proc_macro::TokenStream,
|
||||||
trait_name: String,
|
trait_name: String,
|
||||||
|
|
@ -258,7 +267,7 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
|
||||||
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(), true);
|
||||||
let field_type = &f.ty;
|
let field_type = &f.ty;
|
||||||
let span = f.span();
|
let span = f.span();
|
||||||
if unchecked {
|
if unchecked {
|
||||||
|
|
@ -342,7 +351,7 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
|
||||||
#struct_name::#variant_name
|
#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(), true);
|
||||||
match size {
|
match size {
|
||||||
Some(size) => {
|
Some(size) => {
|
||||||
quote_spanned! { span =>
|
quote_spanned! { span =>
|
||||||
|
|
@ -362,18 +371,7 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let discriminant_token: TokenStream = match Discriminant::from(variant) {
|
let discriminant_token = get_discriminant_token(variant, &mut last_discriminant);
|
||||||
Discriminant::Int(discriminant) => {
|
|
||||||
last_discriminant = discriminant as isize;
|
|
||||||
quote_spanned! { span => #discriminant }
|
|
||||||
}
|
|
||||||
Discriminant::Wildcard => quote_spanned! { span => _ },
|
|
||||||
Discriminant::Default => {
|
|
||||||
let new_discriminant = (last_discriminant + 1) as usize;
|
|
||||||
last_discriminant += 1;
|
|
||||||
quote_spanned! { span => #new_discriminant }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
quote_spanned! {span=>
|
quote_spanned! {span=>
|
||||||
#discriminant_token => #read_fields,
|
#discriminant_token => #read_fields,
|
||||||
}
|
}
|
||||||
|
|
@ -404,7 +402,7 @@ fn size(data: Data, struct_name: &Ident, attrs: &[Attribute], has_input_size: bo
|
||||||
let sizes = fields.iter().map(|f| {
|
let sizes = fields.iter().map(|f| {
|
||||||
// Get attributes `#[..]` on each field
|
// Get attributes `#[..]` on each field
|
||||||
if is_const_size(&f.attrs, has_input_size) {
|
if is_const_size(&f.attrs, has_input_size) {
|
||||||
let size = get_field_size(&f.attrs, f.span());
|
let size = get_field_size(&f.attrs, f.span(), true);
|
||||||
let field_type = &f.ty;
|
let field_type = &f.ty;
|
||||||
let span = f.span();
|
let span = f.span();
|
||||||
match size {
|
match size {
|
||||||
|
|
@ -463,6 +461,187 @@ fn size(data: Data, struct_name: &Ident, attrs: &[Attribute], has_input_size: bo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_discriminant_token(variant: &Variant, last_discriminant: &mut isize) -> TokenStream {
|
||||||
|
let span = variant.span();
|
||||||
|
match Discriminant::from(variant) {
|
||||||
|
Discriminant::Int(discriminant) => {
|
||||||
|
*last_discriminant = discriminant as isize;
|
||||||
|
quote_spanned! { span => #discriminant }
|
||||||
|
}
|
||||||
|
Discriminant::Wildcard => quote_spanned! { span => _ },
|
||||||
|
Discriminant::Default => {
|
||||||
|
let new_discriminant = (*last_discriminant + 1) as usize;
|
||||||
|
*last_discriminant += 1;
|
||||||
|
quote_spanned! { span => #new_discriminant }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_bitwrite_trait(
|
||||||
|
input: proc_macro::TokenStream,
|
||||||
|
trait_name: String,
|
||||||
|
extra_param: Option<TokenStream>,
|
||||||
|
) -> proc_macro::TokenStream {
|
||||||
|
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
|
let name = &input.ident;
|
||||||
|
|
||||||
|
let endianness = get_attribute_value(&input.attrs, &["endianness"]);
|
||||||
|
let mut trait_generics = input.generics.clone();
|
||||||
|
// we need these separate generics to only add out Endianness param to the 'impl'
|
||||||
|
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||||
|
if endianness.is_none() {
|
||||||
|
trait_generics
|
||||||
|
.params
|
||||||
|
.push(parse_quote!(_E: ::bitbuffer::Endianness));
|
||||||
|
}
|
||||||
|
let (impl_generics, _, _) = trait_generics.split_for_impl();
|
||||||
|
let span = input.span();
|
||||||
|
|
||||||
|
let size = size(
|
||||||
|
input.data.clone(),
|
||||||
|
&name,
|
||||||
|
&input.attrs,
|
||||||
|
extra_param.is_some(),
|
||||||
|
);
|
||||||
|
let write = write(input.data.clone(), &name, &input.attrs);
|
||||||
|
let parsed_unchecked = parse(input.data.clone(), &name, &input.attrs, true);
|
||||||
|
|
||||||
|
let endianness_placeholder = endianness.unwrap_or_else(|| "_E".to_owned());
|
||||||
|
let trait_def_str = format!("::bitbuffer::{}<{}>", trait_name, &endianness_placeholder);
|
||||||
|
let trait_def = parse_str::<Path>(&trait_def_str).unwrap();
|
||||||
|
|
||||||
|
let endianness_ident = Ident::new(&endianness_placeholder, span);
|
||||||
|
|
||||||
|
let size_extra_param = if extra_param.is_some() {
|
||||||
|
Some(quote!(input_size: usize))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let extra_param_call = if extra_param.is_some() {
|
||||||
|
Some(quote!(input_size))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
let expanded = quote! {
|
||||||
|
impl #impl_generics #trait_def for #name #ty_generics #where_clause {
|
||||||
|
fn write(&self, stream: &mut ::bitbuffer::BitWriteStream<#endianness_ident>#extra_param) -> ::bitbuffer::Result<()> {
|
||||||
|
#write
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// panic!("{}", TokenStream::to_string(&expanded));
|
||||||
|
|
||||||
|
proc_macro::TokenStream::from(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(data: Data, struct_name: &Ident, attrs: &[Attribute]) -> TokenStream {
|
||||||
|
let span = struct_name.span();
|
||||||
|
|
||||||
|
match data {
|
||||||
|
Data::Struct(DataStruct { fields, .. }) => {
|
||||||
|
let destructure = fields.iter().map(|field| {
|
||||||
|
let span = field.span();
|
||||||
|
if let Some(name) = &field.ident {
|
||||||
|
quote_spanned! { span => let #name = &self.#name; }
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let write_field = |index: usize, field: &Field| {
|
||||||
|
let span = field.span();
|
||||||
|
let size = get_field_size(&field.attrs, span, false);
|
||||||
|
let field_type = &field.ty;
|
||||||
|
let name = field
|
||||||
|
.ident
|
||||||
|
.as_ref()
|
||||||
|
.map(|name| quote_spanned! { span => #name})
|
||||||
|
.unwrap_or(quote_spanned! { span => 0});
|
||||||
|
match size {
|
||||||
|
Some(size) => {
|
||||||
|
quote_spanned! { span =>
|
||||||
|
{
|
||||||
|
let _size: usize = #size;
|
||||||
|
stream.write_sized::<#field_type>(&self.#name, _size)?
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
quote_spanned! { span =>
|
||||||
|
stream.write::<#field_type>(&self.#name)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let writes = fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, field)| write_field(index, field));
|
||||||
|
|
||||||
|
quote_spanned! { span =>
|
||||||
|
#(#destructure)*
|
||||||
|
#(#writes)*
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Data::Enum(data) => {
|
||||||
|
let discriminant_bits: u64 = get_attribute_value(attrs, &["discriminant_bits"]).expect(
|
||||||
|
"'discriminant_bits' attribute is required when deriving `BinWrite` for enums",
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut last_discriminant = -1;
|
||||||
|
let match_arms = data.variants.iter().map(|variant| {
|
||||||
|
let discriminant_token = get_discriminant_token(variant, &mut last_discriminant);
|
||||||
|
|
||||||
|
let span = variant.span();
|
||||||
|
let variant_name = &variant.ident;
|
||||||
|
match &variant.fields {
|
||||||
|
Fields::Unit => quote_spanned! {span=>
|
||||||
|
#struct_name::#variant_name => stream.write_int(#discriminant_token, #discriminant_bits as usize)
|
||||||
|
},
|
||||||
|
Fields::Unnamed(f) => {
|
||||||
|
let size = get_field_size(&variant.attrs, f.span(), false);
|
||||||
|
match size {
|
||||||
|
Some(size) => {
|
||||||
|
quote_spanned! { span =>
|
||||||
|
#struct_name::#variant_name(inner) => {
|
||||||
|
stream.write_int(#discriminant_token, #discriminant_bits as usize)?;
|
||||||
|
stream.write_sized(inner, #size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
quote_spanned! { span =>
|
||||||
|
#struct_name::#variant_name(inner) => {
|
||||||
|
stream.write_int(#discriminant_token, #discriminant_bits as usize)?;
|
||||||
|
stream.write(inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let span = data.enum_token.span();
|
||||||
|
|
||||||
|
let enum_name = Lit::Str(LitStr::new(&struct_name.to_string(), struct_name.span()));
|
||||||
|
quote_spanned! {span=>
|
||||||
|
match self {
|
||||||
|
#(#match_arms),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_const_size(attrs: &[Attribute], has_input_size: bool) -> bool {
|
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;
|
||||||
|
|
@ -476,7 +655,7 @@ fn is_const_size(attrs: &[Attribute], has_input_size: bool) -> bool {
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_field_size(attrs: &[Attribute], span: Span) -> Option<TokenStream> {
|
fn get_field_size(attrs: &[Attribute], span: Span, is_read: bool) -> Option<TokenStream> {
|
||||||
get_attribute_value(attrs, &["size"])
|
get_attribute_value(attrs, &["size"])
|
||||||
.map(|size_lit| match size_lit {
|
.map(|size_lit| match size_lit {
|
||||||
Lit::Int(size) => {
|
Lit::Int(size) => {
|
||||||
|
|
@ -486,17 +665,28 @@ fn get_field_size(attrs: &[Attribute], span: Span) -> Option<TokenStream> {
|
||||||
}
|
}
|
||||||
Lit::Str(size_field) => {
|
Lit::Str(size_field) => {
|
||||||
let size = parse_str::<Expr>(&size_field.value()).unwrap();
|
let size = parse_str::<Expr>(&size_field.value()).unwrap();
|
||||||
|
if !is_read {
|
||||||
|
// we borrow the field so we need to deref
|
||||||
|
quote_spanned! {span =>
|
||||||
|
*(#size) as usize
|
||||||
|
}
|
||||||
|
} else {
|
||||||
quote_spanned! {span =>
|
quote_spanned! {span =>
|
||||||
(#size) as usize
|
(#size) as usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_ => panic!("Unsupported value for size attribute"),
|
_ => panic!("Unsupported value for size attribute"),
|
||||||
})
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
get_attribute_value::<Lit>(attrs, &["size_bits"]).map(|size_bits_lit| {
|
get_attribute_value::<Lit>(attrs, &["size_bits"]).map(|size_bits_lit| {
|
||||||
|
if is_read {
|
||||||
quote_spanned! {span =>
|
quote_spanned! {span =>
|
||||||
stream.read_int::<usize> (#size_bits_lit) ?
|
stream.read_int::<usize> (#size_bits_lit) ?
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
panic!("size_bits is not allowed here")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
bitbuffer_derive/tests/write_test.rs
Normal file
39
bitbuffer_derive/tests/write_test.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
#![allow(unreachable_patterns)]
|
||||||
|
|
||||||
|
use bitbuffer::{
|
||||||
|
bit_size_of, bit_size_of_sized, BigEndian, BitReadBuffer, BitReadStream, BitWrite, Endianness,
|
||||||
|
LittleEndian,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(BitWrite)]
|
||||||
|
struct TestStruct {
|
||||||
|
foo: u8,
|
||||||
|
str: String,
|
||||||
|
#[size = 2]
|
||||||
|
truncated: String,
|
||||||
|
bar: u16,
|
||||||
|
float: f32,
|
||||||
|
#[size = 3]
|
||||||
|
asd: u8,
|
||||||
|
#[size = "asd"]
|
||||||
|
previous_field: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitWrite)]
|
||||||
|
#[discriminant_bits = 2]
|
||||||
|
enum TestBareEnum {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
Asd = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitWrite)]
|
||||||
|
#[discriminant_bits = 2]
|
||||||
|
enum TestUnnamedFieldEnum {
|
||||||
|
#[size = 5]
|
||||||
|
Foo(i8),
|
||||||
|
Bar(bool),
|
||||||
|
#[discriminant = 3]
|
||||||
|
Asd(u8),
|
||||||
|
}
|
||||||
|
|
@ -57,11 +57,12 @@
|
||||||
use err_derive::Error;
|
use err_derive::Error;
|
||||||
pub use std::string::FromUtf8Error;
|
pub use std::string::FromUtf8Error;
|
||||||
|
|
||||||
pub use bitbuffer_derive::{BitRead, BitReadSized};
|
pub use bitbuffer_derive::{BitRead, BitReadSized, BitWrite};
|
||||||
pub use endianness::*;
|
pub use endianness::*;
|
||||||
pub use read::{BitRead, BitReadSized, LazyBitRead, LazyBitReadSized};
|
pub use read::{BitRead, BitReadSized, LazyBitRead, LazyBitReadSized};
|
||||||
pub use readbuffer::BitReadBuffer;
|
pub use readbuffer::BitReadBuffer;
|
||||||
pub use readstream::BitReadStream;
|
pub use readstream::BitReadStream;
|
||||||
|
pub use write::BitWrite;
|
||||||
pub use writestream::BitWriteStream;
|
pub use writestream::BitWriteStream;
|
||||||
|
|
||||||
mod endianness;
|
mod endianness;
|
||||||
|
|
@ -69,6 +70,7 @@ mod num_traits;
|
||||||
mod read;
|
mod read;
|
||||||
mod readbuffer;
|
mod readbuffer;
|
||||||
mod readstream;
|
mod readstream;
|
||||||
|
mod write;
|
||||||
mod writestream;
|
mod writestream;
|
||||||
|
|
||||||
/// Errors that can be returned when trying to read from a buffer
|
/// Errors that can be returned when trying to read from a buffer
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,8 @@ use std::sync::Arc;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`BitReadSized`]: trait.BitReadSized.html
|
/// [`BitReadSized`]: trait.BitReadSized.html
|
||||||
/// [read_sized]: struct.BitStream.html#method.read_sized
|
/// [read_sized]: struct.BitReadStream.html#method.read_sized
|
||||||
/// [read]: struct.BitStream.html#method.read
|
/// [read]: struct.BitReadStream.html#method.read
|
||||||
pub trait BitRead<E: Endianness>: Sized {
|
pub trait BitRead<E: Endianness>: Sized {
|
||||||
/// Read the type from stream
|
/// Read the type from stream
|
||||||
fn read(stream: &mut BitReadStream<E>) -> Result<Self>;
|
fn read(stream: &mut BitReadStream<E>) -> Result<Self>;
|
||||||
|
|
|
||||||
326
src/write.rs
Normal file
326
src/write.rs
Normal file
|
|
@ -0,0 +1,326 @@
|
||||||
|
use crate::endianness::{BigEndian, LittleEndian};
|
||||||
|
use crate::{BitWriteStream, Endianness, Result};
|
||||||
|
use std::mem::size_of;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Trait for types that can be written to a stream without requiring the size to be configured
|
||||||
|
///
|
||||||
|
/// The `BitWrite` trait can be used with `#[derive]` on structs and enums
|
||||||
|
///
|
||||||
|
/// # Structs
|
||||||
|
///
|
||||||
|
/// The implementation can be derived for a struct as long as every field in the struct implements `BitWrite` or [`BitWriteSized`]
|
||||||
|
///
|
||||||
|
/// The struct is written field by field in the order they are defined in, if the size for a field is set [`stream.write_sized()`][write_sized]
|
||||||
|
/// will be used, otherwise [`stream_write()`][write] will be used.
|
||||||
|
///
|
||||||
|
/// The size for a field can be set using 3 different methods
|
||||||
|
/// - set the size as an integer 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 `size_bits` attribute
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bitbuffer::BitWrite;
|
||||||
|
/// #
|
||||||
|
/// #[derive(BitWrite)]
|
||||||
|
/// struct TestStruct {
|
||||||
|
/// foo: u8,
|
||||||
|
/// str: String,
|
||||||
|
/// #[size = 2] // when `size` is set, the attributed will be read using `read_sized`
|
||||||
|
/// truncated: String,
|
||||||
|
/// bar: u16,
|
||||||
|
/// float: f32,
|
||||||
|
/// #[size = 3]
|
||||||
|
/// asd: u8,
|
||||||
|
/// #[size_bits = 2] // first read 2 bits as unsigned integer, then use the resulting value as size for the read
|
||||||
|
/// dynamic_length: u8,
|
||||||
|
/// #[size = "asd"] // use a previously defined field as size
|
||||||
|
/// previous_field: u8,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # 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 `BitWrite` or [`BitWriteSized`]
|
||||||
|
///
|
||||||
|
/// The enum is written by first writing a set number of bits as the discriminant of the enum, then the variant for the written discriminant is read.
|
||||||
|
///
|
||||||
|
/// For details about setting the input size for fields implementing [`BitWriteSized`] 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 bitbuffer::BitWrite;
|
||||||
|
/// #
|
||||||
|
/// #[derive(BitWrite)]
|
||||||
|
/// #[discriminant_bits = 2]
|
||||||
|
/// enum TestBareEnum {
|
||||||
|
/// Foo,
|
||||||
|
/// Bar,
|
||||||
|
/// Asd = 3, // manually set the discriminant value for a field
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bitbuffer::BitWrite;
|
||||||
|
/// #
|
||||||
|
/// #[derive(BitWrite)]
|
||||||
|
/// #[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
|
||||||
|
/// Asd(u8),
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`BitWriteSized`]: trait.BitWriteSized.html
|
||||||
|
/// [write_sized]: struct.BitWriteStream.html#method.write_sized
|
||||||
|
/// [write]: struct.BitWriteStream.html#method.write
|
||||||
|
pub trait BitWrite<E: Endianness>: Sized {
|
||||||
|
/// Write the type to the stream
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_write_int {
|
||||||
|
($type:ty) => {
|
||||||
|
impl<E: Endianness> BitWrite<E> for $type {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
stream.write_int::<$type>(*self, size_of::<$type>() * 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_write_int_nonzero {
|
||||||
|
($type:ty) => {
|
||||||
|
impl BitWrite<LittleEndian> for Option<$type> {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<LittleEndian>) -> Result<()> {
|
||||||
|
BitWrite::write(&self.map(<$type>::get).unwrap_or(0), stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitWrite<BigEndian> for Option<$type> {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<BigEndian>) -> Result<()> {
|
||||||
|
BitWrite::write(&self.map(<$type>::get).unwrap_or(0), stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_write_int!(u8);
|
||||||
|
impl_write_int!(u16);
|
||||||
|
impl_write_int!(u32);
|
||||||
|
impl_write_int!(u64);
|
||||||
|
impl_write_int!(u128);
|
||||||
|
impl_write_int!(i8);
|
||||||
|
impl_write_int!(i16);
|
||||||
|
impl_write_int!(i32);
|
||||||
|
impl_write_int!(i64);
|
||||||
|
impl_write_int!(i128);
|
||||||
|
|
||||||
|
impl_write_int_nonzero!(std::num::NonZeroU8);
|
||||||
|
impl_write_int_nonzero!(std::num::NonZeroU16);
|
||||||
|
impl_write_int_nonzero!(std::num::NonZeroU32);
|
||||||
|
impl_write_int_nonzero!(std::num::NonZeroU64);
|
||||||
|
impl_write_int_nonzero!(std::num::NonZeroU128);
|
||||||
|
|
||||||
|
impl<E: Endianness> BitWrite<E> for f32 {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
stream.write_float::<f32>(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness> BitWrite<E> for f64 {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
stream.write_float::<f64>(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness> BitWrite<E> for bool {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
stream.write_bool(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness> BitWrite<E> for String {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
stream.write_string(self, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness, T: BitWrite<E>> BitWrite<E> for Rc<T> {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
T::write(self, stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness, T: BitWrite<E>> BitWrite<E> for Arc<T> {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
T::write(self, stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness, T: BitWrite<E>> BitWrite<E> for Box<T> {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
T::write(self, stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_write_tuple {
|
||||||
|
($($type:ident),*) => {
|
||||||
|
impl<E: Endianness, $($type: BitWrite<E>),*> BitWrite<E> for ($($type),*) {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let ($($type),*) = self;
|
||||||
|
|
||||||
|
($($type.write(stream)?),*);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_write_tuple!(T1, T2);
|
||||||
|
impl_write_tuple!(T1, T2, T3);
|
||||||
|
impl_write_tuple!(T1, T2, T3, T4);
|
||||||
|
|
||||||
|
/// Trait for types that can be written from a stream, requiring the size to be configured
|
||||||
|
///
|
||||||
|
/// The meaning of the set sized depends on the type being written (e.g, number of bits for integers,
|
||||||
|
/// number of bytes for strings, etc)
|
||||||
|
///
|
||||||
|
/// The `BitWriteSized` 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 [`BitWrite`] or `BitWriteSized`
|
||||||
|
///
|
||||||
|
/// The struct is written field by field in the order they are defined in, if the size for a field is set [`stream.write_sized()`][write_sized]
|
||||||
|
/// will be used, otherwise [`stream_write()`][write] 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 `size_bits` attribute
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bitbuffer::BitWriteSized;
|
||||||
|
/// #
|
||||||
|
/// #[derive(BitWriteSized, PartialEq, Debug)]
|
||||||
|
/// struct TestStructSized {
|
||||||
|
/// foo: u8,
|
||||||
|
/// #[size = "input_size"]
|
||||||
|
/// string: String,
|
||||||
|
/// #[size = "input_size"]
|
||||||
|
/// int: u8,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # 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 [`BitWrite`] or `BitWriteSized`
|
||||||
|
///
|
||||||
|
/// The enum is written by first reading a set number of bits as the discriminant of the enum, then the variant for the written discriminant is read.
|
||||||
|
///
|
||||||
|
/// For details about setting the input size for fields implementing `BitWriteSized` 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 bitbuffer::BitWriteSized;
|
||||||
|
/// #
|
||||||
|
/// #[derive(BitWriteSized)]
|
||||||
|
/// #[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),
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`BitWrite`]: trait.BitWrite.html
|
||||||
|
/// [read_sized]: struct.BitStream.html#method.read_sized
|
||||||
|
/// [read]: struct.BitStream.html#method.read
|
||||||
|
pub trait BitWriteSized<E: Endianness>: Sized {
|
||||||
|
/// Write the type from stream
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>, size: usize) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_write_int_sized {
|
||||||
|
( $ type: ty) => {
|
||||||
|
impl<E: Endianness> BitWriteSized<E> for $type {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>, size: usize) -> Result<()> {
|
||||||
|
stream.write_int::<$type>(*self, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_write_int_sized!(u8);
|
||||||
|
impl_write_int_sized!(u16);
|
||||||
|
impl_write_int_sized!(u32);
|
||||||
|
impl_write_int_sized!(u64);
|
||||||
|
impl_write_int_sized!(u128);
|
||||||
|
impl_write_int_sized!(i8);
|
||||||
|
impl_write_int_sized!(i16);
|
||||||
|
impl_write_int_sized!(i32);
|
||||||
|
impl_write_int_sized!(i64);
|
||||||
|
impl_write_int_sized!(i128);
|
||||||
|
|
||||||
|
impl<E: Endianness> BitWriteSized<E> for String {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>, size: usize) -> Result<()> {
|
||||||
|
stream.write_string(self, Some(size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness, T: BitWrite<E>> BitWrite<E> for Option<T> {
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
match self.as_ref() {
|
||||||
|
Some(inner) => {
|
||||||
|
stream.write_bool(true)?;
|
||||||
|
T::write(inner, stream)
|
||||||
|
}
|
||||||
|
None => stream.write_bool(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness, T: BitWriteSized<E>> BitWriteSized<E> for Option<T> {
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>, size: usize) -> Result<()> {
|
||||||
|
match self.as_ref() {
|
||||||
|
Some(inner) => {
|
||||||
|
stream.write_bool(true)?;
|
||||||
|
T::write(inner, stream, size)
|
||||||
|
}
|
||||||
|
None => stream.write_bool(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ use std::ops::{BitOrAssign, BitXor};
|
||||||
|
|
||||||
use crate::endianness::Endianness;
|
use crate::endianness::Endianness;
|
||||||
use crate::num_traits::{IntoBytes, IsSigned, UncheckedPrimitiveFloat, UncheckedPrimitiveInt};
|
use crate::num_traits::{IntoBytes, IsSigned, UncheckedPrimitiveFloat, UncheckedPrimitiveInt};
|
||||||
|
use crate::write::{BitWrite, BitWriteSized};
|
||||||
use crate::{BitError, Result};
|
use crate::{BitError, Result};
|
||||||
|
|
||||||
const USIZE_SIZE: usize = size_of::<usize>();
|
const USIZE_SIZE: usize = size_of::<usize>();
|
||||||
|
|
@ -118,11 +119,9 @@ where
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
|
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
|
||||||
/// #
|
/// #
|
||||||
/// # fn main() -> Result<()> {
|
/// # fn main() -> Result<()> {
|
||||||
/// # use bitbuffer::{BitWriteStream, LittleEndian};
|
|
||||||
///
|
|
||||||
/// let mut stream = BitWriteStream::new(LittleEndian);
|
/// let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
/// stream.write_bool(true)?;
|
/// stream.write_bool(true)?;
|
||||||
/// #
|
/// #
|
||||||
|
|
@ -140,11 +139,9 @@ where
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
|
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
|
||||||
/// #
|
/// #
|
||||||
/// # fn main() -> Result<()> {
|
/// # fn main() -> Result<()> {
|
||||||
/// # use bitbuffer::{BitWriteStream, LittleEndian};
|
|
||||||
///
|
|
||||||
/// let mut stream = BitWriteStream::new(LittleEndian);
|
/// let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
/// stream.write_int(123u16, 15)?;
|
/// stream.write_int(123u16, 15)?;
|
||||||
/// #
|
/// #
|
||||||
|
|
@ -183,11 +180,9 @@ where
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
|
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
|
||||||
/// #
|
/// #
|
||||||
/// # fn main() -> Result<()> {
|
/// # fn main() -> Result<()> {
|
||||||
/// # use bitbuffer::{BitWriteStream, LittleEndian};
|
|
||||||
///
|
|
||||||
/// let mut stream = BitWriteStream::new(LittleEndian);
|
/// let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
/// stream.write_float(123.15f32)?;
|
/// stream.write_float(123.15f32)?;
|
||||||
/// #
|
/// #
|
||||||
|
|
@ -217,11 +212,9 @@ where
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
|
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
|
||||||
/// #
|
/// #
|
||||||
/// # fn main() -> Result<()> {
|
/// # fn main() -> Result<()> {
|
||||||
/// # use bitbuffer::{BitWriteStream, LittleEndian};
|
|
||||||
///
|
|
||||||
/// let mut stream = BitWriteStream::new(LittleEndian);
|
/// let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
/// stream.write_bytes(&[0, 1, 2 ,3])?;
|
/// stream.write_bytes(&[0, 1, 2 ,3])?;
|
||||||
/// #
|
/// #
|
||||||
|
|
@ -248,11 +241,9 @@ where
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
|
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
|
||||||
/// #
|
/// #
|
||||||
/// # fn main() -> Result<()> {
|
/// # fn main() -> Result<()> {
|
||||||
/// # use bitbuffer::{BitWriteStream, LittleEndian};
|
|
||||||
///
|
|
||||||
/// let mut stream = BitWriteStream::new(LittleEndian);
|
/// let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
/// stream.write_string("zero terminated string", None)?;
|
/// stream.write_string("zero terminated string", None)?;
|
||||||
/// stream.write_string("fixed size string, zero padded", Some(64))?;
|
/// stream.write_string("fixed size string, zero padded", Some(64))?;
|
||||||
|
|
@ -284,4 +275,70 @@ where
|
||||||
pub fn finish(self) -> Vec<u8> {
|
pub fn finish(self) -> Vec<u8> {
|
||||||
self.bytes
|
self.bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write a value based on the provided type
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
|
||||||
|
/// #
|
||||||
|
/// # fn main() -> Result<()> {
|
||||||
|
/// # let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
|
/// stream.write(&53)?;
|
||||||
|
/// stream.write("fixed size text")?;
|
||||||
|
/// #
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bitbuffer::{BitWriteBuffer, BitWriteStream, LittleEndian, Result};
|
||||||
|
/// use bitbuffer::BitWrite;
|
||||||
|
/// #
|
||||||
|
/// #[derive(BitWrite, Debug, PartialEq)]
|
||||||
|
/// struct ComplexType {
|
||||||
|
/// first: u8,
|
||||||
|
/// #[size = 15]
|
||||||
|
/// second: u16,
|
||||||
|
/// third: bool,
|
||||||
|
/// }
|
||||||
|
/// #
|
||||||
|
/// # fn main() -> Result<()> {
|
||||||
|
/// # let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
|
/// stream.write(&ComplexType {
|
||||||
|
/// first: 73,
|
||||||
|
/// second: 982,
|
||||||
|
/// third: false,
|
||||||
|
/// })?;
|
||||||
|
/// #
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn write<T: BitWrite<E>>(&mut self, value: &T) -> Result<()> {
|
||||||
|
T::write(value, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a value based on the provided type and size
|
||||||
|
///
|
||||||
|
/// The meaning of the size parameter differs depending on the type that is being read
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
|
||||||
|
/// #
|
||||||
|
/// # fn main() -> Result<()> {
|
||||||
|
/// # let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
|
/// stream.write_sized(&53, 12)?;
|
||||||
|
/// stream.write_sized("text", 8)?;
|
||||||
|
/// #
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn write_sized<T: BitWriteSized<E>>(&mut self, value: &T, size: usize) -> Result<()> {
|
||||||
|
T::write(value, self, size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue