Skip to content

Commit

Permalink
Add helper attribute macros for writing free function parsers/writers
Browse files Browse the repository at this point in the history
  • Loading branch information
csnover authored and jam1garner committed Nov 26, 2022
1 parent 3c9edee commit cf19a68
Show file tree
Hide file tree
Showing 16 changed files with 834 additions and 144 deletions.
17 changes: 10 additions & 7 deletions binrw/doc/attribute.md
Expand Up @@ -1067,6 +1067,9 @@ a type, or to parse types which have no `BinRead` implementation at all:
#[br(parse_with = $parse_fn:expr)] or #[br(parse_with($parse_fn:expr))]
```

Use the [`#[parser]`](crate::parser) attribute macro to create compatible
functions.

Any earlier field or [import](#arguments) can be referenced by the
expression in the directive (for example, to construct a parser function at
runtime by calling a function generator).
Expand All @@ -1082,6 +1085,9 @@ implementation at all:
#[bw(write_with = $write_fn:expr)] or #[bw(write_with($write_fn:expr))]
```

Use the [`#[writer]`](crate::writer) attribute macro to create compatible
functions.

Any field or [import](#arguments) can be referenced by the expression in the
directive (for example, to construct a serialisation function at runtime by
calling a function generator).
Expand All @@ -1096,9 +1102,8 @@ calling a function generator).
```
# use binrw::{prelude::*, io::{prelude::*, Cursor}, Endian};
# use std::collections::HashMap;
fn custom_parser<R: Read + Seek>(reader: &mut R, endian: Endian, _: ())
-> BinResult<HashMap<u16, u16>>
{
#[binrw::parser(reader, endian)]
fn custom_parser() -> BinResult<HashMap<u16, u16>> {
let mut map = HashMap::new();
map.insert(
<_>::read_options(reader, endian, ())?,
Expand All @@ -1124,11 +1129,9 @@ struct MyType {
```
# use binrw::{prelude::*, io::{prelude::*, Cursor}, Endian};
# use std::collections::BTreeMap;
fn custom_writer<R: Write + Seek>(
#[binrw::writer(writer, endian)]
fn custom_writer(
map: &BTreeMap<u16, u16>,
writer: &mut R,
endian: Endian,
_: ()
) -> BinResult<()> {
for (key, val) in map.iter() {
key.write_options(writer, endian, ())?;
Expand Down
8 changes: 2 additions & 6 deletions binrw/src/file_ptr.rs
Expand Up @@ -173,13 +173,9 @@ impl<Ptr: BinRead<Args = ()> + IntoSeekFrom, Value> FilePtr<Ptr, Value> {
/// # Errors
///
/// If reading fails, an [`Error`](crate::Error) variant will be returned.
pub fn parse<R, Args>(
reader: &mut R,
endian: Endian,
args: FilePtrArgs<Args>,
) -> BinResult<Value>
#[binrw::parser(reader, endian)]
pub fn parse<Args>(args: FilePtrArgs<Args>, ...) -> BinResult<Value>
where
R: Read + Seek,
Args: Clone,
Value: BinRead<Args = Args>,
{
Expand Down
9 changes: 2 additions & 7 deletions binrw/src/helpers.rs
Expand Up @@ -475,13 +475,8 @@ where
}
}

// Lint: Non-consumed argument is required to match the API.
#[allow(clippy::trivially_copy_pass_by_ref)]
fn default_reader<R: Read + Seek, Arg: Clone, T: BinRead<Args = Arg>>(
reader: &mut R,
endian: Endian,
args: T::Args,
) -> BinResult<T> {
#[binrw::parser(reader, endian)]
fn default_reader<Arg: Clone, T: BinRead<Args = Arg>>(args: T::Args, ...) -> BinResult<T> {
let mut value = T::read_options(reader, endian, args.clone())?;
value.after_parse(reader, endian, args)?;
Ok(value)
Expand Down
174 changes: 174 additions & 0 deletions binrw/src/lib.rs
Expand Up @@ -141,6 +141,180 @@ pub use binrw_derive::binrw;
/// ```
pub use binrw_derive::NamedArgs;

/// Attribute macro used to generate
/// [`parse_with`](docs::attribute#custom-parserswriters) functions.
///
/// Rust functions are transformed by this macro to match the binrw API.
///
/// # Attribute options
///
/// * `#[parser(reader)]` or `#[parser(reader: $ident)]`: Exposes the write
/// stream to the function. If no variable name is given, `reader` is used.
/// * `#[parser(endian)]` or `#[parser(endian: $ident)]`: Exposes the endianness
/// to the function. If no variable name is given, `endian` is used.
///
/// Options are comma-separated.
///
/// # Function parameters
///
/// Parameters are transformed into either
/// [tuple-style arguments](docs::attribute#tuple-style-arguments) or
/// [raw arguments](docs::attribute#raw-arguments) depending upon the function
/// signature.
///
/// ## Tuple-style arguments
///
/// Use a normal function signature. The parameters in the signature will be
/// converted to a tuple. For example:
///
/// ```
/// #[binrw::parser(reader: r, endian)]
/// fn custom_parser(v0: u8, v1: i16) -> binrw::BinResult<()> {
/// Ok(())
/// }
/// # custom_parser(&mut binrw::io::Cursor::new(b""), binrw::Endian::Little, (0, 0)).unwrap();
/// ```
///
/// The transformed output for this function is:
///
/// ```
/// use binrw::{BinResult, Endian, io::{Read, Seek}};
/// fn custom_parser<R: Read + Seek>(
/// r: &mut R,
/// endian: Endian,
/// (v0, v1): (u8, i16)
/// ) -> BinResult<()> {
/// Ok(())
/// }
/// # custom_parser(&mut binrw::io::Cursor::new(b""), binrw::Endian::Little, (0, 0)).unwrap();
/// ```
///
/// ## Raw arguments
///
/// Use a *variadic* function signature with a single parameter. The name and
/// type of the parameter will be used as the raw argument. For example:
///
/// ```
/// # struct ArgsType;
/// #[binrw::parser]
/// fn custom_parser(args: ArgsType, ...) -> binrw::BinResult<()> {
/// Ok(())
/// }
/// # custom_parser(&mut binrw::io::Cursor::new(b""), binrw::Endian::Little, ArgsType).unwrap();
/// ```
///
/// The transformed output for this function is:
///
/// ```
/// # struct ArgsType;
/// use binrw::{BinResult, Endian, io::{Read, Seek}};
/// fn custom_parser<R: Read + Seek>(
/// _: &mut R,
/// _: Endian,
/// args: ArgsType
/// ) -> BinResult<()> {
/// Ok(())
/// }
/// # custom_parser(&mut binrw::io::Cursor::new(b""), binrw::Endian::Little, ArgsType).unwrap();
/// ```
///
/// # Return value
///
/// The return value of a parser function must be [`BinResult<T>`](BinResult),
/// where `T` is the type of the object being parsed.
pub use binrw_derive::parser;

/// Attribute macro used to generate
/// [`write_with`](docs::attribute#custom-parserswriters) functions.
///
/// Rust functions are transformed by this macro to match the binrw API.
///
/// # Attribute options
///
/// * `#[writer(writer)]` or `#[writer(writer: $ident)]`: Exposes the write
/// stream to the function. If no variable name is given, `writer` is used.
/// * `#[writer(endian)]` or `#[writer(endian: $ident)]`: Exposes the endianness
/// to the function. If no variable name is given, `endian` is used.
///
/// Options are comma-separated.
///
/// # Function parameters
///
/// The first parameter is required and receives a reference to the object being
/// written.
///
/// Subsequent parameters are transformed into either
/// [tuple-style arguments](docs::attribute#tuple-style-arguments) or
/// [raw arguments](docs::attribute#raw-arguments) depending upon the function
/// signature.
///
/// ## Tuple-style arguments
///
/// Use a normal function signature. The remaining parameters in the signature
/// will be converted to a tuple. For example:
///
/// ```
/// # struct Object;
/// #[binrw::writer(writer: w, endian)]
/// fn custom_writer(obj: &Object, v0: u8, v1: i16) -> binrw::BinResult<()> {
/// Ok(())
/// }
/// # custom_writer(&Object, &mut binrw::io::Cursor::new(vec![]), binrw::Endian::Little, (0, 0)).unwrap();
/// ```
///
/// The transformed output for this function is:
///
/// ```
/// # struct Object;
/// use binrw::{BinResult, Endian, io::{Seek, Write}};
/// fn custom_writer<W: Write + Seek>(
/// obj: &Object,
/// w: &mut W,
/// endian: Endian,
/// (v0, v1): (u8, i16)
/// ) -> BinResult<()> {
/// Ok(())
/// }
/// # custom_writer(&Object, &mut binrw::io::Cursor::new(vec![]), binrw::Endian::Little, (0, 0)).unwrap();
/// ```
///
/// ## Raw arguments
///
/// Use a *variadic* function signature with a second parameter. The name and
/// type of the second parameter will be used as the raw argument. For example:
///
/// ```
/// # struct Object;
/// # struct ArgsType;
/// #[binrw::writer]
/// fn custom_writer(obj: &Object, args: ArgsType, ...) -> binrw::BinResult<()> {
/// Ok(())
/// }
/// # custom_writer(&Object, &mut binrw::io::Cursor::new(vec![]), binrw::Endian::Little, ArgsType).unwrap();
/// ```
///
/// The transformed output for this function is:
///
/// ```
/// # struct Object;
/// # struct ArgsType;
/// use binrw::{BinResult, Endian, io::{Seek, Write}};
/// fn custom_writer<W: Write + Seek>(
/// obj: &Object,
/// _: &mut W,
/// _: Endian,
/// args: ArgsType
/// ) -> BinResult<()> {
/// Ok(())
/// }
/// # custom_writer(&Object, &mut binrw::io::Cursor::new(vec![]), binrw::Endian::Little, ArgsType).unwrap();
/// ```
///
/// # Return value
///
/// The return value of a writer function must be [`BinResult<()>`](BinResult).
pub use binrw_derive::writer;

/// A specialized [`Result`] type for binrw operations.
pub type BinResult<T> = core::result::Result<T, Error>;

Expand Down
23 changes: 5 additions & 18 deletions binrw/src/punctuated.rs
@@ -1,9 +1,6 @@
//! Type definitions for wrappers which parse interleaved data.

use crate::{
io::{Read, Seek},
BinRead, BinResult, Endian, VecArgs,
};
use crate::{BinRead, BinResult, VecArgs};
use alloc::vec::Vec;
use core::fmt;

Expand Down Expand Up @@ -74,13 +71,8 @@ impl<T: BinRead, P: BinRead<Args = ()>> Punctuated<T, P> {
/// # assert_eq!(*y.x, vec![3, 2, 1]);
/// # assert_eq!(y.x.separators, vec![0, 1]);
/// ```
// Lint: Non-consumed argument is required to match the API.
#[allow(clippy::needless_pass_by_value)]
pub fn separated<R: Read + Seek>(
reader: &mut R,
endian: Endian,
args: VecArgs<T::Args>,
) -> BinResult<Self> {
#[crate::parser(reader, endian)]
pub fn separated(args: VecArgs<T::Args>, ...) -> BinResult<Self> {
let mut data = Vec::with_capacity(args.count);
let mut separators = Vec::with_capacity(args.count.max(1) - 1);

Expand All @@ -102,13 +94,8 @@ impl<T: BinRead, P: BinRead<Args = ()>> Punctuated<T, P> {
/// # Errors
///
/// If reading fails, an [`Error`](crate::Error) variant will be returned.
// Lint: Non-consumed argument is required to match the API.
#[allow(clippy::needless_pass_by_value)]
pub fn separated_trailing<R: Read + Seek>(
reader: &mut R,
endian: Endian,
args: VecArgs<T::Args>,
) -> BinResult<Self> {
#[crate::parser(reader, endian)]
pub fn separated_trailing(args: VecArgs<T::Args>, ...) -> BinResult<Self> {
let mut data = Vec::with_capacity(args.count);
let mut separators = Vec::with_capacity(args.count);

Expand Down
11 changes: 4 additions & 7 deletions binrw/tests/derive/struct.rs
@@ -1,7 +1,7 @@
use binrw::{
args, binread,
io::{Cursor, Read, Seek, SeekFrom},
BinRead, BinResult, Endian, FilePtr, NullString,
io::{Cursor, Seek, SeekFrom},
BinRead, BinResult, FilePtr, NullString,
};

#[test]
Expand All @@ -26,11 +26,8 @@ fn all_the_things() {
calc_test: u32,
}

fn read_offsets<R: Read + Seek>(
reader: &mut R,
endian: Endian,
_: (),
) -> BinResult<(u16, u16)> {
#[binrw::parser(reader, endian)]
fn read_offsets() -> BinResult<(u16, u16)> {
Ok((
u16::read_options(reader, endian, ())?,
u16::read_options(reader, endian, ())?,
Expand Down
8 changes: 2 additions & 6 deletions binrw/tests/derive/write/custom_writer.rs
Expand Up @@ -10,12 +10,8 @@ fn custom_writer() {
y: u16,
}

fn custom_writer<W: binrw::io::Write + binrw::io::Seek>(
_this: &u16,
writer: &mut W,
_: Endian,
_: (),
) -> binrw::BinResult<()> {
#[binrw::writer(writer)]
fn custom_writer(_this: &u16) -> binrw::BinResult<()> {
writer.write_all(b"abcd")?;
Ok(())
}
Expand Down
3 changes: 2 additions & 1 deletion binrw/tests/ui/bad_parse_with_fn.rs
@@ -1,6 +1,7 @@
use binrw::{BinRead, BinResult};

fn wrong<R: binrw::io::Read + binrw::io::Seek>(_: R, _: binrw::Endian, _: ()) -> BinResult<bool> {
#[binrw::parser]
fn wrong() -> BinResult<bool> {
Ok(true)
}

Expand Down

0 comments on commit cf19a68

Please sign in to comment.