Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial version of as attribute #226

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion deku-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ struct FieldData {
/// custom field writer code
writer: Option<TokenStream>,

/// custom type to read/write "as"
as_: Option<syn::Type>,

/// skip field reading/writing
skip: bool,

Expand Down Expand Up @@ -438,6 +441,7 @@ impl FieldData {
update: receiver.update?,
reader: receiver.reader?,
writer: receiver.writer?,
as_: receiver.as_?,
skip: receiver.skip,
pad_bits_before: receiver.pad_bits_before?,
pad_bytes_before: receiver.pad_bytes_before?,
Expand Down Expand Up @@ -490,6 +494,15 @@ impl FieldData {
));
}

// Validate either `reader`/`writer` or `as` is specified
if (data.reader.is_some() || data.writer.is_some()) && data.as_.is_some() {
// FIXME: Use `Span::join` once out of nightly
return Err(cerror(
data.as_.span(),
"conflicting: both `reader`/`writer` and `as` specified on field",
));
}

// Validate usage of `default` attribute
if data.default.is_some() && (!data.skip && data.cond.is_none()) {
// FIXME: Use `Span::join` once out of nightly
Expand Down Expand Up @@ -665,13 +678,20 @@ fn map_litstr_as_tokenstream(
let v = apply_replacements(&v)?;
Some(
v.parse::<TokenStream>()
.expect("could not parse token stream"),
.map_err(|err| err.into_compile_error())?,
)
}
None => None,
})
}

fn map_litstr_as_type(input: Option<syn::LitStr>) -> Result<Option<syn::Type>, TokenStream> {
Ok(match input {
Some(v) => Some(v.parse().map_err(|err| err.into_compile_error())?),
None => None,
})
}

/// Generate field name which supports both un-named/named structs/enums
/// `ident` is Some if the container has named fields
/// `index` is the numerical index of the current field used in un-named containers
Expand Down Expand Up @@ -754,6 +774,10 @@ struct DekuFieldReceiver {
#[darling(default = "default_res_opt", map = "map_litstr_as_tokenstream")]
writer: Result<Option<TokenStream>, ReplacementError>,

/// custom type to read/write "as"
#[darling(rename = "as", default = "default_res_opt", map = "map_litstr_as_type")]
as_: Result<Option<syn::Type>, ReplacementError>,

/// skip field reading/writing
#[darling(default)]
skip: bool,
Expand Down
17 changes: 12 additions & 5 deletions deku-derive/src/macros/deku_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ fn emit_field_read(
let field_endian = f.endian.as_ref().or_else(|| input.endian.as_ref());

let field_reader = &f.reader;
let field_as = &f.as_;

// fields to check usage of bit/byte offset
let field_check_vars = [
Expand Down Expand Up @@ -563,6 +564,12 @@ fn emit_field_read(
f.ctx.as_ref(),
)?;

let read_func = if let Some(field_as) = field_as {
quote! { <#field_as as ::#crate_::DekuReadAs<#field_type, _>>::read_as }
} else {
quote! { ::#crate_::DekuRead::read }
};

// The container limiting options are special, we need to generate `(limit, (other, ..))` for them.
// These have a problem where when it isn't a copy type, the field will be moved.
// e.g. struct FooBar {
Expand All @@ -574,29 +581,29 @@ fn emit_field_read(
quote! {
{
use core::borrow::Borrow;
::#crate_::DekuRead::read(__deku_rest, (::#crate_::ctx::Limit::new_count(usize::try_from(*((#field_count).borrow()))?), (#read_args)))
#read_func(__deku_rest, (::#crate_::ctx::Limit::new_count(usize::try_from(*((#field_count).borrow()))?), (#read_args)))
}
}
} else if let Some(field_bits) = &f.bits_read {
quote! {
{
use core::borrow::Borrow;
::#crate_::DekuRead::read(__deku_rest, (::#crate_::ctx::Limit::new_size(::#crate_::ctx::Size::Bits(usize::try_from(*((#field_bits).borrow()))?)), (#read_args)))
#read_func(__deku_rest, (::#crate_::ctx::Limit::new_size(::#crate_::ctx::Size::Bits(usize::try_from(*((#field_bits).borrow()))?)), (#read_args)))
}
}
} else if let Some(field_bytes) = &f.bytes_read {
quote! {
{
use core::borrow::Borrow;
::#crate_::DekuRead::read(__deku_rest, (::#crate_::ctx::Limit::new_size(::#crate_::ctx::Size::Bytes(usize::try_from(*((#field_bytes).borrow()))?)), (#read_args)))
#read_func(__deku_rest, (::#crate_::ctx::Limit::new_size(::#crate_::ctx::Size::Bytes(usize::try_from(*((#field_bytes).borrow()))?)), (#read_args)))
}
}
} else if let Some(field_until) = &f.until {
// We wrap the input into another closure here to enforce that it is actually a callable
// Otherwise, an incorrectly passed-in integer could unexpectedly convert into a `Count` limit
quote! {::#crate_::DekuRead::read(__deku_rest, (::#crate_::ctx::Limit::new_until(#field_until), (#read_args)))}
quote! {#read_func(__deku_rest, (::#crate_::ctx::Limit::new_until(#field_until), (#read_args)))}
} else {
quote! {::#crate_::DekuRead::read(__deku_rest, (#read_args))}
quote! {#read_func(__deku_rest, (#read_args))}
}
};

Expand Down
10 changes: 9 additions & 1 deletion deku-derive/src/macros/deku_write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ fn emit_field_write(
let (bit_offset, byte_offset) = emit_bit_byte_offsets(&field_check_vars);

let field_writer = &f.writer;
let field_as = &f.as_;
let field_ident = f.get_ident(i, object_prefix.is_none());
let field_ident_str = field_ident.to_string();

Expand Down Expand Up @@ -527,7 +528,14 @@ fn emit_field_write(
f.ctx.as_ref(),
)?;

quote! { #object_prefix #field_ident.write(__deku_output, (#write_args)) }
let write_func = if let Some(field_as) = field_as {
let field_type = &f.ty;
quote! { <#field_as as ::#crate_::DekuWriteAs<#field_type, _>>::write_as }
} else {
quote! { ::#crate_::DekuWrite::write }
};

quote! { #write_func(#object_prefix #field_ident, __deku_output, (#write_args)) }
};

let pad_bits_before = pad_bits(
Expand Down
6 changes: 3 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#![cfg(feature = "alloc")]

use alloc::{format, string::String, string::ToString};
use alloc::{format, string::String};

/// Number of bits needed to retry parsing
#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -47,13 +47,13 @@ pub enum DekuError {

impl From<core::num::TryFromIntError> for DekuError {
fn from(e: core::num::TryFromIntError) -> DekuError {
DekuError::Parse(format!("error parsing int: {}", e.to_string()))
DekuError::Parse(format!("error parsing int: {}", e))
}
}

impl From<core::array::TryFromSliceError> for DekuError {
fn from(e: core::array::TryFromSliceError) -> DekuError {
DekuError::Parse(format!("error parsing from slice: {}", e.to_string()))
DekuError::Parse(format!("error parsing from slice: {}", e))
}
}

Expand Down
44 changes: 44 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,3 +363,47 @@ where
Ok(())
}
}

pub trait DekuReadAs<'a, T, Ctx = ()> {
fn read_as(
input: &'a bitvec::BitSlice<bitvec::Msb0, u8>,
ctx: Ctx,
) -> Result<(&'a bitvec::BitSlice<bitvec::Msb0, u8>, T), DekuError>;
}

pub trait DekuWriteAs<T: ?Sized, Ctx = ()> {
fn write_as(
source: &T,
output: &mut bitvec::BitVec<bitvec::Msb0, u8>,
ctx: Ctx,
) -> Result<(), DekuError>;
}

pub struct Same {
_priv: (),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intention of _priv? So that users cannot construct a Same ?

}

impl<'a, T, Ctx> DekuReadAs<'a, T, Ctx> for Same
where
T: DekuRead<'a, Ctx>,
{
fn read_as(
input: &'a bitvec::BitSlice<bitvec::Msb0, u8>,
ctx: Ctx,
) -> Result<(&'a bitvec::BitSlice<bitvec::Msb0, u8>, T), DekuError> {
T::read(input, ctx)
}
}

impl<T, Ctx> DekuWriteAs<T, Ctx> for Same
where
T: DekuWrite<Ctx> + ?Sized,
{
fn write_as(
source: &T,
output: &mut bitvec::BitVec<bitvec::Msb0, u8>,
ctx: Ctx,
) -> Result<(), DekuError> {
source.write(output, ctx)
}
}
160 changes: 160 additions & 0 deletions tests/test_as.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use deku::ctx::Size;
use deku::prelude::*;
use deku::{bitvec, DekuReadAs, DekuWriteAs, Same};
use std::marker::PhantomData;

#[derive(DekuRead, DekuWrite, PartialEq, Debug)]
struct Foo {
#[deku(as = "VecWithLen<Same>", bytes = "4", ctx = "()")]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand what you're trying to achieve with Same, however it seems slightly non-intuitive.

What about just T ?

Suggested change
#[deku(as = "VecWithLen<Same>", bytes = "4", ctx = "()")]
#[deku(as = "VecWithLen<deku::T>", bytes = "4", ctx = "()")]

ints: Box<[u32]>,
}

#[test]
fn test_roundtrip() {
let foo = Foo {
ints: Box::new([6, 1, 25, 9]),
};
let bytes = foo.to_bytes().unwrap();
let foo2 = Foo::from_bytes((&bytes, 0)).unwrap().1;
assert_eq!(foo2, foo);
}

#[test]
fn test_vecwithlen() {
let foo = Foo {
ints: Box::new([6, 1, 25, 9]),
};
let bytes = foo.to_bytes().unwrap();
#[rustfmt::skip]
assert_eq!(
bytes,
[
4, 0, 0, 0,
6, 0, 0, 0,
1, 0, 0, 0,
25, 0, 0, 0,
9, 0, 0, 0
]
);
}

struct VecWithLen<T> {
_marker: PhantomData<T>,
}

impl<'a, Ctx: Copy, T, U: DekuReadAs<'a, T, Ctx>> DekuReadAs<'a, Box<[T]>, (Size, Ctx)>
for VecWithLen<U>
{
fn read_as(
input: &'a bitvec::BitSlice<bitvec::Msb0, u8>,
ctx: (Size, Ctx),
) -> Result<(&'a bitvec::BitSlice<bitvec::Msb0, u8>, Box<[T]>), DekuError> {
let (rest, vec) = Self::read_as(input, ctx)?;
Ok((rest, Vec::into_boxed_slice(vec)))
}
}

impl<'a, T, U: DekuReadAs<'a, T, ()>> DekuReadAs<'a, Box<[T]>> for VecWithLen<U> {
fn read_as(
input: &'a bitvec::BitSlice<bitvec::Msb0, u8>,
ctx: (),
) -> Result<(&'a bitvec::BitSlice<bitvec::Msb0, u8>, Box<[T]>), DekuError> {
let (rest, vec) = Self::read_as(input, ctx)?;
Ok((rest, Vec::into_boxed_slice(vec)))
}
}

impl<'a, Ctx: Copy, T, U: DekuReadAs<'a, T, Ctx>> DekuReadAs<'a, Vec<T>, (Size, Ctx)>
for VecWithLen<U>
{
fn read_as(
input: &'a bitvec::BitSlice<bitvec::Msb0, u8>,
(size, ctx): (Size, Ctx),
) -> Result<(&'a bitvec::BitSlice<bitvec::Msb0, u8>, Vec<T>), DekuError> {
let mut rest = input;
let (r, len) = usize::read(rest, size)?;
rest = r;
let mut out = Vec::with_capacity(len);
for _ in 0..len {
let (r, elem) = U::read_as(rest, ctx)?;
out.push(elem);
rest = r
}
Ok((rest, out))
}
}

impl<'a, T, U: DekuReadAs<'a, T, ()>> DekuReadAs<'a, Vec<T>> for VecWithLen<U> {
fn read_as(
input: &'a bitvec::BitSlice<bitvec::Msb0, u8>,
_ctx: (),
) -> Result<(&'a bitvec::BitSlice<bitvec::Msb0, u8>, Vec<T>), DekuError> {
Self::read_as(input, (Size::of::<usize>(), ()))
}
}

impl<Ctx: Copy, T, U: DekuWriteAs<T, Ctx>> DekuWriteAs<[T], (Size, Ctx)> for VecWithLen<U> {
fn write_as(
source: &[T],
output: &mut bitvec::BitVec<bitvec::Msb0, u8>,
(size, ctx): (Size, Ctx),
) -> Result<(), DekuError> {
dbg!(&output);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: leftover dbg!

source.len().write(output, size)?;
dbg!(&output);
for elem in source {
U::write_as(elem, output, ctx)?;
}
Ok(())
}
}

impl<T, U: DekuWriteAs<T, ()>> DekuWriteAs<[T]> for VecWithLen<U> {
fn write_as(
source: &[T],
output: &mut bitvec::BitVec<bitvec::Msb0, u8>,
_ctx: (),
) -> Result<(), DekuError> {
Self::write_as(source, output, (Size::of::<usize>(), ()))
}
}

impl<Ctx: Copy, T, U: DekuWriteAs<T, Ctx>> DekuWriteAs<Box<[T]>, (Size, Ctx)> for VecWithLen<U> {
fn write_as(
source: &Box<[T]>,
output: &mut bitvec::BitVec<bitvec::Msb0, u8>,
ctx: (Size, Ctx),
) -> Result<(), DekuError> {
Self::write_as(&**source, output, ctx)
}
}

impl<T, U: DekuWriteAs<T, ()>> DekuWriteAs<Box<[T]>> for VecWithLen<U> {
fn write_as(
source: &Box<[T]>,
output: &mut bitvec::BitVec<bitvec::Msb0, u8>,
ctx: (),
) -> Result<(), DekuError> {
Self::write_as(&**source, output, ctx)
}
}

impl<Ctx: Copy, T, U: DekuWriteAs<T, Ctx>> DekuWriteAs<Vec<T>, (Size, Ctx)> for VecWithLen<U> {
fn write_as(
source: &Vec<T>,
output: &mut bitvec::BitVec<bitvec::Msb0, u8>,
ctx: (Size, Ctx),
) -> Result<(), DekuError> {
Self::write_as(&**source, output, ctx)
}
}

impl<T, U: DekuWriteAs<T, ()>> DekuWriteAs<Vec<T>> for VecWithLen<U> {
fn write_as(
source: &Vec<T>,
output: &mut bitvec::BitVec<bitvec::Msb0, u8>,
ctx: (),
) -> Result<(), DekuError> {
Self::write_as(&**source, output, ctx)
}
}