#![cfg_attr(not(feature = "std"), no_std)]
use core::{convert::TryFrom, fmt};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum UnitSystem {
Decimal,
Binary,
}
impl UnitSystem {
fn factor(self, prefix: u8) -> Option<u64> {
Some(match (self, prefix) {
(Self::Decimal, b'k') | (Self::Decimal, b'K') => 1_000,
(Self::Decimal, b'm') | (Self::Decimal, b'M') => 1_000_000,
(Self::Decimal, b'g') | (Self::Decimal, b'G') => 1_000_000_000,
(Self::Decimal, b't') | (Self::Decimal, b'T') => 1_000_000_000_000,
(Self::Decimal, b'p') | (Self::Decimal, b'P') => 1_000_000_000_000_000,
(Self::Decimal, b'e') | (Self::Decimal, b'E') => 1_000_000_000_000_000_000,
(Self::Binary, b'k') | (Self::Binary, b'K') => 1_u64 << 10,
(Self::Binary, b'm') | (Self::Binary, b'M') => 1_u64 << 20,
(Self::Binary, b'g') | (Self::Binary, b'G') => 1_u64 << 30,
(Self::Binary, b't') | (Self::Binary, b'T') => 1_u64 << 40,
(Self::Binary, b'p') | (Self::Binary, b'P') => 1_u64 << 50,
(Self::Binary, b'e') | (Self::Binary, b'E') => 1_u64 << 60,
_ => return None,
})
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ByteSuffix {
Deny,
Allow,
Require,
}
#[derive(Clone, Debug)]
pub struct Config {
unit_system: UnitSystem,
default_factor: u64,
byte_suffix: ByteSuffix,
}
impl Config {
pub const fn new() -> Self {
Self {
unit_system: UnitSystem::Decimal,
default_factor: 1,
byte_suffix: ByteSuffix::Allow,
}
}
pub const fn with_unit_system(mut self, unit_system: UnitSystem) -> Self {
self.unit_system = unit_system;
self
}
pub const fn with_binary(self) -> Self {
self.with_unit_system(UnitSystem::Binary)
}
pub const fn with_decimal(self) -> Self {
self.with_unit_system(UnitSystem::Decimal)
}
pub const fn with_default_factor(mut self, factor: u64) -> Self {
self.default_factor = factor;
self
}
pub const fn with_byte_suffix(mut self, byte_suffix: ByteSuffix) -> Self {
self.byte_suffix = byte_suffix;
self
}
pub fn parse_size<T: AsRef<[u8]>>(&self, src: T) -> Result<u64, Error> {
parse_size_inner(self, src.as_ref())
}
}
impl Default for Config {
fn default() -> Self {
Self::new()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum Error {
Empty,
InvalidDigit,
PosOverflow,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Empty => "cannot parse integer from empty string",
Self::InvalidDigit => "invalid digit found in string",
Self::PosOverflow => "number too large to fit in target type",
})
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
pub fn parse_size<T: AsRef<[u8]>>(src: T) -> Result<u64, Error> {
parse_size_inner(&Config::new(), src.as_ref())
}
fn parse_size_inner(cfg: &Config, mut src: &[u8]) -> Result<u64, Error> {
let mut multiply = cfg.default_factor;
match src {
[init @ .., b'b'] | [init @ .., b'B'] => {
if cfg.byte_suffix == ByteSuffix::Deny {
return Err(Error::InvalidDigit);
}
src = init;
multiply = 1;
}
_ => {
if cfg.byte_suffix == ByteSuffix::Require {
return Err(Error::InvalidDigit);
}
}
}
let mut unit_system = cfg.unit_system;
match src {
[init @ .., b'i'] | [init @ .., b'I'] => {
src = init;
unit_system = UnitSystem::Binary;
}
_ => {}
}
if let [init @ .., prefix] = src {
if let Some(f) = unit_system.factor(*prefix) {
multiply = f;
src = init;
}
}
#[derive(Copy, Clone, PartialEq)]
enum Ps {
Empty,
Integer,
IntegerOverflow,
Fraction,
FractionOverflow,
PosExponent,
NegExponent,
}
macro_rules! append_digit {
($before:expr, $method:ident, $digit_char:expr) => {
$before
.checked_mul(10)
.and_then(|v| v.$method(($digit_char - b'0').into()))
};
}
let mut mantissa = 0_u64;
let mut fractional_exponent = 0;
let mut exponent = 0_i32;
let mut state = Ps::Empty;
for b in src {
match (state, *b) {
(Ps::Integer, b'0'..=b'9') | (Ps::Empty, b'0'..=b'9') => {
if let Some(m) = append_digit!(mantissa, checked_add, *b) {
mantissa = m;
state = Ps::Integer;
} else {
if *b >= b'5' {
mantissa += 1;
}
state = Ps::IntegerOverflow;
fractional_exponent += 1;
}
}
(Ps::IntegerOverflow, b'0'..=b'9') => {
fractional_exponent += 1;
}
(Ps::Fraction, b'0'..=b'9') => {
if let Some(m) = append_digit!(mantissa, checked_add, *b) {
mantissa = m;
fractional_exponent -= 1;
} else {
if *b >= b'5' {
mantissa += 1;
}
state = Ps::FractionOverflow;
}
}
(Ps::FractionOverflow, b'0'..=b'9') => {}
(Ps::PosExponent, b'0'..=b'9') => {
if let Some(e) = append_digit!(exponent, checked_add, *b) {
exponent = e;
} else {
return Err(Error::PosOverflow);
}
}
(Ps::NegExponent, b'0'..=b'9') => {
if let Some(e) = append_digit!(exponent, checked_sub, *b) {
exponent = e;
}
}
(_, b'_') | (_, b' ') | (Ps::PosExponent, b'+') => {}
(Ps::Integer, b'e')
| (Ps::Integer, b'E')
| (Ps::Fraction, b'e')
| (Ps::Fraction, b'E')
| (Ps::IntegerOverflow, b'e')
| (Ps::IntegerOverflow, b'E')
| (Ps::FractionOverflow, b'e')
| (Ps::FractionOverflow, b'E') => state = Ps::PosExponent,
(Ps::PosExponent, b'-') => state = Ps::NegExponent,
(Ps::Integer, b'.') => state = Ps::Fraction,
(Ps::IntegerOverflow, b'.') => state = Ps::FractionOverflow,
_ => return Err(Error::InvalidDigit),
}
}
if state == Ps::Empty {
return Err(Error::Empty);
}
let exponent = exponent.saturating_add(fractional_exponent);
if exponent >= 0 {
let power = 10_u64
.checked_pow(exponent as u32)
.ok_or(Error::PosOverflow)?;
let multiply = multiply.checked_mul(power).ok_or(Error::PosOverflow)?;
mantissa.checked_mul(multiply).ok_or(Error::PosOverflow)
} else if exponent >= -38 {
let power = 10_u128.pow(-exponent as u32);
let result = (u128::from(mantissa) * u128::from(multiply) + power / 2) / power;
u64::try_from(result).map_err(|_| Error::PosOverflow)
} else {
Ok(0)
}
}
#[test]
fn test_passes() {
assert_eq!(parse_size("0"), Ok(0));
assert_eq!(parse_size("3"), Ok(3));
assert_eq!(parse_size("30"), Ok(30));
assert_eq!(parse_size("32"), Ok(32));
assert_eq!(parse_size("_5_"), Ok(5));
assert_eq!(parse_size("1 234 567"), Ok(1_234_567));
assert_eq!(
parse_size("18_446_744_073_709_551_581"),
Ok(18_446_744_073_709_551_581)
);
assert_eq!(
parse_size("18_446_744_073_709_551_615"),
Ok(18_446_744_073_709_551_615)
);
assert_eq!(
parse_size("18_446_744_073_709_551_616"),
Err(Error::PosOverflow)
);
assert_eq!(
parse_size("18_446_744_073_709_551_620"),
Err(Error::PosOverflow)
);
assert_eq!(parse_size("1kB"), Ok(1_000));
assert_eq!(parse_size("2MB"), Ok(2_000_000));
assert_eq!(parse_size("3GB"), Ok(3_000_000_000));
assert_eq!(parse_size("4TB"), Ok(4_000_000_000_000));
assert_eq!(parse_size("5PB"), Ok(5_000_000_000_000_000));
assert_eq!(parse_size("6EB"), Ok(6_000_000_000_000_000_000));
assert_eq!(parse_size("7 KiB"), Ok(7 << 10));
assert_eq!(parse_size("8 MiB"), Ok(8 << 20));
assert_eq!(parse_size("9 GiB"), Ok(9 << 30));
assert_eq!(parse_size("10 TiB"), Ok(10 << 40));
assert_eq!(parse_size("11 PiB"), Ok(11 << 50));
assert_eq!(parse_size("12 EiB"), Ok(12 << 60));
assert_eq!(parse_size("1mib"), Ok(1_048_576));
assert_eq!(parse_size("5k"), Ok(5000));
assert_eq!(parse_size("1.1 K"), Ok(1100));
assert_eq!(parse_size("1.2345 K"), Ok(1235));
assert_eq!(parse_size("1.2345m"), Ok(1_234_500));
assert_eq!(parse_size("5.k"), Ok(5000));
assert_eq!(parse_size("0.0025KB"), Ok(3));
assert_eq!(
parse_size("3.141_592_653_589_793_238e"),
Ok(3_141_592_653_589_793_238)
);
assert_eq!(
parse_size("18.446_744_073_709_551_615 EB"),
Ok(18_446_744_073_709_551_615)
);
assert_eq!(
parse_size("18.446_744_073_709_551_616 EB"),
Err(Error::PosOverflow)
);
assert_eq!(
parse_size("1.000_000_000_000_000_001 EB"),
Ok(1_000_000_000_000_000_001)
);
assert_eq!(parse_size("1e2 KIB"), Ok(102_400));
assert_eq!(parse_size("1E+6"), Ok(1_000_000));
assert_eq!(parse_size("1e-4 MiB"), Ok(105));
assert_eq!(parse_size("1.1e2"), Ok(110));
assert_eq!(parse_size("5.7E3"), Ok(5700));
assert_eq!(parse_size("20_000_000_000_000_000_000e-18"), Ok(20));
assert_eq!(parse_size("98_765_432_109_876_543_210e-16"), Ok(9877));
assert_eq!(parse_size("1e21"), Err(Error::PosOverflow));
assert_eq!(parse_size("0.01e21"), Ok(10_000_000_000_000_000_000));
assert_eq!(
parse_size("3.333_333_333_333_333_333_333_333_333_333_333_333_333_333_333_333 EB"),
Ok(3_333_333_333_333_333_333)
);
assert_eq!(parse_size("2e+0_9"), Ok(2_000_000_000));
assert_eq!(
parse_size("3e+66666666666666666666"),
Err(Error::PosOverflow)
);
assert_eq!(parse_size("4e-77777777777777777777"), Ok(0));
assert_eq!(parse_size("5e-88888888888888888888 EiB"), Ok(0));
assert_eq!(
parse_size("123_456_789_012_345_678_901_234_567.890e-16"),
Ok(12_345_678_901)
);
assert_eq!(parse_size("0.1e-2147483648"), Ok(0));
assert_eq!(parse_size("184467440737095516150e-38EiB"), Ok(2));
}
#[test]
fn test_parse_errors() {
assert_eq!(parse_size(""), Err(Error::Empty));
assert_eq!(parse_size("."), Err(Error::InvalidDigit));
assert_eq!(parse_size(".5k"), Err(Error::InvalidDigit));
assert_eq!(parse_size("k"), Err(Error::Empty));
assert_eq!(parse_size("kb"), Err(Error::Empty));
assert_eq!(parse_size("kib"), Err(Error::Empty));
assert_eq!(parse_size(" "), Err(Error::Empty));
assert_eq!(parse_size("__"), Err(Error::Empty));
assert_eq!(parse_size("a"), Err(Error::InvalidDigit));
assert_eq!(parse_size("-1"), Err(Error::InvalidDigit));
assert_eq!(parse_size("1,5"), Err(Error::InvalidDigit));
assert_eq!(parse_size("1e+f"), Err(Error::InvalidDigit));
assert_eq!(parse_size("1e0.5"), Err(Error::InvalidDigit));
assert_eq!(parse_size("1 ZiB"), Err(Error::InvalidDigit));
assert_eq!(parse_size("1 YiB"), Err(Error::InvalidDigit));
}
#[test]
fn test_config() {
let cfg = Config::new().with_binary().with_default_factor(1_048_576);
assert_eq!(cfg.parse_size("3.5"), Ok(3_670_016));
assert_eq!(cfg.parse_size("35 B"), Ok(35));
assert_eq!(cfg.parse_size("5K"), Ok(5120));
}