1
0
Fork 0
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:
Robin Appelman 2020-02-16 22:05:47 +01:00
commit ab86b164aa
7 changed files with 653 additions and 39 deletions

View file

@ -57,11 +57,12 @@
use err_derive::Error;
pub use std::string::FromUtf8Error;
pub use bitbuffer_derive::{BitRead, BitReadSized};
pub use bitbuffer_derive::{BitRead, BitReadSized, BitWrite};
pub use endianness::*;
pub use read::{BitRead, BitReadSized, LazyBitRead, LazyBitReadSized};
pub use readbuffer::BitReadBuffer;
pub use readstream::BitReadStream;
pub use write::BitWrite;
pub use writestream::BitWriteStream;
mod endianness;
@ -69,6 +70,7 @@ mod num_traits;
mod read;
mod readbuffer;
mod readstream;
mod write;
mod writestream;
/// Errors that can be returned when trying to read from a buffer

View file

@ -87,8 +87,8 @@ use std::sync::Arc;
/// ```
///
/// [`BitReadSized`]: trait.BitReadSized.html
/// [read_sized]: struct.BitStream.html#method.read_sized
/// [read]: struct.BitStream.html#method.read
/// [read_sized]: struct.BitReadStream.html#method.read_sized
/// [read]: struct.BitReadStream.html#method.read
pub trait BitRead<E: Endianness>: Sized {
/// Read the type from stream
fn read(stream: &mut BitReadStream<E>) -> Result<Self>;

326
src/write.rs Normal file
View 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),
}
}
}

View file

@ -6,6 +6,7 @@ use std::ops::{BitOrAssign, BitXor};
use crate::endianness::Endianness;
use crate::num_traits::{IntoBytes, IsSigned, UncheckedPrimitiveFloat, UncheckedPrimitiveInt};
use crate::write::{BitWrite, BitWriteSized};
use crate::{BitError, Result};
const USIZE_SIZE: usize = size_of::<usize>();
@ -118,11 +119,9 @@ where
/// # Examples
///
/// ```
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
/// #
/// # fn main() -> Result<()> {
/// # use bitbuffer::{BitWriteStream, LittleEndian};
///
/// let mut stream = BitWriteStream::new(LittleEndian);
/// stream.write_bool(true)?;
/// #
@ -140,11 +139,9 @@ where
/// # Examples
///
/// ```
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
/// #
/// # fn main() -> Result<()> {
/// # use bitbuffer::{BitWriteStream, LittleEndian};
///
/// let mut stream = BitWriteStream::new(LittleEndian);
/// stream.write_int(123u16, 15)?;
/// #
@ -183,11 +180,9 @@ where
/// # Examples
///
/// ```
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
/// #
/// # fn main() -> Result<()> {
/// # use bitbuffer::{BitWriteStream, LittleEndian};
///
/// let mut stream = BitWriteStream::new(LittleEndian);
/// stream.write_float(123.15f32)?;
/// #
@ -217,11 +212,9 @@ where
/// # Examples
///
/// ```
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
/// #
/// # fn main() -> Result<()> {
/// # use bitbuffer::{BitWriteStream, LittleEndian};
///
/// let mut stream = BitWriteStream::new(LittleEndian);
/// stream.write_bytes(&[0, 1, 2 ,3])?;
/// #
@ -248,11 +241,9 @@ where
/// # Examples
///
/// ```
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
/// # use bitbuffer::{BitWriteStream, LittleEndian, Result};
/// #
/// # fn main() -> Result<()> {
/// # use bitbuffer::{BitWriteStream, LittleEndian};
///
/// let mut stream = BitWriteStream::new(LittleEndian);
/// stream.write_string("zero terminated string", None)?;
/// stream.write_string("fixed size string, zero padded", Some(64))?;
@ -284,4 +275,70 @@ where
pub fn finish(self) -> Vec<u8> {
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)
}
}