mirror of
https://codeberg.org/icewind/bitbuffer.git
synced 2026-06-03 16:44:06 +02:00
bunch of write stuff
This commit is contained in:
parent
4d2ea4ee7c
commit
23ed7b0e4a
12 changed files with 840 additions and 116 deletions
36
bitbuffer_derive/src/discriminant.rs
Normal file
36
bitbuffer_derive/src/discriminant.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
use syn::{Expr, Lit, Variant};
|
||||||
|
use syn_util::get_attribute_value;
|
||||||
|
|
||||||
|
pub enum Discriminant {
|
||||||
|
Int(usize),
|
||||||
|
Default,
|
||||||
|
Wildcard,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Lit> for Discriminant {
|
||||||
|
fn from(lit: Lit) -> Self {
|
||||||
|
match lit {
|
||||||
|
Lit::Int(lit) => Discriminant::Int(lit.base10_parse::<usize>().unwrap()),
|
||||||
|
Lit::Str(lit) => match lit.value().as_str() {
|
||||||
|
"_" => Discriminant::Wildcard,
|
||||||
|
_ => panic!("discriminant is required to be an integer literal or \"_\""),
|
||||||
|
},
|
||||||
|
_ => panic!("discriminant is required to be an integer literal or \"_\""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Variant> for Discriminant {
|
||||||
|
fn from(variant: &Variant) -> Self {
|
||||||
|
variant
|
||||||
|
.discriminant
|
||||||
|
.as_ref()
|
||||||
|
.map(|(_, expr)| match expr {
|
||||||
|
Expr::Lit(expr_lit) => expr_lit.lit.clone(),
|
||||||
|
_ => panic!("discriminant is required to be an integer literal"),
|
||||||
|
})
|
||||||
|
.or_else(|| get_attribute_value(&variant.attrs, &["discriminant"]))
|
||||||
|
.map(Discriminant::from)
|
||||||
|
.unwrap_or(Discriminant::Default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -131,14 +131,20 @@
|
||||||
//! stream: BitReadStream<'a, BigEndian>,
|
//! stream: BitReadStream<'a, BigEndian>,
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
//!
|
||||||
|
mod discriminant;
|
||||||
|
mod write;
|
||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use crate::write::derive_bitwrite_trait;
|
||||||
|
use discriminant::Discriminant;
|
||||||
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, parse_str, Attribute, Data, DataStruct, DeriveInput, Expr,
|
parse_macro_input, parse_quote, parse_str, Attribute, Data, DataStruct, DeriveInput, Expr,
|
||||||
Fields, GenericParam, Ident, Lit, LitStr, Path, Variant,
|
Fields, GenericParam, Ident, Lit, LitStr, Path,
|
||||||
};
|
};
|
||||||
use syn_util::get_attribute_value;
|
use syn_util::get_attribute_value;
|
||||||
|
|
||||||
|
|
@ -162,6 +168,31 @@ 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".into(), "write".into(), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
/// See the [crate documentation](index.html) for details
|
||||||
|
#[proc_macro_derive(
|
||||||
|
BitWriteSized,
|
||||||
|
attributes(size, size_bits, discriminant_bits, discriminant, endianness)
|
||||||
|
)]
|
||||||
|
pub fn derive_bitwrite_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let extra_param = parse_str::<TokenStream>(", input_size: usize").unwrap();
|
||||||
|
derive_bitwrite_trait(
|
||||||
|
input,
|
||||||
|
"BitWriteSized".into(),
|
||||||
|
"write_sized".into(),
|
||||||
|
Some(extra_param),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn derive_bitread_trait(
|
fn derive_bitread_trait(
|
||||||
input: proc_macro::TokenStream,
|
input: proc_macro::TokenStream,
|
||||||
trait_name: String,
|
trait_name: String,
|
||||||
|
|
@ -514,37 +545,3 @@ fn get_field_size(attrs: &[Attribute], span: Span) -> Option<TokenStream> {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Discriminant {
|
|
||||||
Int(usize),
|
|
||||||
Default,
|
|
||||||
Wildcard,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Lit> for Discriminant {
|
|
||||||
fn from(lit: Lit) -> Self {
|
|
||||||
match lit {
|
|
||||||
Lit::Int(lit) => Discriminant::Int(lit.base10_parse::<usize>().unwrap()),
|
|
||||||
Lit::Str(lit) => match lit.value().as_str() {
|
|
||||||
"_" => Discriminant::Wildcard,
|
|
||||||
_ => panic!("discriminant is required to be an integer literal or \"_\""),
|
|
||||||
},
|
|
||||||
_ => panic!("discriminant is required to be an integer literal or \"_\""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Variant> for Discriminant {
|
|
||||||
fn from(variant: &Variant) -> Self {
|
|
||||||
variant
|
|
||||||
.discriminant
|
|
||||||
.as_ref()
|
|
||||||
.map(|(_, expr)| match expr {
|
|
||||||
Expr::Lit(expr_lit) => expr_lit.lit.clone(),
|
|
||||||
_ => panic!("discriminant is required to be an integer literal"),
|
|
||||||
})
|
|
||||||
.or_else(|| get_attribute_value(&variant.attrs, &["discriminant"]))
|
|
||||||
.map(Discriminant::from)
|
|
||||||
.unwrap_or(Discriminant::Default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
275
bitbuffer_derive/src/write.rs
Normal file
275
bitbuffer_derive/src/write.rs
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
use crate::discriminant::Discriminant;
|
||||||
|
use crate::size;
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{
|
||||||
|
parse_macro_input, parse_quote, parse_str, Attribute, Data, DataStruct, DeriveInput, Expr,
|
||||||
|
Fields, GenericParam, Ident, Index, Lit, Member, Path,
|
||||||
|
};
|
||||||
|
use syn_util::get_attribute_value;
|
||||||
|
|
||||||
|
pub fn derive_bitwrite_trait(
|
||||||
|
input: proc_macro::TokenStream,
|
||||||
|
trait_name: String,
|
||||||
|
write_method_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();
|
||||||
|
let lifetime: Option<&GenericParam> = trait_generics
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.find(|param| matches!(param, GenericParam::Lifetime(_)));
|
||||||
|
let _lifetime = match lifetime {
|
||||||
|
Some(GenericParam::Lifetime(lifetime)) => lifetime.lifetime.clone(),
|
||||||
|
_ => {
|
||||||
|
// trait_generics.params.push(parse_quote!('a));
|
||||||
|
parse_quote!('a)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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 parsed = write(input.data.clone(), &name, &input.attrs);
|
||||||
|
let _parsed_unchecked = write(input.data.clone(), &name, &input.attrs);
|
||||||
|
|
||||||
|
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).expect("trait");
|
||||||
|
|
||||||
|
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 write_method = Ident::new(&write_method_name, span);
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
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<()> {
|
||||||
|
#parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 expand = fields.iter().enumerate().map(|(i, field)| {
|
||||||
|
let name = field
|
||||||
|
.ident
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| Ident::new(&format!("__{}", i), span));
|
||||||
|
let member = field.ident.clone().map(Member::Named).unwrap_or_else(|| {
|
||||||
|
Member::Unnamed(Index {
|
||||||
|
index: i as u32,
|
||||||
|
span: field.span(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
quote_spanned! { field.span() =>
|
||||||
|
let #name = &self.#member;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let writes = fields.iter().enumerate().map(|(i, f)| {
|
||||||
|
// Get attributes `#[..]` on each field
|
||||||
|
let size = get_field_size(&f.attrs, f.span());
|
||||||
|
let span = f.span();
|
||||||
|
let name = f
|
||||||
|
.ident
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| Ident::new(&format!("__{}", i), span));
|
||||||
|
match size {
|
||||||
|
Some(size) => {
|
||||||
|
quote_spanned! { span =>
|
||||||
|
{
|
||||||
|
let _size: usize = #size;
|
||||||
|
__target__stream.write_sized(#name, _size)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
quote_spanned! { span => {
|
||||||
|
__target__stream.write(#name)?;
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote_spanned! {span=>
|
||||||
|
#(#expand)*
|
||||||
|
#(#writes)*
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Data::Enum(data) => {
|
||||||
|
let discriminant_bits: u64 = get_attribute_value(attrs, &["discriminant_bits"]).expect(
|
||||||
|
"'discriminant_bits' attribute is required when deriving `BinRead` for enums",
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut last_discriminant = -1;
|
||||||
|
|
||||||
|
let max_discriminant = data
|
||||||
|
.variants
|
||||||
|
.iter()
|
||||||
|
.map(|variant| match Discriminant::from(variant) {
|
||||||
|
Discriminant::Int(discriminant) => {
|
||||||
|
last_discriminant = discriminant as isize;
|
||||||
|
discriminant
|
||||||
|
}
|
||||||
|
Discriminant::Wildcard => 0,
|
||||||
|
Discriminant::Default => {
|
||||||
|
let new_discriminant = (last_discriminant + 1) as usize;
|
||||||
|
last_discriminant += 1;
|
||||||
|
new_discriminant
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
let mut last_discriminant = -1;
|
||||||
|
|
||||||
|
let discriminant_value = data.variants.iter().map(|variant| {
|
||||||
|
let span = variant.span();
|
||||||
|
let variant_name = &variant.ident;
|
||||||
|
|
||||||
|
let discriminant_token: TokenStream = match Discriminant::from(variant) {
|
||||||
|
Discriminant::Int(discriminant) => {
|
||||||
|
last_discriminant = discriminant as isize;
|
||||||
|
quote_spanned! { span => #discriminant }
|
||||||
|
}
|
||||||
|
Discriminant::Wildcard => {
|
||||||
|
let free_discriminant = max_discriminant + 1;
|
||||||
|
quote_spanned! { span => #free_discriminant }
|
||||||
|
}
|
||||||
|
Discriminant::Default => {
|
||||||
|
let new_discriminant = (last_discriminant + 1) as usize;
|
||||||
|
last_discriminant += 1;
|
||||||
|
quote_spanned! { span => #new_discriminant }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match &variant.fields {
|
||||||
|
Fields::Unit => quote_spanned! {span =>
|
||||||
|
#struct_name::#variant_name => #discriminant_token
|
||||||
|
},
|
||||||
|
Fields::Unnamed(_f) => {
|
||||||
|
quote_spanned! { span =>
|
||||||
|
#struct_name::#variant_name(_) => #discriminant_token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let write_inner = data.variants.iter().map(|variant| {
|
||||||
|
let span = variant.span();
|
||||||
|
let variant_name = &variant.ident;
|
||||||
|
|
||||||
|
match &variant.fields {
|
||||||
|
Fields::Unit => quote_spanned! {span =>
|
||||||
|
#struct_name::#variant_name => {},
|
||||||
|
},
|
||||||
|
Fields::Unnamed(f) => {
|
||||||
|
let size = get_field_size(&variant.attrs, f.span());
|
||||||
|
match size {
|
||||||
|
Some(size) => {
|
||||||
|
quote_spanned! { span =>
|
||||||
|
#struct_name::#variant_name(inner) => {
|
||||||
|
let size:usize = #size;
|
||||||
|
__target__stream.write_sized(inner, size)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
quote_spanned! { span =>
|
||||||
|
#struct_name::#variant_name(inner) => { __target__stream.write(inner)?; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let span = data.enum_token.span();
|
||||||
|
|
||||||
|
quote_spanned! {span=>
|
||||||
|
let discriminant = match &self {
|
||||||
|
#(#discriminant_value),*
|
||||||
|
};
|
||||||
|
__target__stream.write_int(discriminant as usize, #discriminant_bits as usize)?;
|
||||||
|
match &self {
|
||||||
|
#(#write_inner)*
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_field_size(attrs: &[Attribute], span: Span) -> Option<TokenStream> {
|
||||||
|
get_attribute_value(attrs, &["size"])
|
||||||
|
.map(|size_lit| match size_lit {
|
||||||
|
Lit::Int(size) => {
|
||||||
|
quote_spanned! {span =>
|
||||||
|
#size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Lit::Str(size_field) => {
|
||||||
|
let size = parse_str::<Expr>(&size_field.value()).expect("size");
|
||||||
|
if size_field.value() == "input_size" {
|
||||||
|
quote_spanned! {span =>
|
||||||
|
(#size) as usize
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote_spanned! {span =>
|
||||||
|
(*#size) as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("Unsupported value for size attribute"),
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
get_attribute_value::<Lit>(attrs, &["size_bits"]).map(|_| {
|
||||||
|
quote_spanned! {span =>
|
||||||
|
compile_error!("#[size_bits] is not supported when deriving BitWrite or BitWriteSized")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
#![allow(unreachable_patterns)]
|
#![allow(unreachable_patterns)]
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
use bitbuffer_derive::BitRead;
|
use bitbuffer::{BitReadStream, Endianness};
|
||||||
|
use bitbuffer_derive::{BitWrite, BitWriteSized};
|
||||||
|
|
||||||
#[derive(BitRead)]
|
#[derive(BitWrite)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
foo: u8,
|
foo: u8,
|
||||||
str: String,
|
str: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(BitWrite)]
|
||||||
|
struct UnnamedSize(u8, #[size = 5] String, bool);
|
||||||
|
|
|
||||||
262
bitbuffer_derive/tests/write.rs
Normal file
262
bitbuffer_derive/tests/write.rs
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
#![allow(unreachable_patterns)]
|
||||||
|
|
||||||
|
use bitbuffer::{
|
||||||
|
BigEndian, BitReadBuffer, BitReadSized, BitReadStream, BitWriteStream, Endianness, LittleEndian,
|
||||||
|
};
|
||||||
|
use bitbuffer_derive::{BitRead, BitWrite, BitWriteSized};
|
||||||
|
|
||||||
|
#[derive(BitWrite, PartialEq, Debug)]
|
||||||
|
struct TestStruct {
|
||||||
|
foo: u8,
|
||||||
|
str: String,
|
||||||
|
#[size = 2]
|
||||||
|
truncated: String,
|
||||||
|
bar: u16,
|
||||||
|
float: f32,
|
||||||
|
#[size = 3]
|
||||||
|
asd: u8,
|
||||||
|
#[size = "asd"]
|
||||||
|
previous_field: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_struct() {
|
||||||
|
let float: [u8; 4] = 12.5f32.to_bits().to_le_bytes();
|
||||||
|
let bytes = vec![
|
||||||
|
12,
|
||||||
|
'h' as u8,
|
||||||
|
'e' as u8,
|
||||||
|
'l' as u8,
|
||||||
|
'l' as u8,
|
||||||
|
'o' as u8,
|
||||||
|
0,
|
||||||
|
'f' as u8,
|
||||||
|
'o' as u8,
|
||||||
|
'o' as u8,
|
||||||
|
0,
|
||||||
|
float[0],
|
||||||
|
float[1],
|
||||||
|
float[2],
|
||||||
|
float[3],
|
||||||
|
0b1010_0101,
|
||||||
|
];
|
||||||
|
let val = TestStruct {
|
||||||
|
foo: 12,
|
||||||
|
str: "hello".to_owned(),
|
||||||
|
truncated: "fo".to_owned(),
|
||||||
|
bar: 'o' as u16,
|
||||||
|
float: 12.5,
|
||||||
|
asd: 0b101,
|
||||||
|
previous_field: 0b1010_0,
|
||||||
|
};
|
||||||
|
let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
|
stream.write(&val).unwrap();
|
||||||
|
assert_eq!(bytes, stream.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitWrite, PartialEq, Debug)]
|
||||||
|
#[discriminant_bits = 2]
|
||||||
|
enum TestBareEnum {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
Asd = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_bare_enum() {
|
||||||
|
let bytes = vec![0b1100_0100];
|
||||||
|
let mut stream = BitWriteStream::new(BigEndian);
|
||||||
|
stream.write(&TestBareEnum::Asd).unwrap();
|
||||||
|
stream.write(&TestBareEnum::Foo).unwrap();
|
||||||
|
stream.write(&TestBareEnum::Bar).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(bytes, stream.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitWrite, BitRead, PartialEq, Debug)]
|
||||||
|
#[discriminant_bits = 2]
|
||||||
|
enum TestUnnamedFieldEnum {
|
||||||
|
#[size = 5]
|
||||||
|
Foo(i8),
|
||||||
|
Bar(bool),
|
||||||
|
#[discriminant = 3]
|
||||||
|
Asd(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_unnamed_field_enum() {
|
||||||
|
let bytes = vec![0b1100_0110, 0b1000_0110, 0b1011_0000];
|
||||||
|
let mut stream = BitWriteStream::new(BigEndian);
|
||||||
|
stream
|
||||||
|
.write(&TestUnnamedFieldEnum::Asd(0b_00_0110_10))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(10, stream.bit_len());
|
||||||
|
|
||||||
|
stream.write(&TestUnnamedFieldEnum::Foo(0b0110_1)).unwrap();
|
||||||
|
assert_eq!(17, stream.bit_len());
|
||||||
|
|
||||||
|
stream.write(&TestUnnamedFieldEnum::Bar(true)).unwrap();
|
||||||
|
|
||||||
|
let result = stream.finish();
|
||||||
|
let mut read = BitReadStream::<BigEndian>::from(result.as_slice());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
TestUnnamedFieldEnum::Asd(0b_00_0110_10),
|
||||||
|
read.read().unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(TestUnnamedFieldEnum::Foo(0b11_0_1), read.read().unwrap());
|
||||||
|
assert_eq!(TestUnnamedFieldEnum::Bar(true), read.read().unwrap());
|
||||||
|
|
||||||
|
assert_eq!(bytes, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitWriteSized, 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, 0b1000_0000];
|
||||||
|
let mut stream = BitWriteStream::new(BigEndian);
|
||||||
|
let val = TestStructSized {
|
||||||
|
foo: 12,
|
||||||
|
string: "hel".to_owned(),
|
||||||
|
int: 4,
|
||||||
|
};
|
||||||
|
stream.write_sized(&val, 3).unwrap();
|
||||||
|
let result = stream.finish();
|
||||||
|
let mut read = BitReadStream::<BigEndian>::from(result.as_slice());
|
||||||
|
|
||||||
|
assert_eq!(val, read.read_sized(3).unwrap());
|
||||||
|
|
||||||
|
assert_eq!(bytes, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitWriteSized, 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];
|
||||||
|
let mut stream = BitWriteStream::new(BigEndian);
|
||||||
|
stream
|
||||||
|
.write_sized(&TestUnnamedFieldEnumSized::Asd(0b_00_0110), 6)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(bytes, stream.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitWrite, PartialEq, Debug)]
|
||||||
|
struct TestStruct2 {
|
||||||
|
size: u8,
|
||||||
|
#[size = "size * 2"]
|
||||||
|
str: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_struct2() {
|
||||||
|
let bytes = vec![
|
||||||
|
0b0000_0101,
|
||||||
|
'h' as u8,
|
||||||
|
'e' as u8,
|
||||||
|
'l' as u8,
|
||||||
|
'l' as u8,
|
||||||
|
'o' as u8,
|
||||||
|
' ' as u8,
|
||||||
|
'w' as u8,
|
||||||
|
'o' as u8,
|
||||||
|
'r' as u8,
|
||||||
|
'l' as u8,
|
||||||
|
];
|
||||||
|
let mut stream = BitWriteStream::new(BigEndian);
|
||||||
|
stream
|
||||||
|
.write(&TestStruct2 {
|
||||||
|
size: 5,
|
||||||
|
str: "hello worl".to_owned(),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(bytes, stream.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitWrite)]
|
||||||
|
#[endianness = "E"]
|
||||||
|
struct TestStruct3<'a, E: Endianness> {
|
||||||
|
size: u8,
|
||||||
|
#[size = "size"]
|
||||||
|
stream: BitReadStream<'a, E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_struct3() {
|
||||||
|
let bytes = vec![0b0000_0101, 0b1010_1000];
|
||||||
|
let mut stream = BitWriteStream::new(BigEndian);
|
||||||
|
let mut inner = BitReadStream::from(BitReadBuffer::new(&[0b1010_1010], BigEndian));
|
||||||
|
|
||||||
|
let inner = inner.read_bits(5).unwrap();
|
||||||
|
|
||||||
|
let val: TestStruct3<BigEndian> = TestStruct3 {
|
||||||
|
size: 5,
|
||||||
|
stream: inner,
|
||||||
|
};
|
||||||
|
stream.write(&val).unwrap();
|
||||||
|
assert_eq!(bytes, stream.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitWrite, PartialEq, Debug)]
|
||||||
|
#[discriminant_bits = 2]
|
||||||
|
enum TestEnumRest {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
#[discriminant = "_"]
|
||||||
|
Asd,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_rest_enum() {
|
||||||
|
let bytes = vec![0b1000_0110];
|
||||||
|
let mut stream = BitWriteStream::new(BigEndian);
|
||||||
|
|
||||||
|
stream.write(&TestEnumRest::Asd).unwrap();
|
||||||
|
stream.write(&TestEnumRest::Foo).unwrap();
|
||||||
|
stream.write(&TestEnumRest::Bar).unwrap();
|
||||||
|
stream.write(&TestEnumRest::Asd).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(bytes, stream.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitWrite, PartialEq, Debug)]
|
||||||
|
struct UnnamedSize(u8, #[size = 5] String, bool);
|
||||||
|
|
||||||
|
fn test_unnamed_struct() {
|
||||||
|
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 mut stream = BitWriteStream::new(LittleEndian);
|
||||||
|
stream
|
||||||
|
.write(&UnnamedSize(12, "hello".to_string(), false))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(bytes, stream.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitWrite, PartialEq, Debug)]
|
||||||
|
struct EmptyStruct;
|
||||||
|
|
||||||
|
fn test_empty_struct() {
|
||||||
|
let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
|
stream.write(&EmptyStruct).unwrap();
|
||||||
|
assert_eq!(Vec::<u8>::new(), stream.finish());
|
||||||
|
}
|
||||||
|
|
@ -18,11 +18,11 @@ pub trait Endianness: private::Sealed {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks the buffer or stream as big endian
|
/// Marks the buffer or stream as big endian
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct BigEndian;
|
pub struct BigEndian;
|
||||||
|
|
||||||
/// Marks the buffer or stream as little endian
|
/// Marks the buffer or stream as little endian
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct LittleEndian;
|
pub struct LittleEndian;
|
||||||
|
|
||||||
macro_rules! impl_endianness {
|
macro_rules! impl_endianness {
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ pub use readbuffer::BitReadBuffer;
|
||||||
pub use readstream::BitReadStream;
|
pub use readstream::BitReadStream;
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
|
pub use write::{BitWrite, BitWriteSized};
|
||||||
pub use writestream::BitWriteStream;
|
pub use writestream::BitWriteStream;
|
||||||
|
|
||||||
mod endianness;
|
mod endianness;
|
||||||
|
|
@ -70,6 +71,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
|
||||||
|
|
|
||||||
|
|
@ -202,63 +202,13 @@ pub trait IntoBytes: Sized {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_into_bytes {
|
macro_rules! impl_into_bytes {
|
||||||
($type:ty, $iter:ident) => {
|
($type:ty, $bytes:expr) => {
|
||||||
// once std::array:IntoIter is stabilized we can get rid of this iterator
|
|
||||||
// https://github.com/rust-lang/rust/issues/65798
|
|
||||||
pub struct $iter {
|
|
||||||
data: [u8; std::mem::size_of::<$type>()],
|
|
||||||
start: usize,
|
|
||||||
end: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl $iter {
|
|
||||||
pub fn new(int: $type) -> Self {
|
|
||||||
$iter {
|
|
||||||
data: int.to_le_bytes(),
|
|
||||||
start: 0,
|
|
||||||
end: std::mem::size_of::<$type>(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for $iter {
|
|
||||||
type Item = u8;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.start < self.end {
|
|
||||||
let byte = self.data[self.start];
|
|
||||||
self.start += 1;
|
|
||||||
Some(byte)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
||||||
let size = self.end - self.start;
|
|
||||||
(size, Some(size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::iter::DoubleEndedIterator for $iter {
|
|
||||||
fn next_back(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.end > self.start {
|
|
||||||
self.end -= 1;
|
|
||||||
Some(self.data[self.end])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::iter::ExactSizeIterator for $iter {}
|
|
||||||
|
|
||||||
impl IntoBytes for $type {
|
impl IntoBytes for $type {
|
||||||
type Iter = $iter;
|
type Iter = std::array::IntoIter<u8, $bytes>;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_bytes(self) -> Self::Iter {
|
fn into_bytes(self) -> Self::Iter {
|
||||||
<$iter>::new(self)
|
Self::Iter::new(self.to_le_bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -277,15 +227,24 @@ impl_is_signed!(i64, true);
|
||||||
impl_is_signed!(i128, true);
|
impl_is_signed!(i128, true);
|
||||||
impl_is_signed!(isize, true);
|
impl_is_signed!(isize, true);
|
||||||
|
|
||||||
impl_into_bytes!(u8, BytesIterU8);
|
impl_into_bytes!(u8, 1);
|
||||||
impl_into_bytes!(u16, BytesIterU16);
|
impl_into_bytes!(u16, 2);
|
||||||
impl_into_bytes!(u32, BytesIterU32);
|
impl_into_bytes!(u32, 4);
|
||||||
impl_into_bytes!(u64, BytesIterU64);
|
impl_into_bytes!(u64, 8);
|
||||||
impl_into_bytes!(u128, BytesIterU128);
|
impl_into_bytes!(u128, 16);
|
||||||
impl_into_bytes!(usize, BytesIterUsize);
|
|
||||||
impl_into_bytes!(i8, BytesIterI8);
|
#[cfg(target_pointer_width = "64")]
|
||||||
impl_into_bytes!(i16, BytesIterI16);
|
impl_into_bytes!(usize, 8);
|
||||||
impl_into_bytes!(i32, BytesIterI32);
|
#[cfg(target_pointer_width = "32")]
|
||||||
impl_into_bytes!(i64, BytesIterI64);
|
impl_into_bytes!(usize, 4);
|
||||||
impl_into_bytes!(i128, BytesIterI128);
|
|
||||||
impl_into_bytes!(isize, BytesIterIsize);
|
impl_into_bytes!(i8, 1);
|
||||||
|
impl_into_bytes!(i16, 2);
|
||||||
|
impl_into_bytes!(i32, 4);
|
||||||
|
impl_into_bytes!(i64, 8);
|
||||||
|
impl_into_bytes!(i128, 16);
|
||||||
|
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
impl_into_bytes!(isize, 8);
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
impl_into_bytes!(isize, 4);
|
||||||
|
|
|
||||||
122
src/write.rs
Normal file
122
src/write.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
use crate::{BitReadStream, BitWriteStream, Endianness, Result};
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
/// Trait for types that can be written to a stream without requiring the size to be configured
|
||||||
|
pub trait BitWrite<E: Endianness> {
|
||||||
|
/// Write the type to 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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<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 str {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
stream.write_string(self, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness> BitWrite<E> for String {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
stream.write_string(self, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness> BitWrite<E> for BitReadStream<'_, E> {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, stream: &mut BitWriteStream<E>) -> Result<()> {
|
||||||
|
stream.write_bits(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for types that can be written to a stream, requiring the size to be configured
|
||||||
|
pub trait BitWriteSized<E: Endianness> {
|
||||||
|
/// Write the type to stream
|
||||||
|
fn write_sized(&self, stream: &mut BitWriteStream<E>, len: usize) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness> BitWriteSized<E> for str {
|
||||||
|
#[inline]
|
||||||
|
fn write_sized(&self, stream: &mut BitWriteStream<E>, len: usize) -> Result<()> {
|
||||||
|
stream.write_string(self, Some(len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Endianness> BitWriteSized<E> for String {
|
||||||
|
#[inline]
|
||||||
|
fn write_sized(&self, stream: &mut BitWriteStream<E>, len: usize) -> Result<()> {
|
||||||
|
stream.write_string(self, Some(len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_write_sized_int {
|
||||||
|
($type:ty) => {
|
||||||
|
impl<E: Endianness> BitWriteSized<E> for $type {
|
||||||
|
#[inline]
|
||||||
|
fn write_sized(&self, stream: &mut BitWriteStream<E>, len: usize) -> Result<()> {
|
||||||
|
stream.write_int::<$type>(*self, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_write_sized_int!(u8);
|
||||||
|
impl_write_sized_int!(u16);
|
||||||
|
impl_write_sized_int!(u32);
|
||||||
|
impl_write_sized_int!(u64);
|
||||||
|
impl_write_sized_int!(u128);
|
||||||
|
impl_write_sized_int!(i8);
|
||||||
|
impl_write_sized_int!(i16);
|
||||||
|
impl_write_sized_int!(i32);
|
||||||
|
impl_write_sized_int!(i64);
|
||||||
|
impl_write_sized_int!(i128);
|
||||||
|
|
||||||
|
impl<E: Endianness> BitWriteSized<E> for BitReadStream<'_, E> {
|
||||||
|
#[inline]
|
||||||
|
fn write_sized(&self, stream: &mut BitWriteStream<E>, len: usize) -> Result<()> {
|
||||||
|
let bits = self.clone().read_bits(len)?;
|
||||||
|
stream.write_bits(&bits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,9 @@ 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::{BitError, Result};
|
use crate::{BitError, BitReadStream, BitWrite, BitWriteSized, Result};
|
||||||
|
use std::cmp::min;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
const USIZE_SIZE: usize = size_of::<usize>();
|
const USIZE_SIZE: usize = size_of::<usize>();
|
||||||
const USIZE_BITS: usize = USIZE_SIZE * 8;
|
const USIZE_BITS: usize = USIZE_SIZE * 8;
|
||||||
|
|
@ -29,6 +31,7 @@ const USIZE_BITS: usize = USIZE_SIZE * 8;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`BitBuffer`]: struct.BitBuffer.html
|
/// [`BitBuffer`]: struct.BitBuffer.html
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct BitWriteStream<E>
|
pub struct BitWriteStream<E>
|
||||||
where
|
where
|
||||||
E: Endianness,
|
E: Endianness,
|
||||||
|
|
@ -79,14 +82,17 @@ where
|
||||||
I: ExactSizeIterator,
|
I: ExactSizeIterator,
|
||||||
I: DoubleEndedIterator<Item = u8>,
|
I: DoubleEndedIterator<Item = u8>,
|
||||||
{
|
{
|
||||||
|
let full_bytes = min(bits.len() - 1, count / 8);
|
||||||
|
|
||||||
let counts = repeat(8)
|
let counts = repeat(8)
|
||||||
.take(bits.len() - 1)
|
.take(full_bytes)
|
||||||
.chain(once(count - (bits.len() - 1) * 8));
|
.chain(once(count - full_bytes * 8));
|
||||||
if E::is_le() {
|
if E::is_le() {
|
||||||
bits.zip(counts)
|
bits.zip(counts)
|
||||||
.for_each(|(chunk, count)| self.push_bits(chunk as usize, count))
|
.for_each(|(chunk, count)| self.push_bits(chunk as usize, count))
|
||||||
} else {
|
} else {
|
||||||
bits.rev()
|
bits.take(count / 8 + 1)
|
||||||
|
.rev()
|
||||||
.zip(counts)
|
.zip(counts)
|
||||||
.for_each(|(chunk, count)| self.push_bits(chunk as usize, count))
|
.for_each(|(chunk, count)| self.push_bits(chunk as usize, count))
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +103,11 @@ where
|
||||||
debug_assert!(count < USIZE_BITS - 8);
|
debug_assert!(count < USIZE_BITS - 8);
|
||||||
|
|
||||||
let bit_offset = self.bit_len & 7;
|
let bit_offset = self.bit_len & 7;
|
||||||
let last_written_byte = self.bytes.pop().unwrap_or(0);
|
let last_written_byte = if bit_offset > 0 {
|
||||||
|
self.bytes.pop().unwrap_or(0)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
let merged_byte_count = (count + bit_offset + 7) / 8;
|
let merged_byte_count = (count + bit_offset + 7) / 8;
|
||||||
|
|
||||||
if E::is_le() {
|
if E::is_le() {
|
||||||
|
|
@ -154,7 +164,7 @@ where
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn write_int<T>(&mut self, value: T, count: usize) -> Result<()>
|
pub fn write_int<T>(&mut self, value: T, count: usize) -> Result<()>
|
||||||
where
|
where
|
||||||
T: PrimInt + BitOrAssign + IsSigned + UncheckedPrimitiveInt + BitXor + IntoBytes,
|
T: PrimInt + BitOrAssign + IsSigned + UncheckedPrimitiveInt + BitXor + IntoBytes + Debug,
|
||||||
{
|
{
|
||||||
let type_bit_size = size_of::<T>() * 8;
|
let type_bit_size = size_of::<T>() * 8;
|
||||||
|
|
||||||
|
|
@ -166,11 +176,13 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if type_bit_size < USIZE_BITS {
|
if type_bit_size < USIZE_BITS {
|
||||||
if T::is_signed() {
|
// if T::is_signed() && count < type_bit_size {
|
||||||
todo!()
|
// // set
|
||||||
} else {
|
// let sign_bit = T::one() << (count - 1);
|
||||||
|
// let value = abs(value) | sign_bit;
|
||||||
|
// }
|
||||||
|
|
||||||
self.push_bits(value.into_usize_unchecked(), count);
|
self.push_bits(value.into_usize_unchecked(), count);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.push_non_fit_bits(value.into_bytes(), count)
|
self.push_non_fit_bits(value.into_bytes(), count)
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +242,33 @@ where
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<()> {
|
pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<()> {
|
||||||
self.push_non_fit_bits(bytes.iter().copied(), bytes.len() * 8);
|
bytes
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.for_each(|chunk| self.push_bits(chunk as usize, 8));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write bits from a read stream into the buffer
|
||||||
|
#[inline]
|
||||||
|
pub fn write_bits(&mut self, bits: &BitReadStream<E>) -> Result<()> {
|
||||||
|
let mut bits = bits.clone();
|
||||||
|
let bit_offset = self.bit_len % 8;
|
||||||
|
if bit_offset > 0 {
|
||||||
|
let start = bits.read_int::<u8>(8 - bit_offset)?;
|
||||||
|
self.push_bits(start as usize, 8 - bit_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
while bits.bits_left() > 32 {
|
||||||
|
let chunk = bits.read::<u32>()?;
|
||||||
|
self.push_bits(chunk as usize, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
if bits.bits_left() > 0 {
|
||||||
|
let end_bits = bits.bits_left();
|
||||||
|
let end = bits.read_int::<u32>(end_bits)?;
|
||||||
|
self.push_bits(end as usize, end_bits);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,4 +322,16 @@ where
|
||||||
pub fn finish(self) -> Vec<u8> {
|
pub fn finish(self) -> Vec<u8> {
|
||||||
self.bytes
|
self.bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write the type to stream
|
||||||
|
#[inline]
|
||||||
|
pub fn write<T: BitWrite<E>>(&mut self, value: &T) -> Result<()> {
|
||||||
|
value.write(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the type to stream
|
||||||
|
#[inline]
|
||||||
|
pub fn write_sized<T: BitWriteSized<E>>(&mut self, value: &T, length: usize) -> Result<()> {
|
||||||
|
value.write_sized(self, length)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,22 @@ fn test_write_float_be() {
|
||||||
fn test_write_string_le() {
|
fn test_write_string_le() {
|
||||||
let mut stream = BitWriteStream::new(LittleEndian);
|
let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
|
|
||||||
|
stream.write_string("null terminated", None).unwrap();
|
||||||
|
stream.write_string("fixed length1", Some(16)).unwrap();
|
||||||
|
stream.write_string("fixed length2", Some(16)).unwrap();
|
||||||
|
|
||||||
|
let data = stream.finish();
|
||||||
|
let mut read = BitReadStream::from(BitReadBuffer::new(&data, LittleEndian));
|
||||||
|
|
||||||
|
assert_eq!("null terminated", read.read_string(None).unwrap());
|
||||||
|
assert_eq!("fixed length1", read.read_string(Some(16)).unwrap());
|
||||||
|
assert_eq!("fixed length2", read.read_string(Some(16)).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_string_le_unaligned() {
|
||||||
|
let mut stream = BitWriteStream::new(LittleEndian);
|
||||||
|
|
||||||
stream.write_bool(true).unwrap();
|
stream.write_bool(true).unwrap();
|
||||||
stream.write_string("null terminated", None).unwrap();
|
stream.write_string("null terminated", None).unwrap();
|
||||||
stream.write_string("fixed length1", Some(16)).unwrap();
|
stream.write_string("fixed length1", Some(16)).unwrap();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue