1#![expect(
6 clippy::cast_lossless,
7 clippy::cast_possible_truncation,
8 trivial_numeric_casts,
9 reason = "Macros made me do it.",
10)]
11
12use crate::int;
13
14
15
16pub trait HexToUnsigned: Sized {
45 fn htou(hex: &[u8]) -> Option<Self>;
47}
48
49macro_rules! unsigned {
54 ($($ty:ty)+) => ($(
55 impl HexToUnsigned for $ty {
56 #[inline]
57 fn htou(mut src: &[u8]) -> Option<Self> {
59 if src.is_empty() { return None; }
61
62 while let [ b'0', rest @ .. ] = src { src = rest; }
64
65 if size_of::<Self>() * 2 < src.len() { return None; }
68
69 let mut out: Self = 0;
71 while let [ v, rest @ .. ] = src {
72 out *= 16;
73 let v = (*v as char).to_digit(16)?;
74 out += v as Self;
75 src = rest;
76 }
77
78 Some(out)
79 }
80 }
81 )+);
82}
83
84unsigned!(u8 u16 u32 u64 u128);
85
86impl HexToUnsigned for usize {
87 #[inline]
88 fn htou(src: &[u8]) -> Option<Self> {
90 <int!(@alias usize)>::htou(src).map(|n| n as Self)
91 }
92}
93
94
95
96pub trait HexToSigned: Sized {
123 fn htoi(hex: &[u8]) -> Option<Self>;
125}
126
127macro_rules! signed {
132 ($($ty:ident)+) => ($(
133 impl HexToSigned for $ty {
134 #[inline]
135 fn htoi(src: &[u8]) -> Option<Self> {
137 <int!(@flip $ty)>::htou(src).map(<int!(@flip $ty)>::cast_signed)
138 }
139 }
140 )+);
141}
142
143signed!{ i8 i16 i32 i64 i128 isize }
144
145
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[cfg(not(miri))]
152 const SAMPLE_SIZE: usize = 1_000_000;
153
154 #[cfg(miri)]
155 const SAMPLE_SIZE: usize = 500; macro_rules! test_all {
158 ($tfn:ident, $hfn:ident, $ty:ty) => (
159 #[test]
160 fn $tfn() {
161 for i in <$ty>::MIN..=<$ty>::MAX { hex!($hfn, i, $ty); }
162 }
163 );
164 }
165 macro_rules! test_rng {
166 ($tfn:ident, $hfn:ident, $ty:ident) => (
167 #[test]
168 fn $tfn() {
169 let mut rng = fastrand::Rng::new();
171 for i in std::iter::repeat_with(|| rng.$ty(..)).take(SAMPLE_SIZE) {
172 hex!($hfn, i, $ty);
173 }
174
175 for i in [<$ty>::MIN, 0, <$ty>::MAX] { hex!($hfn, i, $ty); }
177 }
178 );
179 }
180
181 macro_rules! hex {
182 ($fn:ident, $num:ident, $ty:ty) => (
183 let mut s = format!("{:x}", $num);
185 assert_eq!(<$ty>::$fn(s.as_bytes()), Some($num));
186 s.make_ascii_uppercase();
187 assert_eq!(<$ty>::$fn(s.as_bytes()), Some($num));
188
189 let width = std::mem::size_of::<$ty>() * 2;
191 if s.len() < width {
192 while s.len() < width { s.insert(0, '0'); }
193 assert_eq!(<$ty>::$fn(s.as_bytes()), Some($num));
194 s.make_ascii_lowercase();
195 assert_eq!(<$ty>::$fn(s.as_bytes()), Some($num));
196 }
197 );
198 }
199
200 test_all!(t_u8, htou, u8);
202 #[cfg(not(miri))] test_all!(t_u16, htou, u16);
203
204 test_all!(t_i8, htoi, i8);
205 #[cfg(not(miri))] test_all!(t_i16, htoi, i16);
206
207 #[cfg(miri)] test_rng!(t_u16, htou, u16);
209 test_rng!(t_u32, htou, u32);
210 test_rng!(t_u64, htou, u64);
211 test_rng!(t_u128, htou, u128);
212 test_rng!(t_usize, htou, usize);
213
214 #[cfg(miri)] test_rng!(t_i16, htoi, i16);
215 test_rng!(t_i32, htoi, i32);
216 test_rng!(t_i64, htoi, i64);
217 test_rng!(t_i128, htoi, i128);
218 test_rng!(t_isize, htoi, isize);
219}