#![deny(missing_docs)]
use core::str::FromStr;
use num_traits::identities::Zero;
use num_traits::{sign, CheckedAdd, CheckedMul, CheckedSub, Num};
fn check_range<T: Ord + std::fmt::Display>(val: T, min: T, max: T) -> Result<T, String>
where
T: FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
if val > max {
Err(format!("exceeds maximum of {}", max))
} else if val < min {
Err(format!("exceeds minimum of {}", min))
} else {
Ok(val)
}
}
pub fn number_range<T: Ord + PartialOrd + std::fmt::Display>(
s: &str,
min: T,
max: T,
) -> Result<T, String>
where
T: FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
debug_assert!(min <= max, "minimum of {} exceeds maximum of {}", min, max);
let val = s.parse::<T>().map_err(stringify)?;
check_range(val, min, max)
}
static OVERFLOW_MSG: &str = "number too large to fit in target type";
fn stringify<T: std::fmt::Display>(e: T) -> String {
format!("{}", e)
}
#[derive(Copy, Clone)]
enum SiPrefix {
Yotta,
Zetta,
Exa,
Peta,
Tera,
Giga,
Mega,
Kilo,
}
impl From<SiPrefix> for char {
fn from(p: SiPrefix) -> Self {
match p {
SiPrefix::Yotta => 'Y',
SiPrefix::Zetta => 'Z',
SiPrefix::Exa => 'E',
SiPrefix::Peta => 'P',
SiPrefix::Tera => 'T',
SiPrefix::Giga => 'G',
SiPrefix::Mega => 'M',
SiPrefix::Kilo => 'k',
}
}
}
impl SiPrefix {
fn from_char(symbol: char) -> Option<Self> {
match symbol {
'Y' => Some(Self::Yotta),
'Z' => Some(Self::Zetta),
'E' => Some(Self::Exa),
'P' => Some(Self::Peta),
'T' => Some(Self::Tera),
'G' => Some(Self::Giga),
'M' => Some(Self::Mega),
'k' => Some(Self::Kilo),
_ => None,
}
}
fn multiplier(&self) -> u128 {
match self {
SiPrefix::Yotta => 1_000_000_000_000_000_000_000_000,
SiPrefix::Zetta => 1_000_000_000_000_000_000_000,
SiPrefix::Exa => 1_000_000_000_000_000_000,
SiPrefix::Peta => 1_000_000_000_000_000,
SiPrefix::Tera => 1_000_000_000_000,
SiPrefix::Giga => 1_000_000_000,
SiPrefix::Mega => 1_000_000,
SiPrefix::Kilo => 1_000,
}
}
fn digits(&self) -> usize {
match self {
SiPrefix::Yotta => 24,
SiPrefix::Zetta => 21,
SiPrefix::Exa => 18,
SiPrefix::Peta => 15,
SiPrefix::Tera => 12,
SiPrefix::Giga => 9,
SiPrefix::Mega => 6,
SiPrefix::Kilo => 3,
}
}
}
fn parse_post<T>(mut post: String, digits: usize) -> Result<T, String>
where
<T as std::str::FromStr>::Err: std::fmt::Display,
T: std::cmp::PartialOrd + std::str::FromStr,
{
if post.len() > digits {
Err(String::from("not an integer"))
} else {
while post.len() < digits {
post.push('0');
}
post.parse::<T>().map_err(stringify)
}
}
pub fn si_number<T>(s: &str) -> Result<T, String>
where
<T as std::convert::TryFrom<u128>>::Error: std::fmt::Display,
<T as std::str::FromStr>::Err: std::fmt::Display,
T: CheckedAdd,
T: CheckedMul,
T: CheckedSub,
T: FromStr,
T: std::cmp::PartialOrd,
T: TryFrom<u128>,
T: Zero,
{
if let Some(si_prefix) = s.chars().find_map(SiPrefix::from_char) {
let multiplier: T = T::try_from(si_prefix.multiplier()).map_err(|_| OVERFLOW_MSG)?;
let (pre_si, post_si) = s.split_once(char::from(si_prefix)).unwrap();
if pre_si.is_empty() {
return Err("no value found before SI symbol".to_string());
}
let (pre, post) = if !post_si.is_empty() {
(
pre_si.parse::<T>().map_err(stringify)?,
parse_post(post_si.to_string(), si_prefix.digits())?,
)
} else if let Some((pre_dec, post_dec)) = s.split_once('.') {
let mut post_dec: String = post_dec.to_string();
post_dec.pop(); let post_dec = parse_post(post_dec, si_prefix.digits())?;
(pre_dec.parse::<T>().map_err(stringify)?, post_dec)
} else {
(pre_si.parse::<T>().map_err(stringify)?, T::zero())
};
let pre = pre.checked_mul(&multiplier).ok_or(OVERFLOW_MSG)?;
if pre >= T::zero() {
pre.checked_add(&post)
} else {
pre.checked_sub(&post)
}
.ok_or_else(|| OVERFLOW_MSG.to_string())
} else {
s.parse::<T>().map_err(stringify)
}
}
pub fn si_number_range<T: Ord + PartialOrd + std::fmt::Display>(
s: &str,
min: T,
max: T,
) -> Result<T, String>
where
<T as std::convert::TryFrom<u128>>::Error: std::fmt::Display,
<T as std::str::FromStr>::Err: std::fmt::Display,
T: CheckedAdd,
T: CheckedMul,
T: CheckedSub,
T: FromStr,
T: std::cmp::PartialOrd,
T: TryFrom<u128>,
T: Zero,
{
let val = si_number(s)?;
check_range(val, min, max)
}
pub fn maybe_hex<T: Num + sign::Unsigned>(s: &str) -> Result<T, String>
where
<T as num_traits::Num>::FromStrRadixErr: std::fmt::Display,
{
const HEX_PREFIX: &str = "0x";
const HEX_PREFIX_LEN: usize = HEX_PREFIX.len();
let result = if s.to_ascii_lowercase().starts_with(HEX_PREFIX) {
T::from_str_radix(&s[HEX_PREFIX_LEN..], 16)
} else {
T::from_str_radix(s, 10)
};
match result {
Ok(v) => Ok(v),
Err(e) => Err(format!("{}", e)),
}
}
pub fn maybe_hex_range<T: Num + sign::Unsigned>(s: &str, min: T, max: T) -> Result<T, String>
where
<T as num_traits::Num>::FromStrRadixErr: std::fmt::Display,
<T as std::str::FromStr>::Err: std::fmt::Display,
T: FromStr,
T: std::fmt::Display,
T: std::cmp::Ord,
{
let val = maybe_hex(s)?;
check_range(val, min, max)
}