From d33804c5386e409efa96085603ce0ac58c16de79 Mon Sep 17 00:00:00 2001 From: Nick Babcock Date: Wed, 27 Dec 2023 20:04:00 -0600 Subject: [PATCH] Add binary::Token::write for outputting binary data This has been useful to write tests, so I've decided to expose it as part of the public API for the use cases where one wants to create the binary output themselves. --- src/binary/lexer.rs | 75 ++++++++++++++++++++++++++++ src/binary/reader.rs | 115 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 168 insertions(+), 22 deletions(-) diff --git a/src/binary/lexer.rs b/src/binary/lexer.rs index bab54a0..e685420 100644 --- a/src/binary/lexer.rs +++ b/src/binary/lexer.rs @@ -219,6 +219,81 @@ pub enum Token<'a> { Id(u16), } +impl<'a> Token<'a> { + fn write_u32(mut wtr: impl std::io::Write, num: u32) -> Result<(), std::io::Error> { + wtr.write_all(&LexemeId::U32.0.to_le_bytes())?; + wtr.write_all(&num.to_le_bytes()) + } + + /// Write the binary representation of a token to a writer + /// + /// ```rust + /// use jomini::binary::Token; + /// let out = Vec::new(); + /// let mut cursor = std::io::Cursor::new(out); + /// for token in &[Token::Id(0x00e1), Token::Equal, Token::U32(10)] { + /// token.write(&mut cursor)?; + /// } + /// + /// assert_eq!(&cursor.into_inner(), &[0xe1, 0x00, 0x01, 0x00, 0x14, 0x00, 0x0a, 0x00, 0x00, 0x00]); + /// # Ok::<(), Box>(()) + /// ``` + pub fn write(&self, mut wtr: impl std::io::Write) -> Result<(), std::io::Error> { + match self { + Token::Open => wtr.write_all(&LexemeId::OPEN.0.to_le_bytes()), + Token::Close => wtr.write_all(&LexemeId::CLOSE.0.to_le_bytes()), + Token::Equal => wtr.write_all(&LexemeId::EQUAL.0.to_le_bytes()), + Token::U32(x) => Token::write_u32(wtr, *x), + Token::U64(x) => { + wtr.write_all(&LexemeId::U64.0.to_le_bytes())?; + wtr.write_all(&x.to_le_bytes()) + } + Token::I32(x) => { + wtr.write_all(&LexemeId::I32.0.to_le_bytes())?; + wtr.write_all(&x.to_le_bytes()) + } + Token::Bool(x) => { + wtr.write_all(&LexemeId::BOOL.0.to_le_bytes())?; + wtr.write_all(&[if *x { 1u8 } else { 0 }]) + } + Token::Quoted(x) => { + wtr.write_all(&LexemeId::QUOTED.0.to_le_bytes())?; + wtr.write_all(&(x.as_bytes().len() as u16).to_le_bytes())?; + wtr.write_all(x.as_bytes()) + } + Token::Unquoted(x) => { + wtr.write_all(&LexemeId::UNQUOTED.0.to_le_bytes())?; + wtr.write_all(&(x.as_bytes().len() as u16).to_le_bytes())?; + wtr.write_all(x.as_bytes()) + } + Token::F32(x) => { + wtr.write_all(&LexemeId::F32.0.to_le_bytes())?; + wtr.write_all(x) + } + Token::F64(x) => { + wtr.write_all(&LexemeId::F64.0.to_le_bytes())?; + wtr.write_all(x) + } + Token::Rgb(x) => { + wtr.write_all(&LexemeId::RGB.0.to_le_bytes())?; + wtr.write_all(&LexemeId::OPEN.0.to_le_bytes())?; + Token::write_u32(&mut wtr, x.r)?; + Token::write_u32(&mut wtr, x.g)?; + Token::write_u32(&mut wtr, x.b)?; + if let Some(a) = x.a.as_ref() { + Token::write_u32(&mut wtr, *a)?; + } + wtr.write_all(&LexemeId::CLOSE.0.to_le_bytes()) + } + Token::I64(x) => { + wtr.write_all(&LexemeId::I64.0.to_le_bytes())?; + wtr.write_all(&x.to_le_bytes()) + } + Token::Id(x) => wtr.write_all(&x.to_le_bytes()), + } + } +} + #[inline] pub(crate) fn read_token(data: &[u8]) -> Result<(Token, &[u8]), LexError> { let (id, data) = read_id(data)?; diff --git a/src/binary/reader.rs b/src/binary/reader.rs index 16215ed..d647930 100644 --- a/src/binary/reader.rs +++ b/src/binary/reader.rs @@ -391,33 +391,104 @@ impl From for ReaderErrorKind { #[cfg(test)] mod tests { use super::*; + use crate::{binary::Rgb, Scalar}; + use rstest::*; + + #[rstest] + #[case(&[ + Token::Id(0x2838), + Token::Equal, + Token::Open, + Token::Id(0x2863), + Token::Equal, + Token::Unquoted(Scalar::new(b"western")), + Token::Quoted(Scalar::new(b"1446.5.31")), + Token::Equal, + Token::Id(0x2838), + Token::Close, + ])] + #[case(&[ + Token::Id(0x2ec9), + Token::Equal, + Token::Open, + Token::Id(0x28e2), + Token::Equal, + Token::I32(1), + Token::Id(0x28e3), + Token::Equal, + Token::I32(11), + Token::Id(0x2ec7), + Token::Equal, + Token::I32(4), + Token::Id(0x2ec8), + Token::Equal, + Token::I32(0), + Token::Close, + ])] + #[case(&[ + Token::Id(0x053a), + Token::Equal, + Token::Rgb(Rgb { + r: 110, + g: 28, + b: 27, + a: None + }) + ])] + #[case(&[ + Token::Id(0x053a), + Token::Equal, + Token::Rgb(Rgb { + r: 110, + g: 28, + b: 27, + a: Some(128), + }) + ])] + #[case(&[ + Token::Id(0x326b), Token::Equal, Token::U64(128), + Token::Id(0x326b), Token::Equal, Token::I64(-1), + Token::Id(0x2d82), Token::Equal, Token::F64([0xc7, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), + Token::Id(0x2d82), Token::Equal, Token::F32([0x8f, 0xc2, 0x75, 0x3e]), + Token::Id(0x2d82), Token::Equal, Token::U32(89) + ])] + fn test_roundtrip(#[case] input: &[Token]) { + let data = Vec::new(); + let mut writer = std::io::Cursor::new(data); + for tok in input { + tok.write(&mut writer).unwrap(); + } - fn test_reader(data: &[u8], expected: &[Token]) { - fn eq(mut reader: TokenReader, expected: &[Token]) - where - R: Read, - { - for token in expected { - assert_eq!(reader.next().unwrap(), Some(*token)); - } - assert_eq!(reader.next().unwrap(), None); + let data = writer.into_inner(); + + // `Read` + let mut reader = TokenReader::new(data.as_slice()); + for (i, e) in input.iter().enumerate() { + assert_eq!(*e, reader.read().unwrap(), "failure at token idx: {}", i); } - eq(TokenReader::new(data), expected); + reader.read().unwrap_err(); + assert_eq!(reader.position(), data.len()); - let data_with_header: Vec<_> = b"EU4bin".iter().chain(data).copied().collect(); - let mut reader = TokenReader::new(data_with_header.as_slice()); - assert_eq!(reader.read_bytes(6).unwrap(), &b"EU4bin"[..]); - eq(reader, expected); - } + // `from_slice` + let mut reader = TokenReader::from_slice(data.as_slice()); + for (i, e) in input.iter().enumerate() { + assert_eq!(*e, reader.read().unwrap(), "failure at token idx: {}", i); + } - #[test] - fn test_binary_token_reader() { - let data = [0xe1, 0x00, 0x01, 0x00, 0x03, 0x00, 0x04, 0x00]; - test_reader( - &data, - &[Token::Id(0x00e1), Token::Equal, Token::Open, Token::Close], - ); + reader.read().unwrap_err(); + assert_eq!(reader.position(), data.len()); + + // reader buffer size + for i in 30..40 { + let mut reader = TokenReader::builder().buffer_len(i).build(data.as_slice()); + for e in input { + assert_eq!(*e, reader.read().unwrap(), "failure at token idx: {}", i); + } + + reader.read().unwrap_err(); + assert_eq!(reader.position(), data.len()); + } } #[test]