const_str/__ctfe/
hex.rs

1pub struct Hex<T>(pub T);
2
3struct Iter<'a> {
4    s: &'a [u8],
5    i: usize,
6}
7
8impl<'a> Iter<'a> {
9    const fn new(s: &'a str) -> Self {
10        Self {
11            s: s.as_bytes(),
12            i: 0,
13        }
14    }
15
16    const fn next(mut self) -> (Self, Option<u8>) {
17        let mut i = self.i;
18        let s = self.s;
19
20        macro_rules! next {
21            () => {{
22                if i < s.len() {
23                    let b = s[i];
24                    i += 1;
25                    Some(b)
26                } else {
27                    None
28                }
29            }};
30        }
31
32        while let Some(b) = next!() {
33            let high = match b {
34                b'0'..=b'9' => b - b'0',
35                b'a'..=b'f' => b - b'a' + 10,
36                b'A'..=b'F' => b - b'A' + 10,
37                b' ' | b'\r' | b'\n' | b'\t' => continue,
38                _ => panic!("invalid character"),
39            };
40
41            let low = match next!() {
42                None => panic!("expected even number of hex characters"),
43                Some(b) => match b {
44                    b'0'..=b'9' => b - b'0',
45                    b'a'..=b'f' => b - b'a' + 10,
46                    b'A'..=b'F' => b - b'A' + 10,
47                    _ => panic!("expected hex character"),
48                },
49            };
50
51            self.i = i;
52            let val = (high << 4) | low;
53            return (self, Some(val));
54        }
55        (self, None)
56    }
57}
58
59impl Hex<&[&str]> {
60    pub const fn output_len(&self) -> usize {
61        let mut i = 0;
62        let mut ans = 0;
63
64        while i < self.0.len() {
65            let mut iter = Iter::new(self.0[i]);
66            while let (next, Some(_)) = iter.next() {
67                iter = next;
68                ans += 1;
69            }
70            i += 1;
71        }
72        ans
73    }
74
75    pub const fn const_eval<const N: usize>(&self) -> [u8; N] {
76        let mut buf = [0; N];
77        let mut pos = 0;
78
79        let mut i = 0;
80        while i < self.0.len() {
81            let mut iter = Iter::new(self.0[i]);
82            while let (next, Some(val)) = iter.next() {
83                iter = next;
84                buf[pos] = val;
85                pos += 1;
86            }
87            i += 1;
88        }
89        assert!(pos == N);
90        buf
91    }
92}
93
94impl Hex<&str> {
95    pub const fn output_len(&self) -> usize {
96        let ss: &[&str] = &[self.0];
97        Hex(ss).output_len()
98    }
99    pub const fn const_eval<const N: usize>(&self) -> [u8; N] {
100        let ss: &[&str] = &[self.0];
101        Hex(ss).const_eval()
102    }
103}
104
105impl<const L: usize> Hex<[&str; L]> {
106    pub const fn output_len(&self) -> usize {
107        let ss: &[&str] = &self.0;
108        Hex(ss).output_len()
109    }
110    pub const fn const_eval<const N: usize>(&self) -> [u8; N] {
111        let ss: &[&str] = &self.0;
112        Hex(ss).const_eval()
113    }
114}
115
116/// Converts hexadecimal string slices to a byte array.
117///
118/// It accepts the following characters in the input string:
119///
120/// - `'0'...'9'`, `'a'...'f'`, `'A'...'F'` — hex characters which will be used
121///   in construction of the output byte array
122/// - `' '`, `'\r'`, `'\n'`, `'\t'` — formatting characters which will be
123///   ignored
124///
125/// This macro is [const-context only](./index.html#const-context-only).
126///
127/// # Examples
128/// ```
129/// use const_str::hex;
130///
131/// const DATA: [u8; 4] = hex!("01020304");
132/// assert_eq!(DATA, [1, 2, 3, 4]);
133///
134/// assert_eq!(hex!("a1 b2 c3 d4"), [0xA1, 0xB2, 0xC3, 0xD4]);
135/// assert_eq!(hex!("E5 E6 90 92"), [0xE5, 0xE6, 0x90, 0x92]);
136///
137/// assert_eq!(hex!(["0a0B", "0C0d"]), [10, 11, 12, 13]);
138///
139/// const S1: &str = "00010203 04050607 08090a0b 0c0d0e0f";
140/// const B1: &[u8] = &hex!(S1);
141/// const B2: &[u8] = &hex!([
142///     "00010203 04050607", // first half
143///     "08090a0b 0c0d0e0f", // second half
144/// ]);
145///
146/// assert_eq!(B1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
147/// assert_eq!(B2, B1);
148/// ```
149#[macro_export]
150macro_rules! hex {
151    ($s: expr) => {{
152        const OUTPUT_LEN: usize = $crate::__ctfe::Hex($s).output_len();
153        const OUTPUT_BUF: [u8; OUTPUT_LEN] = $crate::__ctfe::Hex($s).const_eval();
154        OUTPUT_BUF
155    }};
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn test_hex() {
164        const DATA: [u8; 4] = hex!("01020304");
165        assert_eq!(DATA, [1, 2, 3, 4]);
166
167        const DATA2: [u8; 4] = hex!("a1 b2 c3 d4");
168        assert_eq!(DATA2, [0xA1, 0xB2, 0xC3, 0xD4]);
169
170        const DATA3: [u8; 4] = hex!("E5 E6 90 92");
171        assert_eq!(DATA3, [0xE5, 0xE6, 0x90, 0x92]);
172
173        const DATA4: [u8; 4] = hex!(["0a0B", "0C0d"]);
174        assert_eq!(DATA4, [10, 11, 12, 13]);
175
176        const S1: &str = "00010203 04050607 08090a0b 0c0d0e0f";
177        const B1: &[u8] = &hex!(S1);
178        const B2: &[u8] = &hex!([
179            "00010203 04050607", // first half
180            "08090a0b 0c0d0e0f", // second half
181        ]);
182
183        assert_eq!(B1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
184        assert_eq!(B2, B1);
185
186        const EMPTY: [u8; 0] = hex!("");
187        assert_eq!(EMPTY, []);
188
189        const WITH_NEWLINE: [u8; 2] = hex!("0A\n0B");
190        assert_eq!(WITH_NEWLINE, [10, 11]);
191
192        const WITH_TAB: [u8; 2] = hex!("0C\t0D");
193        assert_eq!(WITH_TAB, [12, 13]);
194    }
195
196    #[test]
197    fn test_hex_runtime() {
198        // Runtime tests for Hex with &[&str]
199        let strs: &[&str] = &["01", "02", "03"];
200        let hex_slice = Hex(strs);
201        assert_eq!(hex_slice.output_len(), 3);
202        let buf: [u8; 3] = hex_slice.const_eval();
203        assert_eq!(buf, [1, 2, 3]);
204
205        // Test with single string
206        let single = "FF00";
207        let hex_str = Hex(single);
208        assert_eq!(hex_str.output_len(), 2);
209        let buf2: [u8; 2] = hex_str.const_eval();
210        assert_eq!(buf2, [0xFF, 0x00]);
211
212        // Test with array
213        let arr = ["AB", "CD"];
214        let hex_arr = Hex(arr);
215        assert_eq!(hex_arr.output_len(), 2);
216        let buf3: [u8; 2] = hex_arr.const_eval();
217        assert_eq!(buf3, [0xAB, 0xCD]);
218
219        // Test with whitespace
220        let with_space = "12 34\t56\n78";
221        let hex_ws = Hex(with_space);
222        assert_eq!(hex_ws.output_len(), 4);
223        let buf4: [u8; 4] = hex_ws.const_eval();
224        assert_eq!(buf4, [0x12, 0x34, 0x56, 0x78]);
225
226        // Test uppercase and lowercase
227        let mixed = "aAbBcC";
228        let hex_mixed = Hex(mixed);
229        assert_eq!(hex_mixed.output_len(), 3);
230        let buf5: [u8; 3] = hex_mixed.const_eval();
231        assert_eq!(buf5, [0xAA, 0xBB, 0xCC]);
232    }
233}