hlctl/src/hlwm/octal.rs

100 lines
2.2 KiB
Rust

use std::num::TryFromIntError;
use thiserror::Error;
macro_rules! octal_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),
_ => Err(OctalError::InvalidCharacter($digit)),
}
};
}
#[derive(Debug, Error, Clone)]
pub enum OctalError {
#[error("invalid/non-octal character: [{0}]")]
InvalidCharacter(char),
#[error("try_from_int failed: [{0}]")]
TryFromIntError(#[from] TryFromIntError),
}
pub trait FromOctal: Sized {
fn from_octal(octal: &str) -> Result<Self, OctalError>;
}
pub trait ParseOctal<T> {
fn parse_octal(&self) -> Result<T, OctalError>;
}
impl<T, V: AsRef<str>> ParseOctal<T> for V
where
T: FromOctal,
{
fn parse_octal(&self) -> Result<T, OctalError> {
T::from_octal(self.as_ref())
}
}
macro_rules! impl_from_octal {
($ty:ty) => {
impl FromOctal for $ty {
fn from_octal(octal: &str) -> Result<Self, OctalError> {
if let Some(without_prefix) = octal.strip_prefix("0o") {
return Self::from_octal(without_prefix);
}
Ok(octal
.chars()
.map(|c| octal_digit!(c))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.rev()
.enumerate()
.map(|(idx, value)| -> Result<_, TryFromIntError> {
Ok((8 as $ty).pow(idx.try_into()?) * value)
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.sum())
}
}
};
($($ty:ty),+) => {
$(
impl_from_octal!($ty);
)+
};
}
impl_from_octal!(i8, i16, i32, i64, u8, u16, u32, u64);
#[cfg(test)]
mod test {
use super::FromOctal;
macro_rules! octal {
($val:literal) => {
(stringify!($val), $val as u32)
};
}
#[test]
fn test_hex_parse() {
for (str, expected) in [
octal!(0o15),
octal!(0o777),
octal!(0o1337),
octal!(0o0),
octal!(0o10),
] {
let actual = u32::from_octal(str).expect("from_hex");
assert_eq!(expected, actual);
}
}
}