From 60e4f0d41bcd80a5c20e9786974e32fe8fec9fb2 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 25 Nov 2024 23:20:43 +0000 Subject: [PATCH 1/8] Pass `BufEncoder::is_full` to inner buffer --- src/buf_encoder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buf_encoder.rs b/src/buf_encoder.rs index 8dd91dc..f2a41e5 100644 --- a/src/buf_encoder.rs +++ b/src/buf_encoder.rs @@ -90,7 +90,7 @@ impl BufEncoder { /// Returns true if no more bytes can be written into the buffer. #[inline] - pub fn is_full(&self) -> bool { self.space_remaining() == 0 } + pub fn is_full(&self) -> bool { self.buf.is_full() } /// Returns the written bytes as a hex `str`. #[inline] From 0c21aa0274191d0b532e245ea68d0ebeead5b535 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 26 Nov 2024 00:18:28 +0000 Subject: [PATCH 2/8] refactor: create `write_pad_left` to pad left --- src/display.rs | 70 ++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/src/display.rs b/src/display.rs index 9dce504..6a805d0 100644 --- a/src/display.rs +++ b/src/display.rs @@ -116,37 +116,7 @@ fn internal_display(bytes: &[u8], f: &mut fmt::Formatter, case: Case) -> fmt::Re // This would complicate the code so I was too lazy to do them but feel free to send a PR! let mut encoder = BufEncoder::<1024>::new(case); - - let pad_right = if let Some(width) = f.width() { - let string_len = match f.precision() { - Some(max) => core::cmp::min(max, bytes.len() * 2), - None => bytes.len() * 2, - }; - - if string_len < width { - let (left, right) = match f.align().unwrap_or(fmt::Alignment::Left) { - fmt::Alignment::Left => (0, width - string_len), - fmt::Alignment::Right => (width - string_len, 0), - fmt::Alignment::Center => ((width - string_len) / 2, (width - string_len + 1) / 2), - }; - // Avoid division by zero and optimize for common case. - if left > 0 { - let c = f.fill(); - let chunk_len = encoder.put_filler(c, left); - let padding = encoder.as_str(); - for _ in 0..(left / chunk_len) { - f.write_str(padding)?; - } - f.write_str(&padding[..((left % chunk_len) * c.len_utf8())])?; - encoder.clear(); - } - right - } else { - 0 - } - } else { - 0 - }; + let pad_right = write_pad_left(f, bytes.len(), &mut encoder)?; match f.precision() { Some(max) if bytes.len() > max / 2 => { @@ -181,6 +151,44 @@ fn internal_display(bytes: &[u8], f: &mut fmt::Formatter, case: Case) -> fmt::Re Ok(()) } +fn write_pad_left( + f: &mut fmt::Formatter, + bytes_len: usize, + encoder: &mut BufEncoder<1024>, +) -> Result { + let pad_right = if let Some(width) = f.width() { + let string_len = match f.precision() { + Some(max) => core::cmp::min(max, bytes_len * 2), + None => bytes_len * 2, + }; + + if string_len < width { + let (left, right) = match f.align().unwrap_or(fmt::Alignment::Left) { + fmt::Alignment::Left => (0, width - string_len), + fmt::Alignment::Right => (width - string_len, 0), + fmt::Alignment::Center => ((width - string_len) / 2, (width - string_len + 1) / 2), + }; + // Avoid division by zero and optimize for common case. + if left > 0 { + let c = f.fill(); + let chunk_len = encoder.put_filler(c, left); + let padding = encoder.as_str(); + for _ in 0..(left / chunk_len) { + f.write_str(padding)?; + } + f.write_str(&padding[..((left % chunk_len) * c.len_utf8())])?; + encoder.clear(); + } + right + } else { + 0 + } + } else { + 0 + }; + Ok(pad_right) +} + mod sealed { /// Trait marking a shared reference. pub trait IsRef: Copy {} From 44f46865ee491dd4a4a959372a109a02f4e0b45c Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 26 Nov 2024 00:23:37 +0000 Subject: [PATCH 3/8] refactor: create `write_pad_right` to pad right --- src/display.rs | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/display.rs b/src/display.rs index 6a805d0..912f1d7 100644 --- a/src/display.rs +++ b/src/display.rs @@ -137,18 +137,7 @@ fn internal_display(bytes: &[u8], f: &mut fmt::Formatter, case: Case) -> fmt::Re } } - // Avoid division by zero and optimize for common case. - if pad_right > 0 { - encoder.clear(); - let c = f.fill(); - let chunk_len = encoder.put_filler(c, pad_right); - let padding = encoder.as_str(); - for _ in 0..(pad_right / chunk_len) { - f.write_str(padding)?; - } - f.write_str(&padding[..((pad_right % chunk_len) * c.len_utf8())])?; - } - Ok(()) + write_pad_right(f, pad_right, &mut encoder) } fn write_pad_left( @@ -189,6 +178,25 @@ fn write_pad_left( Ok(pad_right) } +fn write_pad_right( + f: &mut fmt::Formatter, + pad_right: usize, + encoder: &mut BufEncoder<1024>, +) -> fmt::Result { + // Avoid division by zero and optimize for common case. + if pad_right > 0 { + encoder.clear(); + let c = f.fill(); + let chunk_len = encoder.put_filler(c, pad_right); + let padding = encoder.as_str(); + for _ in 0..(pad_right / chunk_len) { + f.write_str(padding)?; + } + f.write_str(&padding[..((pad_right % chunk_len) * c.len_utf8())])?; + } + Ok(()) +} + mod sealed { /// Trait marking a shared reference. pub trait IsRef: Copy {} From a69b0d515aaa1d93c1567ac523264dd33a885780 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 26 Nov 2024 00:37:17 +0000 Subject: [PATCH 4/8] `DisplayArray` and `DisplayByteSlice` honor '#' --- src/display.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/display.rs b/src/display.rs index 912f1d7..1ad9c5e 100644 --- a/src/display.rs +++ b/src/display.rs @@ -118,6 +118,9 @@ fn internal_display(bytes: &[u8], f: &mut fmt::Formatter, case: Case) -> fmt::Re let mut encoder = BufEncoder::<1024>::new(case); let pad_right = write_pad_left(f, bytes.len(), &mut encoder)?; + if f.alternate() { + f.write_str("0x")?; + } match f.precision() { Some(max) if bytes.len() > max / 2 => { write!(f, "{}", bytes[..(max / 2)].as_hex())?; @@ -146,9 +149,11 @@ fn write_pad_left( encoder: &mut BufEncoder<1024>, ) -> Result { let pad_right = if let Some(width) = f.width() { + // Add space for 2 characters if the '#' flag is set + let full_string_len = if f.alternate() { bytes_len * 2 + 2 } else { bytes_len * 2 }; let string_len = match f.precision() { - Some(max) => core::cmp::min(max, bytes_len * 2), - None => bytes_len * 2, + Some(max) => core::cmp::min(max, full_string_len), + None => full_string_len, }; if string_len < width { From 8f01c45ed8a9c8a8c5fc8a29f0cf74df76b30318 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 26 Nov 2024 03:22:34 +0000 Subject: [PATCH 5/8] test: add test for alternate ('#') formatting --- src/display.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/display.rs b/src/display.rs index 1ad9c5e..cff3e8d 100644 --- a/src/display.rs +++ b/src/display.rs @@ -673,6 +673,12 @@ mod tests { }; } + #[test] + fn alternate_flag() { + test_display_hex!("{:#?}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe"); + test_display_hex!("{:#}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe"); + } + #[test] fn display_short_with_padding() { test_display_hex!("Hello {:<8}!", [0xbe, 0xef], "Hello beef !"); From aa4000011ccd53114fc6a7134784bd878deb302f Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 26 Nov 2024 00:27:46 +0000 Subject: [PATCH 6/8] `fmt_hex_exact` now accounts for precision --- src/display.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/display.rs b/src/display.rs index cff3e8d..b77b026 100644 --- a/src/display.rs +++ b/src/display.rs @@ -543,6 +543,12 @@ where I: IntoIterator, I::Item: Borrow, { + let mut padding_encoder = BufEncoder::<1024>::new(case); + let pad_right = write_pad_left(f, N / 2, &mut padding_encoder)?; + + if f.alternate() { + f.write_str("0x")?; + } let mut encoder = BufEncoder::::new(case); let encoded = match f.precision() { Some(p) if p < N => { @@ -555,7 +561,9 @@ where encoder.as_str() } }; - f.pad_integral(true, "0x", encoded) + f.write_str(encoded)?; + + write_pad_right(f, pad_right, &mut padding_encoder) } /// Given a `T:` [`fmt::Write`], `HexWriter` implements [`std::io::Write`] From 3236694c8b6bf354594d27d52973f3c82270b14c Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 26 Nov 2024 03:18:32 +0000 Subject: [PATCH 7/8] test: add `fmt_hex_exact` to `test_display_hex` --- src/display.rs | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/display.rs b/src/display.rs index b77b026..4e914dd 100644 --- a/src/display.rs +++ b/src/display.rs @@ -672,23 +672,45 @@ mod tests { assert_eq!(format!("{:.65}", dummy), "2a".repeat(32)); } + macro_rules! define_dummy { + ($len:literal) => { + struct Dummy([u8; $len]); + impl fmt::Debug for Dummy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt_hex_exact!(f, $len, &self.0, Case::Lower) + } + } + impl fmt::Display for Dummy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt_hex_exact!(f, $len, &self.0, Case::Lower) + } + } + }; + } + macro_rules! test_display_hex { ($fs: expr, $a: expr, $check: expr) => { - let to_display_array = $a; - let to_display_byte_slice = Vec::from($a); - assert_eq!(format!($fs, to_display_array.as_hex()), $check); - assert_eq!(format!($fs, to_display_byte_slice.as_hex()), $check); + let array = $a; + let slice = &$a; + let vec = Vec::from($a); + let dummy = Dummy($a); + assert_eq!(format!($fs, array.as_hex()), $check); + assert_eq!(format!($fs, slice.as_hex()), $check); + assert_eq!(format!($fs, vec.as_hex()), $check); + assert_eq!(format!($fs, dummy), $check); }; } #[test] fn alternate_flag() { + define_dummy!(4); test_display_hex!("{:#?}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe"); test_display_hex!("{:#}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe"); } #[test] fn display_short_with_padding() { + define_dummy!(2); test_display_hex!("Hello {:<8}!", [0xbe, 0xef], "Hello beef !"); test_display_hex!("Hello {:-<8}!", [0xbe, 0xef], "Hello beef----!"); test_display_hex!("Hello {:^8}!", [0xbe, 0xef], "Hello beef !"); @@ -701,6 +723,7 @@ mod tests { let a = [0xab; 512]; let mut want = "0".repeat(2000 - 1024); want.extend(core::iter::repeat("ab").take(512)); + define_dummy!(512); test_display_hex!("{:0>2000}", a, want); } @@ -710,6 +733,7 @@ mod tests { fn precision_truncates() { // Precision gets the most significant bytes. // Remember the integer is number of hex chars not number of bytes. + define_dummy!(4); test_display_hex!("{0:.4}", [0x12, 0x34, 0x56, 0x78], "1234"); test_display_hex!("{0:.5}", [0x12, 0x34, 0x56, 0x78], "12345"); } @@ -717,43 +741,51 @@ mod tests { #[test] fn precision_with_padding_truncates() { // Precision gets the most significant bytes. + define_dummy!(4); test_display_hex!("{0:10.4}", [0x12, 0x34, 0x56, 0x78], "1234 "); test_display_hex!("{0:10.5}", [0x12, 0x34, 0x56, 0x78], "12345 "); } #[test] fn precision_with_padding_pads_right() { + define_dummy!(4); test_display_hex!("{0:10.20}", [0x12, 0x34, 0x56, 0x78], "12345678 "); test_display_hex!("{0:10.14}", [0x12, 0x34, 0x56, 0x78], "12345678 "); } #[test] fn precision_with_padding_pads_left() { + define_dummy!(4); test_display_hex!("{0:>10.20}", [0x12, 0x34, 0x56, 0x78], " 12345678"); } #[test] fn precision_with_padding_pads_center() { + define_dummy!(4); test_display_hex!("{0:^10.20}", [0x12, 0x34, 0x56, 0x78], " 12345678 "); } #[test] fn precision_with_padding_pads_center_odd() { + define_dummy!(4); test_display_hex!("{0:^11.20}", [0x12, 0x34, 0x56, 0x78], " 12345678 "); } #[test] fn precision_does_not_extend() { + define_dummy!(4); test_display_hex!("{0:.16}", [0x12, 0x34, 0x56, 0x78], "12345678"); } #[test] fn padding_extends() { + define_dummy!(2); test_display_hex!("{:0>8}", [0xab; 2], "0000abab"); } #[test] fn padding_does_not_truncate() { + define_dummy!(4); test_display_hex!("{:0>4}", [0x12, 0x34, 0x56, 0x78], "12345678"); } From 228a40c238786d1bd11243b5b5fddadee3236269 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 26 Nov 2024 04:43:31 +0000 Subject: [PATCH 8/8] test: check alternate format on all display tests --- src/display.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/display.rs b/src/display.rs index 4e914dd..79bc87a 100644 --- a/src/display.rs +++ b/src/display.rs @@ -704,6 +704,7 @@ mod tests { #[test] fn alternate_flag() { define_dummy!(4); + test_display_hex!("{:#?}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe"); test_display_hex!("{:#}", [0xc0, 0xde, 0xca, 0xfe], "0xc0decafe"); } @@ -711,20 +712,32 @@ mod tests { #[test] fn display_short_with_padding() { define_dummy!(2); + test_display_hex!("Hello {:<8}!", [0xbe, 0xef], "Hello beef !"); test_display_hex!("Hello {:-<8}!", [0xbe, 0xef], "Hello beef----!"); test_display_hex!("Hello {:^8}!", [0xbe, 0xef], "Hello beef !"); test_display_hex!("Hello {:>8}!", [0xbe, 0xef], "Hello beef!"); + + test_display_hex!("Hello {:<#8}!", [0xbe, 0xef], "Hello 0xbeef !"); + test_display_hex!("Hello {:-<#8}!", [0xbe, 0xef], "Hello 0xbeef--!"); + test_display_hex!("Hello {:^#8}!", [0xbe, 0xef], "Hello 0xbeef !"); + test_display_hex!("Hello {:>#8}!", [0xbe, 0xef], "Hello 0xbeef!"); } #[test] fn display_long() { + define_dummy!(512); // Note this string is shorter than the one above. let a = [0xab; 512]; + let mut want = "0".repeat(2000 - 1024); want.extend(core::iter::repeat("ab").take(512)); - define_dummy!(512); test_display_hex!("{:0>2000}", a, want); + + let mut want = "0".repeat(2000 - 1026); + want.push_str("0x"); + want.extend(core::iter::repeat("ab").take(512)); + test_display_hex!("{:0>#2000}", a, want); } // Precision and padding act the same as for strings in the stdlib (because we use `Formatter::pad`). @@ -734,59 +747,91 @@ mod tests { // Precision gets the most significant bytes. // Remember the integer is number of hex chars not number of bytes. define_dummy!(4); + test_display_hex!("{0:.4}", [0x12, 0x34, 0x56, 0x78], "1234"); test_display_hex!("{0:.5}", [0x12, 0x34, 0x56, 0x78], "12345"); + + test_display_hex!("{0:#.4}", [0x12, 0x34, 0x56, 0x78], "0x1234"); + test_display_hex!("{0:#.5}", [0x12, 0x34, 0x56, 0x78], "0x12345"); } #[test] fn precision_with_padding_truncates() { // Precision gets the most significant bytes. define_dummy!(4); + test_display_hex!("{0:10.4}", [0x12, 0x34, 0x56, 0x78], "1234 "); test_display_hex!("{0:10.5}", [0x12, 0x34, 0x56, 0x78], "12345 "); + + test_display_hex!("{0:#10.4}", [0x12, 0x34, 0x56, 0x78], "0x1234 "); + test_display_hex!("{0:#10.5}", [0x12, 0x34, 0x56, 0x78], "0x12345 "); } #[test] fn precision_with_padding_pads_right() { define_dummy!(4); + test_display_hex!("{0:10.20}", [0x12, 0x34, 0x56, 0x78], "12345678 "); test_display_hex!("{0:10.14}", [0x12, 0x34, 0x56, 0x78], "12345678 "); + + test_display_hex!("{0:#12.20}", [0x12, 0x34, 0x56, 0x78], "0x12345678 "); + test_display_hex!("{0:#12.14}", [0x12, 0x34, 0x56, 0x78], "0x12345678 "); } #[test] fn precision_with_padding_pads_left() { define_dummy!(4); + test_display_hex!("{0:>10.20}", [0x12, 0x34, 0x56, 0x78], " 12345678"); + + test_display_hex!("{0:>#12.20}", [0x12, 0x34, 0x56, 0x78], " 0x12345678"); } #[test] fn precision_with_padding_pads_center() { define_dummy!(4); + test_display_hex!("{0:^10.20}", [0x12, 0x34, 0x56, 0x78], " 12345678 "); + + test_display_hex!("{0:^#12.20}", [0x12, 0x34, 0x56, 0x78], " 0x12345678 "); } #[test] fn precision_with_padding_pads_center_odd() { define_dummy!(4); + test_display_hex!("{0:^11.20}", [0x12, 0x34, 0x56, 0x78], " 12345678 "); + + test_display_hex!("{0:^#13.20}", [0x12, 0x34, 0x56, 0x78], " 0x12345678 "); } #[test] fn precision_does_not_extend() { define_dummy!(4); + test_display_hex!("{0:.16}", [0x12, 0x34, 0x56, 0x78], "12345678"); + + test_display_hex!("{0:#.16}", [0x12, 0x34, 0x56, 0x78], "0x12345678"); } #[test] fn padding_extends() { define_dummy!(2); + test_display_hex!("{:0>8}", [0xab; 2], "0000abab"); + + test_display_hex!("{:0>#8}", [0xab; 2], "000xabab"); } #[test] fn padding_does_not_truncate() { define_dummy!(4); + + test_display_hex!("{:0>4}", [0x12, 0x34, 0x56, 0x78], "12345678"); test_display_hex!("{:0>4}", [0x12, 0x34, 0x56, 0x78], "12345678"); + + test_display_hex!("{:0>#4}", [0x12, 0x34, 0x56, 0x78], "0x12345678"); + test_display_hex!("{:0>#4}", [0x12, 0x34, 0x56, 0x78], "0x12345678"); } #[test]