From 9899f6b7b9bf6bd84e05e24438efd2a85b51e3f8 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 18 Sep 2022 17:03:59 -0500 Subject: [PATCH] Implement `with` directive Closes jam1garner/binrw#98. --- binrw/doc/attribute.md | 22 +- binrw/doc/index.md | 4 +- binrw/src/binread/mod.rs | 277 ++++++++++++++++ binrw/src/binwrite/mod.rs | 308 ++++++++++++++++++ binrw/src/file_ptr.rs | 47 ++- binrw/src/lib.rs | 4 +- binrw/src/meta.rs | 2 +- binrw/src/strings.rs | 283 ++++++---------- binrw/src/with.rs | 104 ++++++ binrw/tests/derive/struct.rs | 18 +- binrw/tests/strings.rs | 122 ++----- .../ui/invalid_keyword_struct_field.stderr | 4 +- binrw/tests/ui/non_blocking_errors.stderr | 10 +- .../src/binrw/codegen/read_options/struct.rs | 21 +- .../src/binrw/codegen/sanitization.rs | 2 + .../codegen/write_options/struct_field.rs | 32 +- binrw_derive/src/binrw/parser/attrs.rs | 1 + .../src/binrw/parser/field_level_attrs.rs | 2 +- binrw_derive/src/binrw/parser/keywords.rs | 1 + .../src/binrw/parser/types/field_mode.rs | 7 + 20 files changed, 925 insertions(+), 346 deletions(-) create mode 100644 binrw/src/with.rs diff --git a/binrw/doc/attribute.md b/binrw/doc/attribute.md index cfa408a1..8280f7b4 100644 --- a/binrw/doc/attribute.md +++ b/binrw/doc/attribute.md @@ -1162,11 +1162,11 @@ assert_eq!(output.into_inner(), b"\0\0\0\x01\0\x02\0\x03"); ### Using `FilePtr::parse` to read a `NullString` without storing a `FilePtr` ``` -# use binrw::{prelude::*, io::Cursor, FilePtr32, NullString}; +# use binrw::{prelude::*, io::Cursor, FilePtr32, NullString, ReadFrom}; #[derive(BinRead)] struct MyType { - #[br(parse_with = FilePtr32::parse)] - some_string: NullString, + #[br(parse_with = FilePtr32::parse_with(<_ as ReadFrom>::read_from))] + some_string: String, } # let val: MyType = Cursor::new(b"\0\0\0\x04Test\0").read_be().unwrap(); @@ -1881,8 +1881,8 @@ referenced by the expressions in any of these directives. # use binrw::{prelude::*, NullString, io::SeekFrom}; #[derive(BinRead)] struct MyType { - #[br(align_before = 4, pad_after = 1, align_after = 4)] - str: NullString, + #[br(align_before = 4, pad_after = 1, align_after = 4, with(NullString))] + str: String, #[br(pad_size_to = 0x10)] test: u64, @@ -1898,8 +1898,8 @@ struct MyType { # use binrw::{prelude::*, NullString, io::SeekFrom}; #[derive(BinWrite)] struct MyType { - #[bw(align_before = 4, pad_after = 1, align_after = 4)] - str: NullString, + #[bw(align_before = 4, pad_after = 1, align_after = 4, with(NullString))] + str: String, #[bw(pad_size_to = 0x10)] test: u64, @@ -1948,12 +1948,12 @@ this to happen. ## Examples ``` -# use binrw::{prelude::*, FilePtr32, NullString, io::Cursor}; +# use binrw::{prelude::*, FilePtr32, FilePtrWith, NullString, io::Cursor}; #[derive(BinRead, Debug)] #[br(big, magic = b"TEST")] struct TestFile { - #[br(deref_now)] - ptr: FilePtr32, + #[br(deref_now, with(FilePtrWith))] + ptr: FilePtr32, value: i32, @@ -1966,7 +1966,7 @@ struct TestFile { # let test = Cursor::new(test_contents).read_be::().unwrap(); # assert_eq!(test.ptr_len, 11); # assert_eq!(test.value, -1); -# assert_eq!(test.ptr.to_string(), "Test string"); +# assert_eq!(*test.ptr, "Test string"); ``` diff --git a/binrw/doc/index.md b/binrw/doc/index.md index 5d4af425..9857a75d 100644 --- a/binrw/doc/index.md +++ b/binrw/doc/index.md @@ -158,8 +158,8 @@ struct Dog { #[br(count = bone_pile_count)] bone_piles: Vec, - #[br(align_before = 0xA)] - name: NullString + #[br(align_before = 0xA, with(NullString))] + name: String } let mut data = Cursor::new(b"DOG\x02\x00\x01\x00\x12\0\0Rudy\0"); diff --git a/binrw/src/binread/mod.rs b/binrw/src/binread/mod.rs index 704ff10d..51da72ff 100644 --- a/binrw/src/binread/mod.rs +++ b/binrw/src/binread/mod.rs @@ -178,6 +178,167 @@ pub trait BinRead: Sized + 'static { } } +/// Extension methods for reading [`BinRead`] objects using a converter. +pub trait ReadWith { + /// Read `Self` from the reader using the given converter. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_with(reader: &mut R) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + T: ReadEndian, + >::Args: Required, + { + Self::read_args_with::(reader, >::Args::args()) + } + + /// Read `Self` from the reader, using the given converter, assuming + /// big-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_be_with(reader: &mut R) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + >::Args: Required, + { + Self::read_be_args_with::(reader, >::Args::args()) + } + + /// Read `Self` from the reader, using the given converter, assuming + /// little-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_le_with(reader: &mut R) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + >::Args: Required, + { + Self::read_le_args_with::(reader, >::Args::args()) + } + + /// Read `Self` from the reader, using the given converter, assuming + /// native-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_ne_with(reader: &mut R) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + >::Args: Required, + { + Self::read_ne_args_with::(reader, >::Args::args()) + } + + /// Read `Self` from the reader, using the given converter and arguments. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_args_with(reader: &mut R, args: >::Args) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + T: ReadEndian, + { + Self::read_from(reader, Endian::Little, args) + } + + /// Read `Self` from the reader, using the given converter and arguments, + /// assuming big-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_be_args_with(reader: &mut R, args: >::Args) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + { + Self::read_from(reader, Endian::Big, args) + } + + /// Read `Self` from the reader, using the given converter and arguments, + /// assuming little-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_le_args_with(reader: &mut R, args: >::Args) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + { + Self::read_from(reader, Endian::Little, args) + } + + /// Read `Self` from the reader, using the given converter and arguments, + /// assuming native-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_ne_args_with(reader: &mut R, args: >::Args) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + { + Self::read_from(reader, Endian::NATIVE, args) + } +} + +impl ReadWith for T {} + +/// The `ReadFrom` trait enables transparent deserialisation into a +/// non-[`BinRead`] type. +pub trait ReadFrom: Sized { + /// The type used for the `args` parameter of [`read_from()`]. + /// + /// [`read_from()`]: Self::read_from + type Args: Clone; + + /// Read `T` from the reader using the given arguments. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + fn read_from( + reader: &mut R, + endian: Endian, + args: Self::Args, + ) -> BinResult; +} + +impl ReadFrom for T { + type Args = T::Args; + + fn read_from( + reader: &mut R, + endian: Endian, + args: Self::Args, + ) -> BinResult { + Self::read_options(reader, endian, args) + } +} + /// Extension methods for reading [`BinRead`] objects directly from a reader. /// /// # Examples @@ -289,6 +450,122 @@ pub trait BinReaderExt: Read + Seek + Sized { fn read_ne_args(&mut self, args: T::Args) -> BinResult { self.read_type_args(Endian::NATIVE, args) } + + /// Read `T` from the reader using the given converter. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_with(&mut self) -> BinResult + where + T: ReadFrom, + C: ReadEndian, + >::Args: Required, + { + self.read_args_with::(>::Args::args()) + } + + /// Read `T` from the reader, using the given converter, assuming + /// big-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_be_with(&mut self) -> BinResult + where + T: ReadFrom, + >::Args: Required, + { + self.read_be_args_with::(>::Args::args()) + } + + /// Read `T` from the reader, using the given converter, assuming + /// little-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_le_with(&mut self) -> BinResult + where + T: ReadFrom, + >::Args: Required, + { + self.read_le_args_with::(>::Args::args()) + } + + /// Read `T` from the reader, using the given converter, assuming + /// native-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_ne_with(&mut self) -> BinResult + where + T: ReadFrom, + >::Args: Required, + { + self.read_ne_args_with::(>::Args::args()) + } + + /// Read `T` from the reader, using the given converter and arguments. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_args_with(&mut self, args: >::Args) -> BinResult + where + T: ReadFrom, + C: ReadEndian, + { + T::read_from(self, Endian::Little, args) + } + + /// Read `T` from the reader, using the given converter and arguments, + /// assuming big-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_be_args_with(&mut self, args: >::Args) -> BinResult + where + T: ReadFrom, + { + T::read_from(self, Endian::Big, args) + } + + /// Read `T` from the reader, using the given converter and arguments, + /// assuming little-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_le_args_with(&mut self, args: >::Args) -> BinResult + where + T: ReadFrom, + { + T::read_from(self, Endian::Little, args) + } + + /// Read `T` from the reader, using the given converter and arguments, + /// assuming native-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_ne_args_with(&mut self, args: >::Args) -> BinResult + where + T: ReadFrom, + { + T::read_from(self, Endian::NATIVE, args) + } } impl BinReaderExt for R {} diff --git a/binrw/src/binwrite/mod.rs b/binrw/src/binwrite/mod.rs index 61e06767..67c7129a 100644 --- a/binrw/src/binwrite/mod.rs +++ b/binrw/src/binwrite/mod.rs @@ -4,6 +4,7 @@ use crate::{ io::{Seek, Write}, BinResult, Endian, __private::Required, + meta::WriteEndian, }; /// The `BinWrite` trait serialises objects and writes them to streams. @@ -135,6 +136,185 @@ pub trait BinWrite { ) -> BinResult<()>; } +/// Extension methods for writing [`BinWrite`] objects using a converter. +pub trait WriteWith { + /// Write `Self` to the writer using the given converter. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_with(&self, writer: &mut W) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + T: WriteEndian, + >::Args: Required, + { + self.write_args_with::(writer, >::Args::args()) + } + + /// Write `Self` to the writer, using the given converter, assuming + /// big-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_be_with(&self, writer: &mut W) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + >::Args: Required, + { + self.write_be_args_with::(writer, >::Args::args()) + } + + /// Write `Self` to the writer, using the given converter, assuming + /// little-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_le_with(&self, writer: &mut W) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + >::Args: Required, + { + self.write_le_args_with::(writer, >::Args::args()) + } + + /// Write `Self` to the writer, using the given converter, assuming + /// native-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_ne_with(&self, writer: &mut W) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + >::Args: Required, + { + self.write_ne_args_with::(writer, >::Args::args()) + } + + /// Write `Self` to the writer, using the given converter and arguments. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_args_with( + &self, + writer: &mut W, + args: >::Args, + ) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + T: WriteEndian, + { + self.write_into(writer, Endian::Little, args) + } + + /// Write `Self` to the writer, using the given converter and arguments, + /// assuming big-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_be_args_with( + &self, + writer: &mut W, + args: >::Args, + ) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + { + self.write_into(writer, Endian::Big, args) + } + + /// Write `Self` to the writer, using the given converter and arguments, + /// assuming little-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_le_args_with( + &self, + writer: &mut W, + args: >::Args, + ) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + { + self.write_into(writer, Endian::Little, args) + } + + /// Write `Self` to the writer, using the given converter and arguments, + /// assuming native-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_ne_args_with( + &self, + writer: &mut W, + args: >::Args, + ) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + { + self.write_into(writer, Endian::NATIVE, args) + } +} + +impl WriteWith for T {} + +/// The `WriteInto` trait enables transparent conversion from a non-[`BinWrite`] +/// type. +pub trait WriteInto { + /// The type used for the `args` parameter of [`write_into()`]. + /// + /// [`write_into()`]: Self::write_into + type Args: Clone; + + /// Write `Self` into the writer using the given arguments. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()>; +} + +impl WriteInto for T { + type Args = T::Args; + + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + self.write_options(writer, endian, args) + } +} + /// Extension methods for writing [`BinWrite`] objects directly to a writer. /// /// # Examples @@ -245,6 +425,134 @@ pub trait BinWriterExt: Write + Seek + Sized { fn write_ne_args(&mut self, value: &T, args: T::Args) -> BinResult<()> { self.write_type_args(value, Endian::NATIVE, args) } + + /// Write `T` to the writer using the given converter. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_with(&mut self, value: &T) -> BinResult<()> + where + C: WriteEndian, + T: WriteInto + ?Sized, + >::Args: Required, + { + self.write_args_with::(value, >::Args::args()) + } + + /// Write `T` to the writer, using the given converter, assuming + /// big-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_be_with(&mut self, value: &T) -> BinResult<()> + where + T: WriteInto + ?Sized, + >::Args: Required, + { + self.write_be_args_with::(value, >::Args::args()) + } + + /// Write `T` to the writer, using the given converter, assuming + /// little-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_le_with(&mut self, value: &T) -> BinResult<()> + where + T: WriteInto + ?Sized, + >::Args: Required, + { + self.write_le_args_with::(value, >::Args::args()) + } + + /// Write `T` to the writer, using the given converter, assuming + /// native-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_ne_with(&mut self, value: &T) -> BinResult<()> + where + T: WriteInto + ?Sized, + >::Args: Required, + { + self.write_ne_args_with::(value, >::Args::args()) + } + + /// Write `T` to the writer, using the given converter and arguments. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_args_with(&mut self, value: &T, args: >::Args) -> BinResult<()> + where + C: WriteEndian, + T: WriteInto + ?Sized, + { + value.write_into(self, Endian::Little, args) + } + + /// Write `T` to the writer, using the given converter and arguments, + /// assuming big-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_be_args_with( + &mut self, + value: &T, + args: >::Args, + ) -> BinResult<()> + where + T: WriteInto + ?Sized, + { + value.write_into(self, Endian::Big, args) + } + + /// Write `T` to the writer, using the given converter and arguments, + /// assuming little-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_le_args_with( + &mut self, + value: &T, + args: >::Args, + ) -> BinResult<()> + where + T: WriteInto + ?Sized, + { + value.write_into(self, Endian::Little, args) + } + + /// Write `T` to the writer, using the given converter and arguments, + /// assuming native-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_ne_args_with( + &mut self, + value: &T, + args: >::Args, + ) -> BinResult<()> + where + T: WriteInto + ?Sized, + { + value.write_into(self, Endian::NATIVE, args) + } } impl BinWriterExt for W {} diff --git a/binrw/src/file_ptr.rs b/binrw/src/file_ptr.rs index 40e2af17..171585ea 100644 --- a/binrw/src/file_ptr.rs +++ b/binrw/src/file_ptr.rs @@ -1,17 +1,18 @@ //! Type definitions for wrappers which represent a layer of indirection within //! a file. -use crate::NamedArgs; use crate::{ io::{Read, Seek, SeekFrom}, - BinRead, BinResult, Endian, + BinRead, BinResult, Endian, NamedArgs, ReadFrom, }; -use core::fmt; -use core::num::{ - NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16, - NonZeroU32, NonZeroU64, NonZeroU8, +use core::{ + fmt, + num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16, + NonZeroU32, NonZeroU64, NonZeroU8, + }, + ops::{Deref, DerefMut}, }; -use core::ops::{Deref, DerefMut}; /// A type alias for [`FilePtr`] with 8-bit offsets. pub type FilePtr8 = FilePtr; @@ -35,6 +36,34 @@ pub type NonZeroFilePtr64 = FilePtr; /// A type alias for [`FilePtr`] with non-zero 128-bit offsets. pub type NonZeroFilePtr128 = FilePtr; +/// A converter for non-[`BinRead`] [`FilePtr`] values. +pub enum FilePtrWith { + #[doc(hidden)] + _Phantom(Private), +} +#[doc(hidden)] +pub struct Private(core::marker::PhantomData); + +impl + IntoSeekFrom, C, T: ReadFrom> ReadFrom> + for FilePtr +{ + type Args = FilePtrArgs<>::Args>; + + fn read_from( + reader: &mut R, + endian: Endian, + args: Self::Args, + ) -> BinResult { + Self::read_with_parser( + |reader, endian, args| >::read_from(reader, endian, args), + |_, _, _, _| Ok(()), + reader, + endian, + args, + ) + } +} + /// A wrapper type which represents a layer of indirection within a file. /// /// `FilePtr` is composed of two types. The pointer type `P` is the @@ -250,7 +279,7 @@ impl + IntoSeekFrom, Value> FilePtr { /// /// Will panic if `FilePtr` hasn’t been finalized by calling /// [`after_parse()`](Self::after_parse). -impl Deref for FilePtr { +impl Deref for FilePtr { type Target = Value; fn deref(&self) -> &Self::Target { @@ -266,7 +295,7 @@ impl Deref for FilePtr { /// # Panics /// Will panic if the `FilePtr` has not been read yet using /// [`BinRead::after_parse`](BinRead::after_parse) -impl DerefMut for FilePtr { +impl DerefMut for FilePtr { fn deref_mut(&mut self) -> &mut Value { match self.value.as_mut() { Some(x) => x, diff --git a/binrw/src/lib.rs b/binrw/src/lib.rs index 5b76b736..aaf1c108 100644 --- a/binrw/src/lib.rs +++ b/binrw/src/lib.rs @@ -35,6 +35,7 @@ pub mod pos_value; pub mod punctuated; #[doc(hidden)] pub mod strings; +mod with; #[cfg(all(doc, not(feature = "std")))] use alloc::vec::Vec; @@ -44,11 +45,12 @@ pub use { binwrite::*, endian::Endian, error::Error, - file_ptr::{FilePtr, FilePtr128, FilePtr16, FilePtr32, FilePtr64, FilePtr8}, + file_ptr::{FilePtr, FilePtr128, FilePtr16, FilePtr32, FilePtr64, FilePtr8, FilePtrWith}, helpers::{count, until, until_eof, until_exclusive}, named_args::*, pos_value::PosValue, strings::{NullString, NullWideString}, + with::With, }; /// Derive macro generating an impl of the trait [`BinRead`]. diff --git a/binrw/src/meta.rs b/binrw/src/meta.rs index ba4a001a..9b8fcc75 100644 --- a/binrw/src/meta.rs +++ b/binrw/src/meta.rs @@ -83,7 +83,7 @@ macro_rules! endian_impl { )+)+} } -endian_impl!(() i8 u8 core::num::NonZeroU8 core::num::NonZeroI8 crate::strings::NullString => EndianKind::None); +endian_impl!(() i8 u8 core::num::NonZeroU8 core::num::NonZeroI8 => EndianKind::None); impl ReadEndian for Box { const ENDIAN: EndianKind = ::ENDIAN; diff --git a/binrw/src/strings.rs b/binrw/src/strings.rs index 02e6232a..bcc29ca2 100644 --- a/binrw/src/strings.rs +++ b/binrw/src/strings.rs @@ -1,14 +1,14 @@ //! Type definitions for string readers. use crate::{ - alloc::string::{FromUtf16Error, FromUtf8Error}, + helpers::until_exclusive, io::{Read, Seek, Write}, - BinRead, BinResult, BinWrite, Endian, + meta::{EndianKind, ReadEndian, WriteEndian}, + BinResult, BinWrite, Endian, ReadFrom, WriteInto, }; -use alloc::{string::String, vec, vec::Vec}; -use core::fmt::{self, Write as _}; +use alloc::{boxed::Box, string::String, vec::Vec}; -/// A null-terminated 8-bit string. +/// A converter for null-terminated 8-bit strings. /// /// The null terminator is consumed and not included in the value. /// @@ -18,112 +18,96 @@ use core::fmt::{self, Write as _}; /// let mut null_separated_strings = Cursor::new(b"null terminated strings? in my system's language?\0no thanks\0"); /// /// assert_eq!( -/// null_separated_strings.read_be::().unwrap().to_string(), +/// null_separated_strings.read_with::().unwrap(), /// "null terminated strings? in my system's language?" /// ); /// /// assert_eq!( -/// null_separated_strings.read_be::().unwrap().to_string(), +/// null_separated_strings.read_with::().unwrap(), /// "no thanks" /// ); /// ``` -#[derive(Clone, Eq, PartialEq, Default)] -pub struct NullString( - /// The raw byte string. - pub Vec, -); +pub enum NullString {} -impl BinRead for NullString { +impl ReadEndian for NullString { + const ENDIAN: EndianKind = EndianKind::None; +} + +impl ReadFrom for String { type Args = (); - fn read_options( + fn read_from( reader: &mut R, endian: Endian, - _: Self::Args, + args: Self::Args, ) -> BinResult { - let mut values = vec![]; - - loop { - let val = ::read_options(reader, endian, ())?; - if val == 0 { - return Ok(Self(values)); - } - values.push(val); - } + let pos = reader.stream_position()?; + as ReadFrom>::read_from(reader, endian, args).and_then(|vec| { + Self::from_utf8(vec).map_err(|err| binrw::Error::Custom { + pos, + err: Box::new(err) as _, + }) + }) } } -impl BinWrite for NullString { +impl ReadFrom for Vec { type Args = (); - fn write_options( - &self, - writer: &mut W, + fn read_from( + reader: &mut R, endian: Endian, args: Self::Args, - ) -> BinResult<()> { - self.0.write_options(writer, endian, args)?; - 0u8.write_options(writer, endian, args)?; - - Ok(()) - } -} - -impl From<&str> for NullString { - fn from(s: &str) -> Self { - Self(s.as_bytes().to_vec()) + ) -> BinResult { + until_exclusive(|b| *b == 0)(reader, endian, args) } } -impl From for NullString { - fn from(s: String) -> Self { - Self(s.into_bytes()) - } +impl WriteEndian for NullString { + const ENDIAN: EndianKind = EndianKind::None; } -impl From for Vec { - fn from(s: NullString) -> Self { - s.0 - } -} - -impl TryFrom for String { - type Error = FromUtf8Error; +impl WriteInto for [u8] { + type Args = (); - fn try_from(value: NullString) -> Result { - String::from_utf8(value.0) + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + self.write_options(writer, endian, args)?; + 0_u8.write_options(writer, endian, args) } } -impl core::ops::Deref for NullString { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} +impl WriteInto for str { + type Args = (); -impl core::ops::DerefMut for NullString { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + <_ as WriteInto>::write_into(self.as_bytes(), writer, endian, args) } } -impl fmt::Debug for NullString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "NullString(\"")?; - display_utf8(&self.0, f, str::escape_debug)?; - write!(f, "\")") - } -} +impl WriteInto for String { + type Args = (); -impl fmt::Display for NullString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - display_utf8(&self.0, f, str::chars) + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + <_ as WriteInto>::write_into(self.as_str(), writer, endian, args) } } -/// A null-terminated 16-bit string. +/// A converter for null-terminated 16-bit strings. /// /// The null terminator must also be 16-bits, and is consumed and not included /// in the value. @@ -139,147 +123,74 @@ impl fmt::Display for NullString { /// /// assert_eq!( /// // notice: read_le -/// wide_strings.read_le::().unwrap().to_string(), +/// wide_strings.read_le_with::().unwrap(), /// "wide strings" /// ); /// /// assert_eq!( /// // notice: read_be -/// are_endian_dependent.read_be::().unwrap().to_string(), +/// are_endian_dependent.read_be_with::().unwrap(), /// "are endian dependent" /// ); /// ``` -#[derive(Clone, Eq, PartialEq, Default)] -pub struct NullWideString( - /// The raw wide byte string. - pub Vec, -); +pub enum NullWideString {} -impl BinRead for NullWideString { +impl ReadFrom for Vec { type Args = (); - fn read_options( + fn read_from( reader: &mut R, endian: Endian, - _: Self::Args, + args: Self::Args, ) -> BinResult { - let mut values = vec![]; - - loop { - let val = ::read_options(reader, endian, ())?; - if val == 0 { - return Ok(Self(values)); - } - values.push(val); - } + until_exclusive(|b| *b == 0)(reader, endian, args) } } -impl BinWrite for NullWideString { +impl ReadFrom for String { type Args = (); - fn write_options( - &self, - writer: &mut W, + fn read_from( + reader: &mut R, endian: Endian, args: Self::Args, - ) -> BinResult<()> { - self.0.write_options(writer, endian, args)?; - 0u16.write_options(writer, endian, args)?; - - Ok(()) - } -} - -impl From for Vec { - fn from(s: NullWideString) -> Self { - s.0 - } -} - -impl From<&str> for NullWideString { - fn from(s: &str) -> Self { - Self(s.encode_utf16().collect()) - } -} - -impl From for NullWideString { - fn from(s: String) -> Self { - Self(s.encode_utf16().collect()) - } -} - -impl TryFrom for String { - type Error = FromUtf16Error; - - fn try_from(value: NullWideString) -> Result { - String::from_utf16(&value.0) - } -} - -impl core::ops::Deref for NullWideString { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl core::ops::DerefMut for NullWideString { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + ) -> BinResult { + let pos = reader.stream_position()?; + as ReadFrom>::read_from(reader, endian, args).and_then(|vec| { + String::from_utf16(&vec).map_err(|err| binrw::Error::Custom { + pos, + err: Box::new(err) as _, + }) + }) } } -impl fmt::Display for NullWideString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - display_utf16(&self.0, f, core::iter::once) - } -} +impl WriteInto for [u16] { + type Args = (); -impl fmt::Debug for NullWideString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "NullWideString(\"")?; - display_utf16(&self.0, f, char::escape_debug)?; - write!(f, "\")") + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + self.write_options(writer, endian, args)?; + 0_u16.write_options(writer, endian, args) } } -fn display_utf16 O, O: Iterator>( - input: &[u16], - f: &mut fmt::Formatter<'_>, - t: Transformer, -) -> fmt::Result { - char::decode_utf16(input.iter().copied()) - .flat_map(|r| t(r.unwrap_or(char::REPLACEMENT_CHARACTER))) - .try_for_each(|c| f.write_char(c)) -} - -fn display_utf8<'a, Transformer: Fn(&'a str) -> O, O: Iterator + 'a>( - mut input: &'a [u8], - f: &mut fmt::Formatter<'_>, - t: Transformer, -) -> fmt::Result { - // Adapted from - loop { - match core::str::from_utf8(input) { - Ok(valid) => { - t(valid).try_for_each(|c| f.write_char(c))?; - break; - } - Err(error) => { - let (valid, after_valid) = input.split_at(error.valid_up_to()); - - t(core::str::from_utf8(valid).unwrap()).try_for_each(|c| f.write_char(c))?; - f.write_char(char::REPLACEMENT_CHARACTER)?; +impl WriteInto for str { + type Args = (); - if let Some(invalid_sequence_length) = error.error_len() { - input = &after_valid[invalid_sequence_length..]; - } else { - break; - } - } + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + for c in self.encode_utf16() { + c.write_options(writer, endian, ())?; } + 0_u16.write_options(writer, endian, args) } - Ok(()) } diff --git a/binrw/src/with.rs b/binrw/src/with.rs new file mode 100644 index 00000000..9d7744f1 --- /dev/null +++ b/binrw/src/with.rs @@ -0,0 +1,104 @@ +//! Wrapper type for conversions. + +use super::{io, BinRead, BinResult, BinWrite, Endian, ReadFrom, WriteInto}; +use core::marker::PhantomData; + +/// A wrapper for reading or writing types through a converter. +/// +/// The converter must implement [`ReadFrom`] for reads and [`WriteInto`] +/// for writes. +#[derive(Clone, Copy)] +pub struct With(T, PhantomData); + +impl With { + /// Consumes this wrapper, returning the wrapped value. + pub fn into_inner(self) -> T { + self.0 + } +} + +impl core::fmt::Debug for With +where + T: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("With").field(&self.0).finish() + } +} + +impl core::ops::Deref for With { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::DerefMut for With { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl ReadFrom for With +where + T: ReadFrom, +{ + type Args = >::Args; + + fn read_from( + reader: &mut R, + endian: Endian, + args: Self::Args, + ) -> BinResult { + >::read_from(reader, endian, args).map(|value| Self(value, PhantomData)) + } +} + +impl BinRead for With +where + C: 'static, + T: ReadFrom + 'static, +{ + type Args = >::Args; + + fn read_options( + reader: &mut R, + endian: Endian, + args: Self::Args, + ) -> BinResult { + >::read_from(reader, endian, args) + } +} + +impl WriteInto for With +where + T: WriteInto, +{ + type Args = >::Args; + + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + >::write_into(&self.0, writer, endian, args) + } +} + +impl BinWrite for With +where + T: WriteInto, +{ + type Args = >::Args; + + fn write_options( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + >::write_into(self, writer, endian, args) + } +} diff --git a/binrw/tests/derive/struct.rs b/binrw/tests/derive/struct.rs index abd7a483..8f811ec8 100644 --- a/binrw/tests/derive/struct.rs +++ b/binrw/tests/derive/struct.rs @@ -1,7 +1,7 @@ use binrw::{ args, binread, io::{Cursor, Seek, SeekFrom}, - BinRead, BinResult, FilePtr, NullString, + BinRead, BinResult, FilePtr, FilePtrWith, NullString, }; #[test] @@ -49,7 +49,7 @@ fn all_the_things() { offsets: (u16, u16), #[br(if(offsets.0 == 0x20))] - name: Option>, + name: Option>>, #[br(calc(extra_val))] extra_val: u8, @@ -153,8 +153,8 @@ fn deref_now() { struct Test { // deref_now on the first field tests that the reader position is correctly // restored before reading the second field - #[br(deref_now)] - a: FilePtr, + #[br(deref_now, with(FilePtrWith))] + a: FilePtr, b: i32, } @@ -164,7 +164,7 @@ fn deref_now() { Test { a: FilePtr { ptr: 0x10, - value: Some(NullString(b"Test string".to_vec())) + value: Some("Test string".to_string()) }, b: -1, } @@ -177,17 +177,17 @@ fn move_temp_field() { #[binread] #[derive(Debug, Eq, PartialEq)] struct Foo { - #[br(temp, postprocess_now)] - foo: binrw::NullString, + #[br(temp, postprocess_now, with(binrw::NullString))] + foo: String, #[br(calc = foo)] - bar: binrw::NullString, + bar: String, } assert_eq!( Foo::read_le(&mut Cursor::new(b"hello\0goodbyte\0")).unwrap(), Foo { - bar: binrw::NullString::from("hello"), + bar: String::from("hello"), } ); } diff --git a/binrw/tests/strings.rs b/binrw/tests/strings.rs index 1835a085..20c15929 100644 --- a/binrw/tests/strings.rs +++ b/binrw/tests/strings.rs @@ -4,54 +4,17 @@ fn null_wide_strings() { assert_eq!( Cursor::new(b"w\0i\0d\0e\0 \0s\0t\0r\0i\0n\0g\0s\0\0\0") - .read_le::() - .unwrap() - .to_string(), + .read_le_with::() + .unwrap(), "wide strings" ); assert_eq!( Cursor::new(b"\0a\0r\0e\0 \0e\0n\0d\0i\0a\0n\0 \0d\0e\0p\0e\0n\0d\0e\0n\0t\0\0") - .read_be::() - .unwrap() - .to_string(), + .read_be_with::() + .unwrap(), "are endian dependent" ); - - assert_eq!( - format!( - "{:?}", - Cursor::new(b"d\0e\0b\0u\0g\0\x3a\x26\n\0\0\0") - .read_le::() - .unwrap() - ), - "NullWideString(\"debug☺\\n\")" - ); - - assert_eq!( - format!( - "{:?}", - Cursor::new(b"b\0a\0d\0 \0\0\xdc\0\xdc \0s\0u\0r\0r\0o\0g\0a\0t\0e\0\0\0") - .read_le::() - .unwrap() - ), - "NullWideString(\"bad \u{FFFD}\u{FFFD} surrogate\")" - ); - - // Default/Deref/DerefMut - let mut s = NullWideString::default(); - s.extend_from_slice(&[b'h'.into(), b'e'.into(), b'y'.into()]); - assert_eq!(&s[0..2], &[b'h'.into(), b'e'.into()]); - - // Clone/TryFrom - let t = String::try_from(s.clone()).unwrap(); - assert_eq!(t, "hey"); - s.push(0xdc00); - String::try_from(s).expect_err("accepted bad data"); - - // From - let s = NullWideString::from(t.clone()); - assert_eq!(Vec::from(s), t.encode_utf16().collect::>()); } #[test] @@ -63,92 +26,47 @@ fn null_strings() { assert_eq!( null_separated_strings - .read_be::() - .unwrap() - .to_string(), + .read_with::() + .unwrap(), "null terminated strings? in my system's language?" ); assert_eq!( null_separated_strings - .read_be::() - .unwrap() - .to_string(), + .read_with::() + .unwrap(), "no thanks" ); - - assert_eq!( - format!( - "{:?}", - Cursor::new(b"debug\xe2\x98\xba\n\0") - .read_be::() - .unwrap() - ), - "NullString(\"debug☺\\n\")" - ); - - assert_eq!( - format!( - "{:?}", - Cursor::new(b"bad \xfe utf8 \xfe\0") - .read_be::() - .unwrap() - ), - "NullString(\"bad \u{FFFD} utf8 \u{FFFD}\")" - ); - - assert_eq!( - format!( - "{:?}", - Cursor::new(b"truncated\xe2\0") - .read_be::() - .unwrap() - ), - "NullString(\"truncated\u{FFFD}\")" - ); - - // Default/Deref/DerefMut - let mut s = NullString::default(); - s.extend_from_slice(b"hey"); - assert_eq!(&s[0..2], b"he"); - - // Clone/TryFrom - let t = String::try_from(s.clone()).unwrap(); - assert_eq!(t, "hey"); - s.extend_from_slice(b"\xe2"); - String::try_from(s).expect_err("accepted bad data"); - - // From - let s = NullString::from(t.clone()); - assert_eq!(Vec::from(s), t.as_bytes()); } #[test] fn null_string_round_trip() { use binrw::{io::Cursor, BinReaderExt, BinWriterExt, NullString}; - let data = "test test test"; - let s = NullString::from(data); + let s = "test test test"; let mut x = Cursor::new(Vec::new()); - x.write_be(&s).unwrap(); + x.write_with::(s).unwrap(); - let s2: NullString = Cursor::new(x.into_inner()).read_be().unwrap(); + let s2: String = Cursor::new(x.into_inner()) + .read_with::() + .unwrap(); - assert_eq!(&s2.to_string(), data); + assert_eq!(s2, s); } #[test] fn null_wide_string_round_trip() { use binrw::{io::Cursor, BinReaderExt, BinWriterExt, NullWideString}; - let data = "test test test"; - let s = NullWideString::from(data); + let s = "test test test"; let mut x = Cursor::new(Vec::new()); - x.write_be(&s).unwrap(); + x.write_be_with::(s).unwrap(); - let s2: NullWideString = Cursor::new(x.into_inner()).read_be().unwrap(); + let s2: String = Cursor::new(x.into_inner()) + .read_be_with::() + .unwrap(); - assert_eq!(&s2.to_string(), data); + assert_eq!(&s2, s); } diff --git a/binrw/tests/ui/invalid_keyword_struct_field.stderr b/binrw/tests/ui/invalid_keyword_struct_field.stderr index cd84daab..98c5d53a 100644 --- a/binrw/tests/ui/invalid_keyword_struct_field.stderr +++ b/binrw/tests/ui/invalid_keyword_struct_field.stderr @@ -1,5 +1,5 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` - --> $DIR/invalid_keyword_struct_field.rs:5:10 +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` + --> tests/ui/invalid_keyword_struct_field.rs:5:10 | 5 | #[br(invalid_struct_field_keyword)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/binrw/tests/ui/non_blocking_errors.stderr b/binrw/tests/ui/non_blocking_errors.stderr index c1b10de5..5cef88a1 100644 --- a/binrw/tests/ui/non_blocking_errors.stderr +++ b/binrw/tests/ui/non_blocking_errors.stderr @@ -1,17 +1,17 @@ error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` - --> $DIR/non_blocking_errors.rs:6:6 + --> tests/ui/non_blocking_errors.rs:6:6 | 6 | #[br(invalid_keyword_struct)] | ^^^^^^^^^^^^^^^^^^^^^^ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` - --> $DIR/non_blocking_errors.rs:8:10 +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` + --> tests/ui/non_blocking_errors.rs:8:10 | 8 | #[br(invalid_keyword_struct_field_a)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` - --> $DIR/non_blocking_errors.rs:10:10 +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` + --> tests/ui/non_blocking_errors.rs:10:10 | 10 | #[br(invalid_keyword_struct_field_b)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/binrw_derive/src/binrw/codegen/read_options/struct.rs b/binrw_derive/src/binrw/codegen/read_options/struct.rs index b3293afc..dfa4571d 100644 --- a/binrw_derive/src/binrw/codegen/read_options/struct.rs +++ b/binrw_derive/src/binrw/codegen/read_options/struct.rs @@ -8,8 +8,8 @@ use crate::{ sanitization::{ make_ident, AFTER_PARSE, ARGS_MACRO, ARGS_TYPE_HINT, BACKTRACE_FRAME, BINREAD_TRAIT, COERCE_FN, DBG_EPRINTLN, MAP_ARGS_TYPE_HINT, OPT, - PARSE_FN_TYPE_HINT, POS, READER, READ_FUNCTION, READ_METHOD, REQUIRED_ARG_TRAIT, - SAVED_POSITION, SEEK_FROM, SEEK_TRAIT, TEMP, WITH_CONTEXT, + PARSE_FN_TYPE_HINT, POS, READER, READ_FROM_TRAIT, READ_FUNCTION, READ_METHOD, + REQUIRED_ARG_TRAIT, SAVED_POSITION, SEEK_FROM, SEEK_TRAIT, TEMP, WITH_CONTEXT, }, }, parser::{ErrContext, FieldMode, Input, Map, Struct, StructField}, @@ -369,6 +369,12 @@ impl<'field> FieldGenerator<'field> { let #READ_FUNCTION = #PARSE_FN_TYPE_HINT(#parser); } } + FieldMode::Converter(converter) => { + let ty = &self.field.ty; + quote_spanned_any! { converter.span()=> + let #READ_FUNCTION = <#ty as #READ_FROM_TRAIT<#converter>>::read_from; + } + } FieldMode::Normal => quote! { let #READ_FUNCTION = #READ_METHOD; }, @@ -390,8 +396,11 @@ impl<'field> FieldGenerator<'field> { let args = get_passed_args(self.field); let ty = &self.field.ty; - if let FieldMode::Function(_) = &self.field.read_mode { - quote_spanned! {ty.span()=> + if matches!( + self.field.read_mode, + FieldMode::Function(_) | FieldMode::Converter(_) + ) { + quote_spanned! { ty.span()=> let #args_var = #ARGS_TYPE_HINT::(#READ_FUNCTION, #args); } } else { @@ -443,11 +452,11 @@ impl<'field> FieldGenerator<'field> { self.out = match &self.field.read_mode { FieldMode::Default => quote! { <_>::default() }, FieldMode::Calc(calc) => quote! { #calc }, - read_mode @ (FieldMode::Normal | FieldMode::Function(_)) => { + read_mode @ (FieldMode::Normal | FieldMode::Function(_) | FieldMode::Converter(_)) => { let args_arg = get_args_argument(self.field, &self.args_var); let endian_var = &self.endian_var; - if let FieldMode::Function(f) = read_mode { + if let FieldMode::Function(f) | FieldMode::Converter(f) = read_mode { let ty = &self.field.ty; // Adding a closure suppresses mentions of the generated // READ_FUNCTION variable in errors; mapping the value with diff --git a/binrw_derive/src/binrw/codegen/sanitization.rs b/binrw_derive/src/binrw/codegen/sanitization.rs index 97fcd167..df3456c3 100644 --- a/binrw_derive/src/binrw/codegen/sanitization.rs +++ b/binrw_derive/src/binrw/codegen/sanitization.rs @@ -33,6 +33,8 @@ ident_str! { pub(crate) READ_METHOD = from_read_trait!(read_options); pub(crate) AFTER_PARSE = from_read_trait!(after_parse); pub(crate) WRITE_METHOD = from_write_trait!(write_options); + pub(crate) READ_FROM_TRAIT = from_crate!(ReadFrom); + pub(crate) WRITE_INTO_TRAIT = from_crate!(WriteInto); pub(crate) READER = "__binrw_generated_var_reader"; pub(crate) WRITER = "__binrw_generated_var_writer"; pub(crate) OPT = "__binrw_generated_var_endian"; diff --git a/binrw_derive/src/binrw/codegen/write_options/struct_field.rs b/binrw_derive/src/binrw/codegen/write_options/struct_field.rs index 116e8046..17e993d6 100644 --- a/binrw_derive/src/binrw/codegen/write_options/struct_field.rs +++ b/binrw_derive/src/binrw/codegen/write_options/struct_field.rs @@ -1,19 +1,23 @@ -use crate::binrw::{ - codegen::{ - get_assertions, get_endian, get_passed_args, - sanitization::{ - make_ident, BEFORE_POS, BINWRITE_TRAIT, SAVED_POSITION, SEEK_FROM, SEEK_TRAIT, WRITER, - WRITE_ARGS_TYPE_HINT, WRITE_FN_MAP_OUTPUT_TYPE_HINT, WRITE_FN_TRY_MAP_OUTPUT_TYPE_HINT, - WRITE_FN_TYPE_HINT, WRITE_FUNCTION, WRITE_MAP_ARGS_TYPE_HINT, - WRITE_MAP_INPUT_TYPE_HINT, WRITE_METHOD, WRITE_TRY_MAP_ARGS_TYPE_HINT, WRITE_ZEROES, +use crate::{ + binrw::{ + codegen::{ + get_assertions, get_endian, get_passed_args, + sanitization::{ + make_ident, BEFORE_POS, BINWRITE_TRAIT, SAVED_POSITION, SEEK_FROM, SEEK_TRAIT, + WRITER, WRITE_ARGS_TYPE_HINT, WRITE_FN_MAP_OUTPUT_TYPE_HINT, + WRITE_FN_TRY_MAP_OUTPUT_TYPE_HINT, WRITE_FN_TYPE_HINT, WRITE_FUNCTION, + WRITE_INTO_TRAIT, WRITE_MAP_ARGS_TYPE_HINT, WRITE_MAP_INPUT_TYPE_HINT, + WRITE_METHOD, WRITE_TRY_MAP_ARGS_TYPE_HINT, WRITE_ZEROES, + }, }, + parser::{FieldMode, Map, StructField}, }, - parser::{FieldMode, Map, StructField}, + util::quote_spanned_any, }; use core::ops::Not; use proc_macro2::TokenStream; use quote::quote; -use syn::Ident; +use syn::{spanned::Spanned, Ident}; pub(crate) fn write_field(field: &StructField) -> TokenStream { StructFieldGenerator::new(field) @@ -61,6 +65,12 @@ impl<'a> StructFieldGenerator<'a> { let write_fn = match &self.field.read_mode { FieldMode::Normal | FieldMode::Calc(_) => quote! { #WRITE_METHOD }, FieldMode::Function(write_fn) => write_fn.clone(), + FieldMode::Converter(converter) => { + let ty = &self.field.ty; + quote_spanned_any! {converter.span()=> + <#ty as #WRITE_INTO_TRAIT<#converter>>::write_into + } + } FieldMode::Default => unreachable!("Ignored fields are not written"), }; @@ -230,7 +240,7 @@ impl<'a> StructFieldGenerator<'a> { let #args = (); #out }, - FieldMode::Function(_) => { + FieldMode::Function(_) | FieldMode::Converter(_) => { let ty = &self.field.ty; quote! { let #args = #WRITE_ARGS_TYPE_HINT::<#ty, W, _, _>( diff --git a/binrw_derive/src/binrw/parser/attrs.rs b/binrw_derive/src/binrw/parser/attrs.rs index 5c9ac040..a4d0b9b1 100644 --- a/binrw_derive/src/binrw/parser/attrs.rs +++ b/binrw_derive/src/binrw/parser/attrs.rs @@ -43,4 +43,5 @@ pub(super) type SeekBefore = MetaExpr; pub(super) type Temp = MetaVoid; pub(super) type Try = MetaVoid; pub(super) type TryMap = MetaExpr; +pub(super) type With = MetaType; pub(super) type WriteWith = MetaExpr; diff --git a/binrw_derive/src/binrw/parser/field_level_attrs.rs b/binrw_derive/src/binrw/parser/field_level_attrs.rs index c6041aa4..5418b750 100644 --- a/binrw_derive/src/binrw/parser/field_level_attrs.rs +++ b/binrw_derive/src/binrw/parser/field_level_attrs.rs @@ -24,7 +24,7 @@ attr_struct! { pub(crate) magic: Magic, #[from(RW:Args, RW:ArgsRaw)] pub(crate) args: PassedArgs, - #[from(RW:Calc, RO:Default, RW:Ignore, RO:ParseWith, WO:WriteWith)] + #[from(RW:Calc, RO:Default, RW:Ignore, RW:With, RO:ParseWith, WO:WriteWith)] pub(crate) read_mode: FieldMode, #[from(RO:Count)] pub(crate) count: Option, diff --git a/binrw_derive/src/binrw/parser/keywords.rs b/binrw_derive/src/binrw/parser/keywords.rs index f8806db9..c0fd0c5e 100644 --- a/binrw_derive/src/binrw/parser/keywords.rs +++ b/binrw_derive/src/binrw/parser/keywords.rs @@ -47,5 +47,6 @@ define_keywords! { seek_before, temp, try_map, + with, write_with, } diff --git a/binrw_derive/src/binrw/parser/types/field_mode.rs b/binrw_derive/src/binrw/parser/types/field_mode.rs index bc6c65e0..f6ede0db 100644 --- a/binrw_derive/src/binrw/parser/types/field_mode.rs +++ b/binrw_derive/src/binrw/parser/types/field_mode.rs @@ -11,6 +11,7 @@ pub(crate) enum FieldMode { Default, Calc(TokenStream), Function(TokenStream), + Converter(TokenStream), } impl Default for FieldMode { @@ -49,6 +50,12 @@ impl From for FieldMode { } } +impl From for FieldMode { + fn from(with: attrs::With) -> Self { + Self::Converter(with.into_token_stream()) + } +} + impl + KeywordToken> TrySet for T { fn try_set(self, to: &mut FieldMode) -> syn::Result<()> { if matches!(*to, FieldMode::Normal) {