Macro hex_magic::parse_struct [−][src]
parse_struct!() { /* proc-macro */ }
Macro for parsing bytes from Read
readers into structs
with the ability to skip padding bytes.
Syntax
parse_struct!(READER => STRUCT { ... FIELD: [BINDING @] BYTE_PATTERN [=> EXPRESSION], ... })
First, the macro expects a reader or an expression the result of which would be a reader.
The reader is followed by =>
and then by a modified form of struct instantiation.
The basic syntax of struct instantiation takes the form of FIELD: BYTE_PATTERN
. This will assign
the read bytes ([u8; N]
) to the given field if it matches the pattern.
For more advanced scenarios, such as for converting the bytes to other types,
bindings and expressions can be used:
FIELD: BINDING @ BYTE_PATTERN => EXPRESSION
.
In this case, the result of EXPRESSION
will be assigned to FIELD
.
A special _
field is available for matching against bytes without including them in the
struct. _
fields can be specified multiple times and
can be used for skipping padding bytes or for matching against bytes without including them in
the struct. Bindings and expressions can be used with these fields as well but expressions must
evaluate to ().
Patterns can be any of:
[1, 2, 3, _, 5]
- standard byte array patternsb"byte string!"
- byte strings"FF00FF 00FF00"
- hex strings usable with thehex!
macro
Patterns can include _
but not ..
wildcards since the length of the pattern is
used to determine the amount of bytes to read.
Structs or enum variants with unnamed members (Item(A, B)
) can be used with the
Struct { 0: ..., 1: ... }
syntax.
This macro returns Result
containing either the resulting struct
or std::io::Error
if an error occurred while reading or matching the bytes.
std::io::ErrorKind::InvalidData
if the bytes were not matched successfully.
Example
use hex_magic::parse_struct; use std::io::{Read, Result}; #[derive(Debug)] struct Data { a: [u8; 2], b: u32, } fn main() -> Result<()> { let bytes = [ 0x48, 0x45, 0x58, 0x00, 0x01, 0x02, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, ]; let data = parse_struct!(bytes.as_ref() => Data { _: b"HEX", _: [0], a: [0x01, _], _: "00", b: buf @ "AABB ____" => u32::from_le_bytes(*buf), })?; println!("{:X?}", data); // Data { a: [1, 2], b: DDCCBBAA } Ok(()) }
Details
This macro would be parsed into a closure which is instantly called so that any
potential errors caused by Read
can be handled explicitly by the user.
The macro in the example above would be parsed into the following code
(internal variable names prefixed with _
changed for clarity):
(|| { use std::convert::TryInto; #[allow(non_snake_case)] let mut _READER = bytes.as_ref(); #[allow(non_snake_case)] let mut _ARRAY: [u8; 4usize] = [0; 4usize]; // length of the longest pattern let _: () = { _READER.read_exact(&mut _ARRAY[0..3usize])?; #[allow(non_snake_case)] let _BUFFER: &[u8; 3usize] = _ARRAY[0..3usize].try_into().unwrap(); #[allow(dead_code)] match _BUFFER { [72u8, 69u8, 88u8] => (), // b"HEX" _ => { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("expected `{}`, got `{:02X?}`", "b\"HEX\"", _BUFFER), )) } } }; let _: () = { _READER.read_exact(&mut _ARRAY[0..1usize])?; #[allow(non_snake_case)] let _BUFFER: &[u8; 1usize] = _ARRAY[0..1usize].try_into().unwrap(); #[allow(dead_code)] match _BUFFER { [0] => (), _ => { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("expected `{}`, got `{:02X?}`", "[0]", _BUFFER), )) } } }; let _a = { _READER.read_exact(&mut _ARRAY[0..2usize])?; #[allow(non_snake_case)] let _BUFFER: &[u8; 2usize] = _ARRAY[0..2usize].try_into().unwrap(); #[allow(dead_code)] match _BUFFER { [0x01, _] => (), _ => { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("expected `{}`, got `{:02X?}`", "[0x01, _]", _BUFFER), )) } } *_BUFFER // [u8; 2] as the result if no expression given }; let _: () = { _READER.read_exact(&mut _ARRAY[0..1usize])?; #[allow(non_snake_case)] let _BUFFER: &[u8; 1usize] = _ARRAY[0..1usize].try_into().unwrap(); #[allow(dead_code)] match _BUFFER { [0u8] => (), _ => { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("expected `{}`, got `{:02X?}`", "[0x00]", _BUFFER), )) } } }; let _b = { _READER.read_exact(&mut _ARRAY[0..4usize])?; #[allow(non_snake_case)] let buf: &[u8; 4usize] = _ARRAY[0..4usize].try_into().unwrap(); // assign binding #[allow(dead_code)] match buf { [170u8, 187u8, _, _] => (), // "AABB ____" _ => { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("expected `{}`, got `{:02X?}`", "[0xAA, 0xBB, _, _]", buf), )) } } u32::from_le_bytes(*buf) // provided expression }; Ok(Data { a: _a, b: _b }) // `_` fields are not included in the resulting struct })()