109 lines
2.6 KiB
Rust
109 lines
2.6 KiB
Rust
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<Self, HexError>;
|
|
}
|
|
|
|
pub trait ParseHex<T> {
|
|
fn parse_hex(&self) -> Result<T, HexError>;
|
|
}
|
|
|
|
impl<T, V: AsRef<str>> ParseHex<T> for V
|
|
where
|
|
T: FromHex,
|
|
{
|
|
fn parse_hex(&self) -> Result<T, HexError> {
|
|
T::from_hex(self.as_ref())
|
|
}
|
|
}
|
|
|
|
macro_rules! impl_from_hex {
|
|
($ty:ty) => {
|
|
impl FromHex for $ty {
|
|
fn from_hex(hex: &str) -> Result<Self, HexError> {
|
|
if let Some(without_prefix) = hex.strip_prefix("0x") {
|
|
return Self::from_hex(without_prefix);
|
|
}
|
|
Ok(hex
|
|
.chars()
|
|
.map(|c| hex_digit!(c))
|
|
.collect::<Result<Vec<_>, _>>()?
|
|
.into_iter()
|
|
.rev()
|
|
.enumerate()
|
|
.map(|(idx, value)| -> Result<_, TryFromIntError> {
|
|
Ok((16 as $ty).pow(idx.try_into()?) * value)
|
|
})
|
|
.collect::<Result<Vec<_>, _>>()?
|
|
.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);
|
|
}
|
|
}
|
|
}
|