const-str 1.1.0

compile-time string operations
Documentation
pub struct Hex<T>(pub T);

struct Iter<'a> {
    s: &'a [u8],
    i: usize,
}

impl<'a> Iter<'a> {
    const fn new(s: &'a str) -> Self {
        Self {
            s: s.as_bytes(),
            i: 0,
        }
    }

    const fn next(mut self) -> (Self, Option<u8>) {
        let mut i = self.i;
        let s = self.s;

        macro_rules! next {
            () => {{
                if i < s.len() {
                    let b = s[i];
                    i += 1;
                    Some(b)
                } else {
                    None
                }
            }};
        }

        while let Some(b) = next!() {
            let high = match b {
                b'0'..=b'9' => b - b'0',
                b'a'..=b'f' => b - b'a' + 10,
                b'A'..=b'F' => b - b'A' + 10,
                b' ' | b'\r' | b'\n' | b'\t' => continue,
                _ => panic!("invalid character"),
            };

            let low = match next!() {
                None => panic!("expected even number of hex characters"),
                Some(b) => match b {
                    b'0'..=b'9' => b - b'0',
                    b'a'..=b'f' => b - b'a' + 10,
                    b'A'..=b'F' => b - b'A' + 10,
                    _ => panic!("expected hex character"),
                },
            };

            self.i = i;
            let val = (high << 4) | low;
            return (self, Some(val));
        }
        (self, None)
    }
}

impl Hex<&[&str]> {
    pub const fn output_len(&self) -> usize {
        let mut i = 0;
        let mut ans = 0;

        while i < self.0.len() {
            let mut iter = Iter::new(self.0[i]);
            while let (next, Some(_)) = iter.next() {
                iter = next;
                ans += 1;
            }
            i += 1;
        }
        ans
    }

    pub const fn const_eval<const N: usize>(&self) -> [u8; N] {
        let mut buf = [0; N];
        let mut pos = 0;

        let mut i = 0;
        while i < self.0.len() {
            let mut iter = Iter::new(self.0[i]);
            while let (next, Some(val)) = iter.next() {
                iter = next;
                buf[pos] = val;
                pos += 1;
            }
            i += 1;
        }
        assert!(pos == N);
        buf
    }
}

impl Hex<&str> {
    pub const fn output_len(&self) -> usize {
        let ss: &[&str] = &[self.0];
        Hex(ss).output_len()
    }
    pub const fn const_eval<const N: usize>(&self) -> [u8; N] {
        let ss: &[&str] = &[self.0];
        Hex(ss).const_eval()
    }
}

impl<const L: usize> Hex<[&str; L]> {
    pub const fn output_len(&self) -> usize {
        let ss: &[&str] = &self.0;
        Hex(ss).output_len()
    }
    pub const fn const_eval<const N: usize>(&self) -> [u8; N] {
        let ss: &[&str] = &self.0;
        Hex(ss).const_eval()
    }
}

/// Converts hexadecimal string slices to a byte array.
///
/// It accepts the following characters in the input string:
///
/// - `'0'...'9'`, `'a'...'f'`, `'A'...'F'` — hex characters which will be used
///   in construction of the output byte array
/// - `' '`, `'\r'`, `'\n'`, `'\t'` — formatting characters which will be
///   ignored
///
/// This macro is [const-context only](./index.html#const-context-only).
///
/// # Examples
/// ```
/// use const_str::hex;
///
/// const DATA: [u8; 4] = hex!("01020304");
/// assert_eq!(DATA, [1, 2, 3, 4]);
///
/// assert_eq!(hex!("a1 b2 c3 d4"), [0xA1, 0xB2, 0xC3, 0xD4]);
/// assert_eq!(hex!("E5 E6 90 92"), [0xE5, 0xE6, 0x90, 0x92]);
///
/// assert_eq!(hex!(["0a0B", "0C0d"]), [10, 11, 12, 13]);
///
/// const S1: &str = "00010203 04050607 08090a0b 0c0d0e0f";
/// const B1: &[u8] = &hex!(S1);
/// const B2: &[u8] = &hex!([
///     "00010203 04050607", // first half
///     "08090a0b 0c0d0e0f", // second half
/// ]);
///
/// assert_eq!(B1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
/// assert_eq!(B2, B1);
/// ```
#[macro_export]
macro_rules! hex {
    ($s: expr) => {{
        const OUTPUT_LEN: usize = $crate::__ctfe::Hex($s).output_len();
        const OUTPUT_BUF: [u8; OUTPUT_LEN] = $crate::__ctfe::Hex($s).const_eval();
        OUTPUT_BUF
    }};
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_hex() {
        const DATA: [u8; 4] = hex!("01020304");
        assert_eq!(DATA, [1, 2, 3, 4]);

        const DATA2: [u8; 4] = hex!("a1 b2 c3 d4");
        assert_eq!(DATA2, [0xA1, 0xB2, 0xC3, 0xD4]);

        const DATA3: [u8; 4] = hex!("E5 E6 90 92");
        assert_eq!(DATA3, [0xE5, 0xE6, 0x90, 0x92]);

        const DATA4: [u8; 4] = hex!(["0a0B", "0C0d"]);
        assert_eq!(DATA4, [10, 11, 12, 13]);

        const S1: &str = "00010203 04050607 08090a0b 0c0d0e0f";
        const B1: &[u8] = &hex!(S1);
        const B2: &[u8] = &hex!([
            "00010203 04050607", // first half
            "08090a0b 0c0d0e0f", // second half
        ]);

        assert_eq!(B1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
        assert_eq!(B2, B1);

        const EMPTY: [u8; 0] = hex!("");
        assert_eq!(EMPTY, []);

        const WITH_NEWLINE: [u8; 2] = hex!("0A\n0B");
        assert_eq!(WITH_NEWLINE, [10, 11]);

        const WITH_TAB: [u8; 2] = hex!("0C\t0D");
        assert_eq!(WITH_TAB, [12, 13]);
    }

    #[test]
    fn test_hex_runtime() {
        // Runtime tests for Hex with &[&str]
        let strs: &[&str] = &["01", "02", "03"];
        let hex_slice = Hex(strs);
        assert_eq!(hex_slice.output_len(), 3);
        let buf: [u8; 3] = hex_slice.const_eval();
        assert_eq!(buf, [1, 2, 3]);

        // Test with single string
        let single = "FF00";
        let hex_str = Hex(single);
        assert_eq!(hex_str.output_len(), 2);
        let buf2: [u8; 2] = hex_str.const_eval();
        assert_eq!(buf2, [0xFF, 0x00]);

        // Test with array
        let arr = ["AB", "CD"];
        let hex_arr = Hex(arr);
        assert_eq!(hex_arr.output_len(), 2);
        let buf3: [u8; 2] = hex_arr.const_eval();
        assert_eq!(buf3, [0xAB, 0xCD]);

        // Test with whitespace
        let with_space = "12 34\t56\n78";
        let hex_ws = Hex(with_space);
        assert_eq!(hex_ws.output_len(), 4);
        let buf4: [u8; 4] = hex_ws.const_eval();
        assert_eq!(buf4, [0x12, 0x34, 0x56, 0x78]);

        // Test uppercase and lowercase
        let mixed = "aAbBcC";
        let hex_mixed = Hex(mixed);
        assert_eq!(hex_mixed.output_len(), 3);
        let buf5: [u8; 3] = hex_mixed.const_eval();
        assert_eq!(buf5, [0xAA, 0xBB, 0xCC]);
    }
}