|
fn parse_size(mut src: &[u8]) -> Result<u64, ParseError> { |
|
// trim starting and trailing spaces |
|
while let [b' ', init @ ..] = src { |
|
src = init; |
|
} |
|
while let [init @ .., b' '] = src { |
|
src = init; |
|
} |
|
|
|
// trim trailing 'b' or 'B' |
|
if let [init @ .., b'b' | b'B'] = src { |
|
src = init; |
|
}; |
|
|
|
let mut multiply = 1u64; |
|
if let [init @ .., b'i' | b'I'] = src { |
|
src = init; |
|
if let [init @ .., prefix] = src { |
|
match prefix { |
|
b'k' | b'K' => multiply = 1 << 10, |
|
b'm' | b'M' => multiply = 1 << 20, |
|
b'g' | b'G' => multiply = 1 << 30, |
|
b't' | b'T' => multiply = 1 << 40, |
|
b'p' | b'P' => multiply = 1 << 50, |
|
b'e' | b'E' => multiply = 1 << 60, |
|
_ => return Err(ParseError::Malformed), |
|
} |
|
|
|
src = init; |
|
} else { |
|
// [iI][bB] is malformed suffix. |
|
return Err(ParseError::Malformed); |
|
} |
|
} else { |
|
if let [init @ .., prefix] = src { |
|
'skip: { |
|
match prefix { |
|
b'k' | b'K' => multiply = 1_000, |
|
b'm' | b'M' => multiply = 1_000_000, |
|
b'g' | b'G' => multiply = 1_000_000_000, |
|
b't' | b'T' => multiply = 1_000_000_000_000, |
|
b'p' | b'P' => multiply = 1_000_000_000_000_000, |
|
b'e' | b'E' => multiply = 1_000_000_000_000_000_000, |
|
_ => break 'skip, |
|
} |
|
src = init; |
|
} |
|
} |
|
} |
|
|
|
// trim spaces between numeric part and unit part |
|
while let [init @ .., b' '] = src { |
|
src = init; |
|
} |
|
|
|
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())) |
|
}; |
|
} |
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
|
enum ParseState { |
|
Empty, |
|
Integer, |
|
IntegerOverflow, |
|
Fraction, |
|
FractionOverflow, |
|
} |
|
|
|
let mut mantissa = 0u64; |
|
let mut exponent = 0i32; |
|
let mut state = ParseState::Empty; |
|
|
|
for b in src { |
|
match (state, *b) { |
|
(ParseState::Integer | ParseState::Empty, b'0'..=b'9') => { |
|
if let Some(m) = append_digit!(mantissa, checked_add, *b) { |
|
mantissa = m; |
|
state = ParseState::Integer; |
|
} else { |
|
if *b >= b'5' { |
|
mantissa += 1; |
|
} |
|
state = ParseState::IntegerOverflow; |
|
exponent += 1; |
|
} |
|
} |
|
(ParseState::IntegerOverflow, b'0'..=b'9') => { |
|
exponent += 1; |
|
} |
|
(ParseState::Fraction, b'0'..=b'9') => { |
|
if let Some(m) = append_digit!(mantissa, checked_add, *b) { |
|
mantissa = m; |
|
exponent -= 1; |
|
} else { |
|
if *b >= b'5' { |
|
mantissa += 1; |
|
} |
|
state = ParseState::FractionOverflow; |
|
} |
|
} |
|
(_, b'_') => {} |
|
(ParseState::Integer, b'.') => state = ParseState::Fraction, |
|
(ParseState::IntegerOverflow, b'.') => state = ParseState::FractionOverflow, |
|
_ => return Err(ParseError::Malformed), |
|
} |
|
} |
|
|
|
if matches!(state, ParseState::Empty) { |
|
return Err(ParseError::Empty); |
|
} |
|
|
|
let abs_exponent = exponent.unsigned_abs(); |
|
if exponent >= 0 { |
|
let power = 10_u64 |
|
.checked_pow(abs_exponent) |
|
.ok_or(ParseError::Overflow)?; |
|
let multiply = multiply.checked_mul(power).ok_or(ParseError::Overflow)?; |
|
mantissa.checked_mul(multiply).ok_or(ParseError::Overflow) |
|
} else if exponent >= -38 { |
|
let power = 10_u128.pow(abs_exponent); |
|
let result = (u128::from(mantissa) * u128::from(multiply) + power / 2) / power; |
|
u64::try_from(result).map_err(|_| ParseError::Overflow) |
|
} else { |
|
// (2^128) * 1e-39 < 1, always, and thus saturate to 0. |
|
Ok(0) |
|
} |
|
} |
So far, the
parse_sizefunction is derived from theparse-sizecrate.bsize/bsize/src/parse.rs
Lines 68 to 198 in ab464e6
It works well now. But I'd suggest there is always room for improving its performance and accuracy.
File this tracking issue for such a topic.