#![deny(missing_docs)]
use core::{iter, str::FromStr};
use num_traits::identities::Zero;
use num_traits::{sign, CheckedAdd, CheckedMul, CheckedSub, Num};
fn check_range<T>(val: T, min: T, max: T) -> Result<T, String>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
T: Ord,
T: std::fmt::Display,
{
if val > max {
Err(format!("exceeds maximum of {max}"))
} else if val < min {
Err(format!("less than minimum of {min}"))
} else {
Ok(val)
}
}
pub fn number_range<T>(s: &str, min: T, max: T) -> Result<T, String>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
T: Ord,
T: PartialOrd,
T: std::fmt::Display,
{
debug_assert!(min <= max, "minimum of {min} exceeds maximum of {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 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' | '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 FromStr>::Err: std::fmt::Display,
T: PartialOrd + FromStr,
{
if let Some(zeros) = digits.checked_sub(post.len()) {
post.extend(iter::repeat('0').take(zeros));
post.parse::<T>().map_err(stringify)
} else {
Err(String::from("not an integer"))
}
}
pub fn si_number<T>(s: &str) -> Result<T, String>
where
<T as TryFrom<u128>>::Error: std::fmt::Display,
<T as FromStr>::Err: std::fmt::Display,
T: CheckedAdd,
T: CheckedMul,
T: CheckedSub,
T: FromStr,
T: PartialOrd,
T: TryFrom<u128>,
T: Zero,
{
if let Some(si_prefix_index) = s.find(|c| SiPrefix::from_char(c).is_some()) {
let si_prefix = SiPrefix::from_char(s.as_bytes()[si_prefix_index] as char).unwrap();
let multiplier: T = T::try_from(si_prefix.multiplier()).map_err(|_| OVERFLOW_MSG)?;
let (pre_si, post_si) = s.split_at(si_prefix_index);
let post_si = &post_si[1..];
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.chars()
.filter(|&c| c != '_')
.collect::<String>()
.parse::<T>()
.map_err(stringify)
}
}
pub fn si_number_range<T>(s: &str, min: T, max: T) -> Result<T, String>
where
<T as TryFrom<u128>>::Error: std::fmt::Display,
<T as FromStr>::Err: std::fmt::Display,
T: CheckedAdd,
T: CheckedMul,
T: CheckedSub,
T: FromStr,
T: PartialOrd,
T: TryFrom<u128>,
T: Zero,
T: Ord,
T: PartialOrd,
T: std::fmt::Display,
{
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>::FromStrRadixErr: std::fmt::Display,
{
const HEX_PREFIX: &str = "0x";
const HEX_PREFIX_UPPER: &str = "0X";
const HEX_PREFIX_LEN: usize = HEX_PREFIX.len();
let result = if s.starts_with(HEX_PREFIX) || s.starts_with(HEX_PREFIX_UPPER) {
T::from_str_radix(&s[HEX_PREFIX_LEN..], 16)
} else {
T::from_str_radix(s, 10)
};
result.map_err(stringify)
}
pub fn maybe_hex_range<T>(s: &str, min: T, max: T) -> Result<T, String>
where
<T as Num>::FromStrRadixErr: std::fmt::Display,
<T as FromStr>::Err: std::fmt::Display,
T: FromStr,
T: std::fmt::Display,
T: Ord,
T: Num,
T: sign::Unsigned,
{
let val = maybe_hex(s)?;
check_range(val, min, max)
}
pub fn maybe_bin<T: Num + sign::Unsigned>(s: &str) -> Result<T, String>
where
<T as Num>::FromStrRadixErr: std::fmt::Display,
{
const BIN_PREFIX: &str = "0b";
const BIN_PREFIX_LEN: usize = BIN_PREFIX.len();
let result = if s.starts_with(BIN_PREFIX) {
T::from_str_radix(&s[BIN_PREFIX_LEN..], 2)
} else {
T::from_str_radix(s, 10)
};
result.map_err(stringify)
}
pub fn maybe_bin_range<T>(s: &str, min: T, max: T) -> Result<T, String>
where
<T as Num>::FromStrRadixErr: std::fmt::Display,
<T as FromStr>::Err: std::fmt::Display,
T: FromStr,
T: std::fmt::Display,
T: Ord,
T: Num,
T: sign::Unsigned,
{
let val = maybe_bin(s)?;
check_range(val, min, max)
}