#![expect(
clippy::cast_lossless,
clippy::cast_possible_truncation,
trivial_numeric_casts,
reason = "Macros made me do it.",
)]
use crate::int;
pub trait HexToUnsigned: Sized {
fn htou(hex: &[u8]) -> Option<Self>;
}
macro_rules! unsigned {
($($ty:ty)+) => ($(
impl HexToUnsigned for $ty {
#[inline]
fn htou(mut src: &[u8]) -> Option<Self> {
if src.is_empty() { return None; }
while let [ b'0', rest @ .. ] = src { src = rest; }
if size_of::<Self>() * 2 < src.len() { return None; }
let mut out: Self = 0;
while let [ v, rest @ .. ] = src {
out *= 16;
let v = (*v as char).to_digit(16)?;
out += v as Self;
src = rest;
}
Some(out)
}
}
)+);
}
unsigned!(u8 u16 u32 u64 u128);
impl HexToUnsigned for usize {
#[inline]
fn htou(src: &[u8]) -> Option<Self> {
<int!(@alias usize)>::htou(src).map(|n| n as Self)
}
}
pub trait HexToSigned: Sized {
fn htoi(hex: &[u8]) -> Option<Self>;
}
macro_rules! signed {
($($ty:ident)+) => ($(
impl HexToSigned for $ty {
#[inline]
fn htoi(src: &[u8]) -> Option<Self> {
<int!(@flip $ty)>::htou(src).map(<int!(@flip $ty)>::cast_signed)
}
}
)+);
}
signed!{ i8 i16 i32 i64 i128 isize }
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(miri))]
const SAMPLE_SIZE: usize = 1_000_000;
#[cfg(miri)]
const SAMPLE_SIZE: usize = 500;
macro_rules! test_all {
($tfn:ident, $hfn:ident, $ty:ty) => (
#[test]
fn $tfn() {
for i in <$ty>::MIN..=<$ty>::MAX { hex!($hfn, i, $ty); }
}
);
}
macro_rules! test_rng {
($tfn:ident, $hfn:ident, $ty:ident) => (
#[test]
fn $tfn() {
let mut rng = fastrand::Rng::new();
for i in std::iter::repeat_with(|| rng.$ty(..)).take(SAMPLE_SIZE) {
hex!($hfn, i, $ty);
}
for i in [<$ty>::MIN, 0, <$ty>::MAX] { hex!($hfn, i, $ty); }
}
);
}
macro_rules! hex {
($fn:ident, $num:ident, $ty:ty) => (
let mut s = format!("{:x}", $num);
assert_eq!(<$ty>::$fn(s.as_bytes()), Some($num));
s.make_ascii_uppercase();
assert_eq!(<$ty>::$fn(s.as_bytes()), Some($num));
let width = std::mem::size_of::<$ty>() * 2;
if s.len() < width {
while s.len() < width { s.insert(0, '0'); }
assert_eq!(<$ty>::$fn(s.as_bytes()), Some($num));
s.make_ascii_lowercase();
assert_eq!(<$ty>::$fn(s.as_bytes()), Some($num));
}
);
}
test_all!(t_u8, htou, u8);
#[cfg(not(miri))] test_all!(t_u16, htou, u16);
test_all!(t_i8, htoi, i8);
#[cfg(not(miri))] test_all!(t_i16, htoi, i16);
#[cfg(miri)] test_rng!(t_u16, htou, u16);
test_rng!(t_u32, htou, u32);
test_rng!(t_u64, htou, u64);
test_rng!(t_u128, htou, u128);
test_rng!(t_usize, htou, usize);
#[cfg(miri)] test_rng!(t_i16, htoi, i16);
test_rng!(t_i32, htoi, i32);
test_rng!(t_i64, htoi, i64);
test_rng!(t_i128, htoi, i128);
test_rng!(t_isize, htoi, isize);
}