From 98a1fb63485dfbbe74d5112a58b61741de4aa4b3 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 6 Nov 2022 23:24:08 -0600 Subject: [PATCH] Add directives for accessing and remapping streams Closes jam1garner/binrw#121. --- binrw/doc/attribute.md | 268 ++++++++++++++++++ binrw/src/private.rs | 18 ++ binrw/tests/derive/struct.rs | 97 +++++++ binrw/tests/derive/write/map_stream.rs | 70 +++++ binrw/tests/derive/write/mod.rs | 2 + binrw/tests/derive/write/stream.rs | 59 ++++ binrw/tests/ui/invalid_keyword_enum.stderr | 4 +- .../ui/invalid_keyword_enum_variant.stderr | 4 +- binrw/tests/ui/invalid_keyword_struct.stderr | 4 +- .../ui/invalid_keyword_struct_field.stderr | 2 +- .../tests/ui/invalid_keyword_unit_enum.stderr | 2 +- .../ui/invalid_keyword_with_imports.stderr | 4 +- binrw/tests/ui/non_blocking_errors.stderr | 6 +- .../src/binrw/codegen/read_options.rs | 54 +++- .../src/binrw/codegen/read_options/enum.rs | 22 +- .../src/binrw/codegen/read_options/map.rs | 6 +- .../src/binrw/codegen/read_options/struct.rs | 152 +++++++--- .../src/binrw/codegen/sanitization.rs | 2 + .../src/binrw/codegen/write_options.rs | 10 +- .../src/binrw/codegen/write_options/enum.rs | 47 ++- .../binrw/codegen/write_options/prelude.rs | 48 +++- .../src/binrw/codegen/write_options/struct.rs | 16 +- .../codegen/write_options/struct_field.rs | 101 ++++--- binrw_derive/src/binrw/parser/attrs.rs | 6 +- .../src/binrw/parser/field_level_attrs.rs | 3 + binrw_derive/src/binrw/parser/keywords.rs | 2 + .../src/binrw/parser/top_level_attrs.rs | 36 ++- binrw_derive/src/meta_types.rs | 15 +- 28 files changed, 908 insertions(+), 152 deletions(-) create mode 100644 binrw/tests/derive/write/map_stream.rs create mode 100644 binrw/tests/derive/write/stream.rs diff --git a/binrw/doc/attribute.md b/binrw/doc/attribute.md index 46b77c4e..f60f7d67 100644 --- a/binrw/doc/attribute.md +++ b/binrw/doc/attribute.md @@ -92,6 +92,7 @@ Glossary of directives in binrw attributes (`#[br]`, `#[bw]`, `#[brw]`). | rw | [`little`](#byte-order) | all except unit variant | Sets the byte order to little-endian. | rw | [`magic`](#magic) | all | MatchesWrites a magic number. | rw | [`map`](#map) | all except unit variant | Maps an object or value to a new value. +| rw | [`map_stream`](#stream-access-and-manipulation) | all except unit variant | Maps the readwrite stream to a new stream. | r | [`offset`](#offset) | field | Modifies the offset used by a [`FilePtr`](crate::FilePtr) while parsing. | r | [`offset_after`](#offset) | field | Modifies the offset used by a [`FilePtr`](crate::FilePtr) after parsing. | rw | [`pad_after`](#padding-and-alignment) | field | Skips N bytes after readingwriting a field. @@ -105,6 +106,7 @@ Glossary of directives in binrw attributes (`#[br]`, `#[bw]`, `#[brw]`). | r | [`return_all_errors`](#enum-errors) | non-unit enum | Returns a [`Vec`] containing the error which occurred on each variant of an enum on failure. This is the default. | r | [`return_unexpected_error`](#enum-errors) | non-unit enum | Returns a single generic error on failure. | rw | [`seek_before`](#padding-and-alignment) | field | Moves the readerwriter to a specific position before readingwriting data. +| rw | [`stream`](#stream-access-and-manipulation) | struct, non-unit enum, unit-like enum | Exposes the underlying readwrite stream. | r | [`temp`](#temp) | field | Uses a field as a temporary variable. Only usable with the [`binread`](macro@crate::binread) attribute macro. | r | [`try`](#try) | field | Tries to parse and stores the [`default`](core::default::Default) value for the type if parsing fails instead of returning an error. | rw | [`try_calc`](#calculations) | field | Like `calc`, but returns a [`Result`](Result). @@ -2223,6 +2225,272 @@ position is reset to where it was before parsingserialisation started. +# Stream access and manipulation + +The `stream` directive allows direct access to the underlying +readwrite stream on a struct or +enum: + +
+ +```text +#[br(stream = $ident:ident)] or #[br(stream($ident:ident))] +``` +
+
+ +```text +#[bw(stream = $ident:ident)] or #[bw(stream($ident:ident))] +``` +
+ +The `map_stream` directive allows the readwrite +stream to be replaced with another stream when readingwriting +an object or field: + +
+ +```text +#[br(map_stream = $map_fn:expr)] or #[br(map_stream($map_fn:expr))] +``` +
+
+ +```text +#[bw(map_stream = $map_fn:expr)] or #[bw(map_stream($map_fn:expr))] +``` +
+ +The map function can be a plain function, closure, or call expression which +returns a plain function or closure. The returned object must implement +[`Read`](crate::io::Read) + [`Seek`](crate::io::Seek). + +## Examples + +
+ +### Verifying a checksum + +``` +# use core::num::Wrapping; +# use binrw::{binread, BinRead, io::{Cursor, Read, Seek, SeekFrom}}; +# +# struct Checksum { +# inner: T, +# check: Wrapping, +# } +# +# impl Checksum { +# fn new(inner: T) -> Self { +# Self { +# inner, +# check: Wrapping(0), +# } +# } +# +# fn check(&self) -> u8 { +# self.check.0 +# } +# } +# +# impl Read for Checksum { +# fn read(&mut self, buf: &mut [u8]) -> binrw::io::Result { +# let size = self.inner.read(buf)?; +# for b in &buf[0..size] { +# self.check += b; +# } +# Ok(size) +# } +# } +# +# impl Seek for Checksum { +# fn seek(&mut self, pos: SeekFrom) -> binrw::io::Result { +# self.inner.seek(pos) +# } +# } +# +#[binread] +# #[derive(Debug, PartialEq)] +#[br(little, stream = r, map_stream = Checksum::new)] +struct Test { + a: u16, + b: u16, + #[br(temp, assert(c == r.check() - c, "bad checksum: {:#x?} != {:#x?}", c, r.check() - c))] + c: u8, +} + +assert_eq!( + Test::read(&mut Cursor::new(b"\x01\x02\x03\x04\x0a")).unwrap(), + Test { + a: 0x201, + b: 0x403, + } +); +``` + +### Reading encrypted blocks + +``` +# use binrw::{binread, BinRead, io::{Cursor, Read, Seek, SeekFrom}, helpers::until_eof}; +# +# struct BadCrypt { +# inner: T, +# key: u8, +# } +# +# impl BadCrypt { +# fn new(inner: T, key: u8) -> Self { +# Self { inner, key } +# } +# } +# +# impl Read for BadCrypt { +# fn read(&mut self, buf: &mut [u8]) -> binrw::io::Result { +# let size = self.inner.read(buf)?; +# for b in &mut buf[0..size] { +# *b ^= core::mem::replace(&mut self.key, *b); +# } +# Ok(size) +# } +# } +# +# impl Seek for BadCrypt { +# fn seek(&mut self, pos: SeekFrom) -> binrw::io::Result { +# self.inner.seek(pos) +# } +# } +# +#[binread] +# #[derive(Debug, PartialEq)] +#[br(little)] +struct Test { + iv: u8, + #[br(parse_with = until_eof, map_stream = |reader| BadCrypt::new(reader, iv))] + data: Vec, +} + +assert_eq!( + Test::read(&mut Cursor::new(b"\x01\x03\0\x04\x01")).unwrap(), + Test { + iv: 1, + data: vec![2, 3, 4, 5], + } +); +``` + +
+ +
+ +### Writing a checksum + +``` +# use core::num::Wrapping; +# use binrw::{binwrite, BinWrite, io::{Cursor, Write, Seek, SeekFrom}}; +# struct Checksum { +# inner: T, +# check: Wrapping, +# } +# +# impl Checksum { +# fn new(inner: T) -> Self { +# Self { +# inner, +# check: Wrapping(0), +# } +# } +# +# fn check(&self) -> u8 { +# self.check.0 +# } +# } +# +# impl Write for Checksum { +# fn write(&mut self, buf: &[u8]) -> binrw::io::Result { +# for b in buf { +# self.check += b; +# } +# self.inner.write(buf) +# } +# +# fn flush(&mut self) -> binrw::io::Result<()> { +# self.inner.flush() +# } +# } +# +# impl Seek for Checksum { +# fn seek(&mut self, pos: SeekFrom) -> binrw::io::Result { +# self.inner.seek(pos) +# } +# } +# +#[binwrite] +#[bw(little, stream = w, map_stream = Checksum::new)] +struct Test { + a: u16, + b: u16, + #[bw(calc(w.check()))] + c: u8, +} + +let mut out = Cursor::new(Vec::new()); +Test { a: 0x201, b: 0x403 }.write(&mut out).unwrap(); + +assert_eq!(out.into_inner(), b"\x01\x02\x03\x04\x0a"); +``` + +### Writing encrypted blocks + +``` +# use binrw::{binwrite, BinWrite, io::{Cursor, Write, Seek, SeekFrom}}; +# +# struct BadCrypt { +# inner: T, +# key: u8, +# } +# +# impl BadCrypt { +# fn new(inner: T, key: u8) -> Self { +# Self { inner, key } +# } +# } +# +# impl Write for BadCrypt { +# fn write(&mut self, buf: &[u8]) -> binrw::io::Result { +# let mut w = 0; +# for b in buf { +# self.key ^= b; +# w += self.inner.write(&[self.key])?; +# } +# Ok(w) +# } +# +# fn flush(&mut self) -> binrw::io::Result<()> { +# self.inner.flush() +# } +# } +# +# impl Seek for BadCrypt { +# fn seek(&mut self, pos: SeekFrom) -> binrw::io::Result { +# self.inner.seek(pos) +# } +# } +# +#[binwrite] +# #[derive(Debug, PartialEq)] +#[bw(little)] +struct Test { + iv: u8, + #[bw(map_stream = |writer| BadCrypt::new(writer, *iv))] + data: Vec, +} + +let mut out = Cursor::new(Vec::new()); +Test { iv: 1, data: vec![2, 3, 4, 5] }.write(&mut out).unwrap(); +assert_eq!(out.into_inner(), b"\x01\x03\0\x04\x01"); +``` +
+
# Temp diff --git a/binrw/src/private.rs b/binrw/src/private.rs index e0c50fe2..62aa0cb3 100644 --- a/binrw/src/private.rs +++ b/binrw/src/private.rs @@ -128,6 +128,24 @@ where args } +pub fn map_reader_type_hint<'a, Reader, MapFn, Output>(x: MapFn) -> MapFn +where + Reader: Read + Seek + 'a, + MapFn: Fn(&'a mut Reader) -> Output, + Output: Read + Seek + 'a, +{ + x +} + +pub fn map_writer_type_hint<'a, Writer, MapFn, Output>(x: MapFn) -> MapFn +where + Writer: Write + Seek + 'a, + MapFn: Fn(&'a mut Writer) -> Output, + Output: Write + Seek + 'a, +{ + x +} + pub fn write_fn_type_hint(x: WriterFn) -> WriterFn where Writer: Write + Seek, diff --git a/binrw/tests/derive/struct.rs b/binrw/tests/derive/struct.rs index 7d8fbd59..f3a3b4b3 100644 --- a/binrw/tests/derive/struct.rs +++ b/binrw/tests/derive/struct.rs @@ -360,6 +360,48 @@ fn magic_const() { assert_eq!(Test::MAGIC, b'a'); } +#[test] +fn map_stream() { + use binrw::io::TakeSeekExt; + + #[derive(BinRead, Debug, PartialEq)] + #[br(map_stream = |reader| reader.take_seek(4))] + struct Test { + #[br(parse_with = binrw::helpers::until_eof)] + a: Vec, + } + + assert_eq!( + Test::read_le(&mut Cursor::new(b"hello world")).unwrap(), + Test { + a: b"hell".to_vec() + } + ); +} + +#[test] +fn map_stream_field() { + use binrw::io::TakeSeekExt; + + #[derive(BinRead, Debug, PartialEq)] + struct Test { + #[br(map_stream = |reader| reader.take_seek(5), parse_with = binrw::helpers::until_eof)] + a: Vec, + b: u8, + #[br(map_stream = |reader| reader.take_seek(5), parse_with = binrw::helpers::until_eof)] + c: Vec, + } + + assert_eq!( + Test::read_le(&mut Cursor::new(b"hello world")).unwrap(), + Test { + a: b"hello".to_vec(), + b: b' ', + c: b"world".to_vec(), + } + ); +} + #[test] fn named_args_trailing_commas() { #[rustfmt::skip] @@ -551,6 +593,61 @@ fn raw_ident() { Test::read_le(&mut Cursor::new(vec![0x00, 0x00, 0x00, 0x00])).unwrap(); } +#[test] +fn reader_var() { + struct Checksum { + inner: T, + check: core::num::Wrapping, + } + + impl Checksum { + fn new(inner: T) -> Self { + Self { + inner, + check: core::num::Wrapping(0), + } + } + + fn check(&self) -> u8 { + self.check.0 + } + } + + impl binrw::io::Read for Checksum { + fn read(&mut self, buf: &mut [u8]) -> binrw::io::Result { + let size = self.inner.read(buf)?; + for b in &buf[0..size] { + self.check += b; + } + Ok(size) + } + } + + impl Seek for Checksum { + fn seek(&mut self, pos: SeekFrom) -> binrw::io::Result { + self.inner.seek(pos) + } + } + + #[derive(BinRead, Debug, PartialEq)] + #[br(little, stream = r, map_stream = Checksum::new)] + struct Test { + a: u16, + b: u16, + #[br(calc(r.check()))] + c: u8, + } + + assert_eq!( + Test::read(&mut Cursor::new(b"\x01\x02\x03\x04")).unwrap(), + Test { + a: 0x201, + b: 0x403, + c: 10, + } + ); +} + #[test] fn rewind_on_assert() { #[allow(dead_code)] diff --git a/binrw/tests/derive/write/map_stream.rs b/binrw/tests/derive/write/map_stream.rs new file mode 100644 index 00000000..128fb0f8 --- /dev/null +++ b/binrw/tests/derive/write/map_stream.rs @@ -0,0 +1,70 @@ +use binrw::{ + io::{Cursor, Seek, SeekFrom, Write}, + BinWrite, +}; + +struct BadCrypt { + inner: T, + key: u8, +} + +impl BadCrypt { + fn new(inner: T) -> Self { + Self { inner, key: 0 } + } +} + +impl Write for BadCrypt { + fn write(&mut self, buf: &[u8]) -> binrw::io::Result { + let mut w = 0; + for b in buf { + self.key ^= b; + w += self.inner.write(&[self.key])?; + } + Ok(w) + } + + fn flush(&mut self) -> binrw::io::Result<()> { + self.inner.flush() + } +} + +impl Seek for BadCrypt { + fn seek(&mut self, pos: SeekFrom) -> binrw::io::Result { + self.inner.seek(pos) + } +} + +#[test] +fn map_stream() { + #[derive(BinWrite, Debug, PartialEq)] + #[bw(big, map_stream = |inner| BadCrypt { inner, key: 0x80 })] + struct Test(Vec); + + let mut out = Cursor::new(vec![]); + Test(vec![0, 1, 2, 3]).write(&mut out).unwrap(); + + assert_eq!(out.into_inner(), &[0x80, 0x81, 0x83, 0x80],); +} + +#[test] +fn map_stream_field() { + #[derive(BinWrite, Debug, PartialEq)] + #[bw(big)] + struct Test { + #[bw(map_stream = BadCrypt::new)] + a: Vec, + #[bw(map_stream = |inner| BadCrypt { inner, key: 0x80 })] + b: Vec, + } + + let mut out = Cursor::new(vec![]); + Test { + a: vec![0, 1, 2, 3], + b: vec![4, 5, 6, 7], + } + .write(&mut out) + .unwrap(); + + assert_eq!(out.into_inner(), &[0, 1, 3, 0, 132, 129, 135, 128],); +} diff --git a/binrw/tests/derive/write/mod.rs b/binrw/tests/derive/write/mod.rs index acb4e86e..e8adea3a 100644 --- a/binrw/tests/derive/write/mod.rs +++ b/binrw/tests/derive/write/mod.rs @@ -11,7 +11,9 @@ mod ignore; mod import; mod magic; mod map; +mod map_stream; mod padding; mod restore_position; mod simple; +mod stream; mod top_level_map; diff --git a/binrw/tests/derive/write/stream.rs b/binrw/tests/derive/write/stream.rs new file mode 100644 index 00000000..8b6f5332 --- /dev/null +++ b/binrw/tests/derive/write/stream.rs @@ -0,0 +1,59 @@ +use binrw::{ + binwrite, + io::{Cursor, Seek, SeekFrom, Write}, + BinWrite, +}; + +#[test] +fn writer_var() { + struct Checksum { + inner: T, + check: core::num::Wrapping, + } + + impl Checksum { + fn new(inner: T) -> Self { + Self { + inner, + check: core::num::Wrapping(0), + } + } + + fn check(&self) -> u8 { + self.check.0 + } + } + + impl Write for Checksum { + fn write(&mut self, buf: &[u8]) -> binrw::io::Result { + for b in buf { + self.check += b; + } + self.inner.write(buf) + } + + fn flush(&mut self) -> binrw::io::Result<()> { + self.inner.flush() + } + } + + impl Seek for Checksum { + fn seek(&mut self, pos: SeekFrom) -> binrw::io::Result { + self.inner.seek(pos) + } + } + + #[binwrite] + #[bw(little, stream = w, map_stream = Checksum::new)] + struct Test { + a: u16, + b: u16, + #[bw(calc(w.check()))] + c: u8, + } + + let mut out = Cursor::new(vec![]); + Test { a: 0x201, b: 0x403 }.write(&mut out).unwrap(); + + assert_eq!(out.into_inner(), b"\x01\x02\x03\x04\x0a"); +} diff --git a/binrw/tests/ui/invalid_keyword_enum.stderr b/binrw/tests/ui/invalid_keyword_enum.stderr index 447f66e9..77b20d67 100644 --- a/binrw/tests/ui/invalid_keyword_enum.stderr +++ b/binrw/tests/ui/invalid_keyword_enum.stderr @@ -1,5 +1,5 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw`, `assert`, `pre_assert`, `return_all_errors`, `return_unexpected_error` - --> $DIR/invalid_keyword_enum.rs:4:6 +error: expected one of: `stream`, `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `import`, `import_raw`, `assert`, `pre_assert`, `return_all_errors`, `return_unexpected_error` + --> tests/ui/invalid_keyword_enum.rs:4:6 | 4 | #[br(invalid_enum_keyword)] | ^^^^^^^^^^^^^^^^^^^^ diff --git a/binrw/tests/ui/invalid_keyword_enum_variant.stderr b/binrw/tests/ui/invalid_keyword_enum_variant.stderr index 7c5fdf3c..94086714 100644 --- a/binrw/tests/ui/invalid_keyword_enum_variant.stderr +++ b/binrw/tests/ui/invalid_keyword_enum_variant.stderr @@ -1,5 +1,5 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` - --> $DIR/invalid_keyword_enum_variant.rs:5:10 +error: expected one of: `stream`, `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` + --> tests/ui/invalid_keyword_enum_variant.rs:5:10 | 5 | #[br(invalid_enum_variant_keyword)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/binrw/tests/ui/invalid_keyword_struct.stderr b/binrw/tests/ui/invalid_keyword_struct.stderr index 52f4d0ac..de1346ed 100644 --- a/binrw/tests/ui/invalid_keyword_struct.stderr +++ b/binrw/tests/ui/invalid_keyword_struct.stderr @@ -1,5 +1,5 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` - --> $DIR/invalid_keyword_struct.rs:4:6 +error: expected one of: `stream`, `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` + --> tests/ui/invalid_keyword_struct.rs:4:6 | 4 | #[br(invalid_struct_keyword)] | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/binrw/tests/ui/invalid_keyword_struct_field.stderr b/binrw/tests/ui/invalid_keyword_struct_field.stderr index c97aa444..db4c9fbe 100644 --- a/binrw/tests/ui/invalid_keyword_struct_field.stderr +++ b/binrw/tests/ui/invalid_keyword_struct_field.stderr @@ -1,4 +1,4 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `try_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` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `args`, `args_raw`, `calc`, `try_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` --> tests/ui/invalid_keyword_struct_field.rs:5:10 | 5 | #[br(invalid_struct_field_keyword)] diff --git a/binrw/tests/ui/invalid_keyword_unit_enum.stderr b/binrw/tests/ui/invalid_keyword_unit_enum.stderr index 22b0d2bc..6c4a87f9 100644 --- a/binrw/tests/ui/invalid_keyword_unit_enum.stderr +++ b/binrw/tests/ui/invalid_keyword_unit_enum.stderr @@ -1,4 +1,4 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw` +error: expected one of: `stream`, `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `import`, `import_raw` --> tests/ui/invalid_keyword_unit_enum.rs:4:6 | 4 | #[br(invalid_unit_enum_keyword)] diff --git a/binrw/tests/ui/invalid_keyword_with_imports.stderr b/binrw/tests/ui/invalid_keyword_with_imports.stderr index d2594dc1..ca2d1f85 100644 --- a/binrw/tests/ui/invalid_keyword_with_imports.stderr +++ b/binrw/tests/ui/invalid_keyword_with_imports.stderr @@ -1,5 +1,5 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` - --> $DIR/invalid_keyword_with_imports.rs:5:6 +error: expected one of: `stream`, `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` + --> tests/ui/invalid_keyword_with_imports.rs:5:6 | 5 | #[br(invalid_struct_keyword)] | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/binrw/tests/ui/non_blocking_errors.stderr b/binrw/tests/ui/non_blocking_errors.stderr index 293402c5..0321efa4 100644 --- a/binrw/tests/ui/non_blocking_errors.stderr +++ b/binrw/tests/ui/non_blocking_errors.stderr @@ -1,16 +1,16 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` +error: expected one of: `stream`, `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` --> 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`, `try_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` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `args`, `args_raw`, `calc`, `try_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` --> 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`, `try_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` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `map_stream`, `magic`, `args`, `args_raw`, `calc`, `try_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` --> 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.rs b/binrw_derive/src/binrw/codegen/read_options.rs index 239f8153..a095e84d 100644 --- a/binrw_derive/src/binrw/codegen/read_options.rs +++ b/binrw_derive/src/binrw/codegen/read_options.rs @@ -3,18 +3,23 @@ mod map; mod r#struct; use super::{get_assertions, get_destructured_imports}; -use crate::binrw::{ - codegen::{ - get_endian, - sanitization::{ARGS, ASSERT_MAGIC, OPT, POS, READER, SEEK_FROM, SEEK_TRAIT}, +use crate::{ + binrw::{ + codegen::{ + get_endian, + sanitization::{ + ARGS, ASSERT_MAGIC, MAP_READER_TYPE_HINT, OPT, POS, READER, SEEK_FROM, SEEK_TRAIT, + }, + }, + parser::{Input, Magic, Map}, }, - parser::{Input, Magic, Map}, + util::quote_spanned_any, }; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use r#enum::{generate_data_enum, generate_unit_enum}; use r#struct::{generate_struct, generate_unit_struct}; -use syn::Ident; +use syn::{spanned::Spanned, Ident}; pub(crate) fn generate(input: &Input, derive_input: &syn::DeriveInput) -> TokenStream { let name = Some(&derive_input.ident); @@ -37,12 +42,15 @@ pub(crate) fn generate(input: &Input, derive_input: &syn::DeriveInput) -> TokenS }, }; + let reader_var = input.stream_ident_or(READER); + quote! { - let #POS = #SEEK_TRAIT::stream_position(#READER)?; + let #reader_var = #READER; + let #POS = #SEEK_TRAIT::stream_position(#reader_var)?; (|| { #inner })().or_else(|error| { - #SEEK_TRAIT::seek(#READER, #SEEK_FROM::Start(#POS))?; + #SEEK_TRAIT::seek(#reader_var, #SEEK_FROM::Start(#POS))?; Err(error) }) } @@ -50,13 +58,16 @@ pub(crate) fn generate(input: &Input, derive_input: &syn::DeriveInput) -> TokenS struct PreludeGenerator<'input> { input: &'input Input, + reader_var: TokenStream, out: TokenStream, } impl<'input> PreludeGenerator<'input> { fn new(input: &'input Input) -> Self { + let reader_var = input.stream_ident_or(READER); Self { input, + reader_var, out: TokenStream::new(), } } @@ -89,7 +100,7 @@ impl<'input> PreludeGenerator<'input> { fn add_magic_pre_assertion(mut self) -> Self { let head = self.out; - let magic = get_magic(self.input.magic(), OPT); + let magic = get_magic(self.input.magic(), &self.reader_var, OPT); let pre_assertions = get_assertions(self.input.pre_assertions()); self.out = quote! { #head @@ -100,12 +111,27 @@ impl<'input> PreludeGenerator<'input> { self } + fn add_map_stream(mut self) -> Self { + if let Some(map_stream) = self.input.map_stream() { + let outer_reader = self.input.stream_ident_or(READER); + let inner_reader = &self.reader_var; + let tail = self.out; + self.out = quote_spanned_any! { map_stream.span()=> + let #inner_reader = &mut #MAP_READER_TYPE_HINT::(#map_stream)(#outer_reader); + #tail + } + } + + self + } + fn reset_position_after_magic(mut self) -> Self { if self.input.magic().is_some() { + let reader_var = &self.reader_var; let head = self.out; self.out = quote! { #head - let #POS = #SEEK_TRAIT::stream_position(#READER)?; + let #POS = #SEEK_TRAIT::stream_position(#reader_var)?; }; }; @@ -113,11 +139,15 @@ impl<'input> PreludeGenerator<'input> { } } -fn get_magic(magic: &Magic, endian_var: impl ToTokens) -> Option { +fn get_magic( + magic: &Magic, + reader_var: impl ToTokens, + endian_var: impl ToTokens, +) -> Option { magic.as_ref().map(|magic| { let magic = magic.deref_value(); quote! { - #ASSERT_MAGIC(#READER, #magic, #endian_var)?; + #ASSERT_MAGIC(#reader_var, #magic, #endian_var)?; } }) } diff --git a/binrw_derive/src/binrw/codegen/read_options/enum.rs b/binrw_derive/src/binrw/codegen/read_options/enum.rs index 77f6d54e..266dc5ee 100644 --- a/binrw_derive/src/binrw/codegen/read_options/enum.rs +++ b/binrw_derive/src/binrw/codegen/read_options/enum.rs @@ -28,8 +28,8 @@ pub(super) fn generate_unit_enum( .finish(); let read = match en.map.as_repr() { - Some(repr) => generate_unit_enum_repr(repr, &en.fields), - None => generate_unit_enum_magic(&en.fields), + Some(repr) => generate_unit_enum_repr(&input.stream_ident_or(READER), repr, &en.fields), + None => generate_unit_enum_magic(&input.stream_ident_or(READER), &en.fields), }; quote! { @@ -38,7 +38,11 @@ pub(super) fn generate_unit_enum( } } -fn generate_unit_enum_repr(repr: &TokenStream, variants: &[UnitEnumField]) -> TokenStream { +fn generate_unit_enum_repr( + reader_var: &TokenStream, + repr: &TokenStream, + variants: &[UnitEnumField], +) -> TokenStream { let clauses = variants.iter().map(|variant| { let ident = &variant.ident; let pre_assertions = variant @@ -54,7 +58,7 @@ fn generate_unit_enum_repr(repr: &TokenStream, variants: &[UnitEnumField]) -> To }); quote! { - let #TEMP: #repr = #READ_METHOD(#READER, #OPT, ())?; + let #TEMP: #repr = #READ_METHOD(#reader_var, #OPT, ())?; #(#clauses else)* { Err(#WITH_CONTEXT( #BIN_ERROR::NoVariantMatch { @@ -69,7 +73,7 @@ fn generate_unit_enum_repr(repr: &TokenStream, variants: &[UnitEnumField]) -> To } } -fn generate_unit_enum_magic(variants: &[UnitEnumField]) -> TokenStream { +fn generate_unit_enum_magic(reader_var: &TokenStream, variants: &[UnitEnumField]) -> TokenStream { // group fields by the type (Kind) of their magic value, preserve the order let group_by_magic_type = variants.iter().fold( Vec::new(), @@ -117,7 +121,7 @@ fn generate_unit_enum_magic(variants: &[UnitEnumField]) -> TokenStream { }); let body = quote! { - match #amp #READ_METHOD(#READER, #OPT, ())? { + match #amp #READ_METHOD(#reader_var, #OPT, ())? { #(#matches,)* _ => Err(#BIN_ERROR::NoVariantMatch { pos: #POS }) } @@ -132,7 +136,7 @@ fn generate_unit_enum_magic(variants: &[UnitEnumField]) -> TokenStream { return #TEMP; } - #SEEK_TRAIT::seek(#READER, #SEEK_FROM::Start(#POS))?; + #SEEK_TRAIT::seek(#reader_var, #SEEK_FROM::Start(#POS))?; } }); @@ -182,6 +186,8 @@ pub(super) fn generate_data_enum(input: &Input, name: Option<&Ident>, en: &Enum) .reset_position_after_magic() .finish(); + let reader_var = input.stream_ident_or(READER); + let try_each_variant = en.variants.iter().map(|variant| { let body = generate_variant_impl(en, variant); @@ -203,7 +209,7 @@ pub(super) fn generate_data_enum(input: &Input, name: Option<&Ident>, en: &Enum) return #TEMP; } else { #handle_error - #SEEK_TRAIT::seek(#READER, #SEEK_FROM::Start(#POS))?; + #SEEK_TRAIT::seek(#reader_var, #SEEK_FROM::Start(#POS))?; } } }); diff --git a/binrw_derive/src/binrw/codegen/read_options/map.rs b/binrw_derive/src/binrw/codegen/read_options/map.rs index e784b8ee..49e763c4 100644 --- a/binrw_derive/src/binrw/codegen/read_options/map.rs +++ b/binrw_derive/src/binrw/codegen/read_options/map.rs @@ -19,13 +19,14 @@ pub(crate) fn generate_map(input: &Input, name: Option<&Ident>, map: &TokenStrea let destructure_ref = destructure_ref(input); let assertions = field_asserts(input).chain(get_assertions(input.assertions())); + let reader_var = input.stream_ident_or(READER); // TODO: replace args with top-level arguments and only // use `()` as a default quote! { #prelude - #READ_METHOD(#READER, #OPT, ()) + #READ_METHOD(#reader_var, #OPT, ()) .map(#map) .and_then(|this| { #destructure_ref @@ -55,13 +56,14 @@ pub(crate) fn generate_try_map( let destructure_ref = destructure_ref(input); let assertions = field_asserts(input).chain(get_assertions(input.assertions())); + let reader_var = input.stream_ident_or(READER); // TODO: replace args with top-level arguments and only // use `()` as a default quote! { #prelude - #READ_METHOD(#READER, #OPT, #ARGS).and_then(|value| { + #READ_METHOD(#reader_var, #OPT, #ARGS).and_then(|value| { (#map)(value)#map_err }) .and_then(|this| { diff --git a/binrw_derive/src/binrw/codegen/read_options/struct.rs b/binrw_derive/src/binrw/codegen/read_options/struct.rs index 85f241a0..686b40f8 100644 --- a/binrw_derive/src/binrw/codegen/read_options/struct.rs +++ b/binrw_derive/src/binrw/codegen/read_options/struct.rs @@ -7,9 +7,9 @@ use crate::{ get_assertions, get_endian, get_map_err, get_passed_args, get_try_calc, 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, + BINREAD_TRAIT, COERCE_FN, DBG_EPRINTLN, MAP_ARGS_TYPE_HINT, MAP_READER_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, }, }, parser::{ErrContext, FieldMode, Input, Map, Struct, StructField}, @@ -81,10 +81,17 @@ impl<'input> StructGenerator<'input> { .st .fields .iter() - .map(|field| generate_field(field, name, variant_name)); + .map(|field| generate_field(self.input, field, name, variant_name)); let after_parse = { - let after_parse = self.st.fields.iter().map(generate_after_parse); - wrap_save_restore(quote!(#(#after_parse)*)) + let after_parse = self + .st + .fields + .iter() + .map(|field| generate_after_parse(self.input, field)); + wrap_save_restore( + &self.input.stream_ident_or(READER), + quote!(#(#after_parse)*), + ) }; self.out = quote! { #prelude @@ -114,13 +121,13 @@ impl<'input> StructGenerator<'input> { } } -fn generate_after_parse(field: &StructField) -> Option { +fn generate_after_parse(input: &Input, field: &StructField) -> Option { if field.deref_now.is_none() { get_after_parse_handler(field).map(|after_parse_fn| { - let (args_var, endian_var) = make_field_vars(field); + let (reader_var, endian_var, args_var) = make_field_vars(input, field); AfterParseCallGenerator::new(field) .get_value_from_ident() - .call_after_parse(after_parse_fn, &endian_var, &args_var) + .call_after_parse(after_parse_fn, &reader_var, &endian_var, &args_var) .finish() }) } else { @@ -129,6 +136,7 @@ fn generate_after_parse(field: &StructField) -> Option { } fn generate_field( + input: &Input, field: &StructField, name: Option<&Ident>, variant_name: Option<&str>, @@ -138,7 +146,7 @@ fn generate_field( return TokenStream::new(); } - FieldGenerator::new(field) + FieldGenerator::new(input, field) .read_value() .try_conversion(name, variant_name) .map_value() @@ -153,6 +161,7 @@ fn generate_field( .prefix_args_and_options() .prefix_map_function() .prefix_read_function() + .prefix_map_stream() .finish() } @@ -172,6 +181,7 @@ impl<'field> AfterParseCallGenerator<'field> { fn call_after_parse( mut self, after_parse_fn: IdentStr, + reader_var: &TokenStream, endian_var: &TokenStream, args_var: &Option, ) -> Self { @@ -194,7 +204,7 @@ impl<'field> AfterParseCallGenerator<'field> { }; self.out = quote! { - #after_parse_fn(#value, #READER, #endian_var, #args_arg)?; + #after_parse_fn(#value, #reader_var, #endian_var, #args_arg)?; }; self @@ -221,25 +231,30 @@ impl<'field> AfterParseCallGenerator<'field> { struct FieldGenerator<'field> { field: &'field StructField, out: TokenStream, - args_var: Option, + outer_reader_var: TokenStream, + reader_var: TokenStream, endian_var: TokenStream, + args_var: Option, } impl<'field> FieldGenerator<'field> { - fn new(field: &'field StructField) -> Self { - let (args_var, endian_var) = make_field_vars(field); + fn new(input: &Input, field: &'field StructField) -> Self { + let (reader_var, endian_var, args_var) = make_field_vars(input, field); Self { field, out: TokenStream::new(), - args_var, + outer_reader_var: input.stream_ident_or(READER), + reader_var, endian_var, + args_var, } } fn append_debug(mut self) -> Self { if self.field.debug.is_some() { let head = self.out; + let reader_var = &self.reader_var; let ident = &self.field.ident; let at = if ident.span().start().line == 0 { quote!(::core::line!()) @@ -248,7 +263,7 @@ impl<'field> FieldGenerator<'field> { }; self.out = quote! { - let #SAVED_POSITION = #SEEK_TRAIT::seek(#READER, #SEEK_FROM::Current(0))?; + let #SAVED_POSITION = #SEEK_TRAIT::seek(#reader_var, #SEEK_FROM::Current(0))?; #head @@ -290,7 +305,12 @@ impl<'field> FieldGenerator<'field> { if let Some(after_parse) = get_after_parse_handler(self.field) { let after_parse = AfterParseCallGenerator::new(self.field) .get_value_from_temp() - .call_after_parse(after_parse, &self.endian_var, &self.args_var) + .call_after_parse( + after_parse, + &self.reader_var, + &self.endian_var, + &self.args_var, + ) .finish(); let value = self.out; @@ -321,8 +341,9 @@ impl<'field> FieldGenerator<'field> { // TODO: Position should always just be saved once for a field if used let value = self.out; let map_err = get_map_err(SAVED_POSITION, t.span()); + let reader_var = &self.reader_var; quote! {{ - let #SAVED_POSITION = #SEEK_TRAIT::stream_position(#READER)?; + let #SAVED_POSITION = #SEEK_TRAIT::stream_position(#reader_var)?; #map_func(#value)#map_err? }} @@ -368,6 +389,20 @@ impl<'field> FieldGenerator<'field> { self } + fn prefix_map_stream(mut self) -> Self { + if let Some(map_stream) = &self.field.map_stream { + let rest = self.out; + let reader_var = &self.reader_var; + let outer_reader_var = &self.outer_reader_var; + self.out = quote_spanned_any! { map_stream.span()=> + let #reader_var = &mut #MAP_READER_TYPE_HINT::(#map_stream)(#outer_reader_var); + #rest + }; + } + + self + } + fn prefix_read_function(mut self) -> Self { let read_function = match &self.field.field_mode { FieldMode::Function(parser) => { @@ -434,7 +469,7 @@ impl<'field> FieldGenerator<'field> { } fn prefix_magic(mut self) -> Self { - if let Some(magic) = get_magic(&self.field.magic, &self.endian_var) { + if let Some(magic) = get_magic(&self.field.magic, &self.reader_var, &self.endian_var) { let tail = self.out; self.out = quote! { #magic @@ -452,6 +487,7 @@ impl<'field> FieldGenerator<'field> { FieldMode::TryCalc(calc) => get_try_calc(POS, &self.field.ty, calc), read_mode @ (FieldMode::Normal | FieldMode::Function(_)) => { let args_arg = get_args_argument(self.field, &self.args_var); + let reader_var = &self.reader_var; let endian_var = &self.endian_var; if let FieldMode::Function(f) = read_mode { @@ -462,12 +498,12 @@ impl<'field> FieldGenerator<'field> { // here as a mismatched type instead of later as a // try-conversion error quote_spanned_any! { f.span()=> - (|| #READ_FUNCTION)()(#READER, #endian_var, #args_arg) + (|| #READ_FUNCTION)()(#reader_var, #endian_var, #args_arg) .map(|v| -> #ty { v }) } } else { quote! { - #READ_FUNCTION(#READER, #endian_var, #args_arg) + #READ_FUNCTION(#reader_var, #endian_var, #args_arg) } } } @@ -512,15 +548,15 @@ impl<'field> FieldGenerator<'field> { fn wrap_restore_position(mut self) -> Self { if self.field.restore_position.is_some() { - self.out = wrap_save_restore(self.out); + self.out = wrap_save_restore(&self.reader_var, self.out); } self } fn wrap_seek(mut self) -> Self { - let seek_before = generate_seek_before(self.field); - let seek_after = generate_seek_after(self.field); + let seek_before = generate_seek_before(&self.reader_var, self.field); + let seek_after = generate_seek_after(&self.reader_var, self.field); if !seek_before.is_empty() || !seek_after.is_empty() { let value = self.out; self.out = quote! {{ @@ -604,21 +640,28 @@ fn get_prelude(input: &Input, name: Option<&Ident>) -> TokenStream { .add_imports(name) .add_endian() .add_magic_pre_assertion() + .add_map_stream() .finish() } -fn generate_seek_after(field: &StructField) -> TokenStream { +fn generate_seek_after(reader_var: &TokenStream, field: &StructField) -> TokenStream { let pad_size_to = field.pad_size_to.as_ref().map(|pad| { quote! {{ let pad = (#pad) as i64; - let size = (#SEEK_TRAIT::stream_position(#READER)? - #POS) as i64; + let size = (#SEEK_TRAIT::stream_position(#reader_var)? - #POS) as i64; if size < pad { - #SEEK_TRAIT::seek(#READER, #SEEK_FROM::Current(pad - size))?; + #SEEK_TRAIT::seek(#reader_var, #SEEK_FROM::Current(pad - size))?; } }} }); - let pad_after = field.pad_after.as_ref().map(map_pad); - let align_after = field.align_after.as_ref().map(map_align); + let pad_after = field + .pad_after + .as_ref() + .map(|value| map_pad(reader_var, value)); + let align_after = field + .align_after + .as_ref() + .map(|value| map_align(reader_var, value)); quote! { #pad_size_to @@ -627,17 +670,23 @@ fn generate_seek_after(field: &StructField) -> TokenStream { } } -fn generate_seek_before(field: &StructField) -> TokenStream { +fn generate_seek_before(reader_var: &TokenStream, field: &StructField) -> TokenStream { let seek_before = field.seek_before.as_ref().map(|seek| { quote! { - #SEEK_TRAIT::seek(#READER, #seek)?; + #SEEK_TRAIT::seek(#reader_var, #seek)?; } }); - let pad_before = field.pad_before.as_ref().map(map_pad); - let align_before = field.align_before.as_ref().map(map_align); + let pad_before = field + .pad_before + .as_ref() + .map(|value| map_pad(reader_var, value)); + let align_before = field + .align_before + .as_ref() + .map(|value| map_align(reader_var, value)); let pad_size_to_before = field.pad_size_to.as_ref().map(|_| { quote! { - let #POS = #SEEK_TRAIT::stream_position(#READER)?; + let #POS = #SEEK_TRAIT::stream_position(#reader_var)?; } }); @@ -657,11 +706,14 @@ fn get_return_type(variant_ident: Option<&Ident>) -> TokenStream { variant_ident.map_or_else(|| quote! { Self }, |ident| quote! { Self::#ident }) } -fn make_field_vars(field: &StructField) -> (Option, TokenStream) { - let args_var = if field.needs_args() { - Some(make_ident(&field.ident, "args")) +fn make_field_vars( + input: &Input, + field: &StructField, +) -> (TokenStream, TokenStream, Option) { + let reader_var = if field.map_stream.is_some() { + make_ident(&field.ident, "reader").into_token_stream() } else { - None + input.stream_ident_or(READER) }; let endian_var = if field.needs_endian() { @@ -670,31 +722,37 @@ fn make_field_vars(field: &StructField) -> (Option, TokenStream) { OPT.to_token_stream() }; - (args_var, endian_var) + let args_var = if field.needs_args() { + Some(make_ident(&field.ident, "args")) + } else { + None + }; + + (reader_var, endian_var, args_var) } -fn map_align(align: &TokenStream) -> TokenStream { +fn map_align(reader_var: &TokenStream, align: &TokenStream) -> TokenStream { quote! {{ let align = (#align) as i64; - let pos = #SEEK_TRAIT::stream_position(#READER)? as i64; - #SEEK_TRAIT::seek(#READER, #SEEK_FROM::Current((align - (pos % align)) % align))?; + let pos = #SEEK_TRAIT::stream_position(#reader_var)? as i64; + #SEEK_TRAIT::seek(#reader_var, #SEEK_FROM::Current((align - (pos % align)) % align))?; }} } -fn map_pad(pad: &TokenStream) -> TokenStream { +fn map_pad(reader_var: &TokenStream, pad: &TokenStream) -> TokenStream { quote! { - #SEEK_TRAIT::seek(#READER, #SEEK_FROM::Current((#pad) as i64))?; + #SEEK_TRAIT::seek(#reader_var, #SEEK_FROM::Current((#pad) as i64))?; } } -fn wrap_save_restore(value: TokenStream) -> TokenStream { +fn wrap_save_restore(reader_var: &TokenStream, value: TokenStream) -> TokenStream { if value.is_empty() { value } else { quote! { - let #SAVED_POSITION = #SEEK_TRAIT::stream_position(#READER)?; + let #SAVED_POSITION = #SEEK_TRAIT::stream_position(#reader_var)?; #value - #SEEK_TRAIT::seek(#READER, #SEEK_FROM::Start(#SAVED_POSITION))?; + #SEEK_TRAIT::seek(#reader_var, #SEEK_FROM::Start(#SAVED_POSITION))?; } } } diff --git a/binrw_derive/src/binrw/codegen/sanitization.rs b/binrw_derive/src/binrw/codegen/sanitization.rs index 88b7c9dc..6f4968ac 100644 --- a/binrw_derive/src/binrw/codegen/sanitization.rs +++ b/binrw_derive/src/binrw/codegen/sanitization.rs @@ -47,6 +47,8 @@ ident_str! { pub(crate) ARGS_TYPE_HINT = from_crate!(__private::parse_function_args_type_hint); pub(crate) MAP_ARGS_TYPE_HINT = from_crate!(__private::map_args_type_hint); pub(crate) REQUIRED_ARG_TRAIT = from_crate!(__private::Required); + pub(crate) MAP_READER_TYPE_HINT = from_crate!(__private::map_reader_type_hint); + pub(crate) MAP_WRITER_TYPE_HINT = from_crate!(__private::map_writer_type_hint); pub(crate) PARSE_FN_TYPE_HINT = from_crate!(__private::parse_fn_type_hint); pub(crate) WRITE_FN_TYPE_HINT = from_crate!(__private::write_fn_type_hint); pub(crate) WRITE_ARGS_TYPE_HINT = from_crate!(__private::write_function_args_type_hint); diff --git a/binrw_derive/src/binrw/codegen/write_options.rs b/binrw_derive/src/binrw/codegen/write_options.rs index b6e97563..abbe386e 100644 --- a/binrw_derive/src/binrw/codegen/write_options.rs +++ b/binrw_derive/src/binrw/codegen/write_options.rs @@ -29,8 +29,11 @@ pub(crate) fn generate(input: &Input, derive_input: &syn::DeriveInput) -> TokenS }, }; + let writer_var = input.stream_ident_or(WRITER); + quote! { - let #POS = #SEEK_TRAIT::stream_position(#WRITER)?; + let #writer_var = #WRITER; + let #POS = #SEEK_TRAIT::stream_position(#writer_var)?; #inner Ok(()) @@ -47,10 +50,11 @@ fn generate_map(input: &Input, name: Option<&Ident>, map: &TokenStream) -> Token } else { map.clone() }; + let writer_var = input.stream_ident_or(WRITER); let write_data = quote! { #WRITE_METHOD( &((#map)(self) #map_try), - #WRITER, + #writer_var, #OPT, () )?; @@ -58,7 +62,7 @@ fn generate_map(input: &Input, name: Option<&Ident>, map: &TokenStream) -> Token let magic = input.magic(); let endian = input.endian(); - prelude::PreludeGenerator::new(write_data, Some(input), name) + prelude::PreludeGenerator::new(write_data, Some(input), name, &writer_var) .prefix_magic(magic) .prefix_endian(endian) .prefix_imports() diff --git a/binrw_derive/src/binrw/codegen/write_options/enum.rs b/binrw_derive/src/binrw/codegen/write_options/enum.rs index bd5c8ecb..29b21c42 100644 --- a/binrw_derive/src/binrw/codegen/write_options/enum.rs +++ b/binrw_derive/src/binrw/codegen/write_options/enum.rs @@ -11,20 +11,22 @@ pub(crate) fn generate_unit_enum( name: Option<&Ident>, en: &UnitOnlyEnum, ) -> TokenStream { + let writer_var = input.stream_ident_or(WRITER); let write = match en.map.as_repr() { - Some(repr) => generate_unit_enum_repr(repr, &en.fields), - None => generate_unit_enum_magic(&en.fields), + Some(repr) => generate_unit_enum_repr(&writer_var, repr, &en.fields), + None => generate_unit_enum_magic(&writer_var, &en.fields), }; - PreludeGenerator::new(write, Some(input), name) + PreludeGenerator::new(write, Some(input), name, &writer_var) .prefix_magic(&en.magic) .prefix_endian(&en.endian) .prefix_imports() + .prefix_map_stream() .finish() } pub(crate) fn generate_data_enum(input: &Input, name: Option<&Ident>, en: &Enum) -> TokenStream { - EnumGenerator::new(input, name, en) + EnumGenerator::new(input, name, en, input.stream_ident_or(WRITER)) .write_variants() .prefix_prelude() .finish() @@ -34,15 +36,22 @@ struct EnumGenerator<'a> { en: &'a Enum, input: &'a Input, name: Option<&'a Ident>, + writer_var: TokenStream, out: TokenStream, } impl<'a> EnumGenerator<'a> { - fn new(input: &'a Input, name: Option<&'a Ident>, en: &'a Enum) -> Self { + fn new( + input: &'a Input, + name: Option<&'a Ident>, + en: &'a Enum, + writer_var: TokenStream, + ) -> Self { Self { input, name, en, + writer_var, out: TokenStream::new(), } } @@ -55,11 +64,14 @@ impl<'a> EnumGenerator<'a> { EnumVariant::Unit(_) => None, }; + let writer_var = &self.writer_var; let writing = match variant { - EnumVariant::Variant { options, .. } => StructGenerator::new(None, options, None) - .write_fields() - .prefix_prelude() - .finish(), + EnumVariant::Variant { options, .. } => { + StructGenerator::new(None, options, None, &self.writer_var) + .write_fields() + .prefix_prelude() + .finish() + } EnumVariant::Unit(variant) => variant .magic .as_ref() @@ -68,7 +80,7 @@ impl<'a> EnumGenerator<'a> { quote! { #WRITE_METHOD ( &#magic, - #WRITER, + #writer_var, #OPT, () )?; @@ -96,10 +108,11 @@ impl<'a> EnumGenerator<'a> { fn prefix_prelude(mut self) -> Self { let out = self.out; - self.out = PreludeGenerator::new(out, Some(self.input), self.name) + self.out = PreludeGenerator::new(out, Some(self.input), self.name, &self.writer_var) .prefix_magic(&self.en.magic) .prefix_endian(&self.en.endian) .prefix_imports() + .prefix_map_stream() .finish(); self @@ -110,7 +123,11 @@ impl<'a> EnumGenerator<'a> { } } -fn generate_unit_enum_repr(repr: &TokenStream, variants: &[UnitEnumField]) -> TokenStream { +fn generate_unit_enum_repr( + writer_var: &TokenStream, + repr: &TokenStream, + variants: &[UnitEnumField], +) -> TokenStream { let branches = variants.iter().map(|variant| { let name = &variant.ident; quote! { @@ -123,14 +140,14 @@ fn generate_unit_enum_repr(repr: &TokenStream, variants: &[UnitEnumField]) -> To &(match self { #(#branches),* } as #repr), - #WRITER, + #writer_var, #OPT, (), )?; } } -fn generate_unit_enum_magic(variants: &[UnitEnumField]) -> TokenStream { +fn generate_unit_enum_magic(writer_var: &TokenStream, variants: &[UnitEnumField]) -> TokenStream { let branches = variants.iter().map(|variant| { let name = &variant.ident; let magic = variant.magic.as_ref().map(|magic| { @@ -139,7 +156,7 @@ fn generate_unit_enum_magic(variants: &[UnitEnumField]) -> TokenStream { quote! { #WRITE_METHOD ( &#magic, - #WRITER, + #writer_var, #OPT, (), )?; diff --git a/binrw_derive/src/binrw/codegen/write_options/prelude.rs b/binrw_derive/src/binrw/codegen/write_options/prelude.rs index dbb1391b..8196e497 100644 --- a/binrw_derive/src/binrw/codegen/write_options/prelude.rs +++ b/binrw_derive/src/binrw/codegen/write_options/prelude.rs @@ -1,22 +1,37 @@ -use crate::binrw::{ - codegen::{ - get_destructured_imports, get_endian, - sanitization::{ARGS, OPT, WRITER, WRITE_METHOD}, +use crate::{ + binrw::{ + codegen::{ + get_destructured_imports, get_endian, + sanitization::{ARGS, MAP_WRITER_TYPE_HINT, OPT, WRITER, WRITE_METHOD}, + }, + parser::{CondEndian, Input, Magic}, }, - parser::{CondEndian, Input, Magic}, + util::quote_spanned_any, }; use proc_macro2::{Ident, TokenStream}; use quote::quote; +use syn::spanned::Spanned; pub(crate) struct PreludeGenerator<'a> { out: TokenStream, input: Option<&'a Input>, name: Option<&'a Ident>, + writer_var: &'a TokenStream, } impl<'a> PreludeGenerator<'a> { - pub(crate) fn new(out: TokenStream, input: Option<&'a Input>, name: Option<&'a Ident>) -> Self { - Self { out, input, name } + pub(crate) fn new( + out: TokenStream, + input: Option<&'a Input>, + name: Option<&'a Ident>, + writer_var: &'a TokenStream, + ) -> Self { + Self { + out, + input, + name, + writer_var, + } } pub(crate) fn prefix_imports(mut self) -> Self { @@ -36,12 +51,13 @@ impl<'a> PreludeGenerator<'a> { pub(crate) fn prefix_magic(mut self, magic: &Magic) -> Self { if let Some(magic) = magic { + let writer_var = &self.writer_var; let magic = magic.match_value(); let out = self.out; self.out = quote! { #WRITE_METHOD ( &#magic, - #WRITER, + #writer_var, #OPT, () )?; @@ -64,6 +80,22 @@ impl<'a> PreludeGenerator<'a> { self } + pub(crate) fn prefix_map_stream(mut self) -> Self { + if let Some(input) = self.input { + if let Some(map_stream) = input.map_stream() { + let outer_writer = input.stream_ident_or(WRITER); + let inner_writer = &self.writer_var; + let tail = self.out; + self.out = quote_spanned_any! { map_stream.span()=> + let #inner_writer = &mut #MAP_WRITER_TYPE_HINT::(#map_stream)(#outer_writer); + #tail + }; + } + } + + self + } + pub(crate) fn finish(self) -> TokenStream { self.out } diff --git a/binrw_derive/src/binrw/codegen/write_options/struct.rs b/binrw_derive/src/binrw/codegen/write_options/struct.rs index 1672331a..c5c04a09 100644 --- a/binrw_derive/src/binrw/codegen/write_options/struct.rs +++ b/binrw_derive/src/binrw/codegen/write_options/struct.rs @@ -1,6 +1,6 @@ use super::{prelude::PreludeGenerator, struct_field::write_field}; use crate::binrw::{ - codegen::get_assertions, + codegen::{get_assertions, sanitization::WRITER}, parser::{Input, Struct}, }; use proc_macro2::TokenStream; @@ -8,7 +8,7 @@ use quote::quote; use syn::Ident; pub(super) fn generate_struct(input: &Input, name: Option<&Ident>, st: &Struct) -> TokenStream { - StructGenerator::new(Some(input), st, name) + StructGenerator::new(Some(input), st, name, &input.stream_ident_or(WRITER)) .write_fields() .prefix_assertions() .prefix_prelude() @@ -20,6 +20,7 @@ pub(crate) struct StructGenerator<'input> { input: Option<&'input Input>, st: &'input Struct, name: Option<&'input Ident>, + writer_var: &'input TokenStream, out: TokenStream, } @@ -28,20 +29,23 @@ impl<'input> StructGenerator<'input> { input: Option<&'input Input>, st: &'input Struct, name: Option<&'input Ident>, + writer_var: &'input TokenStream, ) -> Self { Self { input, st, name, + writer_var, out: TokenStream::new(), } } pub(crate) fn prefix_prelude(mut self) -> Self { - self.out = PreludeGenerator::new(self.out, self.input, self.name) + self.out = PreludeGenerator::new(self.out, self.input, self.name, self.writer_var) .prefix_magic(&self.st.magic) .prefix_endian(&self.st.endian) .prefix_imports() + .prefix_map_stream() .finish(); self @@ -60,7 +64,11 @@ impl<'input> StructGenerator<'input> { } pub(crate) fn write_fields(mut self) -> Self { - let write_fields = self.st.fields.iter().map(write_field); + let write_fields = self + .st + .fields + .iter() + .map(|field| write_field(self.writer_var, field)); self.out = quote! { #(#write_fields)* 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 cfc5aa41..b6a579cb 100644 --- a/binrw_derive/src/binrw/codegen/write_options/struct_field.rs +++ b/binrw_derive/src/binrw/codegen/write_options/struct_field.rs @@ -1,23 +1,27 @@ -use crate::binrw::{ - codegen::{ - get_assertions, get_endian, get_map_err, get_passed_args, get_try_calc, - sanitization::{ - make_ident, BEFORE_POS, BINWRITE_TRAIT, POS, 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_map_err, get_passed_args, get_try_calc, + sanitization::{ + make_ident, BEFORE_POS, BINWRITE_TRAIT, MAP_WRITER_TYPE_HINT, POS, SAVED_POSITION, + SEEK_FROM, SEEK_TRAIT, 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, + }, }, + parser::{FieldMode, Map, StructField}, }, - parser::{FieldMode, Map, StructField}, + util::quote_spanned_any, }; +use alloc::borrow::Cow; use core::ops::Not; use proc_macro2::TokenStream; -use quote::quote; -use syn::Ident; +use quote::{quote, ToTokens}; +use syn::{spanned::Spanned, Ident}; -pub(crate) fn write_field(field: &StructField) -> TokenStream { - StructFieldGenerator::new(field) +pub(crate) fn write_field(writer_var: &TokenStream, field: &StructField) -> TokenStream { + StructFieldGenerator::new(field, writer_var) .write_field() .wrap_padding() .prefix_args() @@ -26,18 +30,27 @@ pub(crate) fn write_field(field: &StructField) -> TokenStream { .prefix_magic() .wrap_condition() .prefix_assertions() + .prefix_map_stream() .finish() } struct StructFieldGenerator<'input> { field: &'input StructField, + outer_writer_var: &'input TokenStream, + writer_var: Cow<'input, TokenStream>, out: TokenStream, } impl<'a> StructFieldGenerator<'a> { - fn new(field: &'a StructField) -> Self { + fn new(field: &'a StructField, outer_writer_var: &'a TokenStream) -> Self { Self { field, + outer_writer_var, + writer_var: if field.map_stream.is_some() { + Cow::Owned(make_ident(&field.ident, "reader").into_token_stream()) + } else { + Cow::Borrowed(outer_writer_var) + }, out: TokenStream::new(), } } @@ -54,6 +67,20 @@ impl<'a> StructFieldGenerator<'a> { self } + fn prefix_map_stream(mut self) -> Self { + if let Some(map_stream) = &self.field.map_stream { + let rest = self.out; + let writer_var = &self.writer_var; + let outer_writer_var = &self.outer_writer_var; + self.out = quote_spanned_any! { map_stream.span()=> + let #writer_var = &mut #MAP_WRITER_TYPE_HINT::(#map_stream)(#outer_writer_var); + #rest + }; + } + + self + } + fn prefix_write_fn(mut self) -> Self { if !self.field.is_written() { return self; @@ -78,7 +105,7 @@ impl<'a> StructFieldGenerator<'a> { quote! { #WRITE_FN_TYPE_HINT(#write_fn) } }; - let out = &self.out; + let out = self.out; self.out = quote! { let #WRITE_FUNCTION = #write_fn; #out @@ -111,6 +138,7 @@ impl<'a> StructFieldGenerator<'a> { let name = &self.field.ident; let args = args_ident(name); let endian = get_endian(&self.field.endian); + let writer_var = &self.writer_var; let initialize = match &self.field.field_mode { FieldMode::Calc(expr) => Some({ @@ -138,7 +166,7 @@ impl<'a> StructFieldGenerator<'a> { }); let store_position = quote! { - let #SAVED_POSITION = #SEEK_TRAIT::stream_position(#WRITER)?; + let #SAVED_POSITION = #SEEK_TRAIT::stream_position(#writer_var)?; }; let name = self @@ -164,7 +192,7 @@ impl<'a> StructFieldGenerator<'a> { #WRITE_FUNCTION ( { #store_position &(#map_fn (#name) #map_try) }, - #WRITER, + #writer_var, #endian, #args )?; @@ -190,10 +218,10 @@ impl<'a> StructFieldGenerator<'a> { } fn wrap_padding(mut self) -> Self { - let out = &self.out; + let out = self.out; - let pad_before = pad_before(self.field); - let pad_after = pad_after(self.field); + let pad_before = pad_before(&self.writer_var, self.field); + let pad_after = pad_after(&self.writer_var, self.field); self.out = quote! { #pad_before #out @@ -259,11 +287,12 @@ impl<'a> StructFieldGenerator<'a> { if let Some(magic) = &self.field.magic { let magic = magic.match_value(); let endian = get_endian(&self.field.endian); + let writer_var = &self.writer_var; let out = self.out; self.out = quote! { #WRITE_METHOD ( &#magic, - #WRITER, + #writer_var, #endian, () )?; @@ -296,36 +325,36 @@ fn map_fn_ident(ident: &Ident) -> Ident { make_ident(ident, "map_fn") } -fn pad_after(field: &StructField) -> TokenStream { +fn pad_after(writer_var: &TokenStream, field: &StructField) -> TokenStream { let pad_size_to = field.pad_size_to.as_ref().map(|size| { quote! {{ let pad_to_size = (#size) as u64; - let after_pos = #SEEK_TRAIT::seek(#WRITER, #SEEK_FROM::Current(0))?; + let after_pos = #SEEK_TRAIT::seek(#writer_var, #SEEK_FROM::Current(0))?; if let Some(size) = after_pos.checked_sub(#BEFORE_POS) { if let Some(padding) = pad_to_size.checked_sub(size) { - #WRITE_ZEROES(#WRITER, padding)?; + #WRITE_ZEROES(#writer_var, padding)?; } } }} }); let pad_after = field.pad_after.as_ref().map(|padding| { quote! { - #WRITE_ZEROES(#WRITER, (#padding) as u64)?; + #WRITE_ZEROES(#writer_var, (#padding) as u64)?; } }); let align_after = field.align_after.as_ref().map(|alignment| { quote! {{ - let pos = #SEEK_TRAIT::seek(#WRITER, #SEEK_FROM::Current(0))?; + let pos = #SEEK_TRAIT::seek(#writer_var, #SEEK_FROM::Current(0))?; let align = ((#alignment) as u64); let rem = pos % align; if rem != 0 { - #WRITE_ZEROES(#WRITER, align - rem)?; + #WRITE_ZEROES(#writer_var, align - rem)?; } }} }); let restore_position = field.restore_position.map(|_| { quote! { - #SEEK_TRAIT::seek(#WRITER, #SEEK_FROM::Start(#SAVED_POSITION))?; + #SEEK_TRAIT::seek(#writer_var, #SEEK_FROM::Start(#SAVED_POSITION))?; } }); @@ -337,38 +366,38 @@ fn pad_after(field: &StructField) -> TokenStream { } } -fn pad_before(field: &StructField) -> TokenStream { +fn pad_before(writer_var: &TokenStream, field: &StructField) -> TokenStream { let seek_before = field.seek_before.as_ref().map(|seek| { quote! { #SEEK_TRAIT::seek( - #WRITER, + #writer_var, #seek, )?; } }); let pad_before = field.pad_before.as_ref().map(|padding| { quote! { - #WRITE_ZEROES(#WRITER, (#padding) as u64)?; + #WRITE_ZEROES(#writer_var, (#padding) as u64)?; } }); let align_before = field.align_before.as_ref().map(|alignment| { quote! {{ - let pos = #SEEK_TRAIT::seek(#WRITER, #SEEK_FROM::Current(0))?; + let pos = #SEEK_TRAIT::seek(#writer_var, #SEEK_FROM::Current(0))?; let align = ((#alignment) as u64); let rem = pos % align; if rem != 0 { - #WRITE_ZEROES(#WRITER, align - rem)?; + #WRITE_ZEROES(#writer_var, align - rem)?; } }} }); let pad_size_to_before = field.pad_size_to.as_ref().map(|_| { quote! { - let #BEFORE_POS = #SEEK_TRAIT::seek(#WRITER, #SEEK_FROM::Current(0))?; + let #BEFORE_POS = #SEEK_TRAIT::seek(#writer_var, #SEEK_FROM::Current(0))?; } }); let store_position = field.restore_position.map(|_| { quote! { - let #SAVED_POSITION = #SEEK_TRAIT::seek(#WRITER, #SEEK_FROM::Current(0))?; + let #SAVED_POSITION = #SEEK_TRAIT::seek(#writer_var, #SEEK_FROM::Current(0))?; } }); diff --git a/binrw_derive/src/binrw/parser/attrs.rs b/binrw_derive/src/binrw/parser/attrs.rs index afc29715..8fb68fcc 100644 --- a/binrw_derive/src/binrw/parser/attrs.rs +++ b/binrw_derive/src/binrw/parser/attrs.rs @@ -1,7 +1,7 @@ use super::keywords as kw; use crate::meta_types::{ - IdentPatType, IdentTypeMaybeDefault, MetaEnclosedList, MetaExpr, MetaList, MetaLit, MetaType, - MetaValue, MetaVoid, + IdentPatType, IdentTypeMaybeDefault, MetaEnclosedList, MetaExpr, MetaIdent, MetaList, MetaLit, + MetaType, MetaValue, MetaVoid, }; use syn::{Expr, FieldValue, Token}; @@ -27,6 +27,7 @@ pub(super) type IsLittle = MetaExpr; pub(super) type Little = MetaVoid; pub(super) type Magic = MetaLit; pub(super) type Map = MetaExpr; +pub(super) type MapStream = MetaExpr; pub(super) type Offset = MetaExpr; pub(super) type OffsetAfter = MetaExpr; pub(super) type PadAfter = MetaExpr; @@ -40,6 +41,7 @@ pub(super) type RestorePosition = MetaVoid; pub(super) type ReturnAllErrors = MetaVoid; pub(super) type ReturnUnexpectedError = MetaVoid; pub(super) type SeekBefore = MetaExpr; +pub(super) type Stream = MetaIdent; pub(super) type Temp = MetaVoid; pub(super) type Try = MetaVoid; pub(super) type TryCalc = MetaExpr; diff --git a/binrw_derive/src/binrw/parser/field_level_attrs.rs b/binrw_derive/src/binrw/parser/field_level_attrs.rs index dc11a6ab..48a5028d 100644 --- a/binrw_derive/src/binrw/parser/field_level_attrs.rs +++ b/binrw_derive/src/binrw/parser/field_level_attrs.rs @@ -20,6 +20,8 @@ attr_struct! { pub(crate) endian: CondEndian, #[from(RW:Map, RW:TryMap, RW:Repr)] pub(crate) map: Map, + #[from(RW:MapStream)] + pub(crate) map_stream: Option, #[from(RW:Magic)] pub(crate) magic: Magic, #[from(RW:Args, RW:ArgsRaw)] @@ -252,6 +254,7 @@ impl FromField for StructField { field: field.clone(), endian: <_>::default(), map: <_>::default(), + map_stream: <_>::default(), magic: <_>::default(), args: <_>::default(), field_mode: <_>::default(), diff --git a/binrw_derive/src/binrw/parser/keywords.rs b/binrw_derive/src/binrw/parser/keywords.rs index c45e90ee..5b1d19b5 100644 --- a/binrw_derive/src/binrw/parser/keywords.rs +++ b/binrw_derive/src/binrw/parser/keywords.rs @@ -32,6 +32,7 @@ define_keywords! { little, magic, map, + map_stream, offset, offset_after, pad_after, @@ -45,6 +46,7 @@ define_keywords! { return_all_errors, return_unexpected_error, seek_before, + stream, temp, try_calc, try_map, diff --git a/binrw_derive/src/binrw/parser/top_level_attrs.rs b/binrw_derive/src/binrw/parser/top_level_attrs.rs index 5ca86fe2..ab743391 100644 --- a/binrw_derive/src/binrw/parser/top_level_attrs.rs +++ b/binrw_derive/src/binrw/parser/top_level_attrs.rs @@ -5,7 +5,8 @@ use super::{ }; use crate::binrw::Options; use proc_macro2::TokenStream; -use syn::spanned::Spanned; +use quote::ToTokens; +use syn::{spanned::Spanned, Ident}; /// The parsed representation of binrw attributes on a data structure. pub(crate) enum Input { @@ -158,6 +159,14 @@ impl Input { } } + pub(crate) fn map_stream(&self) -> Option<&TokenStream> { + match self { + Input::Struct(s) | Input::UnitStruct(s) => s.map_stream.as_ref(), + Input::Enum(en) => en.map_stream.as_ref(), + Input::UnitOnlyEnum(en) => en.map_stream.as_ref(), + } + } + pub(crate) fn pre_assertions(&self) -> &[Assert] { match self { Input::Struct(s) | Input::UnitStruct(s) => &s.pre_assertions, @@ -166,6 +175,19 @@ impl Input { } } + pub(crate) fn stream_ident(&self) -> Option<&Ident> { + match self { + Input::Struct(s) | Input::UnitStruct(s) => s.stream_ident.as_ref(), + Input::Enum(en) => en.stream_ident.as_ref(), + Input::UnitOnlyEnum(en) => en.stream_ident.as_ref(), + } + } + + pub(crate) fn stream_ident_or(&self, or: impl ToTokens) -> TokenStream { + self.stream_ident() + .map_or_else(|| or.to_token_stream(), ToTokens::to_token_stream) + } + pub(crate) fn assertions(&self) -> &[Assert] { match self { Input::Struct(s) | Input::UnitStruct(s) => &s.assertions, @@ -179,10 +201,14 @@ attr_struct! { #[from(StructAttr)] #[derive(Clone, Debug, Default)] pub(crate) struct Struct { + #[from(RW:Stream)] + pub(crate) stream_ident: Option, #[from(RW:Big, RW:Little, RW:IsBig, RW:IsLittle)] pub(crate) endian: CondEndian, #[from(RW:Map, RW:TryMap, RW:Repr)] pub(crate) map: Map, + #[from(RW:MapStream)] + pub(crate) map_stream: Option, #[from(RW:Magic)] pub(crate) magic: Magic, #[from(RW:Import, RW:ImportRaw)] @@ -282,10 +308,14 @@ attr_struct! { #[derive(Clone, Debug, Default)] pub(crate) struct Enum { pub(crate) ident: Option, + #[from(RW:Stream)] + pub(crate) stream_ident: Option, #[from(RW:Big, RW:Little, RW:IsBig, RW:IsLittle)] pub(crate) endian: CondEndian, #[from(RW:Map, RW:TryMap, RW:Repr)] pub(crate) map: Map, + #[from(RW:MapStream)] + pub(crate) map_stream: Option, #[from(RW:Magic)] pub(crate) magic: Magic, #[from(RW:Import, RW:ImportRaw)] @@ -330,10 +360,14 @@ attr_struct! { #[from(UnitEnumAttr)] #[derive(Clone, Debug, Default)] pub(crate) struct UnitOnlyEnum { + #[from(RW:Stream)] + pub(crate) stream_ident: Option, #[from(RW:Big, RW:Little, RW:IsBig, RW:IsLittle)] pub(crate) endian: CondEndian, #[from(RW:Map, RW:TryMap, RW:Repr)] pub(crate) map: Map, + #[from(RW:MapStream)] + pub(crate) map_stream: Option, #[from(RW:Magic)] pub(crate) magic: Magic, #[from(RW:Import, RW:ImportRaw)] diff --git a/binrw_derive/src/meta_types.rs b/binrw_derive/src/meta_types.rs index d0c3af2f..bc11ea4d 100644 --- a/binrw_derive/src/meta_types.rs +++ b/binrw_derive/src/meta_types.rs @@ -6,7 +6,7 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, token::{self, Token}, - Expr, Lit, Token, Type, + Expr, Ident, Lit, Token, Type, }; pub(crate) trait KeywordToken { @@ -39,6 +39,13 @@ pub(crate) type MetaExpr = MetaValue; /// both are always allowed pub(crate) type MetaType = MetaValue; +/// `MetaIdent` represents a key/ident pair +/// Takes two forms: +/// * ident(ident) +/// * ident = ident +/// both are always allowed +pub(crate) type MetaIdent = MetaValue; + /// `MetaLit` represents a key/lit pair /// Takes two forms: /// * ident(lit) @@ -82,6 +89,12 @@ impl From> for TokenStream { } } +impl From> for Ident { + fn from(value: MetaValue) -> Self { + value.value + } +} + impl ToTokens for MetaValue { fn to_tokens(&self, tokens: &mut TokenStream) { self.value.to_tokens(tokens);