fashex 0.0.12

Hexadecimal string encoding and decoding with best-effort SIMD acceleration.
Documentation
//! Generic implementations.

use core::mem::MaybeUninit;

use crate::error::InvalidInput;
use crate::util::lut16;

#[allow(unsafe_code, reason = "XXX")]
/// ## Safety
///
/// We assume that:
///
/// 1. `src.len() <= dst.len()`.
pub(crate) const unsafe fn encode_generic_unchecked<const UPPER: bool>(
    src: &[u8],
    dst: &mut [[MaybeUninit<u8>; 2]],
) {
    const LUT_ENCODE_LOWERCASE: [[u8; 2]; const { u8::MAX as usize + 1 }] = const {
        let mut lut = [[0; 2]; _];

        let mut i: usize = 0;

        while i <= u8::MAX as usize {
            lut[i][0] = lut16::<false>()[i >> 4];
            lut[i][1] = lut16::<false>()[i & 0b1111];

            i += 1;
        }

        lut
    };

    const LUT_ENCODE_UPPERCASE: [[u8; 2]; const { u8::MAX as usize + 1 }] = const {
        let mut lut = [[0; 2]; _];

        let mut i: usize = 0;

        while i <= u8::MAX as usize {
            lut[i][0] = lut16::<true>()[i >> 4];
            lut[i][1] = lut16::<true>()[i & 0b1111];

            i += 1;
        }

        lut
    };

    debug_assert!(
        src.len() <= dst.len(),
        "implementation bug: `src` should not be longer than `dst`"
    );

    let lut = if UPPER {
        &LUT_ENCODE_UPPERCASE
    } else {
        &LUT_ENCODE_LOWERCASE
    };

    let mut i = 0;

    while i < src.len() {
        unsafe {
            *dst.as_mut_ptr().add(i).cast::<[u8; 2]>() = lut[src[i] as usize];
        }

        i += 1;
    }
}

#[allow(unsafe_code, reason = "XXX")]
/// ## Safety
///
/// We assume that:
///
/// 1. `src.len() <= dst.len()`.
/// 2. When `VALIDATED`, the input contains only valid hexadecimal characters.
///
/// ## Errors
///
/// When `!VALIDATED` and the input contains invalid hexadecimal characters.
pub(crate) const unsafe fn decode_generic_unchecked<const VALIDATED: bool>(
    src: *const [[u8; 2]],
    dst: *mut [MaybeUninit<u8>],
) -> Result<(), InvalidInput> {
    const NIL: u8 = u8::MAX;

    const LUT: [u8; u8::MAX as usize + 1] = {
        let mut lut = [NIL; _];

        let mut i = 0;

        while i <= u8::MAX as usize {
            #[allow(clippy::cast_possible_truncation, reason = "XXX")]
            match i as u8 {
                b'0'..=b'9' => {
                    lut[i] = i as u8 - b'0';
                }
                b'a'..=b'f' => {
                    lut[i] = i as u8 - b'a' + 10;
                }
                b'A'..=b'F' => {
                    lut[i] = i as u8 - b'A' + 10;
                }
                _ => {}
            };

            i += 1;
        }

        lut
    };

    debug_assert!(
        src.len() <= dst.len(),
        "implementation bug: `src` should not be longer than `dst`"
    );

    let mut i = 0;

    while i < src.len() {
        let [hi, lo] = *((src.cast::<[u8; 2]>()).add(i));
        let [hi, lo] = [LUT[hi as usize], LUT[lo as usize]];

        if !VALIDATED && (hi | lo == NIL) {
            return Err(InvalidInput);
        }

        (&mut *(dst.cast::<MaybeUninit<u8>>()).add(i)).write(hi << 4 | lo);

        i += 1;
    }

    Ok(())
}

#[cfg(test)]
mod smoking {
    use super::*;
    use crate::backend::tests::{
        check_decode_validation_any_backend, check_encode_decode_any_backend,
    };

    #[test]
    fn test_encode_decode_generic() {
        check_encode_decode_any_backend::<true>(
            encode_generic_unchecked::<true>,
            decode_generic_unchecked::<false>,
        );
        check_encode_decode_any_backend::<false>(
            encode_generic_unchecked::<false>,
            decode_generic_unchecked::<false>,
        );
    }

    #[test]
    fn test_decode_validation_generic() {
        check_decode_validation_any_backend(decode_generic_unchecked::<false>);
    }
}