use std::num::TryFromIntError; use thiserror::Error; macro_rules! hex_digit { ($digit:expr) => { match $digit { '0' => Ok(0), '1' => Ok(1), '2' => Ok(2), '3' => Ok(3), '4' => Ok(4), '5' => Ok(5), '6' => Ok(6), '7' => Ok(7), '8' => Ok(8), '9' => Ok(9), 'a' | 'A' => Ok(10), 'b' | 'B' => Ok(11), 'c' | 'C' => Ok(12), 'd' | 'D' => Ok(13), 'e' | 'E' => Ok(14), 'f' | 'F' => Ok(15), _ => Err(HexError::InvalidCharacter($digit)), } }; } #[derive(Debug, Error, Clone)] pub enum HexError { #[error("invalid/non-hex character: [{0}]")] InvalidCharacter(char), #[error("try_from_int failed: [{0}]")] TryFromIntError(#[from] TryFromIntError), } pub trait FromHex: Sized { fn from_hex(hex: &str) -> Result; } pub trait ParseHex { fn parse_hex(&self) -> Result; } impl> ParseHex for V where T: FromHex, { fn parse_hex(&self) -> Result { T::from_hex(self.as_ref()) } } macro_rules! impl_from_hex { ($ty:ty) => { impl FromHex for $ty { fn from_hex(hex: &str) -> Result { if let Some(without_prefix) = hex.strip_prefix("0x") { return Self::from_hex(without_prefix); } Ok(hex .chars() .map(|c| hex_digit!(c)) .collect::, _>>()? .into_iter() .rev() .enumerate() .map(|(idx, value)| -> Result<_, TryFromIntError> { Ok((16 as $ty).pow(idx.try_into()?) * value) }) .collect::, _>>()? .into_iter() .sum()) } } }; ($($ty:ty),+) => { $( impl_from_hex!($ty); )+ }; } impl_from_hex!(i8, i16, i32, i64, u8, u16, u32, u64); #[cfg(test)] mod test { use super::FromHex; macro_rules! hex { ($val:literal) => { (stringify!($val), $val as u32) }; } #[test] fn test_hex_parse() { for (str, expected) in [ hex!(0x15), hex!(0xFFFF), hex!(0xD3ADBEEF), hex!(0x0), hex!(0x10), ] { let actual = u32::from_hex(str).expect("from_hex"); assert_eq!(expected, actual); } } }