1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
    }
}