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 { _: PATTERN, byte_array_field: PATTERN, field: BINDING @ 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: PATTERN. This will assign
the read byte array to the given field if it matches the pattern. For more complicated
scenarios when the bytes need to be parsed first, bindings can be used: FIELD: BINDING @ PATTERN => EXPRESSION.
In this case, the result of EXPRESSION will be assigned to the FIELD.
There is also the ability to have match-only fields with the _: PATTERN syntax. This is
useful for skipping padding bytes or for matching against bytes that don’t need to be saved in
the struct. These fields are match-only and can’t be used for bindings.
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 _ wildcards but not .. wildcards since the length of the pattern is
used to determine the size of the byte array to be read into.
This macro returns a Result: Ok(STRUCT) or Err(std::io::Error).
Reader errors are returned as is, while errors caused by unsuccessful byte pattern matching
will use std::io::ErrorKind::InvalidData.
Example
use hex_magic::parse_struct; use std::io::{Read, Result}; #[derive(Debug)] struct Data { a: [u8; 2], b: u32, } fn main() -> Result<Data> { let bytes = [0x48, 0x45, 0x58, 0x01, 0x02, 0x00, 0xAA, 0xBB, 0xCC, 0xDD]; let data = parse_struct!( bytes.as_ref() => Data { _: b"HEX", a: [0x01, _], _: "00", b: buf @ "AABB ____" => u32::from_le_bytes(buf) }); println!("{:X?}", data); // Ok(Data { a: [1, 2], b: DDCCBBAA }); data }
Details
The macro invocation above would be parsed into a closure which is instantly called. This
closure would read from the reader and return either Ok(STRUCT) or Err(std::io::Error).
The example above is parsed into the following code (internal variable names changed for clarity):
(|| { #[allow(non_snake_case)] let mut _READER = reader; // reader variable // handle the first `_` field { let mut _BUFFER: [u8; 3usize] = [0; 3usize]; _READER.read(&mut _BUFFER)?; #[allow(dead_code)] match _BUFFER { [72u8, 69u8, 88u8] => (), // b"HEX" parsed into a pattern _ => { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("expected {}, got {:02X?}", "b\"HEX\"", _BUFFER), )) } } } Ok(Data { a: { // `a` has no binding so a generic name is used for the byte array #[allow(non_snake_case)] let mut _BUFFER: [u8; 2usize] = [0; 2usize]; _READER.read(&mut _BUFFER)?; #[allow(dead_code)] match _BUFFER { [0x01, _] => (), _ => { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("expected {}, got {:02X?}", "[0x01, _]", _BUFFER), )) } } #[allow(non_snake_case)] let _VALUE = _BUFFER; // no binding for `a` so the array will be used as is // match second `_` field (after `a`) { let mut _BUFFER: [u8; 1usize] = [0; 1usize]; _READER.read(&mut _BUFFER)?; #[allow(dead_code)] match _BUFFER { [0u8] => (), _ => { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("expected {}, got {:02X?}", "[00]", _BUFFER), )) } } } _VALUE // use the byte array }, b: { #[allow(non_snake_case)] let mut buf: [u8; 4usize] = [0; 4usize]; // `b` has a binding so it's used for the byte array _READER.read(&mut buf)?; #[allow(dead_code)] match buf { [170u8, 187u8, _, _] => (), _ => { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("expected {}, got {:02X?}", "[AA, BB, __, __]", buf), )) } } #[allow(non_snake_case)] let _VALUE = u32::from_le_bytes(buf); // use provided expression to convert the binding to `u32` _VALUE // no `_` after `b` so the result is immediately returned }, }) })();