mirror of
https://codeberg.org/icewind/bitbuffer.git
synced 2026-06-03 16:44:06 +02:00
add derive macro for Read
This commit is contained in:
parent
523f7a3253
commit
8a19225d61
8 changed files with 251 additions and 5 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bitstream_reader"
|
name = "bitstream_reader"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
authors = ["Robin Appelman <robin@icewind.nl>"]
|
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Reading bit sequences from a byte slice"
|
description = "Reading bit sequences from a byte slice"
|
||||||
|
|
@ -9,3 +9,6 @@ repository = "https://github.com/icewind1991/bitstream_reader"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
bitstream_reader_derive = { version = "0.3", path = "bitstream_reader_derive" }
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
|
@ -7,6 +7,11 @@
|
||||||
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/bitstream_reader_derive/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/bitstream_reader_derive/examples" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/bitstream_reader_derive/tests" isTestSource="true" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/bitstream_reader_derive/benches" isTestSource="true" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/bitstream_reader_derive/target" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|
|
||||||
20
bitstream_reader_derive/Cargo.toml
Normal file
20
bitstream_reader_derive/Cargo.toml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "bitstream_reader_derive"
|
||||||
|
version = "0.3.0"
|
||||||
|
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "Reading bit sequences from a byte slice"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
repository = "https://github.com/icewind1991/bitstream_reader"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "bitstream_reader_derive"
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = "0.15"
|
||||||
|
quote = "0.6"
|
||||||
|
proc-macro2 = "0.4"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
bitstream_reader = { path = ".." }
|
||||||
160
bitstream_reader_derive/src/lib.rs
Normal file
160
bitstream_reader_derive/src/lib.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
//! Automatically generate `Read` implementations for structs
|
||||||
|
//!
|
||||||
|
//! The implementation can be derived as long as every field in the struct implements `Read` or `ReadSized`
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use bitstream_reader_derive::Read;
|
||||||
|
//!
|
||||||
|
//! #[derive(Read)]
|
||||||
|
//! 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, then use the resulting value as size as size for the read
|
||||||
|
//! dynamic_length: u8,
|
||||||
|
//! #[size = "asd"] // use a previously defined field as size
|
||||||
|
//! previous_field: u8,
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{
|
||||||
|
parse_macro_input, parse_quote, Data, DeriveInput, Field, Fields, GenericParam, Generics,
|
||||||
|
Ident, Lit, Meta,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// See the [crate documentation](index.html) for details
|
||||||
|
#[proc_macro_derive(Read, attributes(size, size_bits))]
|
||||||
|
pub fn derive_helper_attr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
|
let name = input.ident;
|
||||||
|
|
||||||
|
let generics = add_trait_bounds(input.generics);
|
||||||
|
let mut trait_generics = generics.clone();
|
||||||
|
// we need these separate generics to only add out Endianness param to the 'impl'
|
||||||
|
let (_, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
|
trait_generics
|
||||||
|
.params
|
||||||
|
.push(parse_quote!(_E: ::bitstream_reader::Endianness));
|
||||||
|
let (impl_generics, _, _) = trait_generics.split_for_impl();
|
||||||
|
|
||||||
|
let parse = parse(&input.data, &name);
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl #impl_generics ::bitstream_reader::Read<_E> for #name #ty_generics #where_clause {
|
||||||
|
fn read(stream: &mut ::bitstream_reader::BitStream<_E>) -> ::bitstream_reader::Result<Self> {
|
||||||
|
#parse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// panic!("{}", TokenStream::to_string(&expanded));
|
||||||
|
|
||||||
|
proc_macro::TokenStream::from(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a bound `T: Read` to every type parameter T.
|
||||||
|
fn add_trait_bounds(mut generics: Generics) -> Generics {
|
||||||
|
for param in &mut generics.params {
|
||||||
|
if let GenericParam::Type(ref mut type_param) = *param {
|
||||||
|
type_param
|
||||||
|
.bounds
|
||||||
|
.push(parse_quote!(::bitstream_reader::Read));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
generics
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(data: &Data, struct_name: &Ident) -> TokenStream {
|
||||||
|
match *data {
|
||||||
|
Data::Struct(ref data) => {
|
||||||
|
match data.fields {
|
||||||
|
Fields::Named(ref fields) => {
|
||||||
|
let definitions = fields.named.iter().map(|f| {
|
||||||
|
let name = &f.ident;
|
||||||
|
// Get attributes `#[..]` on each field
|
||||||
|
let size = get_field_size(f);
|
||||||
|
let field_type = &f.ty;
|
||||||
|
match size {
|
||||||
|
Some(size) => {
|
||||||
|
quote_spanned! { f.span() =>
|
||||||
|
let size: usize = #size;
|
||||||
|
let #name:#field_type = stream.read_sized(size)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
quote_spanned! { f.span() =>
|
||||||
|
let #name:#field_type = stream.read()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let struct_definition = fields.named.iter().map(|f| {
|
||||||
|
let name = &f.ident;
|
||||||
|
quote_spanned! { f.span() =>
|
||||||
|
#name,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
quote! {
|
||||||
|
#(#definitions)*
|
||||||
|
|
||||||
|
Ok(#struct_name {
|
||||||
|
#(#struct_definition)*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_field_size(field: &Field) -> Option<TokenStream> {
|
||||||
|
get_field_attr(field, "size")
|
||||||
|
.map(|size_lit| match size_lit {
|
||||||
|
Lit::Int(size) => {
|
||||||
|
quote! {
|
||||||
|
#size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Lit::Str(size_field) => {
|
||||||
|
let size = Ident::new(&size_field.value(), Span::call_site());
|
||||||
|
quote! {
|
||||||
|
#size as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("Unsupported value for size attribute"),
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
get_field_attr(field, "size_bits").map(|size_bits_lit| {
|
||||||
|
quote_spanned! { field.span() =>
|
||||||
|
stream.read_int::<usize>(#size_bits_lit)?
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_field_attr(field: &Field, name: &str) -> Option<Lit> {
|
||||||
|
for attr in field.attrs.iter() {
|
||||||
|
let meta = attr.parse_meta().unwrap();
|
||||||
|
match meta {
|
||||||
|
Meta::NameValue(ref name_value) if name_value.ident == name => {
|
||||||
|
return Some(name_value.lit.clone());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
57
bitstream_reader_derive/tests/test.rs
Normal file
57
bitstream_reader_derive/tests/test.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
use bitstream_reader::{BitBuffer, BitStream, LittleEndian};
|
||||||
|
use bitstream_reader_derive::Read;
|
||||||
|
|
||||||
|
#[derive(Read, PartialEq, Debug)]
|
||||||
|
struct TestStruct {
|
||||||
|
foo: u8,
|
||||||
|
str: String,
|
||||||
|
#[size = 2]
|
||||||
|
truncated: String,
|
||||||
|
bar: u16,
|
||||||
|
float: f32,
|
||||||
|
#[size = 3]
|
||||||
|
asd: u8,
|
||||||
|
#[size_bits = 2]
|
||||||
|
dynamic: 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],
|
||||||
|
0b0101_0101,
|
||||||
|
0b1010_1010,
|
||||||
|
];
|
||||||
|
let buffer = BitBuffer::new(bytes, LittleEndian);
|
||||||
|
let mut stream = BitStream::from(buffer);
|
||||||
|
assert_eq!(
|
||||||
|
TestStruct {
|
||||||
|
foo: 12,
|
||||||
|
str: "hello".to_owned(),
|
||||||
|
truncated: "fo".to_owned(),
|
||||||
|
bar: 'o' as u16,
|
||||||
|
float: 12.5,
|
||||||
|
asd: 0b101,
|
||||||
|
dynamic: 0b10,
|
||||||
|
previous_field: 0b1010_0,
|
||||||
|
},
|
||||||
|
stream.read().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -49,6 +49,7 @@ use std::fmt;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
pub use std::string::FromUtf8Error;
|
pub use std::string::FromUtf8Error;
|
||||||
|
|
||||||
|
pub use bitstream_reader_derive::Read;
|
||||||
pub use buffer::BitBuffer;
|
pub use buffer::BitBuffer;
|
||||||
pub use endianness::*;
|
pub use endianness::*;
|
||||||
pub use read::{Read, ReadSized};
|
pub use read::{Read, ReadSized};
|
||||||
|
|
@ -114,7 +115,7 @@ impl Error for ReadError {
|
||||||
fn cause(&self) -> Option<&Error> {
|
fn cause(&self) -> Option<&Error> {
|
||||||
match self {
|
match self {
|
||||||
ReadError::Utf8Error(err) => Some(err),
|
ReadError::Utf8Error(err) => Some(err),
|
||||||
_ => None
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue