Skip to content

Commit

Permalink
Add bits as an optional feature for bitvec (#446)
Browse files Browse the repository at this point in the history
* Make bitvec completely optional

* Add bits feature to bench

* Add docs

* Add bits to ensure_no_std features

* Add bits feature to deku-derive

* Use bits feature to correctly disallow bit related attributes from being
  used
  • Loading branch information
wcampbell0x2a authored Sep 5, 2024
1 parent f2aeeb1 commit efbe7b4
Show file tree
Hide file tree
Showing 35 changed files with 380 additions and 45 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:
- run: cargo test --all
# run examples
- run: cargo run --example 2>&1 | grep -P ' ' | awk '{print $1}' | xargs -i cargo run --example {}
# test with no bits feature (don't test docs)
- run: cargo test --lib --examples --tests --features std --no-default-features

# Only build on MSRV, since trybuild will fail on older version
build-msrv:
Expand Down
37 changes: 33 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ members = [
]

[features]
default = ["std"]
std = ["deku_derive/std", "bitvec/std", "alloc", "no_std_io/std"]
alloc = ["bitvec/alloc"]
default = ["std", "bits"]
std = ["deku_derive/std", "bitvec?/std", "alloc", "no_std_io/std"]
alloc = ["bitvec?/alloc"]
logging = ["deku_derive/logging", "log"]
no-assert-string = ["deku_derive/no-assert-string"]
error_in_core = []
bits = ["dep:bitvec", "deku_derive/bits"]

[dependencies]
deku_derive = { version = "^0.17.0", path = "deku-derive", default-features = false}
bitvec = { version = "1.0.1", default-features = false }
bitvec = { version = "1.0.1", default-features = false, optional = true }
log = { version = "0.4.21", optional = true }
no_std_io = { version = "0.8.0", default-features = false, features = ["alloc"], package = "no_std_io2" }
rustversion = "1.0.16"
Expand All @@ -55,3 +56,31 @@ workspace = true
# Triggers in macro generated code of darling
# https://github.com/rust-lang/rust-clippy/issues/12643
manual-unwrap-or-default = "allow"

[[example]]
name = "custom_reader_and_writer"
required-features = ["bits"]

[[example]]
name = "deku_input"

[[example]]
name = "enums_catch_all"
required-features = ["bits"]

[[example]]
name = "enums"

[[example]]
name = "example"
required-features = ["bits"]

[[example]]
name = "ipv4"
required-features = ["bits"]

[[example]]
name = "many"

[[example]]
name = "read_all"
3 changes: 3 additions & 0 deletions benches/deku.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::io::{Cursor, Read, Seek};
use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion};
use deku::prelude::*;

#[cfg(feature = "bits")]
#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
struct DekuBits {
#[deku(bits = 1)]
Expand Down Expand Up @@ -61,6 +62,7 @@ fn criterion_benchmark(c: &mut Criterion) {
}))
})
});
#[cfg(feature = "bits")]
c.bench_function("deku_read_bits", |b| {
let reader = Cursor::new(&[0x01; 1]);
b.iter_batched(
Expand All @@ -69,6 +71,7 @@ fn criterion_benchmark(c: &mut Criterion) {
BatchSize::SmallInput,
)
});
#[cfg(feature = "bits")]
c.bench_function("deku_write_bits", |b| {
b.iter(|| {
deku_write(black_box(&DekuBits {
Expand Down
1 change: 1 addition & 0 deletions deku-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ proc-macro = true
std = ["proc-macro-crate"]
logging = []
no-assert-string = []
bits = []

[dependencies]
quote = "1.0"
Expand Down
43 changes: 40 additions & 3 deletions deku-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ struct DekuData {
id_type: Option<TokenStream>,

/// enum only: bit size of the enum `id`
#[cfg(feature = "bits")]
bits: Option<Num>,

/// enum only: byte size of the enum `id`
Expand Down Expand Up @@ -198,6 +199,7 @@ impl DekuData {
magic: receiver.magic,
id: receiver.id,
id_type: receiver.id_type?,
#[cfg(feature = "bits")]
bits: receiver.bits,
bytes: receiver.bytes,
seek_rewind: receiver.seek_rewind,
Expand All @@ -224,7 +226,7 @@ impl DekuData {
match data.data {
ast::Data::Struct(_) => {
// Validate id_* attributes are being used on an enum
if data.id_type.is_some() {
let ret = if data.id_type.is_some() {
Err(cerror(
data.id_type.span(),
"`id_type` only supported on enum",
Expand All @@ -233,11 +235,16 @@ impl DekuData {
Err(cerror(data.id.span(), "`id` only supported on enum"))
} else if data.bytes.is_some() {
Err(cerror(data.bytes.span(), "`bytes` only supported on enum"))
} else if data.bits.is_some() {
Err(cerror(data.bits.span(), "`bits` only supported on enum"))
} else {
Ok(())
};

#[cfg(feature = "bits")]
if ret.is_ok() && data.bits.is_some() {
return Err(cerror(data.bits.span(), "`bits` only supported on enum"));
}

ret
}
ast::Data::Enum(_) => {
// Validate `id_type` or `id` is specified
Expand All @@ -257,6 +264,7 @@ impl DekuData {
}

// Validate `id_*` used correctly
#[cfg(feature = "bits")]
if data.id.is_some() && data.bits.is_some() {
return Err(cerror(
data.ident.span(),
Expand All @@ -271,6 +279,7 @@ impl DekuData {
}

// Validate either `bits` or `bytes` is specified
#[cfg(feature = "bits")]
if data.bits.is_some() && data.bytes.is_some() {
return Err(cerror(
data.bits.span(),
Expand Down Expand Up @@ -336,7 +345,10 @@ impl<'a> TryFrom<&'a DekuData> for DekuDataEnum<'a> {

let id_args = crate::macros::gen_id_args(
deku_data.endian.as_ref(),
#[cfg(feature = "bits")]
deku_data.bits.as_ref(),
#[cfg(not(feature = "bits"))]
None,
deku_data.bytes.as_ref(),
)?;

Expand Down Expand Up @@ -393,6 +405,7 @@ struct FieldData {
endian: Option<syn::LitStr>,

/// field bit size
#[cfg(feature = "bits")]
bits: Option<Num>,

/// field byte size
Expand All @@ -402,6 +415,7 @@ struct FieldData {
count: Option<TokenStream>,

/// tokens providing the number of bits for the length of the container
#[cfg(feature = "bits")]
bits_read: Option<TokenStream>,

/// tokens providing the number of bytes for the length of the container
Expand Down Expand Up @@ -432,12 +446,14 @@ struct FieldData {
skip: bool,

/// pad a number of bits before
#[cfg(feature = "bits")]
pad_bits_before: Option<TokenStream>,

/// pad a number of bytes before
pad_bytes_before: Option<TokenStream>,

/// pad a number of bits after
#[cfg(feature = "bits")]
pad_bits_after: Option<TokenStream>,

/// pad a number of bytes after
Expand Down Expand Up @@ -486,9 +502,11 @@ impl FieldData {
ident: receiver.ident,
ty: receiver.ty,
endian: receiver.endian,
#[cfg(feature = "bits")]
bits: receiver.bits,
bytes: receiver.bytes,
count: receiver.count?,
#[cfg(feature = "bits")]
bits_read: receiver.bits_read?,
bytes_read: receiver.bytes_read?,
until: receiver.until?,
Expand All @@ -499,8 +517,10 @@ impl FieldData {
reader: receiver.reader?,
writer: receiver.writer?,
skip: receiver.skip,
#[cfg(feature = "bits")]
pad_bits_before: receiver.pad_bits_before?,
pad_bytes_before: receiver.pad_bytes_before?,
#[cfg(feature = "bits")]
pad_bits_after: receiver.pad_bits_after?,
pad_bytes_after: receiver.pad_bytes_after?,
temp: receiver.temp,
Expand All @@ -524,6 +544,7 @@ impl FieldData {

fn validate(data: &FieldData) -> Result<(), TokenStream> {
// Validate either `read_bytes` or `read_bits` is specified
#[cfg(feature = "bits")]
if data.bits_read.is_some() && data.bytes_read.is_some() {
return Err(cerror(
data.bits_read.span(),
Expand All @@ -532,6 +553,7 @@ impl FieldData {
}

// Validate either `count` or `bits_read`/`bytes_read` is specified
#[cfg(feature = "bits")]
if data.count.is_some() && (data.bits_read.is_some() || data.bytes_read.is_some()) {
if data.bits_read.is_some() {
return Err(cerror(
Expand All @@ -546,7 +568,16 @@ impl FieldData {
}
}

#[cfg(not(feature = "bits"))]
if data.count.is_some() && data.bytes_read.is_some() {
return Err(cerror(
data.count.span(),
"conflicting: both `count` and `bytes_read` specified on field",
));
}

// Validate either `bits` or `bytes` is specified
#[cfg(feature = "bits")]
if data.bits.is_some() && data.bytes.is_some() {
// FIXME: Use `Span::join` once out of nightly
return Err(cerror(
Expand All @@ -565,6 +596,7 @@ impl FieldData {
}

// Validate usage of read_all
#[cfg(feature = "bits")]
if data.read_all
&& (data.until.is_some()
|| data.count.is_some()
Expand Down Expand Up @@ -707,6 +739,7 @@ struct DekuReceiver {
id_type: Result<Option<TokenStream>, ReplacementError>,

/// enum only: bit size of the enum `id`
#[cfg(feature = "bits")]
#[darling(default)]
bits: Option<Num>,

Expand Down Expand Up @@ -816,6 +849,7 @@ struct DekuFieldReceiver {
endian: Option<syn::LitStr>,

/// field bit size
#[cfg(feature = "bits")]
#[darling(default)]
bits: Option<Num>,

Expand All @@ -828,6 +862,7 @@ struct DekuFieldReceiver {
count: Result<Option<TokenStream>, ReplacementError>,

/// tokens providing the number of bits for the length of the container
#[cfg(feature = "bits")]
#[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
bits_read: Result<Option<TokenStream>, ReplacementError>,

Expand Down Expand Up @@ -871,6 +906,7 @@ struct DekuFieldReceiver {
skip: bool,

/// pad a number of bits before
#[cfg(feature = "bits")]
#[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
pad_bits_before: Result<Option<TokenStream>, ReplacementError>,

Expand All @@ -879,6 +915,7 @@ struct DekuFieldReceiver {
pad_bytes_before: Result<Option<TokenStream>, ReplacementError>,

/// pad a number of bits after
#[cfg(feature = "bits")]
#[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
pad_bits_after: Result<Option<TokenStream>, ReplacementError>,

Expand Down
Loading

0 comments on commit efbe7b4

Please sign in to comment.