Skip to content

Commit

Permalink
Merge pull request #5 from rodrimati1992/0_2_5
Browse files Browse the repository at this point in the history
0.2.5 release
  • Loading branch information
rodrimati1992 authored Oct 10, 2022
2 parents b052e5d + d432d56 commit d788835
Show file tree
Hide file tree
Showing 21 changed files with 738 additions and 50 deletions.
13 changes: 12 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,21 @@ jobs:
strategy:
max-parallel: 2
matrix:
rust: [beta, nightly]
rust: [stable, beta, nightly, 1.64.0, 1.57.0]

steps:
- uses: actions/checkout@v2
- name: ci-1.64
if: ${{ matrix.rust != '1.57.0' }}
run: |
rustup override set ${{ matrix.rust }}
cargo update
cd "${{github.workspace}}/"
cargo test --no-default-features --features "test rust_1_64"
cargo test --no-default-features --features "test rust_1_64 non_basic"
- name: ci-all-versions
run: |
rustup override set ${{ matrix.rust }}
Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "const_panic"
version = "0.2.4"
version = "0.2.5"
authors = ["rodrimati1992 <[email protected]>"]
edition = "2021"
license = "Zlib"
Expand All @@ -24,13 +24,18 @@ version = "=0.2.0"
path = "./const_panic_proc_macros/"
optional = true

[dev-dependencies.arrayvec]
version = "0.7"

[dev-dependencies.rand]
version = "0.8.4"
default_features = false
features = ["small_rng"]

[features]
default = ["non_basic"]

rust_1_64 = []
non_basic = []
docsrs = []
derive = ["const_panic_proc_macros"]
Expand Down
9 changes: 9 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ This changelog is a summary of the changes made in each release.

# 0.2

### 0.2.5

Added `"rust_1_64"` feature, which enables formatting impls which require newer versions.

Added `core::str::Utf8Error` formatting (requires `"rust_1_64"` feature)

Added formatting support for `char` and slices of `char`.


### 0.2.0

Added `concat_assert` macro.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ Enables support for formatting structs, enums, and arrays.
Without this feature, you can effectively only format primitive types
(custom types can manually implement formatting with more difficulty).

- `"rust_1_64"`(disabled by default):
Enables formatting of additional items that require Rust 1.64.0 to do so.

- `"derive"`(disabled by default):
Enables the [`PanicFmt` derive] macro.

Expand Down
6 changes: 4 additions & 2 deletions src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
//! - Manually implementing the [`PanicFmt`] trait as described in its docs.
//!
//! [`PanicFmt` derive]: derive@crate::PanicFmt
//! [`PanicFmt`]: trait@crate::PanicFmt
//! [`PanicFmt`]: trait@crate::fmt::PanicFmt
//! [`impl_panicfmt`]: crate::impl_panicfmt
//! [`flatten_panicvals`]: crate::flatten_panicvals
Expand All @@ -21,6 +21,8 @@ mod non_basic_fmt;
#[cfg(feature = "non_basic")]
mod fmt_compressed;

pub mod char_formatting;

#[cfg(feature = "non_basic")]
pub use self::{fmt_compressed::PackedFmtArg, non_basic_fmt::*};

Expand Down Expand Up @@ -106,7 +108,7 @@ use core::marker::PhantomData;
///
/// ```
/// [`PanicFmt` derive]: derive@crate::PanicFmt
/// [`PanicFmt`]: trait@crate::PanicFmt
/// [`PanicFmt`]: trait@crate::fmt::PanicFmt
/// [`impl_panicfmt`]: crate::impl_panicfmt
/// [`flatten_panicvals`]: crate::flatten_panicvals
pub trait PanicFmt {
Expand Down
146 changes: 146 additions & 0 deletions src/fmt/char_formatting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//! `char`-formatted related items
use crate::{
fmt::{FmtArg, FmtKind},
fmt_impls::basic_fmt_impls::primitive_static_panicfmt,
panic_val::{PanicVal, PanicVariant},
utils::{string_cap, PreFmtString, StartAndBytes},
};

#[cfg(all(test, not(miri)))]
mod tests;

impl PanicVal<'_> {
/// Constructs a `PanicVal` from a `char`.
pub const fn from_char(c: char, fmtarg: FmtArg) -> Self {
let StartAndBytes { start, bytes } = match fmtarg.fmt_kind {
FmtKind::Display => {
let (arr, len) = char_to_utf8(c);
crate::utils::tail_byte_array::<{ string_cap::PREFMT }>(len, &arr)
}
FmtKind::Debug => {
let fmtchar = char_to_debug(c);
crate::utils::tail_byte_array(fmtchar.len(), &fmtchar.encoded)
}
};
// SAFETY:
// char_to_utf8 is exhaustively tested in the tests module.
// char_to_debug is exhaustively tested in the tests module.
// tail_byte_array is also tested for smaller/equal/larger input arrays.
let prefmt = unsafe { PreFmtString::new(start, bytes) };
PanicVal {
var: PanicVariant::PreFmt(prefmt),
}
}
}

primitive_static_panicfmt! {
fn[](&self: char, fmtarg) {
PanicVal::from_char(*self.0, fmtarg)
}
}

/// Converts 0..=0xF to its ascii representation of '0'..='9' and 'A'..='F'
#[inline]
const fn hex_as_ascii(n: u8) -> u8 {
if n < 10 {
n + b'0'
} else {
n - 10 + b'A'
}
}

#[cfg(any(test, feature = "fmt"))]
pub(crate) const fn char_debug_len(c: char) -> usize {
let inner = match c {
'\t' | '\r' | '\n' | '\\' | '\'' => 2,
'\x00'..='\x1F' => 4,
_ => c.len_utf8(),
};
inner + 2
}

const fn char_to_utf8(char: char) -> ([u8; 4], usize) {
let u32 = char as u32;
match u32 {
0..=127 => ([u32 as u8, 0, 0, 0], 1),
0x80..=0x7FF => {
let b0 = 0b1100_0000 | (u32 >> 6) as u8;
let b1 = 0b1000_0000 | (u32 & 0b0011_1111) as u8;
([b0, b1, 0, 0], 2)
}
0x800..=0xFFFF => {
let b0 = 0b1110_0000 | (u32 >> 12) as u8;
let b1 = 0b1000_0000 | ((u32 >> 6) & 0b0011_1111) as u8;
let b2 = 0b1000_0000 | (u32 & 0b0011_1111) as u8;
([b0, b1, b2, 0], 3)
}
0x10000..=u32::MAX => {
let b0 = 0b1111_0000 | (u32 >> 18) as u8;
let b1 = 0b1000_0000 | ((u32 >> 12) & 0b0011_1111) as u8;
let b2 = 0b1000_0000 | ((u32 >> 6) & 0b0011_1111) as u8;
let b3 = 0b1000_0000 | (u32 & 0b0011_1111) as u8;
([b0, b1, b2, b3], 4)
}
}
}

/// Display formats a `char`
pub const fn char_to_display(char: char) -> FmtChar {
let ([b0, b1, b2, b3], len) = char_to_utf8(char);
FmtChar {
encoded: [b0, b1, b2, b3, 0, 0, 0, 0, 0, 0, 0, 0],
len: len as u8,
}
}

/// Debug formats a `char`
pub const fn char_to_debug(c: char) -> FmtChar {
let ([b0, b1, b2, b3], len) = match c {
'\t' => (*br#"\t "#, 2),
'\r' => (*br#"\r "#, 2),
'\n' => (*br#"\n "#, 2),
'\\' => (*br#"\\ "#, 2),
'\'' => (*br#"\' "#, 2),
'\"' => (*br#"" "#, 1),
'\x00'..='\x1F' => {
let n = c as u8;
(
[b'\\', b'x', hex_as_ascii(n >> 4), hex_as_ascii(n & 0b1111)],
4,
)
}
_ => char_to_utf8(c),
};

let mut encoded = [b'\'', b0, b1, b2, b3, 0, 0, 0, 0, 0, 0, 0];
encoded[len + 1] = b'\'';

FmtChar {
encoded,
len: (len as u8) + 2,
}
}

/// An byte slice with a display/debug formatted `char`.
///
/// To get the encoded character, you need to do
/// `&fmt_char.encoded()[..fmt_char.len()]`.
#[derive(Copy, Clone)]
pub struct FmtChar {
encoded: [u8; 12],
len: u8,
}

impl FmtChar {
/// Array which contains the display/debug-formatted `char`,
/// and trailing `0` padding.
pub const fn encoded(&self) -> &[u8; 12] {
&self.encoded
}

/// The length of the subslice that contains the formatted character.
pub const fn len(&self) -> usize {
self.len as usize
}
}
93 changes: 93 additions & 0 deletions src/fmt/char_formatting/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use super::{char_debug_len, char_to_debug, char_to_display, FmtChar};

fn as_bytes(fmt: &FmtChar) -> &[u8] {
&fmt.encoded()[..fmt.len()]
}

#[test]
fn char_to_utf8_encoding_test() {
for c in '\0'..=core::char::MAX {
let mut utf8_std = [0u8; 4];
let utf8_std = c.encode_utf8(&mut utf8_std);

let utf8_here = char_to_display(c);
assert_eq!(utf8_std.as_bytes(), as_bytes(&utf8_here));
}
}

#[test]
fn char_to_utf8_display_test() {
for c in '\0'..=core::char::MAX {
let mut utf8_std = [0u8; 4];
let utf8_std = c.encode_utf8(&mut utf8_std);

let utf8_here = char_to_display(c);
assert_eq!(utf8_std.as_bytes(), as_bytes(&utf8_here));
}
}

#[test]
fn char_to_utf8_debug_test() {
let first_escapes = [
('\x00', r#"'\x00'"#),
('\x01', r#"'\x01'"#),
('\x02', r#"'\x02'"#),
('\x03', r#"'\x03'"#),
('\x04', r#"'\x04'"#),
('\x05', r#"'\x05'"#),
('\x06', r#"'\x06'"#),
('\x07', r#"'\x07'"#),
('\x08', r#"'\x08'"#),
('\t', r#"'\t'"#),
('\n', r#"'\n'"#),
('\x0B', r#"'\x0B'"#),
('\x0C', r#"'\x0C'"#),
('\r', r#"'\r'"#),
('\x0E', r#"'\x0E'"#),
('\x0F', r#"'\x0F'"#),
('\x10', r#"'\x10'"#),
('\x11', r#"'\x11'"#),
('\x12', r#"'\x12'"#),
('\x13', r#"'\x13'"#),
('\x14', r#"'\x14'"#),
('\x15', r#"'\x15'"#),
('\x16', r#"'\x16'"#),
('\x17', r#"'\x17'"#),
('\x18', r#"'\x18'"#),
('\x19', r#"'\x19'"#),
('\x1A', r#"'\x1A'"#),
('\x1B', r#"'\x1B'"#),
('\x1C', r#"'\x1C'"#),
('\x1D', r#"'\x1D'"#),
('\x1E', r#"'\x1E'"#),
('\x1F', r#"'\x1F'"#),
];

for (c, expected) in first_escapes.iter().copied() {
let utf8_here = char_to_debug(c);
assert_eq!(expected.as_bytes(), as_bytes(&utf8_here), "{:?}", c);
assert_eq!(expected.len(), char_debug_len(c), "{:?}", c);
}

let other_escapes = [('\'', r#"'\''"#), ('\"', r#"'"'"#), ('\\', r#"'\\'"#)];

let mut buffer = arrayvec::ArrayString::<12>::new();
for c in '\x20'..=core::char::MAX {
let utf8_here = char_to_debug(c);

if let Some((_, expected)) = Some(c)
.filter(|c| *c <= '\x7F')
.and_then(|c| other_escapes.iter().copied().find(|x| x.0 == c))
{
assert_eq!(expected.as_bytes(), as_bytes(&utf8_here), "{:?}", c);
assert_eq!(expected.len(), char_debug_len(c), "{:?}", c);
} else {
buffer.clear();
buffer.push('\'');
buffer.push(c);
buffer.push('\'');
assert_eq!(buffer.as_bytes(), as_bytes(&utf8_here), "{:?}", c);
assert_eq!(buffer.len(), char_debug_len(c), "{:?}", c);
}
}
}
7 changes: 4 additions & 3 deletions src/fmt_impls/basic_fmt_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ macro_rules! primitive_static_panicfmt {
$($content:tt)*
}
) => {
impl<$($impl)*> PanicFmt for $ty {
impl<$($impl)*> crate::PanicFmt for $ty {
type This = Self;
type Kind = crate::fmt::IsStdType;
const PV_COUNT: usize = 1;
Expand All @@ -20,7 +20,7 @@ macro_rules! primitive_static_panicfmt {
#[doc = concat!(
"Converts this `", stringify!($ty), "` to a single-element `PanicVal` array."
)]
pub const fn to_panicvals($self, $f: FmtArg) -> [PanicVal<'static>; 1] {
pub const fn to_panicvals($self, $f: crate::FmtArg) -> [PanicVal<'static>; 1] {
[{
$($content)*
}]
Expand All @@ -29,12 +29,13 @@ macro_rules! primitive_static_panicfmt {
#[doc = concat!(
"Converts this `", stringify!($ty), "` to a `PanicVal`."
)]
pub const fn to_panicval($self, $f: FmtArg) -> PanicVal<'static> {
pub const fn to_panicval($self, $f: crate::FmtArg) -> PanicVal<'static> {
$($content)*
}
}
}
}
pub(crate) use primitive_static_panicfmt;

macro_rules! impl_panicfmt_panicval_array {
(
Expand Down
Loading

0 comments on commit d788835

Please sign in to comment.