1#![no_std]
96
97use core::{convert::TryFrom, fmt, num::IntErrorKind};
98
99#[derive(Copy, Clone, PartialEq, Eq, Debug)]
101pub enum UnitSystem {
102 Decimal,
104 Binary,
106}
107
108impl UnitSystem {
109 fn factor(self, prefix: u8) -> Option<u64> {
116 Some(match (self, prefix) {
117 (Self::Decimal, b'k' | b'K') => 1_000,
118 (Self::Decimal, b'm' | b'M') => 1_000_000,
119 (Self::Decimal, b'g' | b'G') => 1_000_000_000,
120 (Self::Decimal, b't' | b'T') => 1_000_000_000_000,
121 (Self::Decimal, b'p' | b'P') => 1_000_000_000_000_000,
122 (Self::Decimal, b'e' | b'E') => 1_000_000_000_000_000_000,
123 (Self::Binary, b'k' | b'K') => 1_u64 << 10,
124 (Self::Binary, b'm' | b'M') => 1_u64 << 20,
125 (Self::Binary, b'g' | b'G') => 1_u64 << 30,
126 (Self::Binary, b't' | b'T') => 1_u64 << 40,
127 (Self::Binary, b'p' | b'P') => 1_u64 << 50,
128 (Self::Binary, b'e' | b'E') => 1_u64 << 60,
129 _ => return None,
130 })
131 }
132}
133
134#[derive(Copy, Clone, PartialEq, Eq, Debug)]
136pub enum ByteSuffix {
137 Deny,
140 Allow,
142 Require,
145}
146
147#[derive(Clone, Debug)]
149pub struct Config {
150 unit_system: UnitSystem,
151 default_factor: u64,
152 byte_suffix: ByteSuffix,
153}
154
155impl Config {
156 #[must_use]
158 pub const fn new() -> Self {
159 Self {
160 unit_system: UnitSystem::Decimal,
161 default_factor: 1,
162 byte_suffix: ByteSuffix::Allow,
163 }
164 }
165
166 #[must_use]
170 pub const fn with_unit_system(mut self, unit_system: UnitSystem) -> Self {
171 self.unit_system = unit_system;
172 self
173 }
174
175 #[must_use]
190 pub const fn with_binary(self) -> Self {
191 self.with_unit_system(UnitSystem::Binary)
192 }
193
194 #[must_use]
209 pub const fn with_decimal(self) -> Self {
210 self.with_unit_system(UnitSystem::Decimal)
211 }
212
213 #[must_use]
234 pub const fn with_default_factor(mut self, factor: u64) -> Self {
235 self.default_factor = factor;
236 self
237 }
238
239 #[must_use]
272 pub const fn with_byte_suffix(mut self, byte_suffix: ByteSuffix) -> Self {
273 self.byte_suffix = byte_suffix;
274 self
275 }
276
277 pub fn parse_size<T: AsRef<[u8]>>(&self, src: T) -> Result<u64, Error> {
295 self.parse_size_inner(src.as_ref())
296 }
297}
298
299impl Default for Config {
300 fn default() -> Self {
301 Self::new()
302 }
303}
304
305#[derive(Copy, Clone, PartialEq, Eq, Debug)]
307#[non_exhaustive]
308pub enum Error {
309 Empty,
311 InvalidDigit,
313 PosOverflow,
315}
316
317impl fmt::Display for Error {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 f.write_str(match self {
320 Self::Empty => "cannot parse integer from empty string",
321 Self::InvalidDigit => "invalid digit found in string",
322 Self::PosOverflow => "number too large to fit in target type",
323 })
324 }
325}
326
327impl core::error::Error for Error {}
328
329impl From<Error> for IntErrorKind {
330 fn from(e: Error) -> Self {
331 match e {
332 Error::Empty => Self::Empty,
333 Error::InvalidDigit => Self::InvalidDigit,
334 Error::PosOverflow => Self::PosOverflow,
335 }
336 }
337}
338
339pub fn parse_size<T: AsRef<[u8]>>(src: T) -> Result<u64, Error> {
361 Config::new().parse_size(src)
362}
363
364impl Config {
365 fn parse_size_inner(&self, mut src: &[u8]) -> Result<u64, Error> {
366 let mut multiply = if let [init @ .., b'b' | b'B'] = src {
368 if self.byte_suffix == ByteSuffix::Deny {
369 return Err(Error::InvalidDigit);
370 }
371 src = init;
372 1
373 } else {
374 if self.byte_suffix == ByteSuffix::Require {
375 return Err(Error::InvalidDigit);
376 }
377 self.default_factor
378 };
379
380 let unit_system = if let [init @ .., b'i' | b'I'] = src {
382 src = init;
383 UnitSystem::Binary
384 } else {
385 self.unit_system
386 };
387
388 if let [init @ .., prefix] = src {
389 if let Some(f) = unit_system.factor(*prefix) {
390 multiply = f;
391 src = init;
392 }
393 }
394
395 parse_size_with_multiple(src, multiply)
396 }
397}
398
399fn parse_size_with_multiple(src: &[u8], multiply: u64) -> Result<u64, Error> {
400 #[derive(Copy, Clone, PartialEq)]
401 enum Ps {
402 Empty,
403 Integer,
404 IntegerOverflow,
405 Fraction,
406 FractionOverflow,
407 PosExponent,
408 NegExponent,
409 }
410
411 macro_rules! append_digit {
412 ($before:expr, $method:ident, $digit_char:expr) => {
413 $before
414 .checked_mul(10)
415 .and_then(|v| v.$method(($digit_char - b'0').into()))
416 };
417 }
418
419 let mut mantissa = 0_u64;
420 let mut fractional_exponent = 0;
421 let mut exponent = 0_i32;
422 let mut state = Ps::Empty;
423
424 for b in src {
425 match (state, *b) {
426 (Ps::Integer | Ps::Empty, b'0'..=b'9') => {
427 if let Some(m) = append_digit!(mantissa, checked_add, *b) {
428 mantissa = m;
429 state = Ps::Integer;
430 } else {
431 if *b >= b'5' {
432 mantissa += 1;
433 }
434 state = Ps::IntegerOverflow;
435 fractional_exponent += 1;
436 }
437 }
438 (Ps::IntegerOverflow, b'0'..=b'9') => {
439 fractional_exponent += 1;
440 }
441 (Ps::Fraction, b'0'..=b'9') => {
442 if let Some(m) = append_digit!(mantissa, checked_add, *b) {
443 mantissa = m;
444 fractional_exponent -= 1;
445 } else {
446 if *b >= b'5' {
447 mantissa += 1;
448 }
449 state = Ps::FractionOverflow;
450 }
451 }
452 (Ps::PosExponent, b'0'..=b'9') => {
453 if let Some(e) = append_digit!(exponent, checked_add, *b) {
454 exponent = e;
455 } else {
456 return Err(Error::PosOverflow);
457 }
458 }
459 (Ps::NegExponent, b'0'..=b'9') => {
460 if let Some(e) = append_digit!(exponent, checked_sub, *b) {
461 exponent = e;
462 }
463 }
464
465 (_, b'_' | b' ') | (Ps::PosExponent, b'+') | (Ps::FractionOverflow, b'0'..=b'9') => {}
466 (
467 Ps::Integer | Ps::Fraction | Ps::IntegerOverflow | Ps::FractionOverflow,
468 b'e' | b'E',
469 ) => state = Ps::PosExponent,
470 (Ps::PosExponent, b'-') => state = Ps::NegExponent,
471 (Ps::Integer, b'.') => state = Ps::Fraction,
472 (Ps::IntegerOverflow, b'.') => state = Ps::FractionOverflow,
473 _ => return Err(Error::InvalidDigit),
474 }
475 }
476
477 if state == Ps::Empty {
478 return Err(Error::Empty);
479 }
480
481 let exponent = exponent.saturating_add(fractional_exponent);
482 let abs_exponent = exponent.unsigned_abs();
483 if exponent >= 0 {
484 let power = 10_u64.checked_pow(abs_exponent).ok_or(Error::PosOverflow)?;
485 let multiply = multiply.checked_mul(power).ok_or(Error::PosOverflow)?;
486 mantissa.checked_mul(multiply).ok_or(Error::PosOverflow)
487 } else if exponent >= -38 {
488 let power = 10_u128.pow(abs_exponent);
489 let result = (u128::from(mantissa) * u128::from(multiply) + power / 2) / power;
490 u64::try_from(result).map_err(|_| Error::PosOverflow)
491 } else {
492 Ok(0)
494 }
495}
496
497#[test]
498fn test_passes() {
499 assert_eq!(parse_size("0"), Ok(0));
500 assert_eq!(parse_size("3"), Ok(3));
501 assert_eq!(parse_size("30"), Ok(30));
502 assert_eq!(parse_size("32"), Ok(32));
503 assert_eq!(parse_size("_5_"), Ok(5));
504 assert_eq!(parse_size("1 234 567"), Ok(1_234_567));
505
506 assert_eq!(
507 parse_size("18_446_744_073_709_551_581"),
508 Ok(18_446_744_073_709_551_581)
509 );
510 assert_eq!(
511 parse_size("18_446_744_073_709_551_615"),
512 Ok(18_446_744_073_709_551_615)
513 );
514 assert_eq!(
515 parse_size("18_446_744_073_709_551_616"),
516 Err(Error::PosOverflow)
517 );
518 assert_eq!(
519 parse_size("18_446_744_073_709_551_620"),
520 Err(Error::PosOverflow)
521 );
522
523 assert_eq!(parse_size("1kB"), Ok(1_000));
524 assert_eq!(parse_size("2MB"), Ok(2_000_000));
525 assert_eq!(parse_size("3GB"), Ok(3_000_000_000));
526 assert_eq!(parse_size("4TB"), Ok(4_000_000_000_000));
527 assert_eq!(parse_size("5PB"), Ok(5_000_000_000_000_000));
528 assert_eq!(parse_size("6EB"), Ok(6_000_000_000_000_000_000));
529
530 assert_eq!(parse_size("7 KiB"), Ok(7 << 10));
531 assert_eq!(parse_size("8 MiB"), Ok(8 << 20));
532 assert_eq!(parse_size("9 GiB"), Ok(9 << 30));
533 assert_eq!(parse_size("10 TiB"), Ok(10 << 40));
534 assert_eq!(parse_size("11 PiB"), Ok(11 << 50));
535 assert_eq!(parse_size("12 EiB"), Ok(12 << 60));
536
537 assert_eq!(parse_size("1mib"), Ok(1_048_576));
538
539 assert_eq!(parse_size("5k"), Ok(5000));
540 assert_eq!(parse_size("1.1 K"), Ok(1100));
541 assert_eq!(parse_size("1.2345 K"), Ok(1235));
542 assert_eq!(parse_size("1.2345m"), Ok(1_234_500));
543 assert_eq!(parse_size("5.k"), Ok(5000));
544 assert_eq!(parse_size("0.0025KB"), Ok(3));
545 assert_eq!(
546 parse_size("3.141_592_653_589_793_238e"),
547 Ok(3_141_592_653_589_793_238)
548 );
549
550 assert_eq!(
551 parse_size("18.446_744_073_709_551_615 EB"),
552 Ok(18_446_744_073_709_551_615)
553 );
554 assert_eq!(
555 parse_size("18.446_744_073_709_551_616 EB"),
556 Err(Error::PosOverflow)
557 );
558 assert_eq!(
559 parse_size("1.000_000_000_000_000_001 EB"),
560 Ok(1_000_000_000_000_000_001)
561 );
562
563 assert_eq!(parse_size("1e2 KIB"), Ok(102_400));
564 assert_eq!(parse_size("1E+6"), Ok(1_000_000));
565 assert_eq!(parse_size("1e-4 MiB"), Ok(105));
566 assert_eq!(parse_size("1.1e2"), Ok(110));
567 assert_eq!(parse_size("5.7E3"), Ok(5700));
568
569 assert_eq!(parse_size("20_000_000_000_000_000_000e-18"), Ok(20));
570 assert_eq!(parse_size("98_765_432_109_876_543_210e-16"), Ok(9877));
571 assert_eq!(parse_size("1e21"), Err(Error::PosOverflow));
572 assert_eq!(parse_size("0.01e21"), Ok(10_000_000_000_000_000_000));
573 assert_eq!(
574 parse_size("3.333_333_333_333_333_333_333_333_333_333_333_333_333_333_333_333 EB"),
575 Ok(3_333_333_333_333_333_333)
576 );
577 assert_eq!(parse_size("2e+0_9"), Ok(2_000_000_000));
578 assert_eq!(
579 parse_size("3e+66666666666666666666"),
580 Err(Error::PosOverflow)
581 );
582 assert_eq!(parse_size("4e-77777777777777777777"), Ok(0));
583 assert_eq!(parse_size("5e-88888888888888888888 EiB"), Ok(0));
584
585 assert_eq!(
586 parse_size("123_456_789_012_345_678_901_234_567.890e-16"),
587 Ok(12_345_678_901)
588 );
589 assert_eq!(parse_size("0.1e-2147483648"), Ok(0));
590 assert_eq!(parse_size("184467440737095516150e-38EiB"), Ok(2));
591}
592
593#[test]
594fn test_parse_errors() {
595 assert_eq!(parse_size(""), Err(Error::Empty));
596 assert_eq!(parse_size("."), Err(Error::InvalidDigit));
597 assert_eq!(parse_size(".5k"), Err(Error::InvalidDigit));
598 assert_eq!(parse_size("k"), Err(Error::Empty));
599 assert_eq!(parse_size("kb"), Err(Error::Empty));
600 assert_eq!(parse_size("kib"), Err(Error::Empty));
601 assert_eq!(parse_size(" "), Err(Error::Empty));
602 assert_eq!(parse_size("__"), Err(Error::Empty));
603 assert_eq!(parse_size("a"), Err(Error::InvalidDigit));
604 assert_eq!(parse_size("-1"), Err(Error::InvalidDigit));
605 assert_eq!(parse_size("1,5"), Err(Error::InvalidDigit));
606 assert_eq!(parse_size("1e+f"), Err(Error::InvalidDigit));
607 assert_eq!(parse_size("1e0.5"), Err(Error::InvalidDigit));
608 assert_eq!(parse_size("1 ZiB"), Err(Error::InvalidDigit));
609 assert_eq!(parse_size("1 YiB"), Err(Error::InvalidDigit));
610}
611
612#[test]
613fn test_config() {
614 let cfg = Config::new().with_binary().with_default_factor(1_048_576);
615 assert_eq!(cfg.parse_size("3.5"), Ok(3_670_016));
616 assert_eq!(cfg.parse_size("35 B"), Ok(35));
617 assert_eq!(cfg.parse_size("5K"), Ok(5120));
618 assert_eq!(cfg.parse_size("1.7e13"), Ok(17_825_792_000_000_000_000));
619 assert_eq!(cfg.parse_size("1.8e13"), Err(Error::PosOverflow));
620 assert_eq!(cfg.parse_size("1.8e18"), Err(Error::PosOverflow));
621
622 let cfg = Config::new()
623 .with_decimal()
624 .with_byte_suffix(ByteSuffix::Require);
625 assert_eq!(cfg.parse_size("3.5MB"), Ok(3_500_000));
626 assert_eq!(cfg.parse_size("2.5KiB"), Ok(2560));
627 assert_eq!(cfg.parse_size("7"), Err(Error::InvalidDigit));
628 assert_eq!(cfg.parse_size("9b"), Ok(9));
629
630 let cfg = Config::default().with_byte_suffix(ByteSuffix::Deny);
631 assert_eq!(cfg.parse_size("3.5MB"), Err(Error::InvalidDigit));
632 assert_eq!(cfg.parse_size("3.5M"), Ok(3_500_000));
633 assert_eq!(cfg.parse_size("7b"), Err(Error::InvalidDigit));
634}
635
636#[test]
637fn test_int_error_kind() {
638 extern crate alloc;
639 use alloc::string::ToString as _;
640
641 let test_cases = [
642 (Error::Empty, ""),
643 (Error::InvalidDigit, "?"),
644 (Error::PosOverflow, "99999999999999999999"),
645 ];
646 for (expected_error, parse_from) in test_cases {
647 let std_error = parse_from.parse::<u64>().unwrap_err();
648 let local_error = parse_size(parse_from).unwrap_err();
649 assert_eq!(local_error, expected_error);
650 assert_eq!(local_error.to_string(), std_error.to_string());
651 assert_eq!(&IntErrorKind::from(local_error), std_error.kind());
652 }
653}