From 0a8cc9f5263099a0c06a5f525fd0a8297142f680 Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 9 Jun 2026 16:44:30 +0800 Subject: [PATCH 01/10] feat: impl parse and display Signed-off-by: tison --- bsize/src/lib.rs | 1 + bsize/src/ops.rs | 14 +++ bsize/src/parse.rs | 13 +++ bsize/src/types.rs | 219 +++++++++++++++++++++++++++++++++++++++++- bsize/src/unsigned.rs | 14 +++ 5 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 bsize/src/parse.rs diff --git a/bsize/src/lib.rs b/bsize/src/lib.rs index 79df3e6..aeac132 100644 --- a/bsize/src/lib.rs +++ b/bsize/src/lib.rs @@ -19,6 +19,7 @@ #![no_std] mod ops; +mod parse; mod types; mod unsigned; diff --git a/bsize/src/ops.rs b/bsize/src/ops.rs index 71819fd..61f71b2 100644 --- a/bsize/src/ops.rs +++ b/bsize/src/ops.rs @@ -1,3 +1,17 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use core::ops; use crate::types::BSize; diff --git a/bsize/src/parse.rs b/bsize/src/parse.rs new file mode 100644 index 0000000..6baf070 --- /dev/null +++ b/bsize/src/parse.rs @@ -0,0 +1,13 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/bsize/src/types.rs b/bsize/src/types.rs index a406c49..cabae88 100644 --- a/bsize/src/types.rs +++ b/bsize/src/types.rs @@ -1,7 +1,22 @@ -use crate::unsigned::Unsigned; +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use core::any::type_name; use core::fmt; +use crate::unsigned::Unsigned; + /// Byte size representation. #[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct BSize(pub T); @@ -116,10 +131,104 @@ impl_constructors!(usize => { eib = 1_152_921_504_606_846_976, }); +macro_rules! impl_accessors { + ($ty:ty => { $($name:ident = $size:literal => $unit:literal),* $(,)? }) => { + impl BSize<$ty> { + $( + #[doc = concat!("Returns byte count as ", $unit, ".")] + /// + /// The result is approximate when the byte count cannot be + /// represented exactly as `f64`. + #[inline(always)] + pub fn $name(&self) -> f64 { + (self.0 as f64) / ($size as f64) + } + )* + } + }; +} + +impl_accessors!(u16 => { + as_kb = 1_000u16 => "kilobytes", + as_kib = 1_024u16 => "kibibytes", +}); + +impl_accessors!(u32 => { + as_kb = 1_000u32 => "kilobytes", + as_kib = 1_024u32 => "kibibytes", + as_mb = 1_000_000u32 => "megabytes", + as_mib = 1_048_576u32 => "mebibytes", + as_gb = 1_000_000_000u32 => "gigabytes", + as_gib = 1_073_741_824u32 => "gibibytes", +}); + +impl_accessors!(u64 => { + as_kb = 1_000u64 => "kilobytes", + as_kib = 1_024u64 => "kibibytes", + as_mb = 1_000_000u64 => "megabytes", + as_mib = 1_048_576u64 => "mebibytes", + as_gb = 1_000_000_000u64 => "gigabytes", + as_gib = 1_073_741_824u64 => "gibibytes", + as_tb = 1_000_000_000_000u64 => "terabytes", + as_tib = 1_099_511_627_776u64 => "tebibytes", + as_pb = 1_000_000_000_000_000u64 => "petabytes", + as_pib = 1_125_899_906_842_624u64 => "pebibytes", + as_eb = 1_000_000_000_000_000_000u64 => "exabytes", + as_eib = 1_152_921_504_606_846_976u64 => "exbibytes", +}); + +impl_accessors!(u128 => { + as_kb = 1_000u128 => "kilobytes", + as_kib = 1_024u128 => "kibibytes", + as_mb = 1_000_000u128 => "megabytes", + as_mib = 1_048_576u128 => "mebibytes", + as_gb = 1_000_000_000u128 => "gigabytes", + as_gib = 1_073_741_824u128 => "gibibytes", + as_tb = 1_000_000_000_000u128 => "terabytes", + as_tib = 1_099_511_627_776u128 => "tebibytes", + as_pb = 1_000_000_000_000_000u128 => "petabytes", + as_pib = 1_125_899_906_842_624u128 => "pebibytes", + as_eb = 1_000_000_000_000_000_000u128 => "exabytes", + as_eib = 1_152_921_504_606_846_976u128 => "exbibytes", +}); + +impl_accessors!(usize => { + as_kb = 1_000usize => "kilobytes", + as_kib = 1_024usize => "kibibytes", +}); + +#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] +impl_accessors!(usize => { + as_mb = 1_000_000usize => "megabytes", + as_mib = 1_048_576usize => "mebibytes", + as_gb = 1_000_000_000usize => "gigabytes", + as_gib = 1_073_741_824usize => "gibibytes", +}); + +#[cfg(target_pointer_width = "64")] +impl_accessors!(usize => { + as_tb = 1_000_000_000_000usize => "terabytes", + as_tib = 1_099_511_627_776usize => "tebibytes", + as_pb = 1_000_000_000_000_000usize => "petabytes", + as_pib = 1_125_899_906_842_624usize => "pebibytes", + as_eb = 1_000_000_000_000_000_000usize => "exabytes", + as_eib = 1_152_921_504_606_846_976usize => "exbibytes", +}); + #[cfg(test)] mod tests { use super::BSize; + fn assert_close(actual: f64, expected: f64) { + let delta = (actual - expected).abs(); + let tolerance = f64::EPSILON; + + assert!( + delta <= tolerance, + "actual: {actual}, expected: {expected}, delta: {delta}, tolerance: {tolerance}", + ); + } + #[test] fn defaults() { assert_eq!(BSize::::default(), BSize::b(0)); @@ -141,30 +250,125 @@ mod tests { assert_eq!(BSize::::kib(2).0, 2_048); } + #[test] + fn returns_u16_units() { + assert_close(BSize::::kb(2).as_kb(), 2.0); + assert_close(BSize::::kib(2).as_kib(), 2.0); + } + + #[test] + fn returns_fractional_u16_units() { + let bytes = u16::MAX; + let kb = 1_000u16; + let kib = 1_024u16; + + assert_close(BSize::::b(bytes).as_kb(), (bytes as f64) / (kb as f64)); + assert_close( + BSize::::b(bytes).as_kib(), + (bytes as f64) / (kib as f64), + ); + } + #[test] fn constructs_u32_units() { assert_eq!(BSize::::gb(2).0, 2_000_000_000); assert_eq!(BSize::::gib(2).0, 2_147_483_648); } + #[test] + fn returns_u32_units() { + assert_close(BSize::::gb(2).as_gb(), 2.0); + assert_close(BSize::::gib(2).as_gib(), 2.0); + } + + #[test] + fn returns_fractional_u32_units() { + let bytes = u32::MAX; + let gb = 1_000_000_000u32; + let gib = 1_073_741_824u32; + + assert_close(BSize::::b(bytes).as_gb(), (bytes as f64) / (gb as f64)); + assert_close( + BSize::::b(bytes).as_gib(), + (bytes as f64) / (gib as f64), + ); + } + #[test] fn constructs_u64_units() { assert_eq!(BSize::::eb(2).0, 2_000_000_000_000_000_000); assert_eq!(BSize::::eib(2).0, 2_305_843_009_213_693_952); } + #[test] + fn returns_u64_units() { + assert_close(BSize::::eib(2).as_eib(), 2.0); + } + + #[test] + fn returns_large_fractional_u64_units() { + let bytes = 9_876_543_210_987_654_321_u64; + let eb = 1_000_000_000_000_000_000u64; + let eib = 1_152_921_504_606_846_976u64; + + assert_close(BSize::::b(bytes).as_eb(), (bytes as f64) / (eb as f64)); + assert_close( + BSize::::b(bytes).as_eib(), + (bytes as f64) / (eib as f64), + ); + } + #[test] fn constructs_u128_units() { assert_eq!(BSize::::eb(20).0, 20_000_000_000_000_000_000); assert_eq!(BSize::::eib(20).0, 23_058_430_092_136_939_520); } + #[test] + fn returns_u128_units() { + assert_close(BSize::::eib(20).as_eib(), 20.0); + } + + #[test] + fn returns_large_fractional_u128_units() { + let bytes = (1_u128 << 80) + (1_u128 << 40); + let eb = 1_000_000_000_000_000_000u128; + let eib = 1_152_921_504_606_846_976u128; + + assert_close( + BSize::::b(bytes).as_eb(), + (bytes as f64) / (eb as f64), + ); + assert_close( + BSize::::b(bytes).as_eib(), + (bytes as f64) / (eib as f64), + ); + } + + #[test] + fn returns_u128_units_with_f64_precision() { + let bytes = (1_u128 << 100) + 123_456_789; + let eib = 1_152_921_504_606_846_976u128; + + assert_eq!(bytes as f64, (1_u128 << 100) as f64); + assert_close( + BSize::::b(bytes).as_eib(), + ((1_u128 << 100) as f64) / (eib as f64), + ); + } + #[test] fn constructs_usize_units() { assert_eq!(BSize::::kb(2).0, 2_000); assert_eq!(BSize::::kib(2).0, 2_048); } + #[test] + fn returns_usize_units() { + assert_close(BSize::::kb(2).as_kb(), 2.0); + assert_close(BSize::::kib(2).as_kib(), 2.0); + } + #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] #[test] fn constructs_32_bit_usize_units() { @@ -172,10 +376,23 @@ mod tests { assert_eq!(BSize::::gib(2).0, 2_147_483_648); } + #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] + #[test] + fn returns_32_bit_usize_units() { + assert_close(BSize::::gb(2).as_gb(), 2.0); + assert_close(BSize::::gib(2).as_gib(), 2.0); + } + #[cfg(target_pointer_width = "64")] #[test] fn constructs_64_bit_usize_units() { assert_eq!(BSize::::eb(2).0, 2_000_000_000_000_000_000); assert_eq!(BSize::::eib(2).0, 2_305_843_009_213_693_952); } + + #[cfg(target_pointer_width = "64")] + #[test] + fn returns_64_bit_usize_units() { + assert_close(BSize::::eib(2).as_eib(), 2.0); + } } diff --git a/bsize/src/unsigned.rs b/bsize/src/unsigned.rs index 4d73fa9..fc13f1c 100644 --- a/bsize/src/unsigned.rs +++ b/bsize/src/unsigned.rs @@ -1,3 +1,17 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + mod private { pub trait Sealed {} From 9e7d51217d2de0ef9c6138370d1cff54026e6c03 Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 9 Jun 2026 17:10:34 +0800 Subject: [PATCH 02/10] try impl Signed-off-by: tison --- bsize/src/lib.rs | 1 + bsize/src/parse.rs | 450 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 451 insertions(+) diff --git a/bsize/src/lib.rs b/bsize/src/lib.rs index aeac132..a682056 100644 --- a/bsize/src/lib.rs +++ b/bsize/src/lib.rs @@ -23,5 +23,6 @@ mod parse; mod types; mod unsigned; +pub use self::parse::ParseError; pub use self::types::BSize; pub use self::unsigned::Unsigned; diff --git a/bsize/src/parse.rs b/bsize/src/parse.rs index 6baf070..2619a6e 100644 --- a/bsize/src/parse.rs +++ b/bsize/src/parse.rs @@ -11,3 +11,453 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +use core::fmt; +use core::str::FromStr; + +use crate::types::BSize; + +/// The error returned when parsing a byte size fails. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum ParseError { + /// The input contains no number. + Empty, + /// The input contains an invalid byte. + InvalidDigit, + /// The parsed byte count is too large for the target integer type. + PosOverflow, +} + +impl fmt::Display for ParseError { + 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", + }) + } +} + +impl core::error::Error for ParseError {} + +impl From for core::num::IntErrorKind { + fn from(error: ParseError) -> Self { + match error { + ParseError::Empty => Self::Empty, + ParseError::InvalidDigit => Self::InvalidDigit, + ParseError::PosOverflow => Self::PosOverflow, + } + } +} + +macro_rules! impl_parse { + ($($ty:ty),* $(,)?) => { + $( + impl BSize<$ty> { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. Supported + /// units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, + /// `TiB`, `PB`, `PiB`, `EB`, and `EiB`, case-insensitively. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input is empty, contains an + /// invalid byte, or overflows the target integer type. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + parse(src.as_ref(), <$ty>::MAX as u128).map(|value| BSize(value as $ty)) + } + } + + impl FromStr for BSize<$ty> { + type Err = ParseError; + + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) + } + } + )* + }; +} + +impl_parse!(u8, u16, u32, u64, u128, usize); + +fn parse(mut src: &[u8], max: u128) -> Result { + let multiply = parse_unit(&mut src)?; + if multiply > max { + return Err(ParseError::PosOverflow); + } + + parse_number(src, multiply, max) +} + +fn parse_unit(src: &mut &[u8]) -> Result { + let Some((&suffix, before_b)) = src.split_last() else { + return if src.is_empty() { + Err(ParseError::Empty) + } else { + Err(ParseError::InvalidDigit) + }; + }; + if !suffix.eq_ignore_ascii_case(&b'B') { + return Err(ParseError::InvalidDigit); + } + + *src = before_b; + + if let Some((&infix, before_i)) = src.split_last() { + if infix.eq_ignore_ascii_case(&b'i') { + let Some((&prefix, before_prefix)) = before_i.split_last() else { + return Err(ParseError::InvalidDigit); + }; + let Some(factor) = binary_factor(prefix) else { + return Err(ParseError::InvalidDigit); + }; + + *src = before_prefix; + return Ok(factor); + } + } + + if let Some((&prefix, before_prefix)) = src.split_last() { + if let Some(factor) = decimal_factor(prefix) { + *src = before_prefix; + return Ok(factor); + } + } + + Ok(1) +} + +fn decimal_factor(prefix: u8) -> Option { + Some(match prefix.to_ascii_uppercase() { + b'K' => 1_000, + b'M' => 1_000_000, + b'G' => 1_000_000_000, + b'T' => 1_000_000_000_000, + b'P' => 1_000_000_000_000_000, + b'E' => 1_000_000_000_000_000_000, + _ => return None, + }) +} + +fn binary_factor(prefix: u8) -> Option { + Some(match prefix.to_ascii_uppercase() { + b'K' => 1_u128 << 10, + b'M' => 1_u128 << 20, + b'G' => 1_u128 << 30, + b'T' => 1_u128 << 40, + b'P' => 1_u128 << 50, + b'E' => 1_u128 << 60, + _ => return None, + }) +} + +fn parse_number(src: &[u8], multiply: u128, max: u128) -> Result { + #[derive(Clone, Copy, PartialEq, Eq)] + enum State { + Empty, + Integer, + IntegerOverflow, + Fraction, + FractionOverflow, + PosExponent, + NegExponent, + } + + let mut mantissa = 0_u128; + let mut fractional_exponent = 0_i32; + let mut exponent = 0_i32; + let mut state = State::Empty; + + for b in src { + match (state, *b) { + (State::Integer | State::Empty, b'0'..=b'9') => { + if let Some(next) = append_digit(mantissa, *b) { + mantissa = next; + state = State::Integer; + } else { + mantissa = round_overflowed_mantissa(mantissa, *b); + fractional_exponent = fractional_exponent.saturating_add(1); + state = State::IntegerOverflow; + } + } + (State::IntegerOverflow, b'0'..=b'9') => { + fractional_exponent = fractional_exponent.saturating_add(1); + } + (State::Fraction, b'0'..=b'9') => { + if let Some(next) = append_digit(mantissa, *b) { + mantissa = next; + fractional_exponent = fractional_exponent.saturating_sub(1); + } else { + mantissa = round_overflowed_mantissa(mantissa, *b); + state = State::FractionOverflow; + } + } + (State::PosExponent, b'0'..=b'9') => { + exponent = append_exponent_digit(exponent, *b, true)?; + } + (State::NegExponent, b'0'..=b'9') => { + exponent = append_exponent_digit(exponent, *b, false)?; + } + (_, b'_' | b' ') + | (State::PosExponent, b'+') + | (State::FractionOverflow, b'0'..=b'9') => {} + ( + State::Integer | State::Fraction | State::IntegerOverflow | State::FractionOverflow, + b'e' | b'E', + ) => state = State::PosExponent, + (State::PosExponent, b'-') => state = State::NegExponent, + (State::Integer, b'.') => state = State::Fraction, + (State::IntegerOverflow, b'.') => state = State::FractionOverflow, + _ => return Err(ParseError::InvalidDigit), + } + } + + if state == State::Empty { + return Err(ParseError::Empty); + } + + let exponent = exponent.saturating_add(fractional_exponent); + if exponent >= 0 { + multiply_integer(mantissa, multiply, exponent.unsigned_abs(), max) + } else { + divide_integer(mantissa, multiply, exponent.unsigned_abs(), max) + } +} + +fn append_digit(value: u128, digit: u8) -> Option { + value + .checked_mul(10) + .and_then(|value| value.checked_add(u128::from(digit - b'0'))) +} + +fn round_overflowed_mantissa(mantissa: u128, digit: u8) -> u128 { + if digit >= b'5' { + mantissa.saturating_add(1) + } else { + mantissa + } +} + +fn append_exponent_digit(exponent: i32, digit: u8, positive: bool) -> Result { + let digit = i32::from(digit - b'0'); + if positive { + exponent + .checked_mul(10) + .and_then(|value| value.checked_add(digit)) + .ok_or(ParseError::PosOverflow) + } else { + Ok(exponent + .checked_mul(10) + .and_then(|value| value.checked_sub(digit)) + .unwrap_or(i32::MIN)) + } +} + +fn multiply_integer( + mantissa: u128, + multiply: u128, + exponent: u32, + max: u128, +) -> Result { + let power = 10_u128 + .checked_pow(exponent) + .ok_or(ParseError::PosOverflow)?; + let multiply = multiply.checked_mul(power).ok_or(ParseError::PosOverflow)?; + let value = mantissa + .checked_mul(multiply) + .ok_or(ParseError::PosOverflow)?; + + if value > max { + Err(ParseError::PosOverflow) + } else { + Ok(value) + } +} + +fn divide_integer( + mantissa: u128, + multiply: u128, + exponent: u32, + max: u128, +) -> Result { + if exponent >= 58 { + return Ok(0); + } + + let mut value = U256::multiply(mantissa, multiply); + let mut round = false; + + for _ in 0..exponent { + let (quotient, remainder) = value.div_rem_10(); + value = quotient; + round = remainder >= 5; + } + + if round { + value = value.checked_add_one().ok_or(ParseError::PosOverflow)?; + } + + let value = value.try_into_u128().ok_or(ParseError::PosOverflow)?; + if value > max { + Err(ParseError::PosOverflow) + } else { + Ok(value) + } +} + +#[derive(Clone, Copy)] +struct U256 { + hi: u128, + lo: u128, +} + +impl U256 { + fn multiply(lhs: u128, rhs: u128) -> Self { + let mask = u128::from(u64::MAX); + let lhs_lo = lhs & mask; + let lhs_hi = lhs >> 64; + let rhs_lo = rhs & mask; + let rhs_hi = rhs >> 64; + + let low = lhs_lo * rhs_lo; + let mid_left = lhs_lo * rhs_hi; + let mid_right = lhs_hi * rhs_lo; + let high = lhs_hi * rhs_hi; + + let carry = (low >> 64) + (mid_left & mask) + (mid_right & mask); + let lo = (low & mask) | ((carry & mask) << 64); + let hi = high + (mid_left >> 64) + (mid_right >> 64) + (carry >> 64); + + Self { hi, lo } + } + + fn div_rem_10(self) -> (Self, u8) { + let mut remainder = 0_u128; + + let (hi_hi, next) = div_limb(self.hi >> 64, remainder); + remainder = next; + let (hi_lo, next) = div_limb(self.hi as u64 as u128, remainder); + remainder = next; + let (lo_hi, next) = div_limb(self.lo >> 64, remainder); + remainder = next; + let (lo_lo, remainder) = div_limb(self.lo as u64 as u128, remainder); + + ( + Self { + hi: (hi_hi << 64) | hi_lo, + lo: (lo_hi << 64) | lo_lo, + }, + remainder as u8, + ) + } + + fn checked_add_one(self) -> Option { + let (lo, carry) = self.lo.overflowing_add(1); + let hi = if carry { + self.hi.checked_add(1)? + } else { + self.hi + }; + + Some(Self { hi, lo }) + } + + fn try_into_u128(self) -> Option { + if self.hi == 0 { + Some(self.lo) + } else { + None + } + } +} + +fn div_limb(limb: u128, remainder: u128) -> (u128, u128) { + let value = (remainder << 64) | limb; + (value / 10, value % 10) +} + +#[cfg(test)] +mod tests { + use super::{ParseError, U256}; + use crate::types::BSize; + + #[test] + fn parses_bytes() { + assert_eq!(BSize::::parse(b"255B").unwrap(), BSize(255)); + assert_eq!(BSize::::parse(b"1 B").unwrap(), BSize(1)); + assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); + } + + #[test] + fn parses_units() { + assert_eq!(BSize::::parse(b"1KB").unwrap(), BSize(1_000)); + assert_eq!(BSize::::parse(b"1KiB").unwrap(), BSize(1_024)); + assert_eq!(BSize::::parse(b"1kb").unwrap(), BSize(1_000)); + assert_eq!(BSize::::parse(b"1kib").unwrap(), BSize(1_024)); + assert_eq!(BSize::::parse(b"1KIB").unwrap(), BSize(1_024)); + assert_eq!(BSize::::parse(b"2MB").unwrap(), BSize(2_000_000)); + assert_eq!(BSize::::parse(b"2MiB").unwrap(), BSize(2_097_152)); + } + + #[test] + fn parses_fractional_units() { + assert_eq!(BSize::::parse(b"65.535KB").unwrap(), BSize(u16::MAX)); + assert_eq!(BSize::::parse(b"0.5B").unwrap(), BSize(1)); + assert_eq!(BSize::::parse(b"0.4B").unwrap(), BSize(0)); + assert_eq!(BSize::::parse(b"1.5e3B").unwrap(), BSize(1_500)); + } + + #[test] + fn rejects_invalid_units() { + assert_eq!(BSize::::parse(b""), Err(ParseError::Empty)); + assert_eq!(BSize::::parse(b"1"), Err(ParseError::InvalidDigit)); + assert_eq!(BSize::::parse(b"1K"), Err(ParseError::InvalidDigit)); + assert_eq!(BSize::::parse(b"1XB"), Err(ParseError::InvalidDigit)); + assert_eq!(BSize::::parse(b"1iB"), Err(ParseError::InvalidDigit)); + } + + #[test] + fn rejects_overflow() { + assert_eq!(BSize::::parse(b"256B"), Err(ParseError::PosOverflow)); + assert_eq!(BSize::::parse(b"1KB"), Err(ParseError::PosOverflow)); + assert_eq!(BSize::::parse(b"0.001KB"), Err(ParseError::PosOverflow)); + assert_eq!( + BSize::::parse(b"65.536KB"), + Err(ParseError::PosOverflow) + ); + } + + #[test] + fn parses_u128_max() { + assert_eq!( + BSize::::parse(b"340282366920938463463374607431768211455B"), + Ok(BSize(u128::MAX)), + ); + assert_eq!( + BSize::::parse(b"340282366920938463463374607431768211456B"), + Err(ParseError::PosOverflow), + ); + } + + #[test] + fn parses_u128_max_with_decimal_unit() { + assert_eq!( + BSize::::parse(b"340282366920938463463.374607431768211455EB"), + Ok(BSize(u128::MAX)), + ); + } + + #[test] + fn multiplies_u128_into_u256() { + let value = U256::multiply(u128::MAX, 1_000_000_000_000_000_000); + + assert_eq!(value.hi, 999_999_999_999_999_999); + assert_eq!( + value.lo, + 340_282_366_920_938_463_462_374_607_431_768_211_456 + ); + } +} From dc13172e0857fee6eba9572f23f774e4ec95fd53 Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 9 Jun 2026 17:47:15 +0800 Subject: [PATCH 03/10] take2 Signed-off-by: tison --- Cargo.lock | 99 +++++++++ bsize/Cargo.toml | 8 + bsize/benches/parse.rs | 73 +++++++ bsize/src/parse.rs | 460 ++++++++++++++++++++++++++++++++++------- 4 files changed, 569 insertions(+), 71 deletions(-) create mode 100644 bsize/benches/parse.rs diff --git a/Cargo.lock b/Cargo.lock index 85e901d..ec204fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,9 +52,25 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + [[package]] name = "bsize" version = "0.1.0-rc.1" +dependencies = [ + "divan", + "parse-size", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" @@ -76,6 +92,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim", + "terminal_size", ] [[package]] @@ -102,6 +119,47 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "condtype" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" + +[[package]] +name = "divan" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a405457ec78b8fe08b0e32b4a3570ab5dff6dd16eb9e76a5ee0a9d9cbd898933" +dependencies = [ + "cfg-if", + "clap", + "condtype", + "divan-macros", + "libc", + "regex-lite", +] + +[[package]] +name = "divan-macros" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9556bc800956545d6420a640173e5ba7dfa82f38d3ea5a167eb555bc69ac3323" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "heck" version = "0.5.0" @@ -120,12 +178,24 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "parse-size" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -144,6 +214,25 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "strsim" version = "0.11.1" @@ -161,6 +250,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys", +] + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/bsize/Cargo.toml b/bsize/Cargo.toml index e3442f6..e07f6b1 100644 --- a/bsize/Cargo.toml +++ b/bsize/Cargo.toml @@ -31,5 +31,13 @@ rust-version.workspace = true [features] default = [] +[dev-dependencies] +divan = "0.1.21" +parse-size = "1.1.0" + +[[bench]] +name = "parse" +harness = false + [lints] workspace = true diff --git a/bsize/benches/parse.rs b/bsize/benches/parse.rs new file mode 100644 index 0000000..57ed82b --- /dev/null +++ b/bsize/benches/parse.rs @@ -0,0 +1,73 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use bsize::BSize; +use divan::black_box; +use parse_size::parse_size; + +fn main() { + divan::main(); +} + +#[divan::bench] +fn bsize_bytes() -> u64 { + BSize::::parse(black_box(b"123456789B")).unwrap().0 +} + +#[divan::bench] +fn parse_size_bytes() -> u64 { + parse_size(black_box(b"123456789B")).unwrap() +} + +#[divan::bench] +fn bsize_decimal() -> u64 { + BSize::::parse(black_box(b"123.456MB")).unwrap().0 +} + +#[divan::bench] +fn parse_size_decimal() -> u64 { + parse_size(black_box(b"123.456MB")).unwrap() +} + +#[divan::bench] +fn bsize_binary_exp() -> u64 { + BSize::::parse(black_box(b"1.5e3KiB")).unwrap().0 +} + +#[divan::bench] +fn parse_size_binary_exp() -> u64 { + parse_size(black_box(b"1.5e3KiB")).unwrap() +} + +#[divan::bench] +fn bsize_tiny_decimal() -> u64 { + BSize::::parse(black_box(b"0.001KB")).unwrap().0 +} + +#[divan::bench] +fn parse_size_tiny_decimal() -> u64 { + parse_size(black_box(b"0.001KB")).unwrap() +} + +#[divan::bench] +fn bsize_u64_max() -> u64 { + BSize::::parse(black_box(b"18.446744073709551615EB")) + .unwrap() + .0 +} + +#[divan::bench] +fn parse_size_u64_max() -> u64 { + parse_size(black_box(b"18.446744073709551615EB")).unwrap() +} diff --git a/bsize/src/parse.rs b/bsize/src/parse.rs index 2619a6e..ebc6feb 100644 --- a/bsize/src/parse.rs +++ b/bsize/src/parse.rs @@ -52,44 +52,124 @@ impl From for core::num::IntErrorKind { } macro_rules! impl_parse { - ($($ty:ty),* $(,)?) => { - $( - impl BSize<$ty> { - /// Parses a byte size from a byte slice. - /// - /// The input must end with a `B` or `b` byte suffix. Supported - /// units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, - /// `TiB`, `PB`, `PiB`, `EB`, and `EiB`, case-insensitively. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input is empty, contains an - /// invalid byte, or overflows the target integer type. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - parse(src.as_ref(), <$ty>::MAX as u128).map(|value| BSize(value as $ty)) - } + ($ty:ty, $parse:ident) => { + impl BSize<$ty> { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. Supported + /// units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, + /// `TiB`, `PB`, `PiB`, `EB`, and `EiB`, case-insensitively. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input is empty, contains an + /// invalid byte, or overflows the target integer type. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + $parse(src.as_ref()).map(BSize) } + } - impl FromStr for BSize<$ty> { - type Err = ParseError; + impl FromStr for BSize<$ty> { + type Err = ParseError; - fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) - } + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) } - )* + } }; } -impl_parse!(u8, u16, u32, u64, u128, usize); +impl_parse!(u8, parse_u8); +impl_parse!(u16, parse_u16); +impl_parse!(u32, parse_u32); +impl_parse!(u64, parse_u64); +impl_parse!(usize, parse_usize); +impl_parse!(u128, parse_u128); + +fn parse_u8(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + if multiply > u8::MAX as u128 { + return Err(ParseError::PosOverflow); + } + + parse_number(src, multiply as u16, u8::MAX as u16).map(|value| value as u8) +} + +fn parse_u16(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + if multiply > u16::MAX as u128 { + return Err(ParseError::PosOverflow); + } + + parse_number(src, multiply as u32, u16::MAX as u32).map(|value| value as u16) +} + +fn parse_u32(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + if multiply > u32::MAX as u128 { + return Err(ParseError::PosOverflow); + } + + parse_number(src, multiply as u64, u32::MAX as u64).map(|value| value as u32) +} + +fn parse_u64(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + if multiply > u64::MAX as u128 { + return Err(ParseError::PosOverflow); + } + + parse_number(src, multiply as u64, u64::MAX) +} + +#[cfg(target_pointer_width = "16")] +fn parse_usize(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + if multiply > usize::MAX as u128 { + return Err(ParseError::PosOverflow); + } + + parse_number(src, multiply as u32, usize::MAX as u32).map(|value| value as usize) +} + +#[cfg(target_pointer_width = "32")] +fn parse_usize(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + if multiply > usize::MAX as u128 { + return Err(ParseError::PosOverflow); + } -fn parse(mut src: &[u8], max: u128) -> Result { + parse_number(src, multiply as u64, usize::MAX as u64).map(|value| value as usize) +} + +#[cfg(target_pointer_width = "64")] +fn parse_usize(mut src: &[u8]) -> Result { let multiply = parse_unit(&mut src)?; - if multiply > max { + if multiply > usize::MAX as u128 { return Err(ParseError::PosOverflow); } - parse_number(src, multiply, max) + parse_number(src, multiply as u64, usize::MAX as u64).map(|value| value as usize) +} + +#[cfg(not(any( + target_pointer_width = "16", + target_pointer_width = "32", + target_pointer_width = "64" +)))] +fn parse_usize(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + if multiply > usize::MAX as u128 { + return Err(ParseError::PosOverflow); + } + + parse_number(src, multiply, usize::MAX as u128).map(|value| value as usize) +} + +fn parse_u128(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + + parse_number(src, multiply, u128::MAX) } fn parse_unit(src: &mut &[u8]) -> Result { @@ -154,7 +234,61 @@ fn binary_factor(prefix: u8) -> Option { }) } -fn parse_number(src: &[u8], multiply: u128, max: u128) -> Result { +trait ParseNumber: Copy { + const ZERO: Self; + + fn append_digit(self, digit: u8) -> Option; + fn round_overflowed(self, digit: u8) -> Self; + fn multiply_integer(self, multiply: Self, exponent: u32, max: Self) + -> Result; + fn divide_integer(self, multiply: Self, exponent: u32, max: Self) -> Result; +} + +macro_rules! impl_parse_number { + ($ty:ty, $multiply:ident, $divide:ident) => { + impl ParseNumber for $ty { + const ZERO: Self = 0; + + fn append_digit(self, digit: u8) -> Option { + self.checked_mul(10) + .and_then(|value| value.checked_add((digit - b'0') as $ty)) + } + + fn round_overflowed(self, digit: u8) -> Self { + if digit >= b'5' { + self.saturating_add(1) + } else { + self + } + } + + fn multiply_integer( + self, + multiply: Self, + exponent: u32, + max: Self, + ) -> Result { + $multiply(self, multiply, exponent, max) + } + + fn divide_integer( + self, + multiply: Self, + exponent: u32, + max: Self, + ) -> Result { + $divide(self, multiply, exponent, max) + } + } + }; +} + +impl_parse_number!(u16, multiply_integer_u16, divide_integer_u16); +impl_parse_number!(u32, multiply_integer_u32, divide_integer_u32); +impl_parse_number!(u64, multiply_integer_u64, divide_integer_u64); +impl_parse_number!(u128, multiply_integer_u128, divide_integer_u128); + +fn parse_number(src: &[u8], multiply: N, max: N) -> Result { #[derive(Clone, Copy, PartialEq, Eq)] enum State { Empty, @@ -166,7 +300,7 @@ fn parse_number(src: &[u8], multiply: u128, max: u128) -> Result Result { - if let Some(next) = append_digit(mantissa, *b) { + if let Some(next) = mantissa.append_digit(*b) { mantissa = next; state = State::Integer; } else { - mantissa = round_overflowed_mantissa(mantissa, *b); + mantissa = mantissa.round_overflowed(*b); fractional_exponent = fractional_exponent.saturating_add(1); state = State::IntegerOverflow; } @@ -187,11 +321,11 @@ fn parse_number(src: &[u8], multiply: u128, max: u128) -> Result { - if let Some(next) = append_digit(mantissa, *b) { + if let Some(next) = mantissa.append_digit(*b) { mantissa = next; fractional_exponent = fractional_exponent.saturating_sub(1); } else { - mantissa = round_overflowed_mantissa(mantissa, *b); + mantissa = mantissa.round_overflowed(*b); state = State::FractionOverflow; } } @@ -221,54 +355,182 @@ fn parse_number(src: &[u8], multiply: u128, max: u128) -> Result= 0 { - multiply_integer(mantissa, multiply, exponent.unsigned_abs(), max) + mantissa.multiply_integer(multiply, exponent.unsigned_abs(), max) } else { - divide_integer(mantissa, multiply, exponent.unsigned_abs(), max) + mantissa.divide_integer(multiply, exponent.unsigned_abs(), max) } } -fn append_digit(value: u128, digit: u8) -> Option { - value - .checked_mul(10) - .and_then(|value| value.checked_add(u128::from(digit - b'0'))) +fn append_exponent_digit(exponent: i32, digit: u8, positive: bool) -> Result { + let digit = (digit - b'0') as i32; + if positive { + let Some(exponent) = exponent.checked_mul(10) else { + return Err(ParseError::PosOverflow); + }; + let Some(exponent) = exponent.checked_add(digit) else { + return Err(ParseError::PosOverflow); + }; + + Ok(exponent) + } else { + let Some(exponent) = exponent.checked_mul(10) else { + return Ok(i32::MIN); + }; + let Some(exponent) = exponent.checked_sub(digit) else { + return Ok(i32::MIN); + }; + + Ok(exponent) + } +} + +macro_rules! multiply_integer { + ($name:ident, $ty:ty) => { + fn $name(mantissa: $ty, multiply: $ty, exponent: u32, max: $ty) -> Result<$ty, ParseError> { + let Some(power) = <$ty>::from(10_u8).checked_pow(exponent) else { + return Err(ParseError::PosOverflow); + }; + let Some(multiply) = multiply.checked_mul(power) else { + return Err(ParseError::PosOverflow); + }; + let Some(value) = mantissa.checked_mul(multiply) else { + return Err(ParseError::PosOverflow); + }; + + if value > max { + Err(ParseError::PosOverflow) + } else { + Ok(value) + } + } + }; +} + +multiply_integer!(multiply_integer_u16, u16); +multiply_integer!(multiply_integer_u32, u32); +multiply_integer!(multiply_integer_u64, u64); + +fn divide_integer_u16( + mantissa: u16, + multiply: u16, + exponent: u32, + max: u16, +) -> Result { + let product = (mantissa as u32) * (multiply as u32); + divide_integer_u32_product(product, exponent, max as u32).map(|value| value as u16) } -fn round_overflowed_mantissa(mantissa: u128, digit: u8) -> u128 { - if digit >= b'5' { - mantissa.saturating_add(1) +fn divide_integer_u32( + mantissa: u32, + multiply: u32, + exponent: u32, + max: u32, +) -> Result { + let product = (mantissa as u64) * (multiply as u64); + divide_integer_u64_product(product, exponent, max as u64).map(|value| value as u32) +} + +fn divide_integer_u64( + mantissa: u64, + multiply: u64, + exponent: u32, + max: u64, +) -> Result { + let product = (mantissa as u128) * (multiply as u128); + divide_integer_u128_product(product, exponent, max as u128).map(|value| value as u64) +} + +fn divide_integer_u32_product(product: u32, exponent: u32, max: u32) -> Result { + if exponent >= 10 { + return Ok(0); + } + + let power = 10_u32.pow(exponent); + let quotient = product / power; + let remainder = product % power; + let value = if exponent != 0 && remainder >= power / 2 { + let Some(value) = quotient.checked_add(1) else { + return Err(ParseError::PosOverflow); + }; + value } else { - mantissa + quotient + }; + + if value > max { + Err(ParseError::PosOverflow) + } else { + Ok(value) } } -fn append_exponent_digit(exponent: i32, digit: u8, positive: bool) -> Result { - let digit = i32::from(digit - b'0'); - if positive { - exponent - .checked_mul(10) - .and_then(|value| value.checked_add(digit)) - .ok_or(ParseError::PosOverflow) +fn divide_integer_u64_product(product: u64, exponent: u32, max: u64) -> Result { + if exponent >= 20 { + return Ok(0); + } + + let power = 10_u64.pow(exponent); + let quotient = product / power; + let remainder = product % power; + let value = if exponent != 0 && remainder >= power / 2 { + let Some(value) = quotient.checked_add(1) else { + return Err(ParseError::PosOverflow); + }; + value } else { - Ok(exponent - .checked_mul(10) - .and_then(|value| value.checked_sub(digit)) - .unwrap_or(i32::MIN)) + quotient + }; + + if value > max { + Err(ParseError::PosOverflow) + } else { + Ok(value) } } -fn multiply_integer( +fn divide_integer_u128_product( + product: u128, + exponent: u32, + max: u128, +) -> Result { + if exponent > 38 { + return Ok(0); + } + + let power = 10_u128.pow(exponent); + let quotient = product / power; + let remainder = product % power; + let value = if exponent != 0 && remainder >= power / 2 { + let Some(value) = quotient.checked_add(1) else { + return Err(ParseError::PosOverflow); + }; + value + } else { + quotient + }; + + if value > max { + Err(ParseError::PosOverflow) + } else { + Ok(value) + } +} + +fn multiply_integer_u128( mantissa: u128, multiply: u128, exponent: u32, max: u128, ) -> Result { - let power = 10_u128 - .checked_pow(exponent) - .ok_or(ParseError::PosOverflow)?; - let multiply = multiply.checked_mul(power).ok_or(ParseError::PosOverflow)?; - let value = mantissa - .checked_mul(multiply) - .ok_or(ParseError::PosOverflow)?; + let Some(power) = 10_u128.checked_pow(exponent) else { + return Err(ParseError::PosOverflow); + }; + let Some(multiply) = multiply.checked_mul(power) else { + return Err(ParseError::PosOverflow); + }; + let Some(value) = mantissa.checked_mul(multiply) else { + return Err(ParseError::PosOverflow); + }; if value > max { Err(ParseError::PosOverflow) @@ -277,30 +539,41 @@ fn multiply_integer( } } -fn divide_integer( +fn divide_integer_u128( mantissa: u128, multiply: u128, exponent: u32, max: u128, ) -> Result { + if let Some(result) = divide_integer_u128_fast(mantissa, multiply, exponent, max) { + return result; + } + if exponent >= 58 { return Ok(0); } let mut value = U256::multiply(mantissa, multiply); let mut round = false; + let mut idx = 0; - for _ in 0..exponent { + while idx < exponent { let (quotient, remainder) = value.div_rem_10(); value = quotient; round = remainder >= 5; + idx += 1; } if round { - value = value.checked_add_one().ok_or(ParseError::PosOverflow)?; + let Some(rounded) = value.checked_add_one() else { + return Err(ParseError::PosOverflow); + }; + value = rounded; } - let value = value.try_into_u128().ok_or(ParseError::PosOverflow)?; + let Some(value) = value.try_into_u128() else { + return Err(ParseError::PosOverflow); + }; if value > max { Err(ParseError::PosOverflow) } else { @@ -308,6 +581,39 @@ fn divide_integer( } } +fn divide_integer_u128_fast( + mantissa: u128, + multiply: u128, + exponent: u32, + max: u128, +) -> Option> { + let product = mantissa.checked_mul(multiply)?; + + if exponent >= 39 { + return Some(Ok(0)); + } + + let Some(power) = 10_u128.checked_pow(exponent) else { + return Some(Err(ParseError::PosOverflow)); + }; + let quotient = product / power; + let remainder = product % power; + let value = if remainder >= power / 2 { + let Some(value) = quotient.checked_add(1) else { + return Some(Err(ParseError::PosOverflow)); + }; + value + } else { + quotient + }; + + if value > max { + Some(Err(ParseError::PosOverflow)) + } else { + Some(Ok(value)) + } +} + #[derive(Clone, Copy)] struct U256 { hi: u128, @@ -316,7 +622,7 @@ struct U256 { impl U256 { fn multiply(lhs: u128, rhs: u128) -> Self { - let mask = u128::from(u64::MAX); + let mask = u64::MAX as u128; let lhs_lo = lhs & mask; let lhs_hi = lhs >> 64; let rhs_lo = rhs & mask; @@ -366,11 +672,7 @@ impl U256 { } fn try_into_u128(self) -> Option { - if self.hi == 0 { - Some(self.lo) - } else { - None - } + if self.hi == 0 { Some(self.lo) } else { None } } } @@ -408,6 +710,13 @@ mod tests { assert_eq!(BSize::::parse(b"0.5B").unwrap(), BSize(1)); assert_eq!(BSize::::parse(b"0.4B").unwrap(), BSize(0)); assert_eq!(BSize::::parse(b"1.5e3B").unwrap(), BSize(1_500)); + assert_eq!(BSize::::parse(b"25.55B").unwrap(), BSize(26)); + assert_eq!(BSize::::parse(b"255.4B").unwrap(), BSize(u8::MAX)); + assert_eq!(BSize::::parse(b"65535.4B").unwrap(), BSize(u16::MAX)); + assert_eq!( + BSize::::parse(b"4294967295.4B").unwrap(), + BSize(u32::MAX) + ); } #[test] @@ -424,6 +733,15 @@ mod tests { assert_eq!(BSize::::parse(b"256B"), Err(ParseError::PosOverflow)); assert_eq!(BSize::::parse(b"1KB"), Err(ParseError::PosOverflow)); assert_eq!(BSize::::parse(b"0.001KB"), Err(ParseError::PosOverflow)); + assert_eq!(BSize::::parse(b"255.5B"), Err(ParseError::PosOverflow)); + assert_eq!( + BSize::::parse(b"65535.5B"), + Err(ParseError::PosOverflow) + ); + assert_eq!( + BSize::::parse(b"4294967295.5B"), + Err(ParseError::PosOverflow) + ); assert_eq!( BSize::::parse(b"65.536KB"), Err(ParseError::PosOverflow) From f6a580daabc4ae6245c4b6c7aa2e93b147232333 Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 9 Jun 2026 18:06:32 +0800 Subject: [PATCH 04/10] take 3 Signed-off-by: tison --- bsize/src/parse.rs | 781 ---------------------------------- bsize/src/parse/mod.rs | 214 ++++++++++ bsize/src/parse/number.rs | 134 ++++++ bsize/src/parse/parse_u128.rs | 196 +++++++++ bsize/src/parse/parse_u16.rs | 116 +++++ bsize/src/parse/parse_u32.rs | 80 ++++ bsize/src/parse/parse_u64.rs | 145 +++++++ bsize/src/parse/parse_u8.rs | 78 ++++ bsize/src/parse/u256.rs | 80 ++++ 9 files changed, 1043 insertions(+), 781 deletions(-) delete mode 100644 bsize/src/parse.rs create mode 100644 bsize/src/parse/mod.rs create mode 100644 bsize/src/parse/number.rs create mode 100644 bsize/src/parse/parse_u128.rs create mode 100644 bsize/src/parse/parse_u16.rs create mode 100644 bsize/src/parse/parse_u32.rs create mode 100644 bsize/src/parse/parse_u64.rs create mode 100644 bsize/src/parse/parse_u8.rs create mode 100644 bsize/src/parse/u256.rs diff --git a/bsize/src/parse.rs b/bsize/src/parse.rs deleted file mode 100644 index ebc6feb..0000000 --- a/bsize/src/parse.rs +++ /dev/null @@ -1,781 +0,0 @@ -// Copyright 2026 FastLabs Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use core::fmt; -use core::str::FromStr; - -use crate::types::BSize; - -/// The error returned when parsing a byte size fails. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum ParseError { - /// The input contains no number. - Empty, - /// The input contains an invalid byte. - InvalidDigit, - /// The parsed byte count is too large for the target integer type. - PosOverflow, -} - -impl fmt::Display for ParseError { - 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", - }) - } -} - -impl core::error::Error for ParseError {} - -impl From for core::num::IntErrorKind { - fn from(error: ParseError) -> Self { - match error { - ParseError::Empty => Self::Empty, - ParseError::InvalidDigit => Self::InvalidDigit, - ParseError::PosOverflow => Self::PosOverflow, - } - } -} - -macro_rules! impl_parse { - ($ty:ty, $parse:ident) => { - impl BSize<$ty> { - /// Parses a byte size from a byte slice. - /// - /// The input must end with a `B` or `b` byte suffix. Supported - /// units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, - /// `TiB`, `PB`, `PiB`, `EB`, and `EiB`, case-insensitively. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input is empty, contains an - /// invalid byte, or overflows the target integer type. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - $parse(src.as_ref()).map(BSize) - } - } - - impl FromStr for BSize<$ty> { - type Err = ParseError; - - fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) - } - } - }; -} - -impl_parse!(u8, parse_u8); -impl_parse!(u16, parse_u16); -impl_parse!(u32, parse_u32); -impl_parse!(u64, parse_u64); -impl_parse!(usize, parse_usize); -impl_parse!(u128, parse_u128); - -fn parse_u8(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - if multiply > u8::MAX as u128 { - return Err(ParseError::PosOverflow); - } - - parse_number(src, multiply as u16, u8::MAX as u16).map(|value| value as u8) -} - -fn parse_u16(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - if multiply > u16::MAX as u128 { - return Err(ParseError::PosOverflow); - } - - parse_number(src, multiply as u32, u16::MAX as u32).map(|value| value as u16) -} - -fn parse_u32(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - if multiply > u32::MAX as u128 { - return Err(ParseError::PosOverflow); - } - - parse_number(src, multiply as u64, u32::MAX as u64).map(|value| value as u32) -} - -fn parse_u64(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - if multiply > u64::MAX as u128 { - return Err(ParseError::PosOverflow); - } - - parse_number(src, multiply as u64, u64::MAX) -} - -#[cfg(target_pointer_width = "16")] -fn parse_usize(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - if multiply > usize::MAX as u128 { - return Err(ParseError::PosOverflow); - } - - parse_number(src, multiply as u32, usize::MAX as u32).map(|value| value as usize) -} - -#[cfg(target_pointer_width = "32")] -fn parse_usize(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - if multiply > usize::MAX as u128 { - return Err(ParseError::PosOverflow); - } - - parse_number(src, multiply as u64, usize::MAX as u64).map(|value| value as usize) -} - -#[cfg(target_pointer_width = "64")] -fn parse_usize(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - if multiply > usize::MAX as u128 { - return Err(ParseError::PosOverflow); - } - - parse_number(src, multiply as u64, usize::MAX as u64).map(|value| value as usize) -} - -#[cfg(not(any( - target_pointer_width = "16", - target_pointer_width = "32", - target_pointer_width = "64" -)))] -fn parse_usize(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - if multiply > usize::MAX as u128 { - return Err(ParseError::PosOverflow); - } - - parse_number(src, multiply, usize::MAX as u128).map(|value| value as usize) -} - -fn parse_u128(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - - parse_number(src, multiply, u128::MAX) -} - -fn parse_unit(src: &mut &[u8]) -> Result { - let Some((&suffix, before_b)) = src.split_last() else { - return if src.is_empty() { - Err(ParseError::Empty) - } else { - Err(ParseError::InvalidDigit) - }; - }; - if !suffix.eq_ignore_ascii_case(&b'B') { - return Err(ParseError::InvalidDigit); - } - - *src = before_b; - - if let Some((&infix, before_i)) = src.split_last() { - if infix.eq_ignore_ascii_case(&b'i') { - let Some((&prefix, before_prefix)) = before_i.split_last() else { - return Err(ParseError::InvalidDigit); - }; - let Some(factor) = binary_factor(prefix) else { - return Err(ParseError::InvalidDigit); - }; - - *src = before_prefix; - return Ok(factor); - } - } - - if let Some((&prefix, before_prefix)) = src.split_last() { - if let Some(factor) = decimal_factor(prefix) { - *src = before_prefix; - return Ok(factor); - } - } - - Ok(1) -} - -fn decimal_factor(prefix: u8) -> Option { - Some(match prefix.to_ascii_uppercase() { - b'K' => 1_000, - b'M' => 1_000_000, - b'G' => 1_000_000_000, - b'T' => 1_000_000_000_000, - b'P' => 1_000_000_000_000_000, - b'E' => 1_000_000_000_000_000_000, - _ => return None, - }) -} - -fn binary_factor(prefix: u8) -> Option { - Some(match prefix.to_ascii_uppercase() { - b'K' => 1_u128 << 10, - b'M' => 1_u128 << 20, - b'G' => 1_u128 << 30, - b'T' => 1_u128 << 40, - b'P' => 1_u128 << 50, - b'E' => 1_u128 << 60, - _ => return None, - }) -} - -trait ParseNumber: Copy { - const ZERO: Self; - - fn append_digit(self, digit: u8) -> Option; - fn round_overflowed(self, digit: u8) -> Self; - fn multiply_integer(self, multiply: Self, exponent: u32, max: Self) - -> Result; - fn divide_integer(self, multiply: Self, exponent: u32, max: Self) -> Result; -} - -macro_rules! impl_parse_number { - ($ty:ty, $multiply:ident, $divide:ident) => { - impl ParseNumber for $ty { - const ZERO: Self = 0; - - fn append_digit(self, digit: u8) -> Option { - self.checked_mul(10) - .and_then(|value| value.checked_add((digit - b'0') as $ty)) - } - - fn round_overflowed(self, digit: u8) -> Self { - if digit >= b'5' { - self.saturating_add(1) - } else { - self - } - } - - fn multiply_integer( - self, - multiply: Self, - exponent: u32, - max: Self, - ) -> Result { - $multiply(self, multiply, exponent, max) - } - - fn divide_integer( - self, - multiply: Self, - exponent: u32, - max: Self, - ) -> Result { - $divide(self, multiply, exponent, max) - } - } - }; -} - -impl_parse_number!(u16, multiply_integer_u16, divide_integer_u16); -impl_parse_number!(u32, multiply_integer_u32, divide_integer_u32); -impl_parse_number!(u64, multiply_integer_u64, divide_integer_u64); -impl_parse_number!(u128, multiply_integer_u128, divide_integer_u128); - -fn parse_number(src: &[u8], multiply: N, max: N) -> Result { - #[derive(Clone, Copy, PartialEq, Eq)] - enum State { - Empty, - Integer, - IntegerOverflow, - Fraction, - FractionOverflow, - PosExponent, - NegExponent, - } - - let mut mantissa = N::ZERO; - let mut fractional_exponent = 0_i32; - let mut exponent = 0_i32; - let mut state = State::Empty; - - for b in src { - match (state, *b) { - (State::Integer | State::Empty, b'0'..=b'9') => { - if let Some(next) = mantissa.append_digit(*b) { - mantissa = next; - state = State::Integer; - } else { - mantissa = mantissa.round_overflowed(*b); - fractional_exponent = fractional_exponent.saturating_add(1); - state = State::IntegerOverflow; - } - } - (State::IntegerOverflow, b'0'..=b'9') => { - fractional_exponent = fractional_exponent.saturating_add(1); - } - (State::Fraction, b'0'..=b'9') => { - if let Some(next) = mantissa.append_digit(*b) { - mantissa = next; - fractional_exponent = fractional_exponent.saturating_sub(1); - } else { - mantissa = mantissa.round_overflowed(*b); - state = State::FractionOverflow; - } - } - (State::PosExponent, b'0'..=b'9') => { - exponent = append_exponent_digit(exponent, *b, true)?; - } - (State::NegExponent, b'0'..=b'9') => { - exponent = append_exponent_digit(exponent, *b, false)?; - } - (_, b'_' | b' ') - | (State::PosExponent, b'+') - | (State::FractionOverflow, b'0'..=b'9') => {} - ( - State::Integer | State::Fraction | State::IntegerOverflow | State::FractionOverflow, - b'e' | b'E', - ) => state = State::PosExponent, - (State::PosExponent, b'-') => state = State::NegExponent, - (State::Integer, b'.') => state = State::Fraction, - (State::IntegerOverflow, b'.') => state = State::FractionOverflow, - _ => return Err(ParseError::InvalidDigit), - } - } - - if state == State::Empty { - return Err(ParseError::Empty); - } - - let exponent = exponent.saturating_add(fractional_exponent); - if exponent >= 0 { - mantissa.multiply_integer(multiply, exponent.unsigned_abs(), max) - } else { - mantissa.divide_integer(multiply, exponent.unsigned_abs(), max) - } -} - -fn append_exponent_digit(exponent: i32, digit: u8, positive: bool) -> Result { - let digit = (digit - b'0') as i32; - if positive { - let Some(exponent) = exponent.checked_mul(10) else { - return Err(ParseError::PosOverflow); - }; - let Some(exponent) = exponent.checked_add(digit) else { - return Err(ParseError::PosOverflow); - }; - - Ok(exponent) - } else { - let Some(exponent) = exponent.checked_mul(10) else { - return Ok(i32::MIN); - }; - let Some(exponent) = exponent.checked_sub(digit) else { - return Ok(i32::MIN); - }; - - Ok(exponent) - } -} - -macro_rules! multiply_integer { - ($name:ident, $ty:ty) => { - fn $name(mantissa: $ty, multiply: $ty, exponent: u32, max: $ty) -> Result<$ty, ParseError> { - let Some(power) = <$ty>::from(10_u8).checked_pow(exponent) else { - return Err(ParseError::PosOverflow); - }; - let Some(multiply) = multiply.checked_mul(power) else { - return Err(ParseError::PosOverflow); - }; - let Some(value) = mantissa.checked_mul(multiply) else { - return Err(ParseError::PosOverflow); - }; - - if value > max { - Err(ParseError::PosOverflow) - } else { - Ok(value) - } - } - }; -} - -multiply_integer!(multiply_integer_u16, u16); -multiply_integer!(multiply_integer_u32, u32); -multiply_integer!(multiply_integer_u64, u64); - -fn divide_integer_u16( - mantissa: u16, - multiply: u16, - exponent: u32, - max: u16, -) -> Result { - let product = (mantissa as u32) * (multiply as u32); - divide_integer_u32_product(product, exponent, max as u32).map(|value| value as u16) -} - -fn divide_integer_u32( - mantissa: u32, - multiply: u32, - exponent: u32, - max: u32, -) -> Result { - let product = (mantissa as u64) * (multiply as u64); - divide_integer_u64_product(product, exponent, max as u64).map(|value| value as u32) -} - -fn divide_integer_u64( - mantissa: u64, - multiply: u64, - exponent: u32, - max: u64, -) -> Result { - let product = (mantissa as u128) * (multiply as u128); - divide_integer_u128_product(product, exponent, max as u128).map(|value| value as u64) -} - -fn divide_integer_u32_product(product: u32, exponent: u32, max: u32) -> Result { - if exponent >= 10 { - return Ok(0); - } - - let power = 10_u32.pow(exponent); - let quotient = product / power; - let remainder = product % power; - let value = if exponent != 0 && remainder >= power / 2 { - let Some(value) = quotient.checked_add(1) else { - return Err(ParseError::PosOverflow); - }; - value - } else { - quotient - }; - - if value > max { - Err(ParseError::PosOverflow) - } else { - Ok(value) - } -} - -fn divide_integer_u64_product(product: u64, exponent: u32, max: u64) -> Result { - if exponent >= 20 { - return Ok(0); - } - - let power = 10_u64.pow(exponent); - let quotient = product / power; - let remainder = product % power; - let value = if exponent != 0 && remainder >= power / 2 { - let Some(value) = quotient.checked_add(1) else { - return Err(ParseError::PosOverflow); - }; - value - } else { - quotient - }; - - if value > max { - Err(ParseError::PosOverflow) - } else { - Ok(value) - } -} - -fn divide_integer_u128_product( - product: u128, - exponent: u32, - max: u128, -) -> Result { - if exponent > 38 { - return Ok(0); - } - - let power = 10_u128.pow(exponent); - let quotient = product / power; - let remainder = product % power; - let value = if exponent != 0 && remainder >= power / 2 { - let Some(value) = quotient.checked_add(1) else { - return Err(ParseError::PosOverflow); - }; - value - } else { - quotient - }; - - if value > max { - Err(ParseError::PosOverflow) - } else { - Ok(value) - } -} - -fn multiply_integer_u128( - mantissa: u128, - multiply: u128, - exponent: u32, - max: u128, -) -> Result { - let Some(power) = 10_u128.checked_pow(exponent) else { - return Err(ParseError::PosOverflow); - }; - let Some(multiply) = multiply.checked_mul(power) else { - return Err(ParseError::PosOverflow); - }; - let Some(value) = mantissa.checked_mul(multiply) else { - return Err(ParseError::PosOverflow); - }; - - if value > max { - Err(ParseError::PosOverflow) - } else { - Ok(value) - } -} - -fn divide_integer_u128( - mantissa: u128, - multiply: u128, - exponent: u32, - max: u128, -) -> Result { - if let Some(result) = divide_integer_u128_fast(mantissa, multiply, exponent, max) { - return result; - } - - if exponent >= 58 { - return Ok(0); - } - - let mut value = U256::multiply(mantissa, multiply); - let mut round = false; - let mut idx = 0; - - while idx < exponent { - let (quotient, remainder) = value.div_rem_10(); - value = quotient; - round = remainder >= 5; - idx += 1; - } - - if round { - let Some(rounded) = value.checked_add_one() else { - return Err(ParseError::PosOverflow); - }; - value = rounded; - } - - let Some(value) = value.try_into_u128() else { - return Err(ParseError::PosOverflow); - }; - if value > max { - Err(ParseError::PosOverflow) - } else { - Ok(value) - } -} - -fn divide_integer_u128_fast( - mantissa: u128, - multiply: u128, - exponent: u32, - max: u128, -) -> Option> { - let product = mantissa.checked_mul(multiply)?; - - if exponent >= 39 { - return Some(Ok(0)); - } - - let Some(power) = 10_u128.checked_pow(exponent) else { - return Some(Err(ParseError::PosOverflow)); - }; - let quotient = product / power; - let remainder = product % power; - let value = if remainder >= power / 2 { - let Some(value) = quotient.checked_add(1) else { - return Some(Err(ParseError::PosOverflow)); - }; - value - } else { - quotient - }; - - if value > max { - Some(Err(ParseError::PosOverflow)) - } else { - Some(Ok(value)) - } -} - -#[derive(Clone, Copy)] -struct U256 { - hi: u128, - lo: u128, -} - -impl U256 { - fn multiply(lhs: u128, rhs: u128) -> Self { - let mask = u64::MAX as u128; - let lhs_lo = lhs & mask; - let lhs_hi = lhs >> 64; - let rhs_lo = rhs & mask; - let rhs_hi = rhs >> 64; - - let low = lhs_lo * rhs_lo; - let mid_left = lhs_lo * rhs_hi; - let mid_right = lhs_hi * rhs_lo; - let high = lhs_hi * rhs_hi; - - let carry = (low >> 64) + (mid_left & mask) + (mid_right & mask); - let lo = (low & mask) | ((carry & mask) << 64); - let hi = high + (mid_left >> 64) + (mid_right >> 64) + (carry >> 64); - - Self { hi, lo } - } - - fn div_rem_10(self) -> (Self, u8) { - let mut remainder = 0_u128; - - let (hi_hi, next) = div_limb(self.hi >> 64, remainder); - remainder = next; - let (hi_lo, next) = div_limb(self.hi as u64 as u128, remainder); - remainder = next; - let (lo_hi, next) = div_limb(self.lo >> 64, remainder); - remainder = next; - let (lo_lo, remainder) = div_limb(self.lo as u64 as u128, remainder); - - ( - Self { - hi: (hi_hi << 64) | hi_lo, - lo: (lo_hi << 64) | lo_lo, - }, - remainder as u8, - ) - } - - fn checked_add_one(self) -> Option { - let (lo, carry) = self.lo.overflowing_add(1); - let hi = if carry { - self.hi.checked_add(1)? - } else { - self.hi - }; - - Some(Self { hi, lo }) - } - - fn try_into_u128(self) -> Option { - if self.hi == 0 { Some(self.lo) } else { None } - } -} - -fn div_limb(limb: u128, remainder: u128) -> (u128, u128) { - let value = (remainder << 64) | limb; - (value / 10, value % 10) -} - -#[cfg(test)] -mod tests { - use super::{ParseError, U256}; - use crate::types::BSize; - - #[test] - fn parses_bytes() { - assert_eq!(BSize::::parse(b"255B").unwrap(), BSize(255)); - assert_eq!(BSize::::parse(b"1 B").unwrap(), BSize(1)); - assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); - } - - #[test] - fn parses_units() { - assert_eq!(BSize::::parse(b"1KB").unwrap(), BSize(1_000)); - assert_eq!(BSize::::parse(b"1KiB").unwrap(), BSize(1_024)); - assert_eq!(BSize::::parse(b"1kb").unwrap(), BSize(1_000)); - assert_eq!(BSize::::parse(b"1kib").unwrap(), BSize(1_024)); - assert_eq!(BSize::::parse(b"1KIB").unwrap(), BSize(1_024)); - assert_eq!(BSize::::parse(b"2MB").unwrap(), BSize(2_000_000)); - assert_eq!(BSize::::parse(b"2MiB").unwrap(), BSize(2_097_152)); - } - - #[test] - fn parses_fractional_units() { - assert_eq!(BSize::::parse(b"65.535KB").unwrap(), BSize(u16::MAX)); - assert_eq!(BSize::::parse(b"0.5B").unwrap(), BSize(1)); - assert_eq!(BSize::::parse(b"0.4B").unwrap(), BSize(0)); - assert_eq!(BSize::::parse(b"1.5e3B").unwrap(), BSize(1_500)); - assert_eq!(BSize::::parse(b"25.55B").unwrap(), BSize(26)); - assert_eq!(BSize::::parse(b"255.4B").unwrap(), BSize(u8::MAX)); - assert_eq!(BSize::::parse(b"65535.4B").unwrap(), BSize(u16::MAX)); - assert_eq!( - BSize::::parse(b"4294967295.4B").unwrap(), - BSize(u32::MAX) - ); - } - - #[test] - fn rejects_invalid_units() { - assert_eq!(BSize::::parse(b""), Err(ParseError::Empty)); - assert_eq!(BSize::::parse(b"1"), Err(ParseError::InvalidDigit)); - assert_eq!(BSize::::parse(b"1K"), Err(ParseError::InvalidDigit)); - assert_eq!(BSize::::parse(b"1XB"), Err(ParseError::InvalidDigit)); - assert_eq!(BSize::::parse(b"1iB"), Err(ParseError::InvalidDigit)); - } - - #[test] - fn rejects_overflow() { - assert_eq!(BSize::::parse(b"256B"), Err(ParseError::PosOverflow)); - assert_eq!(BSize::::parse(b"1KB"), Err(ParseError::PosOverflow)); - assert_eq!(BSize::::parse(b"0.001KB"), Err(ParseError::PosOverflow)); - assert_eq!(BSize::::parse(b"255.5B"), Err(ParseError::PosOverflow)); - assert_eq!( - BSize::::parse(b"65535.5B"), - Err(ParseError::PosOverflow) - ); - assert_eq!( - BSize::::parse(b"4294967295.5B"), - Err(ParseError::PosOverflow) - ); - assert_eq!( - BSize::::parse(b"65.536KB"), - Err(ParseError::PosOverflow) - ); - } - - #[test] - fn parses_u128_max() { - assert_eq!( - BSize::::parse(b"340282366920938463463374607431768211455B"), - Ok(BSize(u128::MAX)), - ); - assert_eq!( - BSize::::parse(b"340282366920938463463374607431768211456B"), - Err(ParseError::PosOverflow), - ); - } - - #[test] - fn parses_u128_max_with_decimal_unit() { - assert_eq!( - BSize::::parse(b"340282366920938463463.374607431768211455EB"), - Ok(BSize(u128::MAX)), - ); - } - - #[test] - fn multiplies_u128_into_u256() { - let value = U256::multiply(u128::MAX, 1_000_000_000_000_000_000); - - assert_eq!(value.hi, 999_999_999_999_999_999); - assert_eq!( - value.lo, - 340_282_366_920_938_463_462_374_607_431_768_211_456 - ); - } -} diff --git a/bsize/src/parse/mod.rs b/bsize/src/parse/mod.rs new file mode 100644 index 0000000..d0d9805 --- /dev/null +++ b/bsize/src/parse/mod.rs @@ -0,0 +1,214 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::fmt; + +/// The error returned when parsing a byte size fails. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ParseError; + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid byte size") + } +} + +impl core::error::Error for ParseError {} + +macro_rules! impl_parse { + ($ty:ty, $parse:path) => { + impl crate::types::BSize<$ty> { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. Supported + /// units depend on the target integer type. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input cannot be parsed as a byte + /// size for the target integer type. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + $parse(src.as_ref()).map(crate::types::BSize) + } + } + + impl core::str::FromStr for crate::types::BSize<$ty> { + type Err = crate::parse::ParseError; + + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) + } + } + }; +} + +macro_rules! impl_parse_number { + ($ty:ty, $multiply:path, $divide:path) => { + impl crate::parse::number::ParseNumber for $ty { + const ZERO: Self = 0; + + fn append_digit(self, digit: u8) -> Option { + self.checked_mul(10) + .and_then(|value| value.checked_add((digit - b'0') as $ty)) + } + + fn round_overflowed(self, digit: u8) -> Self { + if digit >= b'5' { + self.saturating_add(1) + } else { + self + } + } + + fn multiply_integer( + self, + multiply: Self, + exponent: u32, + max: Self, + ) -> Result { + $multiply(self, multiply, exponent, max) + } + + fn divide_integer( + self, + multiply: Self, + exponent: u32, + max: Self, + ) -> Result { + $divide(self, multiply, exponent, max) + } + } + }; +} + +mod number; +mod parse_u128; +mod parse_u16; +mod parse_u32; +mod parse_u64; +mod parse_u8; +mod u256; + +fn strip_b_suffix(src: &mut &[u8]) -> Result<(), ParseError> { + let Some((&suffix, before_b)) = src.split_last() else { + return Err(ParseError); + }; + if !suffix.eq_ignore_ascii_case(&b'B') { + return Err(ParseError); + } + + *src = before_b; + Ok(()) +} + +fn is_ascii_unit(byte: u8) -> bool { + matches!( + byte.to_ascii_uppercase(), + b'K' | b'M' | b'G' | b'T' | b'P' | b'E' + ) +} + +#[cfg(test)] +mod tests { + use super::ParseError; + use super::u256::U256; + use crate::types::BSize; + + #[test] + fn parses_bytes() { + assert_eq!(BSize::::parse(b"255B").unwrap(), BSize(255)); + assert_eq!(BSize::::parse(b"1 B").unwrap(), BSize(1)); + assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); + } + + #[test] + fn parses_units() { + assert_eq!(BSize::::parse(b"1KB").unwrap(), BSize(1_000)); + assert_eq!(BSize::::parse(b"1KiB").unwrap(), BSize(1_024)); + assert_eq!(BSize::::parse(b"1kb").unwrap(), BSize(1_000)); + assert_eq!(BSize::::parse(b"1kib").unwrap(), BSize(1_024)); + assert_eq!(BSize::::parse(b"1KIB").unwrap(), BSize(1_024)); + assert_eq!(BSize::::parse(b"2MB").unwrap(), BSize(2_000_000)); + assert_eq!(BSize::::parse(b"2MiB").unwrap(), BSize(2_097_152)); + } + + #[test] + fn parses_fractional_units() { + assert_eq!(BSize::::parse(b"65.535KB").unwrap(), BSize(u16::MAX)); + assert_eq!(BSize::::parse(b"0.5B").unwrap(), BSize(1)); + assert_eq!(BSize::::parse(b"0.4B").unwrap(), BSize(0)); + assert_eq!(BSize::::parse(b"1.5e3B").unwrap(), BSize(1_500)); + assert_eq!(BSize::::parse(b"25.55B").unwrap(), BSize(26)); + assert_eq!(BSize::::parse(b"255.4B").unwrap(), BSize(u8::MAX)); + assert_eq!(BSize::::parse(b"65535.4B").unwrap(), BSize(u16::MAX)); + assert_eq!( + BSize::::parse(b"4294967295.4B").unwrap(), + BSize(u32::MAX) + ); + } + + #[test] + fn rejects_invalid_units() { + assert_eq!(BSize::::parse(b""), Err(ParseError)); + assert_eq!(BSize::::parse(b"1"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1K"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1XB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1iB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1e+B"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1eB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1EB"), Err(ParseError)); + } + + #[test] + fn rejects_overflow() { + assert_eq!(BSize::::parse(b"256B"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1KB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"0.001KB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"255.5B"), Err(ParseError)); + assert_eq!(BSize::::parse(b"65535.5B"), Err(ParseError)); + assert_eq!(BSize::::parse(b"4294967295.5B"), Err(ParseError)); + assert_eq!(BSize::::parse(b"65.536KB"), Err(ParseError)); + } + + #[test] + fn parses_u128_max() { + assert_eq!( + BSize::::parse(b"340282366920938463463374607431768211455B"), + Ok(BSize(u128::MAX)), + ); + assert_eq!( + BSize::::parse(b"340282366920938463463374607431768211456B"), + Err(ParseError), + ); + } + + #[test] + fn parses_u128_max_with_decimal_unit() { + assert_eq!( + BSize::::parse(b"340282366920938463463.374607431768211455EB"), + Ok(BSize(u128::MAX)), + ); + } + + #[test] + fn multiplies_u128_into_u256() { + let value = U256::multiply(u128::MAX, 1_000_000_000_000_000_000); + + assert_eq!(value.hi, 999_999_999_999_999_999); + assert_eq!( + value.lo, + 340_282_366_920_938_463_462_374_607_431_768_211_456 + ); + } +} diff --git a/bsize/src/parse/number.rs b/bsize/src/parse/number.rs new file mode 100644 index 0000000..59daf2b --- /dev/null +++ b/bsize/src/parse/number.rs @@ -0,0 +1,134 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::ParseError; + +pub(in crate::parse) trait ParseNumber: Copy { + const ZERO: Self; + + fn append_digit(self, digit: u8) -> Option; + fn round_overflowed(self, digit: u8) -> Self; + fn multiply_integer(self, multiply: Self, exponent: u32, max: Self) + -> Result; + fn divide_integer(self, multiply: Self, exponent: u32, max: Self) -> Result; +} + +pub(in crate::parse) fn parse_number( + src: &[u8], + multiply: N, + max: N, +) -> Result { + #[derive(Clone, Copy, PartialEq, Eq)] + enum State { + Empty, + Integer, + IntegerOverflow, + Fraction, + FractionOverflow, + PosExponent, + NegExponent, + } + + let mut mantissa = N::ZERO; + let mut fractional_exponent = 0_i32; + let mut exponent = 0_i32; + let mut has_exponent_digit = false; + let mut state = State::Empty; + + for b in src { + match (state, *b) { + (State::Integer | State::Empty, b'0'..=b'9') => { + if let Some(next) = mantissa.append_digit(*b) { + mantissa = next; + state = State::Integer; + } else { + mantissa = mantissa.round_overflowed(*b); + fractional_exponent = fractional_exponent.saturating_add(1); + state = State::IntegerOverflow; + } + } + (State::IntegerOverflow, b'0'..=b'9') => { + fractional_exponent = fractional_exponent.saturating_add(1); + } + (State::Fraction, b'0'..=b'9') => { + if let Some(next) = mantissa.append_digit(*b) { + mantissa = next; + fractional_exponent = fractional_exponent.saturating_sub(1); + } else { + mantissa = mantissa.round_overflowed(*b); + state = State::FractionOverflow; + } + } + (State::PosExponent, b'0'..=b'9') => { + exponent = append_exponent_digit(exponent, *b, true)?; + has_exponent_digit = true; + } + (State::NegExponent, b'0'..=b'9') => { + exponent = append_exponent_digit(exponent, *b, false)?; + has_exponent_digit = true; + } + (_, b'_' | b' ') + | (State::PosExponent, b'+') + | (State::FractionOverflow, b'0'..=b'9') => {} + ( + State::Integer | State::Fraction | State::IntegerOverflow | State::FractionOverflow, + b'e' | b'E', + ) => { + state = State::PosExponent; + has_exponent_digit = false; + } + (State::PosExponent, b'-') => state = State::NegExponent, + (State::Integer, b'.') => state = State::Fraction, + (State::IntegerOverflow, b'.') => state = State::FractionOverflow, + _ => return Err(ParseError), + } + } + + if state == State::Empty { + return Err(ParseError); + } + if matches!(state, State::PosExponent | State::NegExponent) && !has_exponent_digit { + return Err(ParseError); + } + + let exponent = exponent.saturating_add(fractional_exponent); + if exponent >= 0 { + mantissa.multiply_integer(multiply, exponent.unsigned_abs(), max) + } else { + mantissa.divide_integer(multiply, exponent.unsigned_abs(), max) + } +} + +fn append_exponent_digit(exponent: i32, digit: u8, positive: bool) -> Result { + let digit = (digit - b'0') as i32; + if positive { + let Some(exponent) = exponent.checked_mul(10) else { + return Err(ParseError); + }; + let Some(exponent) = exponent.checked_add(digit) else { + return Err(ParseError); + }; + + Ok(exponent) + } else { + let Some(exponent) = exponent.checked_mul(10) else { + return Ok(i32::MIN); + }; + let Some(exponent) = exponent.checked_sub(digit) else { + return Ok(i32::MIN); + }; + + Ok(exponent) + } +} diff --git a/bsize/src/parse/parse_u128.rs b/bsize/src/parse/parse_u128.rs new file mode 100644 index 0000000..731070c --- /dev/null +++ b/bsize/src/parse/parse_u128.rs @@ -0,0 +1,196 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::ParseError; +use super::number::parse_number; +use super::u256::U256; + +impl_parse!(u128, parse_u128); +impl_parse_number!(u128, multiply_integer_u128, divide_integer_u128); + +#[cfg(not(any( + target_pointer_width = "16", + target_pointer_width = "32", + target_pointer_width = "64" +)))] +impl_parse!(usize, parse_usize); + +fn parse_u128(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + + parse_number(src, multiply, u128::MAX) +} + +#[cfg(not(any( + target_pointer_width = "16", + target_pointer_width = "32", + target_pointer_width = "64" +)))] +fn parse_usize(src: &[u8]) -> Result { + let value = parse_u128(src)?; + if value > usize::MAX as u128 { + Err(ParseError) + } else { + Ok(value as usize) + } +} + +fn parse_unit(src: &mut &[u8]) -> Result { + super::strip_b_suffix(src)?; + + if let Some((&infix, before_i)) = src.split_last() { + if infix.eq_ignore_ascii_case(&b'i') { + let Some((&prefix, before_prefix)) = before_i.split_last() else { + return Err(ParseError); + }; + let Some(factor) = binary_factor(prefix) else { + return Err(ParseError); + }; + + *src = before_prefix; + return Ok(factor); + } + } + + if let Some((&prefix, before_prefix)) = src.split_last() { + if let Some(factor) = decimal_factor(prefix) { + *src = before_prefix; + return Ok(factor); + } + } + + Ok(1) +} + +fn decimal_factor(prefix: u8) -> Option { + Some(match prefix.to_ascii_uppercase() { + b'K' => 1_000, + b'M' => 1_000_000, + b'G' => 1_000_000_000, + b'T' => 1_000_000_000_000, + b'P' => 1_000_000_000_000_000, + b'E' => 1_000_000_000_000_000_000, + _ => return None, + }) +} + +fn binary_factor(prefix: u8) -> Option { + Some(match prefix.to_ascii_uppercase() { + b'K' => 1_u128 << 10, + b'M' => 1_u128 << 20, + b'G' => 1_u128 << 30, + b'T' => 1_u128 << 40, + b'P' => 1_u128 << 50, + b'E' => 1_u128 << 60, + _ => return None, + }) +} + +fn multiply_integer_u128( + mantissa: u128, + multiply: u128, + exponent: u32, + max: u128, +) -> Result { + let Some(power) = 10_u128.checked_pow(exponent) else { + return Err(ParseError); + }; + let Some(multiply) = multiply.checked_mul(power) else { + return Err(ParseError); + }; + let Some(value) = mantissa.checked_mul(multiply) else { + return Err(ParseError); + }; + + if value > max { + Err(ParseError) + } else { + Ok(value) + } +} + +fn divide_integer_u128( + mantissa: u128, + multiply: u128, + exponent: u32, + max: u128, +) -> Result { + if let Some(result) = divide_integer_u128_fast(mantissa, multiply, exponent, max) { + return result; + } + + if exponent >= 58 { + return Ok(0); + } + + let mut value = U256::multiply(mantissa, multiply); + let mut round = false; + let mut idx = 0; + + while idx < exponent { + let (quotient, remainder) = value.div_rem_10(); + value = quotient; + round = remainder >= 5; + idx += 1; + } + + if round { + let Some(rounded) = value.checked_add_one() else { + return Err(ParseError); + }; + value = rounded; + } + + let Some(value) = value.try_into_u128() else { + return Err(ParseError); + }; + if value > max { + Err(ParseError) + } else { + Ok(value) + } +} + +fn divide_integer_u128_fast( + mantissa: u128, + multiply: u128, + exponent: u32, + max: u128, +) -> Option> { + let product = mantissa.checked_mul(multiply)?; + + if exponent >= 39 { + return Some(Ok(0)); + } + + let Some(power) = 10_u128.checked_pow(exponent) else { + return Some(Err(ParseError)); + }; + let quotient = product / power; + let remainder = product % power; + let value = if remainder >= power / 2 { + let Some(value) = quotient.checked_add(1) else { + return Some(Err(ParseError)); + }; + value + } else { + quotient + }; + + if value > max { + Some(Err(ParseError)) + } else { + Some(Ok(value)) + } +} diff --git a/bsize/src/parse/parse_u16.rs b/bsize/src/parse/parse_u16.rs new file mode 100644 index 0000000..fefb233 --- /dev/null +++ b/bsize/src/parse/parse_u16.rs @@ -0,0 +1,116 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::ParseError; +use super::number::parse_number; + +impl_parse!(u16, parse_u16); +impl_parse_number!(u32, multiply_integer_u32, divide_integer_u32); + +#[cfg(target_pointer_width = "16")] +impl_parse!(usize, parse_usize); + +fn parse_u16(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + + parse_number(src, multiply, u16::MAX as u32).map(|value| value as u16) +} + +#[cfg(target_pointer_width = "16")] +fn parse_usize(src: &[u8]) -> Result { + parse_u16(src).map(|value| value as usize) +} + +fn parse_unit(src: &mut &[u8]) -> Result { + super::strip_b_suffix(src)?; + + if let Some((&infix, before_i)) = src.split_last() { + if infix.eq_ignore_ascii_case(&b'i') { + let Some((&prefix, before_prefix)) = before_i.split_last() else { + return Err(ParseError); + }; + if prefix.eq_ignore_ascii_case(&b'K') { + *src = before_prefix; + return Ok(1_024); + } + + return Err(ParseError); + } + } + + if let Some((&prefix, before_prefix)) = src.split_last() { + if prefix.eq_ignore_ascii_case(&b'K') { + *src = before_prefix; + return Ok(1_000); + } + if super::is_ascii_unit(prefix) { + return Err(ParseError); + } + } + + Ok(1) +} + +fn multiply_integer_u32( + mantissa: u32, + multiply: u32, + exponent: u32, + max: u32, +) -> Result { + let Some(power) = 10_u32.checked_pow(exponent) else { + return Err(ParseError); + }; + let Some(multiply) = multiply.checked_mul(power) else { + return Err(ParseError); + }; + let Some(value) = mantissa.checked_mul(multiply) else { + return Err(ParseError); + }; + + if value > max { + Err(ParseError) + } else { + Ok(value) + } +} + +fn divide_integer_u32( + mantissa: u32, + multiply: u32, + exponent: u32, + max: u32, +) -> Result { + if exponent >= 13 { + return Ok(0); + } + + let product = (mantissa as u64) * (multiply as u64); + let power = 10_u64.pow(exponent); + let quotient = product / power; + let remainder = product % power; + let value = if exponent != 0 && remainder >= power / 2 { + let Some(value) = quotient.checked_add(1) else { + return Err(ParseError); + }; + value + } else { + quotient + }; + + if value > max as u64 { + Err(ParseError) + } else { + Ok(value as u32) + } +} diff --git a/bsize/src/parse/parse_u32.rs b/bsize/src/parse/parse_u32.rs new file mode 100644 index 0000000..62f9a11 --- /dev/null +++ b/bsize/src/parse/parse_u32.rs @@ -0,0 +1,80 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::ParseError; +use super::number::parse_number; + +impl_parse!(u32, parse_u32); + +#[cfg(target_pointer_width = "32")] +impl_parse!(usize, parse_usize); + +fn parse_u32(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + + parse_number(src, multiply, u32::MAX as u64).map(|value| value as u32) +} + +#[cfg(target_pointer_width = "32")] +fn parse_usize(src: &[u8]) -> Result { + parse_u32(src).map(|value| value as usize) +} + +fn parse_unit(src: &mut &[u8]) -> Result { + super::strip_b_suffix(src)?; + + if let Some((&infix, before_i)) = src.split_last() { + if infix.eq_ignore_ascii_case(&b'i') { + let Some((&prefix, before_prefix)) = before_i.split_last() else { + return Err(ParseError); + }; + let Some(factor) = binary_factor(prefix) else { + return Err(ParseError); + }; + + *src = before_prefix; + return Ok(factor); + } + } + + if let Some((&prefix, before_prefix)) = src.split_last() { + if let Some(factor) = decimal_factor(prefix) { + *src = before_prefix; + return Ok(factor); + } + if super::is_ascii_unit(prefix) { + return Err(ParseError); + } + } + + Ok(1) +} + +fn decimal_factor(prefix: u8) -> Option { + Some(match prefix.to_ascii_uppercase() { + b'K' => 1_000, + b'M' => 1_000_000, + b'G' => 1_000_000_000, + _ => return None, + }) +} + +fn binary_factor(prefix: u8) -> Option { + Some(match prefix.to_ascii_uppercase() { + b'K' => 1_u64 << 10, + b'M' => 1_u64 << 20, + b'G' => 1_u64 << 30, + _ => return None, + }) +} diff --git a/bsize/src/parse/parse_u64.rs b/bsize/src/parse/parse_u64.rs new file mode 100644 index 0000000..829a633 --- /dev/null +++ b/bsize/src/parse/parse_u64.rs @@ -0,0 +1,145 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::ParseError; +use super::number::parse_number; + +impl_parse!(u64, parse_u64); +impl_parse_number!(u64, multiply_integer_u64, divide_integer_u64); + +#[cfg(target_pointer_width = "64")] +impl_parse!(usize, parse_usize); + +fn parse_u64(mut src: &[u8]) -> Result { + let multiply = parse_unit(&mut src)?; + + parse_number(src, multiply, u64::MAX) +} + +#[cfg(target_pointer_width = "64")] +fn parse_usize(src: &[u8]) -> Result { + parse_u64(src).map(|value| value as usize) +} + +fn parse_unit(src: &mut &[u8]) -> Result { + super::strip_b_suffix(src)?; + + if let Some((&infix, before_i)) = src.split_last() { + if infix.eq_ignore_ascii_case(&b'i') { + let Some((&prefix, before_prefix)) = before_i.split_last() else { + return Err(ParseError); + }; + let Some(factor) = binary_factor(prefix) else { + return Err(ParseError); + }; + + *src = before_prefix; + return Ok(factor); + } + } + + if let Some((&prefix, before_prefix)) = src.split_last() { + if let Some(factor) = decimal_factor(prefix) { + *src = before_prefix; + return Ok(factor); + } + } + + Ok(1) +} + +fn decimal_factor(prefix: u8) -> Option { + Some(match prefix.to_ascii_uppercase() { + b'K' => 1_000, + b'M' => 1_000_000, + b'G' => 1_000_000_000, + b'T' => 1_000_000_000_000, + b'P' => 1_000_000_000_000_000, + b'E' => 1_000_000_000_000_000_000, + _ => return None, + }) +} + +fn binary_factor(prefix: u8) -> Option { + Some(match prefix.to_ascii_uppercase() { + b'K' => 1_u64 << 10, + b'M' => 1_u64 << 20, + b'G' => 1_u64 << 30, + b'T' => 1_u64 << 40, + b'P' => 1_u64 << 50, + b'E' => 1_u64 << 60, + _ => return None, + }) +} + +fn multiply_integer_u64( + mantissa: u64, + multiply: u64, + exponent: u32, + max: u64, +) -> Result { + let Some(power) = 10_u64.checked_pow(exponent) else { + return Err(ParseError); + }; + let Some(multiply) = multiply.checked_mul(power) else { + return Err(ParseError); + }; + let Some(value) = mantissa.checked_mul(multiply) else { + return Err(ParseError); + }; + + if value > max { + Err(ParseError) + } else { + Ok(value) + } +} + +fn divide_integer_u64( + mantissa: u64, + multiply: u64, + exponent: u32, + max: u64, +) -> Result { + let product = (mantissa as u128) * (multiply as u128); + divide_integer_u128_product(product, exponent, max as u128).map(|value| value as u64) +} + +fn divide_integer_u128_product( + product: u128, + exponent: u32, + max: u128, +) -> Result { + if exponent > 38 { + return Ok(0); + } + + let power = 10_u128.pow(exponent); + let quotient = product / power; + let remainder = product % power; + let value = if exponent != 0 && remainder >= power / 2 { + let Some(value) = quotient.checked_add(1) else { + return Err(ParseError); + }; + value + } else { + quotient + }; + + if value > max { + Err(ParseError) + } else { + Ok(value) + } +} diff --git a/bsize/src/parse/parse_u8.rs b/bsize/src/parse/parse_u8.rs new file mode 100644 index 0000000..e1aeea6 --- /dev/null +++ b/bsize/src/parse/parse_u8.rs @@ -0,0 +1,78 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::ParseError; +use super::number::parse_number; + +impl_parse!(u8, parse_u8); +impl_parse_number!(u16, multiply_integer_u16, divide_integer_u16); + +fn parse_u8(mut src: &[u8]) -> Result { + super::strip_b_suffix(&mut src)?; + + parse_number(src, 1_u16, u8::MAX as u16).map(|value| value as u8) +} + +fn multiply_integer_u16( + mantissa: u16, + multiply: u16, + exponent: u32, + max: u16, +) -> Result { + let Some(power) = 10_u16.checked_pow(exponent) else { + return Err(ParseError); + }; + let Some(multiply) = multiply.checked_mul(power) else { + return Err(ParseError); + }; + let Some(value) = mantissa.checked_mul(multiply) else { + return Err(ParseError); + }; + + if value > max { + Err(ParseError) + } else { + Ok(value) + } +} + +fn divide_integer_u16( + mantissa: u16, + multiply: u16, + exponent: u32, + max: u16, +) -> Result { + if exponent >= 6 { + return Ok(0); + } + + let product = (mantissa as u32) * (multiply as u32); + let power = 10_u32.pow(exponent); + let quotient = product / power; + let remainder = product % power; + let value = if exponent != 0 && remainder >= power / 2 { + let Some(value) = quotient.checked_add(1) else { + return Err(ParseError); + }; + value + } else { + quotient + }; + + if value > max as u32 { + Err(ParseError) + } else { + Ok(value as u16) + } +} diff --git a/bsize/src/parse/u256.rs b/bsize/src/parse/u256.rs new file mode 100644 index 0000000..9d33c44 --- /dev/null +++ b/bsize/src/parse/u256.rs @@ -0,0 +1,80 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[derive(Clone, Copy)] +pub(in crate::parse) struct U256 { + pub(in crate::parse) hi: u128, + pub(in crate::parse) lo: u128, +} + +impl U256 { + pub(in crate::parse) fn multiply(lhs: u128, rhs: u128) -> Self { + let mask = u64::MAX as u128; + let lhs_lo = lhs & mask; + let lhs_hi = lhs >> 64; + let rhs_lo = rhs & mask; + let rhs_hi = rhs >> 64; + + let low = lhs_lo * rhs_lo; + let mid_left = lhs_lo * rhs_hi; + let mid_right = lhs_hi * rhs_lo; + let high = lhs_hi * rhs_hi; + + let carry = (low >> 64) + (mid_left & mask) + (mid_right & mask); + let lo = (low & mask) | ((carry & mask) << 64); + let hi = high + (mid_left >> 64) + (mid_right >> 64) + (carry >> 64); + + Self { hi, lo } + } + + pub(in crate::parse) fn div_rem_10(self) -> (Self, u8) { + let mut remainder = 0_u128; + + let (hi_hi, next) = div_limb(self.hi >> 64, remainder); + remainder = next; + let (hi_lo, next) = div_limb(self.hi as u64 as u128, remainder); + remainder = next; + let (lo_hi, next) = div_limb(self.lo >> 64, remainder); + remainder = next; + let (lo_lo, remainder) = div_limb(self.lo as u64 as u128, remainder); + + ( + Self { + hi: (hi_hi << 64) | hi_lo, + lo: (lo_hi << 64) | lo_lo, + }, + remainder as u8, + ) + } + + pub(in crate::parse) fn checked_add_one(self) -> Option { + let (lo, carry) = self.lo.overflowing_add(1); + let hi = if carry { + self.hi.checked_add(1)? + } else { + self.hi + }; + + Some(Self { hi, lo }) + } + + pub(in crate::parse) fn try_into_u128(self) -> Option { + if self.hi == 0 { Some(self.lo) } else { None } + } +} + +fn div_limb(limb: u128, remainder: u128) -> (u128, u128) { + let value = (remainder << 64) | limb; + (value / 10, value % 10) +} From b918fdfb859acd5d88a3958837795725926a2117 Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 9 Jun 2026 19:13:57 +0800 Subject: [PATCH 05/10] take 4 Signed-off-by: tison --- bsize/benches/parse.rs | 8 +- bsize/src/parse/mod.rs | 164 +---------------------- bsize/src/parse/number.rs | 134 ------------------- bsize/src/parse/parse_u128.rs | 232 ++++++++++++++++++++------------ bsize/src/parse/parse_u16.rs | 209 ++++++++++++++++++++++++----- bsize/src/parse/parse_u32.rs | 196 ++++++++++++++++++++++++++- bsize/src/parse/parse_u64.rs | 240 +++++++++++++++++++++++++++------- bsize/src/parse/parse_u8.rs | 164 ++++++++++++++++------- bsize/src/parse/u256.rs | 80 ------------ 9 files changed, 833 insertions(+), 594 deletions(-) delete mode 100644 bsize/src/parse/number.rs delete mode 100644 bsize/src/parse/u256.rs diff --git a/bsize/benches/parse.rs b/bsize/benches/parse.rs index 57ed82b..ab8f1d5 100644 --- a/bsize/benches/parse.rs +++ b/bsize/benches/parse.rs @@ -41,13 +41,13 @@ fn parse_size_decimal() -> u64 { } #[divan::bench] -fn bsize_binary_exp() -> u64 { - BSize::::parse(black_box(b"1.5e3KiB")).unwrap().0 +fn bsize_binary_decimal() -> u64 { + BSize::::parse(black_box(b"1.5KiB")).unwrap().0 } #[divan::bench] -fn parse_size_binary_exp() -> u64 { - parse_size(black_box(b"1.5e3KiB")).unwrap() +fn parse_size_binary_decimal() -> u64 { + parse_size(black_box(b"1.5KiB")).unwrap() } #[divan::bench] diff --git a/bsize/src/parse/mod.rs b/bsize/src/parse/mod.rs index d0d9805..c5cc444 100644 --- a/bsize/src/parse/mod.rs +++ b/bsize/src/parse/mod.rs @@ -20,85 +20,17 @@ pub struct ParseError; impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("invalid byte size") + f.write_str("malformed byte size") } } impl core::error::Error for ParseError {} -macro_rules! impl_parse { - ($ty:ty, $parse:path) => { - impl crate::types::BSize<$ty> { - /// Parses a byte size from a byte slice. - /// - /// The input must end with a `B` or `b` byte suffix. Supported - /// units depend on the target integer type. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input cannot be parsed as a byte - /// size for the target integer type. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - $parse(src.as_ref()).map(crate::types::BSize) - } - } - - impl core::str::FromStr for crate::types::BSize<$ty> { - type Err = crate::parse::ParseError; - - fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) - } - } - }; -} - -macro_rules! impl_parse_number { - ($ty:ty, $multiply:path, $divide:path) => { - impl crate::parse::number::ParseNumber for $ty { - const ZERO: Self = 0; - - fn append_digit(self, digit: u8) -> Option { - self.checked_mul(10) - .and_then(|value| value.checked_add((digit - b'0') as $ty)) - } - - fn round_overflowed(self, digit: u8) -> Self { - if digit >= b'5' { - self.saturating_add(1) - } else { - self - } - } - - fn multiply_integer( - self, - multiply: Self, - exponent: u32, - max: Self, - ) -> Result { - $multiply(self, multiply, exponent, max) - } - - fn divide_integer( - self, - multiply: Self, - exponent: u32, - max: Self, - ) -> Result { - $divide(self, multiply, exponent, max) - } - } - }; -} - -mod number; mod parse_u128; mod parse_u16; mod parse_u32; mod parse_u64; mod parse_u8; -mod u256; fn strip_b_suffix(src: &mut &[u8]) -> Result<(), ParseError> { let Some((&suffix, before_b)) = src.split_last() else { @@ -118,97 +50,3 @@ fn is_ascii_unit(byte: u8) -> bool { b'K' | b'M' | b'G' | b'T' | b'P' | b'E' ) } - -#[cfg(test)] -mod tests { - use super::ParseError; - use super::u256::U256; - use crate::types::BSize; - - #[test] - fn parses_bytes() { - assert_eq!(BSize::::parse(b"255B").unwrap(), BSize(255)); - assert_eq!(BSize::::parse(b"1 B").unwrap(), BSize(1)); - assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); - } - - #[test] - fn parses_units() { - assert_eq!(BSize::::parse(b"1KB").unwrap(), BSize(1_000)); - assert_eq!(BSize::::parse(b"1KiB").unwrap(), BSize(1_024)); - assert_eq!(BSize::::parse(b"1kb").unwrap(), BSize(1_000)); - assert_eq!(BSize::::parse(b"1kib").unwrap(), BSize(1_024)); - assert_eq!(BSize::::parse(b"1KIB").unwrap(), BSize(1_024)); - assert_eq!(BSize::::parse(b"2MB").unwrap(), BSize(2_000_000)); - assert_eq!(BSize::::parse(b"2MiB").unwrap(), BSize(2_097_152)); - } - - #[test] - fn parses_fractional_units() { - assert_eq!(BSize::::parse(b"65.535KB").unwrap(), BSize(u16::MAX)); - assert_eq!(BSize::::parse(b"0.5B").unwrap(), BSize(1)); - assert_eq!(BSize::::parse(b"0.4B").unwrap(), BSize(0)); - assert_eq!(BSize::::parse(b"1.5e3B").unwrap(), BSize(1_500)); - assert_eq!(BSize::::parse(b"25.55B").unwrap(), BSize(26)); - assert_eq!(BSize::::parse(b"255.4B").unwrap(), BSize(u8::MAX)); - assert_eq!(BSize::::parse(b"65535.4B").unwrap(), BSize(u16::MAX)); - assert_eq!( - BSize::::parse(b"4294967295.4B").unwrap(), - BSize(u32::MAX) - ); - } - - #[test] - fn rejects_invalid_units() { - assert_eq!(BSize::::parse(b""), Err(ParseError)); - assert_eq!(BSize::::parse(b"1"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1K"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1XB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1iB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1e+B"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1eB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1EB"), Err(ParseError)); - } - - #[test] - fn rejects_overflow() { - assert_eq!(BSize::::parse(b"256B"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1KB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"0.001KB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"255.5B"), Err(ParseError)); - assert_eq!(BSize::::parse(b"65535.5B"), Err(ParseError)); - assert_eq!(BSize::::parse(b"4294967295.5B"), Err(ParseError)); - assert_eq!(BSize::::parse(b"65.536KB"), Err(ParseError)); - } - - #[test] - fn parses_u128_max() { - assert_eq!( - BSize::::parse(b"340282366920938463463374607431768211455B"), - Ok(BSize(u128::MAX)), - ); - assert_eq!( - BSize::::parse(b"340282366920938463463374607431768211456B"), - Err(ParseError), - ); - } - - #[test] - fn parses_u128_max_with_decimal_unit() { - assert_eq!( - BSize::::parse(b"340282366920938463463.374607431768211455EB"), - Ok(BSize(u128::MAX)), - ); - } - - #[test] - fn multiplies_u128_into_u256() { - let value = U256::multiply(u128::MAX, 1_000_000_000_000_000_000); - - assert_eq!(value.hi, 999_999_999_999_999_999); - assert_eq!( - value.lo, - 340_282_366_920_938_463_462_374_607_431_768_211_456 - ); - } -} diff --git a/bsize/src/parse/number.rs b/bsize/src/parse/number.rs deleted file mode 100644 index 59daf2b..0000000 --- a/bsize/src/parse/number.rs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2026 FastLabs Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::ParseError; - -pub(in crate::parse) trait ParseNumber: Copy { - const ZERO: Self; - - fn append_digit(self, digit: u8) -> Option; - fn round_overflowed(self, digit: u8) -> Self; - fn multiply_integer(self, multiply: Self, exponent: u32, max: Self) - -> Result; - fn divide_integer(self, multiply: Self, exponent: u32, max: Self) -> Result; -} - -pub(in crate::parse) fn parse_number( - src: &[u8], - multiply: N, - max: N, -) -> Result { - #[derive(Clone, Copy, PartialEq, Eq)] - enum State { - Empty, - Integer, - IntegerOverflow, - Fraction, - FractionOverflow, - PosExponent, - NegExponent, - } - - let mut mantissa = N::ZERO; - let mut fractional_exponent = 0_i32; - let mut exponent = 0_i32; - let mut has_exponent_digit = false; - let mut state = State::Empty; - - for b in src { - match (state, *b) { - (State::Integer | State::Empty, b'0'..=b'9') => { - if let Some(next) = mantissa.append_digit(*b) { - mantissa = next; - state = State::Integer; - } else { - mantissa = mantissa.round_overflowed(*b); - fractional_exponent = fractional_exponent.saturating_add(1); - state = State::IntegerOverflow; - } - } - (State::IntegerOverflow, b'0'..=b'9') => { - fractional_exponent = fractional_exponent.saturating_add(1); - } - (State::Fraction, b'0'..=b'9') => { - if let Some(next) = mantissa.append_digit(*b) { - mantissa = next; - fractional_exponent = fractional_exponent.saturating_sub(1); - } else { - mantissa = mantissa.round_overflowed(*b); - state = State::FractionOverflow; - } - } - (State::PosExponent, b'0'..=b'9') => { - exponent = append_exponent_digit(exponent, *b, true)?; - has_exponent_digit = true; - } - (State::NegExponent, b'0'..=b'9') => { - exponent = append_exponent_digit(exponent, *b, false)?; - has_exponent_digit = true; - } - (_, b'_' | b' ') - | (State::PosExponent, b'+') - | (State::FractionOverflow, b'0'..=b'9') => {} - ( - State::Integer | State::Fraction | State::IntegerOverflow | State::FractionOverflow, - b'e' | b'E', - ) => { - state = State::PosExponent; - has_exponent_digit = false; - } - (State::PosExponent, b'-') => state = State::NegExponent, - (State::Integer, b'.') => state = State::Fraction, - (State::IntegerOverflow, b'.') => state = State::FractionOverflow, - _ => return Err(ParseError), - } - } - - if state == State::Empty { - return Err(ParseError); - } - if matches!(state, State::PosExponent | State::NegExponent) && !has_exponent_digit { - return Err(ParseError); - } - - let exponent = exponent.saturating_add(fractional_exponent); - if exponent >= 0 { - mantissa.multiply_integer(multiply, exponent.unsigned_abs(), max) - } else { - mantissa.divide_integer(multiply, exponent.unsigned_abs(), max) - } -} - -fn append_exponent_digit(exponent: i32, digit: u8, positive: bool) -> Result { - let digit = (digit - b'0') as i32; - if positive { - let Some(exponent) = exponent.checked_mul(10) else { - return Err(ParseError); - }; - let Some(exponent) = exponent.checked_add(digit) else { - return Err(ParseError); - }; - - Ok(exponent) - } else { - let Some(exponent) = exponent.checked_mul(10) else { - return Ok(i32::MIN); - }; - let Some(exponent) = exponent.checked_sub(digit) else { - return Ok(i32::MIN); - }; - - Ok(exponent) - } -} diff --git a/bsize/src/parse/parse_u128.rs b/bsize/src/parse/parse_u128.rs index 731070c..b7a8d9f 100644 --- a/bsize/src/parse/parse_u128.rs +++ b/bsize/src/parse/parse_u128.rs @@ -13,23 +13,78 @@ // limitations under the License. use super::ParseError; -use super::number::parse_number; -use super::u256::U256; +use crate::types::BSize; +use core::str::FromStr; -impl_parse!(u128, parse_u128); -impl_parse_number!(u128, multiply_integer_u128, divide_integer_u128); +impl BSize { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. Supported units are + /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, `TiB`, `PB`, `PiB`, + /// `EB`, and `EiB`, case-insensitively. + /// + /// The numeric part may be an integer or an ordinary decimal number. + /// Scientific notation is not supported. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input cannot be parsed as a `u128` byte + /// size. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + parse_u128(src.as_ref()).map(BSize) + } +} + +impl FromStr for BSize { + type Err = ParseError; + + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) + } +} #[cfg(not(any( target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64" )))] -impl_parse!(usize, parse_usize); +impl BSize { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. On non-16/32/64-bit + /// targets, supported units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, + /// `GiB`, `TB`, `TiB`, `PB`, `PiB`, `EB`, and `EiB`, + /// case-insensitively. + /// + /// The numeric part may be an integer or an ordinary decimal number. + /// Scientific notation is not supported. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte + /// size. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + parse_usize(src.as_ref()).map(BSize) + } +} + +#[cfg(not(any( + target_pointer_width = "16", + target_pointer_width = "32", + target_pointer_width = "64" +)))] +impl FromStr for BSize { + type Err = ParseError; + + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) + } +} fn parse_u128(mut src: &[u8]) -> Result { let multiply = parse_unit(&mut src)?; - parse_number(src, multiply, u128::MAX) + parse_number(src, multiply) } #[cfg(not(any( @@ -97,100 +152,115 @@ fn binary_factor(prefix: u8) -> Option { }) } -fn multiply_integer_u128( - mantissa: u128, - multiply: u128, - exponent: u32, - max: u128, -) -> Result { - let Some(power) = 10_u128.checked_pow(exponent) else { - return Err(ParseError); - }; - let Some(multiply) = multiply.checked_mul(power) else { - return Err(ParseError); - }; - let Some(value) = mantissa.checked_mul(multiply) else { - return Err(ParseError); - }; +fn parse_number(src: &[u8], multiply: u128) -> Result { + let mut value = 0_u128; + let mut has_digit = false; + let mut idx = 0; - if value > max { - Err(ParseError) - } else { - Ok(value) + while idx < src.len() { + match src[idx] { + digit @ b'0'..=b'9' => { + let Some(next) = value + .checked_mul(10) + .and_then(|value| value.checked_add(u128::from(digit - b'0'))) + else { + return Err(ParseError); + }; + value = next; + has_digit = true; + } + b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), + b'_' | b' ' => {} + _ => return Err(ParseError), + } + + idx += 1; + } + + if !has_digit { + return Err(ParseError); } + + value.checked_mul(multiply).ok_or(ParseError) } -fn divide_integer_u128( - mantissa: u128, +fn parse_fraction( + src: &[u8], + start: usize, + integer: u128, + has_integer_digit: bool, multiply: u128, - exponent: u32, - max: u128, ) -> Result { - if let Some(result) = divide_integer_u128_fast(mantissa, multiply, exponent, max) { - return result; + if !has_integer_digit { + return Err(ParseError); } - if exponent >= 58 { - return Ok(0); - } + let Some(base) = integer.checked_mul(multiply) else { + return Err(ParseError); + }; - let mut value = U256::multiply(mantissa, multiply); - let mut round = false; - let mut idx = 0; + let mut fraction = 0_u128; + let mut scale = 1_u128; + let mut digits = 0_u32; + let mut idx = start; - while idx < exponent { - let (quotient, remainder) = value.div_rem_10(); - value = quotient; - round = remainder >= 5; - idx += 1; - } + while idx < src.len() { + match src[idx] { + digit @ b'0'..=b'9' => { + if digits == 19 { + return Err(ParseError); + } - if round { - let Some(rounded) = value.checked_add_one() else { - return Err(ParseError); - }; - value = rounded; + fraction = fraction * 10 + u128::from(digit - b'0'); + scale *= 10; + digits += 1; + } + b'_' | b' ' => {} + _ => return Err(ParseError), + } + + idx += 1; } - let Some(value) = value.try_into_u128() else { - return Err(ParseError); - }; - if value > max { - Err(ParseError) + let product = fraction * multiply; + let quotient = product / scale; + let remainder = product % scale; + let rounded = if digits != 0 && remainder >= scale / 2 { + quotient + 1 } else { - Ok(value) - } + quotient + }; + + base.checked_add(rounded).ok_or(ParseError) } -fn divide_integer_u128_fast( - mantissa: u128, - multiply: u128, - exponent: u32, - max: u128, -) -> Option> { - let product = mantissa.checked_mul(multiply)?; +#[cfg(test)] +mod tests { + use super::ParseError; + use crate::types::BSize; - if exponent >= 39 { - return Some(Ok(0)); + #[test] + fn parses_u128_max() { + assert_eq!( + BSize::::parse(b"340282366920938463463374607431768211455B"), + Ok(BSize(u128::MAX)), + ); + assert_eq!( + BSize::::parse(b"340282366920938463463374607431768211456B"), + Err(ParseError), + ); } - let Some(power) = 10_u128.checked_pow(exponent) else { - return Some(Err(ParseError)); - }; - let quotient = product / power; - let remainder = product % power; - let value = if remainder >= power / 2 { - let Some(value) = quotient.checked_add(1) else { - return Some(Err(ParseError)); - }; - value - } else { - quotient - }; + #[test] + fn parses_u128_max_with_decimal_unit() { + assert_eq!( + BSize::::parse(b"340282366920938463463.374607431768211455EB"), + Ok(BSize(u128::MAX)), + ); + } - if value > max { - Some(Err(ParseError)) - } else { - Some(Ok(value)) + #[test] + fn rejects_scientific_notation() { + assert_eq!(BSize::::parse(b"1.5e3KiB"), Err(ParseError)); } } diff --git a/bsize/src/parse/parse_u16.rs b/bsize/src/parse/parse_u16.rs index fefb233..4c5ef74 100644 --- a/bsize/src/parse/parse_u16.rs +++ b/bsize/src/parse/parse_u16.rs @@ -13,18 +13,67 @@ // limitations under the License. use super::ParseError; -use super::number::parse_number; +use crate::types::BSize; +use core::str::FromStr; + +impl BSize { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. Supported units are + /// `B`, `KB`, and `KiB`, case-insensitively. + /// + /// The numeric part may be an integer or an ordinary decimal number. + /// Scientific notation is not supported. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input cannot be parsed as a `u16` byte + /// size. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + parse_u16(src.as_ref()).map(BSize) + } +} -impl_parse!(u16, parse_u16); -impl_parse_number!(u32, multiply_integer_u32, divide_integer_u32); +impl FromStr for BSize { + type Err = ParseError; + + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) + } +} #[cfg(target_pointer_width = "16")] -impl_parse!(usize, parse_usize); +impl BSize { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. On 16-bit targets, + /// supported units are `B`, `KB`, and `KiB`, case-insensitively. + /// + /// The numeric part may be an integer or an ordinary decimal number. + /// Scientific notation is not supported. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte + /// size. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + parse_usize(src.as_ref()).map(BSize) + } +} + +#[cfg(target_pointer_width = "16")] +impl FromStr for BSize { + type Err = ParseError; + + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) + } +} fn parse_u16(mut src: &[u8]) -> Result { let multiply = parse_unit(&mut src)?; - parse_number(src, multiply, u16::MAX as u32).map(|value| value as u16) + parse_number(src, multiply).map(|value| value as u16) } #[cfg(target_pointer_width = "16")] @@ -62,55 +111,145 @@ fn parse_unit(src: &mut &[u8]) -> Result { Ok(1) } -fn multiply_integer_u32( - mantissa: u32, - multiply: u32, - exponent: u32, - max: u32, -) -> Result { - let Some(power) = 10_u32.checked_pow(exponent) else { - return Err(ParseError); - }; - let Some(multiply) = multiply.checked_mul(power) else { +fn parse_number(src: &[u8], multiply: u32) -> Result { + let mut value = 0_u32; + let mut has_digit = false; + let mut idx = 0; + + while idx < src.len() { + match src[idx] { + digit @ b'0'..=b'9' => { + let Some(next) = value + .checked_mul(10) + .and_then(|value| value.checked_add(u32::from(digit - b'0'))) + else { + return Err(ParseError); + }; + value = next; + has_digit = true; + } + b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), + b'_' | b' ' => {} + _ => return Err(ParseError), + } + + idx += 1; + } + + if !has_digit { return Err(ParseError); - }; - let Some(value) = mantissa.checked_mul(multiply) else { + } + + let Some(value) = value.checked_mul(multiply) else { return Err(ParseError); }; - - if value > max { + if value > u32::from(u16::MAX) { Err(ParseError) } else { Ok(value) } } -fn divide_integer_u32( - mantissa: u32, +fn parse_fraction( + src: &[u8], + start: usize, + integer: u32, + has_integer_digit: bool, multiply: u32, - exponent: u32, - max: u32, ) -> Result { - if exponent >= 13 { - return Ok(0); + if !has_integer_digit { + return Err(ParseError); } - let product = (mantissa as u64) * (multiply as u64); - let power = 10_u64.pow(exponent); - let quotient = product / power; - let remainder = product % power; - let value = if exponent != 0 && remainder >= power / 2 { - let Some(value) = quotient.checked_add(1) else { - return Err(ParseError); - }; - value + let Some(base) = integer.checked_mul(multiply) else { + return Err(ParseError); + }; + if base > u32::from(u16::MAX) { + return Err(ParseError); + } + + let mut fraction = 0_u64; + let mut scale = 1_u64; + let mut digits = 0_u32; + let mut idx = start; + + while idx < src.len() { + match src[idx] { + digit @ b'0'..=b'9' => { + if digits == 19 { + return Err(ParseError); + } + + fraction = fraction * 10 + u64::from(digit - b'0'); + scale *= 10; + digits += 1; + } + b'_' | b' ' => {} + _ => return Err(ParseError), + } + + idx += 1; + } + + let product = u128::from(fraction) * u128::from(multiply); + let quotient = product / u128::from(scale); + let remainder = product % u128::from(scale); + let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { + quotient + 1 } else { quotient }; - if value > max as u64 { + let value = u128::from(base) + rounded; + if value > u128::from(u16::MAX) { Err(ParseError) } else { Ok(value as u32) } } + +#[cfg(test)] +mod tests { + use super::ParseError; + use crate::types::BSize; + + #[test] + fn parses_bytes() { + assert_eq!(BSize::::parse(b"1 B").unwrap(), BSize(1)); + } + + #[test] + fn parses_units() { + assert_eq!(BSize::::parse(b"1KB").unwrap(), BSize(1_000)); + assert_eq!(BSize::::parse(b"1KiB").unwrap(), BSize(1_024)); + assert_eq!(BSize::::parse(b"1kb").unwrap(), BSize(1_000)); + assert_eq!(BSize::::parse(b"1kib").unwrap(), BSize(1_024)); + assert_eq!(BSize::::parse(b"1KIB").unwrap(), BSize(1_024)); + } + + #[test] + fn parses_fractional_units() { + assert_eq!(BSize::::parse(b"65.535KB").unwrap(), BSize(u16::MAX)); + assert_eq!(BSize::::parse(b"0.5B").unwrap(), BSize(1)); + assert_eq!(BSize::::parse(b"0.4B").unwrap(), BSize(0)); + assert_eq!(BSize::::parse(b"65535.4B").unwrap(), BSize(u16::MAX)); + } + + #[test] + fn rejects_unsupported_units() { + assert_eq!(BSize::::parse(b"1MB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1MiB"), Err(ParseError)); + } + + #[test] + fn rejects_overflow() { + assert_eq!(BSize::::parse(b"65535.5B"), Err(ParseError)); + assert_eq!(BSize::::parse(b"65.536KB"), Err(ParseError)); + } + + #[cfg(target_pointer_width = "16")] + #[test] + fn parses_usize() { + assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); + } +} diff --git a/bsize/src/parse/parse_u32.rs b/bsize/src/parse/parse_u32.rs index 62f9a11..cde427c 100644 --- a/bsize/src/parse/parse_u32.rs +++ b/bsize/src/parse/parse_u32.rs @@ -13,17 +13,68 @@ // limitations under the License. use super::ParseError; -use super::number::parse_number; +use crate::types::BSize; +use core::str::FromStr; -impl_parse!(u32, parse_u32); +impl BSize { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. Supported units are + /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, and `GiB`, case-insensitively. + /// + /// The numeric part may be an integer or an ordinary decimal number. + /// Scientific notation is not supported. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input cannot be parsed as a `u32` byte + /// size. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + parse_u32(src.as_ref()).map(BSize) + } +} + +impl FromStr for BSize { + type Err = ParseError; + + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) + } +} #[cfg(target_pointer_width = "32")] -impl_parse!(usize, parse_usize); +impl BSize { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. On 32-bit targets, + /// supported units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, and `GiB`, + /// case-insensitively. + /// + /// The numeric part may be an integer or an ordinary decimal number. + /// Scientific notation is not supported. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte + /// size. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + parse_usize(src.as_ref()).map(BSize) + } +} + +#[cfg(target_pointer_width = "32")] +impl FromStr for BSize { + type Err = ParseError; + + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) + } +} fn parse_u32(mut src: &[u8]) -> Result { let multiply = parse_unit(&mut src)?; - parse_number(src, multiply, u32::MAX as u64).map(|value| value as u32) + parse_number(src, multiply).map(|value| value as u32) } #[cfg(target_pointer_width = "32")] @@ -78,3 +129,140 @@ fn binary_factor(prefix: u8) -> Option { _ => return None, }) } + +fn parse_number(src: &[u8], multiply: u64) -> Result { + let mut value = 0_u64; + let mut has_digit = false; + let mut idx = 0; + + while idx < src.len() { + match src[idx] { + digit @ b'0'..=b'9' => { + let Some(next) = value + .checked_mul(10) + .and_then(|value| value.checked_add(u64::from(digit - b'0'))) + else { + return Err(ParseError); + }; + value = next; + has_digit = true; + } + b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), + b'_' | b' ' => {} + _ => return Err(ParseError), + } + + idx += 1; + } + + if !has_digit { + return Err(ParseError); + } + + let Some(value) = value.checked_mul(multiply) else { + return Err(ParseError); + }; + if value > u64::from(u32::MAX) { + Err(ParseError) + } else { + Ok(value) + } +} + +fn parse_fraction( + src: &[u8], + start: usize, + integer: u64, + has_integer_digit: bool, + multiply: u64, +) -> Result { + if !has_integer_digit { + return Err(ParseError); + } + + let Some(base) = integer.checked_mul(multiply) else { + return Err(ParseError); + }; + if base > u64::from(u32::MAX) { + return Err(ParseError); + } + + let mut fraction = 0_u64; + let mut scale = 1_u64; + let mut digits = 0_u32; + let mut idx = start; + + while idx < src.len() { + match src[idx] { + digit @ b'0'..=b'9' => { + if digits == 19 { + return Err(ParseError); + } + + fraction = fraction * 10 + u64::from(digit - b'0'); + scale *= 10; + digits += 1; + } + b'_' | b' ' => {} + _ => return Err(ParseError), + } + + idx += 1; + } + + let product = u128::from(fraction) * u128::from(multiply); + let quotient = product / u128::from(scale); + let remainder = product % u128::from(scale); + let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { + quotient + 1 + } else { + quotient + }; + + let value = u128::from(base) + rounded; + if value > u128::from(u32::MAX) { + Err(ParseError) + } else { + Ok(value as u64) + } +} + +#[cfg(test)] +mod tests { + use super::ParseError; + use crate::types::BSize; + + #[test] + fn parses_units() { + assert_eq!(BSize::::parse(b"2MB").unwrap(), BSize(2_000_000)); + assert_eq!(BSize::::parse(b"2MiB").unwrap(), BSize(2_097_152)); + assert_eq!(BSize::::parse(b"1GB").unwrap(), BSize(1_000_000_000)); + assert_eq!(BSize::::parse(b"1GiB").unwrap(), BSize(1_073_741_824)); + } + + #[test] + fn parses_fractional_units() { + assert_eq!(BSize::::parse(b"1.5KB").unwrap(), BSize(1_500)); + assert_eq!( + BSize::::parse(b"4294967295.4B").unwrap(), + BSize(u32::MAX) + ); + } + + #[test] + fn rejects_unsupported_units() { + assert_eq!(BSize::::parse(b"1TB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1TiB"), Err(ParseError)); + } + + #[test] + fn rejects_overflow() { + assert_eq!(BSize::::parse(b"4294967295.5B"), Err(ParseError)); + } + + #[cfg(target_pointer_width = "32")] + #[test] + fn parses_usize() { + assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); + } +} diff --git a/bsize/src/parse/parse_u64.rs b/bsize/src/parse/parse_u64.rs index 829a633..97ba55a 100644 --- a/bsize/src/parse/parse_u64.rs +++ b/bsize/src/parse/parse_u64.rs @@ -13,18 +13,69 @@ // limitations under the License. use super::ParseError; -use super::number::parse_number; +use crate::types::BSize; +use core::str::FromStr; -impl_parse!(u64, parse_u64); -impl_parse_number!(u64, multiply_integer_u64, divide_integer_u64); +impl BSize { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. Supported units are + /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, `TiB`, `PB`, `PiB`, + /// `EB`, and `EiB`, case-insensitively. + /// + /// The numeric part may be an integer or an ordinary decimal number. + /// Scientific notation is not supported. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input cannot be parsed as a `u64` byte + /// size. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + parse_u64(src.as_ref()).map(BSize) + } +} + +impl FromStr for BSize { + type Err = ParseError; + + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) + } +} + +#[cfg(target_pointer_width = "64")] +impl BSize { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. On 64-bit targets, + /// supported units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, + /// `TiB`, `PB`, `PiB`, `EB`, and `EiB`, case-insensitively. + /// + /// The numeric part may be an integer or an ordinary decimal number. + /// Scientific notation is not supported. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte + /// size. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + parse_usize(src.as_ref()).map(BSize) + } +} #[cfg(target_pointer_width = "64")] -impl_parse!(usize, parse_usize); +impl FromStr for BSize { + type Err = ParseError; + + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) + } +} fn parse_u64(mut src: &[u8]) -> Result { let multiply = parse_unit(&mut src)?; - parse_number(src, multiply, u64::MAX) + parse_number(src, multiply) } #[cfg(target_pointer_width = "64")] @@ -83,63 +134,154 @@ fn binary_factor(prefix: u8) -> Option { }) } -fn multiply_integer_u64( - mantissa: u64, +fn parse_number(src: &[u8], multiply: u64) -> Result { + let mut value = 0_u64; + let mut has_digit = false; + let mut idx = 0; + + while idx < src.len() { + match src[idx] { + digit @ b'0'..=b'9' => { + let Some(next) = value + .checked_mul(10) + .and_then(|value| value.checked_add(u64::from(digit - b'0'))) + else { + return Err(ParseError); + }; + value = next; + has_digit = true; + } + b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), + b'_' | b' ' => {} + _ => return Err(ParseError), + } + + idx += 1; + } + + if !has_digit { + return Err(ParseError); + } + + value.checked_mul(multiply).ok_or(ParseError) +} + +fn parse_fraction( + src: &[u8], + start: usize, + integer: u64, + has_integer_digit: bool, multiply: u64, - exponent: u32, - max: u64, ) -> Result { - let Some(power) = 10_u64.checked_pow(exponent) else { - return Err(ParseError); - }; - let Some(multiply) = multiply.checked_mul(power) else { + if !has_integer_digit { return Err(ParseError); - }; - let Some(value) = mantissa.checked_mul(multiply) else { + } + + let Some(base) = integer.checked_mul(multiply) else { return Err(ParseError); }; - if value > max { - Err(ParseError) - } else { - Ok(value) - } -} + let mut fraction = 0_u64; + let mut scale = 1_u64; + let mut digits = 0_u32; + let mut idx = start; -fn divide_integer_u64( - mantissa: u64, - multiply: u64, - exponent: u32, - max: u64, -) -> Result { - let product = (mantissa as u128) * (multiply as u128); - divide_integer_u128_product(product, exponent, max as u128).map(|value| value as u64) -} + while idx < src.len() { + match src[idx] { + digit @ b'0'..=b'9' => { + if digits == 19 { + return Err(ParseError); + } + + fraction = fraction * 10 + u64::from(digit - b'0'); + scale *= 10; + digits += 1; + } + b'_' | b' ' => {} + _ => return Err(ParseError), + } -fn divide_integer_u128_product( - product: u128, - exponent: u32, - max: u128, -) -> Result { - if exponent > 38 { - return Ok(0); - } - - let power = 10_u128.pow(exponent); - let quotient = product / power; - let remainder = product % power; - let value = if exponent != 0 && remainder >= power / 2 { - let Some(value) = quotient.checked_add(1) else { - return Err(ParseError); - }; - value + idx += 1; + } + + let product = u128::from(fraction) * u128::from(multiply); + let quotient = product / u128::from(scale); + let remainder = product % u128::from(scale); + let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { + quotient + 1 } else { quotient }; - if value > max { + let value = u128::from(base) + rounded; + if value > u128::from(u64::MAX) { Err(ParseError) } else { - Ok(value) + Ok(value as u64) + } +} + +#[cfg(test)] +mod tests { + use super::ParseError; + use crate::types::BSize; + + #[test] + fn parses_bytes() { + assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); + } + + #[test] + fn parses_units() { + assert_eq!( + BSize::::parse(b"1TB").unwrap(), + BSize(1_000_000_000_000) + ); + assert_eq!( + BSize::::parse(b"1TiB").unwrap(), + BSize(1_099_511_627_776) + ); + assert_eq!( + BSize::::parse(b"1PB").unwrap(), + BSize(1_000_000_000_000_000) + ); + assert_eq!( + BSize::::parse(b"1PiB").unwrap(), + BSize(1_125_899_906_842_624) + ); + } + + #[test] + fn parses_fractional_units() { + assert_eq!(BSize::::parse(b"1.5KiB").unwrap(), BSize(1_536)); + assert_eq!( + BSize::::parse(b"18.446744073709551615EB").unwrap(), + BSize(u64::MAX) + ); + } + + #[test] + fn rejects_invalid_input() { + assert_eq!(BSize::::parse(b""), Err(ParseError)); + assert_eq!(BSize::::parse(b"1"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1K"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1XB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1iB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1e+B"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1.5e3KiB"), Err(ParseError)); + } + + #[test] + fn rejects_overflow() { + assert_eq!( + BSize::::parse(b"0.00000000000000000001B"), + Err(ParseError) + ); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn parses_usize() { + assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); } } diff --git a/bsize/src/parse/parse_u8.rs b/bsize/src/parse/parse_u8.rs index e1aeea6..f5a4b90 100644 --- a/bsize/src/parse/parse_u8.rs +++ b/bsize/src/parse/parse_u8.rs @@ -13,66 +13,142 @@ // limitations under the License. use super::ParseError; -use super::number::parse_number; +use crate::types::BSize; +use core::str::FromStr; -impl_parse!(u8, parse_u8); -impl_parse_number!(u16, multiply_integer_u16, divide_integer_u16); +impl BSize { + /// Parses a byte size from a byte slice. + /// + /// The input must end with a `B` or `b` byte suffix. No other unit is + /// supported for `u8`. + /// + /// The numeric part may be an integer or an ordinary decimal number. + /// Scientific notation is not supported. + /// + /// # Errors + /// + /// Returns [`ParseError`] if the input cannot be parsed as a `u8` byte + /// size. + pub fn parse(src: impl AsRef<[u8]>) -> Result { + parse_u8(src.as_ref()).map(BSize) + } +} + +impl FromStr for BSize { + type Err = ParseError; + + fn from_str(src: &str) -> Result { + Self::parse(src.as_bytes()) + } +} fn parse_u8(mut src: &[u8]) -> Result { super::strip_b_suffix(&mut src)?; - parse_number(src, 1_u16, u8::MAX as u16).map(|value| value as u8) + parse_number(src) } -fn multiply_integer_u16( - mantissa: u16, - multiply: u16, - exponent: u32, - max: u16, -) -> Result { - let Some(power) = 10_u16.checked_pow(exponent) else { - return Err(ParseError); - }; - let Some(multiply) = multiply.checked_mul(power) else { - return Err(ParseError); - }; - let Some(value) = mantissa.checked_mul(multiply) else { - return Err(ParseError); - }; +fn parse_number(src: &[u8]) -> Result { + let mut value = 0_u8; + let mut has_digit = false; + let mut idx = 0; - if value > max { - Err(ParseError) - } else { + while idx < src.len() { + match src[idx] { + digit @ b'0'..=b'9' => { + let Some(next) = value + .checked_mul(10) + .and_then(|value| value.checked_add(digit - b'0')) + else { + return Err(ParseError); + }; + value = next; + has_digit = true; + } + b'.' => return parse_fraction(src, idx + 1, value, has_digit), + b'_' | b' ' => {} + _ => return Err(ParseError), + } + + idx += 1; + } + + if has_digit { Ok(value) + } else { + Err(ParseError) } } -fn divide_integer_u16( - mantissa: u16, - multiply: u16, - exponent: u32, - max: u16, -) -> Result { - if exponent >= 6 { - return Ok(0); +fn parse_fraction( + src: &[u8], + start: usize, + integer: u8, + has_integer_digit: bool, +) -> Result { + if !has_integer_digit { + return Err(ParseError); } - let product = (mantissa as u32) * (multiply as u32); - let power = 10_u32.pow(exponent); - let quotient = product / power; - let remainder = product % power; - let value = if exponent != 0 && remainder >= power / 2 { - let Some(value) = quotient.checked_add(1) else { - return Err(ParseError); - }; - value + let mut fraction = 0_u64; + let mut scale = 1_u64; + let mut digits = 0_u32; + let mut idx = start; + + while idx < src.len() { + match src[idx] { + digit @ b'0'..=b'9' => { + if digits == 19 { + return Err(ParseError); + } + + fraction = fraction * 10 + u64::from(digit - b'0'); + scale *= 10; + digits += 1; + } + b'_' | b' ' => {} + _ => return Err(ParseError), + } + + idx += 1; + } + + let rounded = if digits != 0 && fraction >= scale / 2 { + 1 } else { - quotient + 0 }; - if value > max as u32 { - Err(ParseError) - } else { - Ok(value as u16) + integer.checked_add(rounded).ok_or(ParseError) +} + +#[cfg(test)] +mod tests { + use super::ParseError; + use crate::types::BSize; + + #[test] + fn parses_bytes() { + assert_eq!(BSize::::parse(b"255B").unwrap(), BSize(255)); + } + + #[test] + fn parses_fractional_bytes() { + assert_eq!(BSize::::parse(b"25.55B").unwrap(), BSize(26)); + assert_eq!(BSize::::parse(b"255.4B").unwrap(), BSize(u8::MAX)); + } + + #[test] + fn rejects_units() { + assert_eq!(BSize::::parse(b"1KB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1eB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"1EB"), Err(ParseError)); + } + + #[test] + fn rejects_overflow() { + assert_eq!(BSize::::parse(b"256B"), Err(ParseError)); + assert_eq!(BSize::::parse(b"0.001KB"), Err(ParseError)); + assert_eq!(BSize::::parse(b"255.5B"), Err(ParseError)); } } diff --git a/bsize/src/parse/u256.rs b/bsize/src/parse/u256.rs deleted file mode 100644 index 9d33c44..0000000 --- a/bsize/src/parse/u256.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2026 FastLabs Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#[derive(Clone, Copy)] -pub(in crate::parse) struct U256 { - pub(in crate::parse) hi: u128, - pub(in crate::parse) lo: u128, -} - -impl U256 { - pub(in crate::parse) fn multiply(lhs: u128, rhs: u128) -> Self { - let mask = u64::MAX as u128; - let lhs_lo = lhs & mask; - let lhs_hi = lhs >> 64; - let rhs_lo = rhs & mask; - let rhs_hi = rhs >> 64; - - let low = lhs_lo * rhs_lo; - let mid_left = lhs_lo * rhs_hi; - let mid_right = lhs_hi * rhs_lo; - let high = lhs_hi * rhs_hi; - - let carry = (low >> 64) + (mid_left & mask) + (mid_right & mask); - let lo = (low & mask) | ((carry & mask) << 64); - let hi = high + (mid_left >> 64) + (mid_right >> 64) + (carry >> 64); - - Self { hi, lo } - } - - pub(in crate::parse) fn div_rem_10(self) -> (Self, u8) { - let mut remainder = 0_u128; - - let (hi_hi, next) = div_limb(self.hi >> 64, remainder); - remainder = next; - let (hi_lo, next) = div_limb(self.hi as u64 as u128, remainder); - remainder = next; - let (lo_hi, next) = div_limb(self.lo >> 64, remainder); - remainder = next; - let (lo_lo, remainder) = div_limb(self.lo as u64 as u128, remainder); - - ( - Self { - hi: (hi_hi << 64) | hi_lo, - lo: (lo_hi << 64) | lo_lo, - }, - remainder as u8, - ) - } - - pub(in crate::parse) fn checked_add_one(self) -> Option { - let (lo, carry) = self.lo.overflowing_add(1); - let hi = if carry { - self.hi.checked_add(1)? - } else { - self.hi - }; - - Some(Self { hi, lo }) - } - - pub(in crate::parse) fn try_into_u128(self) -> Option { - if self.hi == 0 { Some(self.lo) } else { None } - } -} - -fn div_limb(limb: u128, remainder: u128) -> (u128, u128) { - let value = (remainder << 64) | limb; - (value / 10, value % 10) -} From 333a85e26318977a84185c84ad0ffcbc16cd83b3 Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 9 Jun 2026 20:31:37 +0800 Subject: [PATCH 06/10] human loop Signed-off-by: tison --- bsize/src/parse/mod.rs | 16 ++--- bsize/src/parse/parse_u128.rs | 96 +++++++-------------------- bsize/src/parse/parse_u16.rs | 70 +++++++++++--------- bsize/src/parse/parse_u32.rs | 66 +++++++++++-------- bsize/src/parse/parse_u64.rs | 63 +++++++++--------- bsize/src/parse/parse_u8.rs | 119 ++++++++++++---------------------- 6 files changed, 181 insertions(+), 249 deletions(-) diff --git a/bsize/src/parse/mod.rs b/bsize/src/parse/mod.rs index c5cc444..c9ef213 100644 --- a/bsize/src/parse/mod.rs +++ b/bsize/src/parse/mod.rs @@ -15,8 +15,8 @@ use core::fmt; /// The error returned when parsing a byte size fails. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ParseError; +#[derive(Debug, Clone)] +pub struct ParseError(()); impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -33,14 +33,12 @@ mod parse_u64; mod parse_u8; fn strip_b_suffix(src: &mut &[u8]) -> Result<(), ParseError> { - let Some((&suffix, before_b)) = src.split_last() else { - return Err(ParseError); - }; - if !suffix.eq_ignore_ascii_case(&b'B') { - return Err(ParseError); + *src = src.trim_ascii(); + let (suffix, before_b) = src.split_last().ok_or(ParseError(()))?; + if suffix.to_ascii_uppercase() != b'B' { + return Err(ParseError(())); } - - *src = before_b; + *src = before_b.trim_ascii_end(); Ok(()) } diff --git a/bsize/src/parse/parse_u128.rs b/bsize/src/parse/parse_u128.rs index b7a8d9f..05b9b5a 100644 --- a/bsize/src/parse/parse_u128.rs +++ b/bsize/src/parse/parse_u128.rs @@ -43,74 +43,22 @@ impl FromStr for BSize { } } -#[cfg(not(any( - target_pointer_width = "16", - target_pointer_width = "32", - target_pointer_width = "64" -)))] -impl BSize { - /// Parses a byte size from a byte slice. - /// - /// The input must end with a `B` or `b` byte suffix. On non-16/32/64-bit - /// targets, supported units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, - /// `GiB`, `TB`, `TiB`, `PB`, `PiB`, `EB`, and `EiB`, - /// case-insensitively. - /// - /// The numeric part may be an integer or an ordinary decimal number. - /// Scientific notation is not supported. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte - /// size. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - parse_usize(src.as_ref()).map(BSize) - } -} - -#[cfg(not(any( - target_pointer_width = "16", - target_pointer_width = "32", - target_pointer_width = "64" -)))] -impl FromStr for BSize { - type Err = ParseError; - - fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) - } -} - fn parse_u128(mut src: &[u8]) -> Result { let multiply = parse_unit(&mut src)?; parse_number(src, multiply) } -#[cfg(not(any( - target_pointer_width = "16", - target_pointer_width = "32", - target_pointer_width = "64" -)))] -fn parse_usize(src: &[u8]) -> Result { - let value = parse_u128(src)?; - if value > usize::MAX as u128 { - Err(ParseError) - } else { - Ok(value as usize) - } -} - fn parse_unit(src: &mut &[u8]) -> Result { super::strip_b_suffix(src)?; if let Some((&infix, before_i)) = src.split_last() { if infix.eq_ignore_ascii_case(&b'i') { let Some((&prefix, before_prefix)) = before_i.split_last() else { - return Err(ParseError); + return Err(ParseError(())); }; let Some(factor) = binary_factor(prefix) else { - return Err(ParseError); + return Err(ParseError(())); }; *src = before_prefix; @@ -164,24 +112,24 @@ fn parse_number(src: &[u8], multiply: u128) -> Result { .checked_mul(10) .and_then(|value| value.checked_add(u128::from(digit - b'0'))) else { - return Err(ParseError); + return Err(ParseError(())); }; value = next; has_digit = true; } b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), b'_' | b' ' => {} - _ => return Err(ParseError), + _ => return Err(ParseError(())), } idx += 1; } if !has_digit { - return Err(ParseError); + return Err(ParseError(())); } - value.checked_mul(multiply).ok_or(ParseError) + value.checked_mul(multiply).ok_or(ParseError(())) } fn parse_fraction( @@ -192,11 +140,11 @@ fn parse_fraction( multiply: u128, ) -> Result { if !has_integer_digit { - return Err(ParseError); + return Err(ParseError(())); } let Some(base) = integer.checked_mul(multiply) else { - return Err(ParseError); + return Err(ParseError(())); }; let mut fraction = 0_u128; @@ -208,7 +156,7 @@ fn parse_fraction( match src[idx] { digit @ b'0'..=b'9' => { if digits == 19 { - return Err(ParseError); + return Err(ParseError(())); } fraction = fraction * 10 + u128::from(digit - b'0'); @@ -216,7 +164,7 @@ fn parse_fraction( digits += 1; } b'_' | b' ' => {} - _ => return Err(ParseError), + _ => return Err(ParseError(())), } idx += 1; @@ -231,36 +179,36 @@ fn parse_fraction( quotient }; - base.checked_add(rounded).ok_or(ParseError) + base.checked_add(rounded).ok_or(ParseError(())) } #[cfg(test)] mod tests { - use super::ParseError; - use crate::types::BSize; + use super::*; #[test] fn parses_u128_max() { assert_eq!( - BSize::::parse(b"340282366920938463463374607431768211455B"), - Ok(BSize(u128::MAX)), - ); - assert_eq!( - BSize::::parse(b"340282366920938463463374607431768211456B"), - Err(ParseError), + BSize::::parse(b"340282366920938463463374607431768211455B").unwrap(), + BSize::(u128::MAX) ); } + #[test] + fn rejects_overflow() { + BSize::::parse(b"340282366920938463463374607431768211456B").unwrap_err(); + } + #[test] fn parses_u128_max_with_decimal_unit() { assert_eq!( - BSize::::parse(b"340282366920938463463.374607431768211455EB"), - Ok(BSize(u128::MAX)), + BSize::::parse(b"340282366920938463463.374607431768211455EB").unwrap(), + BSize::(u128::MAX), ); } #[test] fn rejects_scientific_notation() { - assert_eq!(BSize::::parse(b"1.5e3KiB"), Err(ParseError)); + BSize::::parse(b"1.5e3KiB").unwrap_err(); } } diff --git a/bsize/src/parse/parse_u16.rs b/bsize/src/parse/parse_u16.rs index 4c5ef74..f43a464 100644 --- a/bsize/src/parse/parse_u16.rs +++ b/bsize/src/parse/parse_u16.rs @@ -87,14 +87,14 @@ fn parse_unit(src: &mut &[u8]) -> Result { if let Some((&infix, before_i)) = src.split_last() { if infix.eq_ignore_ascii_case(&b'i') { let Some((&prefix, before_prefix)) = before_i.split_last() else { - return Err(ParseError); + return Err(ParseError(())); }; if prefix.eq_ignore_ascii_case(&b'K') { *src = before_prefix; return Ok(1_024); } - return Err(ParseError); + return Err(ParseError(())); } } @@ -104,7 +104,7 @@ fn parse_unit(src: &mut &[u8]) -> Result { return Ok(1_000); } if super::is_ascii_unit(prefix) { - return Err(ParseError); + return Err(ParseError(())); } } @@ -123,28 +123,28 @@ fn parse_number(src: &[u8], multiply: u32) -> Result { .checked_mul(10) .and_then(|value| value.checked_add(u32::from(digit - b'0'))) else { - return Err(ParseError); + return Err(ParseError(())); }; value = next; has_digit = true; } b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), b'_' | b' ' => {} - _ => return Err(ParseError), + _ => return Err(ParseError(())), } idx += 1; } if !has_digit { - return Err(ParseError); + return Err(ParseError(())); } let Some(value) = value.checked_mul(multiply) else { - return Err(ParseError); + return Err(ParseError(())); }; if value > u32::from(u16::MAX) { - Err(ParseError) + Err(ParseError(())) } else { Ok(value) } @@ -158,14 +158,14 @@ fn parse_fraction( multiply: u32, ) -> Result { if !has_integer_digit { - return Err(ParseError); + return Err(ParseError(())); } let Some(base) = integer.checked_mul(multiply) else { - return Err(ParseError); + return Err(ParseError(())); }; if base > u32::from(u16::MAX) { - return Err(ParseError); + return Err(ParseError(())); } let mut fraction = 0_u64; @@ -177,7 +177,7 @@ fn parse_fraction( match src[idx] { digit @ b'0'..=b'9' => { if digits == 19 { - return Err(ParseError); + return Err(ParseError(())); } fraction = fraction * 10 + u64::from(digit - b'0'); @@ -185,7 +185,7 @@ fn parse_fraction( digits += 1; } b'_' | b' ' => {} - _ => return Err(ParseError), + _ => return Err(ParseError(())), } idx += 1; @@ -202,7 +202,7 @@ fn parse_fraction( let value = u128::from(base) + rounded; if value > u128::from(u16::MAX) { - Err(ParseError) + Err(ParseError(())) } else { Ok(value as u32) } @@ -210,46 +210,54 @@ fn parse_fraction( #[cfg(test)] mod tests { - use super::ParseError; - use crate::types::BSize; + use super::*; #[test] fn parses_bytes() { - assert_eq!(BSize::::parse(b"1 B").unwrap(), BSize(1)); + assert_eq!(BSize::::parse(b"1 B").unwrap(), BSize::(1)); } #[test] fn parses_units() { - assert_eq!(BSize::::parse(b"1KB").unwrap(), BSize(1_000)); - assert_eq!(BSize::::parse(b"1KiB").unwrap(), BSize(1_024)); - assert_eq!(BSize::::parse(b"1kb").unwrap(), BSize(1_000)); - assert_eq!(BSize::::parse(b"1kib").unwrap(), BSize(1_024)); - assert_eq!(BSize::::parse(b"1KIB").unwrap(), BSize(1_024)); + assert_eq!(BSize::::parse(b"1KB").unwrap(), BSize::(1_000)); + assert_eq!(BSize::::parse(b"1KiB").unwrap(), BSize::(1_024)); + assert_eq!(BSize::::parse(b"1kb").unwrap(), BSize::(1_000)); + assert_eq!(BSize::::parse(b"1kib").unwrap(), BSize::(1_024)); + assert_eq!(BSize::::parse(b"1KIB").unwrap(), BSize::(1_024)); } #[test] fn parses_fractional_units() { - assert_eq!(BSize::::parse(b"65.535KB").unwrap(), BSize(u16::MAX)); - assert_eq!(BSize::::parse(b"0.5B").unwrap(), BSize(1)); - assert_eq!(BSize::::parse(b"0.4B").unwrap(), BSize(0)); - assert_eq!(BSize::::parse(b"65535.4B").unwrap(), BSize(u16::MAX)); + assert_eq!( + BSize::::parse(b"65.535KB").unwrap(), + BSize::(u16::MAX) + ); + assert_eq!(BSize::::parse(b"0.5B").unwrap(), BSize::(1)); + assert_eq!(BSize::::parse(b"0.4B").unwrap(), BSize::(0)); + assert_eq!( + BSize::::parse(b"65535.4B").unwrap(), + BSize::(u16::MAX) + ); } #[test] fn rejects_unsupported_units() { - assert_eq!(BSize::::parse(b"1MB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1MiB"), Err(ParseError)); + BSize::::parse(b"1MB").unwrap_err(); + BSize::::parse(b"1MiB").unwrap_err(); } #[test] fn rejects_overflow() { - assert_eq!(BSize::::parse(b"65535.5B"), Err(ParseError)); - assert_eq!(BSize::::parse(b"65.536KB"), Err(ParseError)); + BSize::::parse(b"65535.5B").unwrap_err(); + BSize::::parse(b"65.536KB").unwrap_err(); } #[cfg(target_pointer_width = "16")] #[test] fn parses_usize() { - assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); + assert_eq!( + BSize::::parse(b"1_234B").unwrap(), + BSize::(1_234) + ); } } diff --git a/bsize/src/parse/parse_u32.rs b/bsize/src/parse/parse_u32.rs index cde427c..e7a5d66 100644 --- a/bsize/src/parse/parse_u32.rs +++ b/bsize/src/parse/parse_u32.rs @@ -88,10 +88,10 @@ fn parse_unit(src: &mut &[u8]) -> Result { if let Some((&infix, before_i)) = src.split_last() { if infix.eq_ignore_ascii_case(&b'i') { let Some((&prefix, before_prefix)) = before_i.split_last() else { - return Err(ParseError); + return Err(ParseError(())); }; let Some(factor) = binary_factor(prefix) else { - return Err(ParseError); + return Err(ParseError(())); }; *src = before_prefix; @@ -105,7 +105,7 @@ fn parse_unit(src: &mut &[u8]) -> Result { return Ok(factor); } if super::is_ascii_unit(prefix) { - return Err(ParseError); + return Err(ParseError(())); } } @@ -142,28 +142,28 @@ fn parse_number(src: &[u8], multiply: u64) -> Result { .checked_mul(10) .and_then(|value| value.checked_add(u64::from(digit - b'0'))) else { - return Err(ParseError); + return Err(ParseError(())); }; value = next; has_digit = true; } b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), b'_' | b' ' => {} - _ => return Err(ParseError), + _ => return Err(ParseError(())), } idx += 1; } if !has_digit { - return Err(ParseError); + return Err(ParseError(())); } let Some(value) = value.checked_mul(multiply) else { - return Err(ParseError); + return Err(ParseError(())); }; if value > u64::from(u32::MAX) { - Err(ParseError) + Err(ParseError(())) } else { Ok(value) } @@ -177,14 +177,14 @@ fn parse_fraction( multiply: u64, ) -> Result { if !has_integer_digit { - return Err(ParseError); + return Err(ParseError(())); } let Some(base) = integer.checked_mul(multiply) else { - return Err(ParseError); + return Err(ParseError(())); }; if base > u64::from(u32::MAX) { - return Err(ParseError); + return Err(ParseError(())); } let mut fraction = 0_u64; @@ -196,7 +196,7 @@ fn parse_fraction( match src[idx] { digit @ b'0'..=b'9' => { if digits == 19 { - return Err(ParseError); + return Err(ParseError(())); } fraction = fraction * 10 + u64::from(digit - b'0'); @@ -204,7 +204,7 @@ fn parse_fraction( digits += 1; } b'_' | b' ' => {} - _ => return Err(ParseError), + _ => return Err(ParseError(())), } idx += 1; @@ -221,7 +221,7 @@ fn parse_fraction( let value = u128::from(base) + rounded; if value > u128::from(u32::MAX) { - Err(ParseError) + Err(ParseError(())) } else { Ok(value as u64) } @@ -229,40 +229,54 @@ fn parse_fraction( #[cfg(test)] mod tests { - use super::ParseError; - use crate::types::BSize; + use super::*; #[test] fn parses_units() { - assert_eq!(BSize::::parse(b"2MB").unwrap(), BSize(2_000_000)); - assert_eq!(BSize::::parse(b"2MiB").unwrap(), BSize(2_097_152)); - assert_eq!(BSize::::parse(b"1GB").unwrap(), BSize(1_000_000_000)); - assert_eq!(BSize::::parse(b"1GiB").unwrap(), BSize(1_073_741_824)); + assert_eq!( + BSize::::parse(b"2MB").unwrap(), + BSize::(2_000_000) + ); + assert_eq!( + BSize::::parse(b"2MiB").unwrap(), + BSize::(2_097_152) + ); + assert_eq!( + BSize::::parse(b"1GB").unwrap(), + BSize::(1_000_000_000) + ); + assert_eq!( + BSize::::parse(b"1GiB").unwrap(), + BSize::(1_073_741_824) + ); } #[test] fn parses_fractional_units() { - assert_eq!(BSize::::parse(b"1.5KB").unwrap(), BSize(1_500)); + assert_eq!(BSize::::parse(b"1.5KB").unwrap(), BSize::(1_500)); assert_eq!( BSize::::parse(b"4294967295.4B").unwrap(), - BSize(u32::MAX) + BSize::(u32::MAX) ); } #[test] fn rejects_unsupported_units() { - assert_eq!(BSize::::parse(b"1TB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1TiB"), Err(ParseError)); + BSize::::parse(b"1TB").unwrap_err(); + BSize::::parse(b"1TiB").unwrap_err(); } #[test] fn rejects_overflow() { - assert_eq!(BSize::::parse(b"4294967295.5B"), Err(ParseError)); + BSize::::parse(b"4294967295.5B").unwrap_err(); } #[cfg(target_pointer_width = "32")] #[test] fn parses_usize() { - assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); + assert_eq!( + BSize::::parse(b"1_234B").unwrap(), + BSize::(1_234) + ); } } diff --git a/bsize/src/parse/parse_u64.rs b/bsize/src/parse/parse_u64.rs index 97ba55a..7bd680a 100644 --- a/bsize/src/parse/parse_u64.rs +++ b/bsize/src/parse/parse_u64.rs @@ -89,10 +89,10 @@ fn parse_unit(src: &mut &[u8]) -> Result { if let Some((&infix, before_i)) = src.split_last() { if infix.eq_ignore_ascii_case(&b'i') { let Some((&prefix, before_prefix)) = before_i.split_last() else { - return Err(ParseError); + return Err(ParseError(())); }; let Some(factor) = binary_factor(prefix) else { - return Err(ParseError); + return Err(ParseError(())); }; *src = before_prefix; @@ -146,24 +146,24 @@ fn parse_number(src: &[u8], multiply: u64) -> Result { .checked_mul(10) .and_then(|value| value.checked_add(u64::from(digit - b'0'))) else { - return Err(ParseError); + return Err(ParseError(())); }; value = next; has_digit = true; } b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), b'_' | b' ' => {} - _ => return Err(ParseError), + _ => return Err(ParseError(())), } idx += 1; } if !has_digit { - return Err(ParseError); + return Err(ParseError(())); } - value.checked_mul(multiply).ok_or(ParseError) + value.checked_mul(multiply).ok_or(ParseError(())) } fn parse_fraction( @@ -174,11 +174,11 @@ fn parse_fraction( multiply: u64, ) -> Result { if !has_integer_digit { - return Err(ParseError); + return Err(ParseError(())); } let Some(base) = integer.checked_mul(multiply) else { - return Err(ParseError); + return Err(ParseError(())); }; let mut fraction = 0_u64; @@ -190,7 +190,7 @@ fn parse_fraction( match src[idx] { digit @ b'0'..=b'9' => { if digits == 19 { - return Err(ParseError); + return Err(ParseError(())); } fraction = fraction * 10 + u64::from(digit - b'0'); @@ -198,7 +198,7 @@ fn parse_fraction( digits += 1; } b'_' | b' ' => {} - _ => return Err(ParseError), + _ => return Err(ParseError(())), } idx += 1; @@ -215,7 +215,7 @@ fn parse_fraction( let value = u128::from(base) + rounded; if value > u128::from(u64::MAX) { - Err(ParseError) + Err(ParseError(())) } else { Ok(value as u64) } @@ -223,65 +223,64 @@ fn parse_fraction( #[cfg(test)] mod tests { - use super::ParseError; - use crate::types::BSize; + use super::*; #[test] fn parses_bytes() { - assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); + assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize::(1_234)); } #[test] fn parses_units() { assert_eq!( BSize::::parse(b"1TB").unwrap(), - BSize(1_000_000_000_000) + BSize::(1_000_000_000_000) ); assert_eq!( BSize::::parse(b"1TiB").unwrap(), - BSize(1_099_511_627_776) + BSize::(1_099_511_627_776) ); assert_eq!( BSize::::parse(b"1PB").unwrap(), - BSize(1_000_000_000_000_000) + BSize::(1_000_000_000_000_000) ); assert_eq!( BSize::::parse(b"1PiB").unwrap(), - BSize(1_125_899_906_842_624) + BSize::(1_125_899_906_842_624) ); } #[test] fn parses_fractional_units() { - assert_eq!(BSize::::parse(b"1.5KiB").unwrap(), BSize(1_536)); + assert_eq!(BSize::::parse(b"1.5KiB").unwrap(), BSize::(1_536)); assert_eq!( BSize::::parse(b"18.446744073709551615EB").unwrap(), - BSize(u64::MAX) + BSize::(u64::MAX) ); } #[test] fn rejects_invalid_input() { - assert_eq!(BSize::::parse(b""), Err(ParseError)); - assert_eq!(BSize::::parse(b"1"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1K"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1XB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1iB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1e+B"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1.5e3KiB"), Err(ParseError)); + BSize::::parse(b"").unwrap_err(); + BSize::::parse(b"1").unwrap_err(); + BSize::::parse(b"1K").unwrap_err(); + BSize::::parse(b"1XB").unwrap_err(); + BSize::::parse(b"1iB").unwrap_err(); + BSize::::parse(b"1e+B").unwrap_err(); + BSize::::parse(b"1.5e3KiB").unwrap_err(); } #[test] fn rejects_overflow() { - assert_eq!( - BSize::::parse(b"0.00000000000000000001B"), - Err(ParseError) - ); + BSize::::parse(b"0.00000000000000000001B").unwrap_err(); } #[cfg(target_pointer_width = "64")] #[test] fn parses_usize() { - assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize(1_234)); + assert_eq!( + BSize::::parse(b"1_234B").unwrap(), + BSize::(1_234) + ); } } diff --git a/bsize/src/parse/parse_u8.rs b/bsize/src/parse/parse_u8.rs index f5a4b90..b23e0b6 100644 --- a/bsize/src/parse/parse_u8.rs +++ b/bsize/src/parse/parse_u8.rs @@ -30,7 +30,8 @@ impl BSize { /// Returns [`ParseError`] if the input cannot be parsed as a `u8` byte /// size. pub fn parse(src: impl AsRef<[u8]>) -> Result { - parse_u8(src.as_ref()).map(BSize) + let bytes = src.as_ref(); + parse_u8(bytes).map(BSize) } } @@ -45,110 +46,74 @@ impl FromStr for BSize { fn parse_u8(mut src: &[u8]) -> Result { super::strip_b_suffix(&mut src)?; - parse_number(src) -} - -fn parse_number(src: &[u8]) -> Result { - let mut value = 0_u8; - let mut has_digit = false; - let mut idx = 0; + let mut value = 0u8; + let mut valid = false; - while idx < src.len() { + let len = src.len(); + for idx in 0..len { match src[idx] { + b'_' => {} + b'.' => { + if idx + 1 < len { + match src[idx + 1] { + b'0'..=b'4' => {} + b'5'..=b'9' => { + value = value.checked_add(1).ok_or(ParseError(()))?; + } + _ => return Err(ParseError(())), + } + valid = true; + } + + for idx in idx + 2..len { + if !matches!(src[idx], b'_' | b'0'..=b'9') { + return Err(ParseError(())); + } + } + break; + } digit @ b'0'..=b'9' => { - let Some(next) = value - .checked_mul(10) - .and_then(|value| value.checked_add(digit - b'0')) - else { - return Err(ParseError); - }; - value = next; - has_digit = true; + value = value.checked_mul(10).ok_or(ParseError(()))?; + value = value.checked_add(digit - b'0').ok_or(ParseError(()))?; + valid = true; } - b'.' => return parse_fraction(src, idx + 1, value, has_digit), - b'_' | b' ' => {} - _ => return Err(ParseError), + _ => return Err(ParseError(())), } - - idx += 1; } - if has_digit { + if valid { Ok(value) } else { - Err(ParseError) - } -} - -fn parse_fraction( - src: &[u8], - start: usize, - integer: u8, - has_integer_digit: bool, -) -> Result { - if !has_integer_digit { - return Err(ParseError); + Err(ParseError(())) } - - let mut fraction = 0_u64; - let mut scale = 1_u64; - let mut digits = 0_u32; - let mut idx = start; - - while idx < src.len() { - match src[idx] { - digit @ b'0'..=b'9' => { - if digits == 19 { - return Err(ParseError); - } - - fraction = fraction * 10 + u64::from(digit - b'0'); - scale *= 10; - digits += 1; - } - b'_' | b' ' => {} - _ => return Err(ParseError), - } - - idx += 1; - } - - let rounded = if digits != 0 && fraction >= scale / 2 { - 1 - } else { - 0 - }; - - integer.checked_add(rounded).ok_or(ParseError) } #[cfg(test)] mod tests { - use super::ParseError; - use crate::types::BSize; + use super::*; #[test] fn parses_bytes() { - assert_eq!(BSize::::parse(b"255B").unwrap(), BSize(255)); + assert_eq!(BSize::::parse(b"255B").unwrap(), BSize::(255)); } #[test] fn parses_fractional_bytes() { - assert_eq!(BSize::::parse(b"25.55B").unwrap(), BSize(26)); - assert_eq!(BSize::::parse(b"255.4B").unwrap(), BSize(u8::MAX)); + assert_eq!(BSize::::parse(b"25.55B").unwrap(), BSize::(26)); + assert_eq!(BSize::::parse(b"255.4B").unwrap(), BSize::(u8::MAX)); } #[test] fn rejects_units() { - assert_eq!(BSize::::parse(b"1KB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1eB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"1EB"), Err(ParseError)); + BSize::::parse(b"1KB").unwrap_err(); + BSize::::parse(b"1eB").unwrap_err(); + BSize::::parse(b"1EB").unwrap_err(); } #[test] fn rejects_overflow() { - assert_eq!(BSize::::parse(b"256B"), Err(ParseError)); - assert_eq!(BSize::::parse(b"0.001KB"), Err(ParseError)); - assert_eq!(BSize::::parse(b"255.5B"), Err(ParseError)); + BSize::::parse(b"256B").unwrap_err(); + BSize::::parse(b"0.001KB").unwrap_err(); + BSize::::parse(b"255.5B").unwrap_err(); } } From 55d13101683e5a64d0f435cbae4908af9df77a34 Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 9 Jun 2026 21:30:24 +0800 Subject: [PATCH 07/10] simplify Signed-off-by: tison --- bsize/benches/parse.rs | 136 ++++---- bsize/src/parse/mod.rs | 86 +++-- bsize/src/parse/parse_u128.rs | 428 ++++++++++++------------- bsize/src/parse/parse_u16.rs | 523 ++++++++++++++++--------------- bsize/src/parse/parse_u32.rs | 564 ++++++++++++++++----------------- bsize/src/parse/parse_u64.rs | 572 +++++++++++++++++----------------- bsize/src/parse/parse_u8.rs | 75 ++--- 7 files changed, 1205 insertions(+), 1179 deletions(-) diff --git a/bsize/benches/parse.rs b/bsize/benches/parse.rs index ab8f1d5..94fb695 100644 --- a/bsize/benches/parse.rs +++ b/bsize/benches/parse.rs @@ -1,73 +1,73 @@ -// Copyright 2026 FastLabs Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// // Copyright 2026 FastLabs Developers +// // +// // Licensed under the Apache License, Version 2.0 (the "License"); +// // you may not use this file except in compliance with the License. +// // You may obtain a copy of the License at +// // +// // http://www.apache.org/licenses/LICENSE-2.0 +// // +// // Unless required by applicable law or agreed to in writing, software +// // distributed under the License is distributed on an "AS IS" BASIS, +// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// // See the License for the specific language governing permissions and +// // limitations under the License. -use bsize::BSize; -use divan::black_box; -use parse_size::parse_size; +// use bsize::BSize; +// use divan::black_box; +// use parse_size::parse_size; fn main() { divan::main(); } - -#[divan::bench] -fn bsize_bytes() -> u64 { - BSize::::parse(black_box(b"123456789B")).unwrap().0 -} - -#[divan::bench] -fn parse_size_bytes() -> u64 { - parse_size(black_box(b"123456789B")).unwrap() -} - -#[divan::bench] -fn bsize_decimal() -> u64 { - BSize::::parse(black_box(b"123.456MB")).unwrap().0 -} - -#[divan::bench] -fn parse_size_decimal() -> u64 { - parse_size(black_box(b"123.456MB")).unwrap() -} - -#[divan::bench] -fn bsize_binary_decimal() -> u64 { - BSize::::parse(black_box(b"1.5KiB")).unwrap().0 -} - -#[divan::bench] -fn parse_size_binary_decimal() -> u64 { - parse_size(black_box(b"1.5KiB")).unwrap() -} - -#[divan::bench] -fn bsize_tiny_decimal() -> u64 { - BSize::::parse(black_box(b"0.001KB")).unwrap().0 -} - -#[divan::bench] -fn parse_size_tiny_decimal() -> u64 { - parse_size(black_box(b"0.001KB")).unwrap() -} - -#[divan::bench] -fn bsize_u64_max() -> u64 { - BSize::::parse(black_box(b"18.446744073709551615EB")) - .unwrap() - .0 -} - -#[divan::bench] -fn parse_size_u64_max() -> u64 { - parse_size(black_box(b"18.446744073709551615EB")).unwrap() -} +// +// #[divan::bench] +// fn bsize_bytes() -> u64 { +// BSize::::parse(black_box(b"123456789B")).unwrap().0 +// } +// +// #[divan::bench] +// fn parse_size_bytes() -> u64 { +// parse_size(black_box(b"123456789B")).unwrap() +// } +// +// #[divan::bench] +// fn bsize_decimal() -> u64 { +// BSize::::parse(black_box(b"123.456MB")).unwrap().0 +// } +// +// #[divan::bench] +// fn parse_size_decimal() -> u64 { +// parse_size(black_box(b"123.456MB")).unwrap() +// } +// +// #[divan::bench] +// fn bsize_binary_decimal() -> u64 { +// BSize::::parse(black_box(b"1.5KiB")).unwrap().0 +// } +// +// #[divan::bench] +// fn parse_size_binary_decimal() -> u64 { +// parse_size(black_box(b"1.5KiB")).unwrap() +// } +// +// #[divan::bench] +// fn bsize_tiny_decimal() -> u64 { +// BSize::::parse(black_box(b"0.001KB")).unwrap().0 +// } +// +// #[divan::bench] +// fn parse_size_tiny_decimal() -> u64 { +// parse_size(black_box(b"0.001KB")).unwrap() +// } +// +// #[divan::bench] +// fn bsize_u64_max() -> u64 { +// BSize::::parse(black_box(b"18.446744073709551615EB")) +// .unwrap() +// .0 +// } +// +// #[divan::bench] +// fn parse_size_u64_max() -> u64 { +// parse_size(black_box(b"18.446744073709551615EB")).unwrap() +// } diff --git a/bsize/src/parse/mod.rs b/bsize/src/parse/mod.rs index c9ef213..5df4e82 100644 --- a/bsize/src/parse/mod.rs +++ b/bsize/src/parse/mod.rs @@ -12,39 +12,83 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod parse_u128; +mod parse_u16; +mod parse_u32; +mod parse_u64; +mod parse_u8; + use core::fmt; /// The error returned when parsing a byte size fails. -#[derive(Debug, Clone)] -pub struct ParseError(()); +#[derive(Debug, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub enum ParseError { + /// The input contains no number. + Empty, + /// The input contains an invalid byte. + InvalidDigit, + /// The parsed byte count is too large for the target integer type. + PosOverflow, +} impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("malformed byte size") + 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", + }) } } impl core::error::Error for ParseError {} -mod parse_u128; -mod parse_u16; -mod parse_u32; -mod parse_u64; -mod parse_u8; +fn strip_unit_suffix(src: &mut &[u8]) -> Result { + if let [init @ .., b'b' | b'B'] = src { + *src = init; + }; + + let mut multiply = 1; -fn strip_b_suffix(src: &mut &[u8]) -> Result<(), ParseError> { - *src = src.trim_ascii(); - let (suffix, before_b) = src.split_last().ok_or(ParseError(()))?; - if suffix.to_ascii_uppercase() != b'B' { - return Err(ParseError(())); + 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::InvalidDigit), + } + + *src = init; + } else { + // [iI][bB] is not a valid suffix. + return Err(ParseError::InvalidDigit); + } + } 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; + } + } + } + + while let [init @ .., b' '] = src { + *src = init; } - *src = before_b.trim_ascii_end(); - Ok(()) -} -fn is_ascii_unit(byte: u8) -> bool { - matches!( - byte.to_ascii_uppercase(), - b'K' | b'M' | b'G' | b'T' | b'P' | b'E' - ) + Ok(multiply) } diff --git a/bsize/src/parse/parse_u128.rs b/bsize/src/parse/parse_u128.rs index 05b9b5a..4b01c6f 100644 --- a/bsize/src/parse/parse_u128.rs +++ b/bsize/src/parse/parse_u128.rs @@ -1,214 +1,214 @@ -// Copyright 2026 FastLabs Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::ParseError; -use crate::types::BSize; -use core::str::FromStr; - -impl BSize { - /// Parses a byte size from a byte slice. - /// - /// The input must end with a `B` or `b` byte suffix. Supported units are - /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, `TiB`, `PB`, `PiB`, - /// `EB`, and `EiB`, case-insensitively. - /// - /// The numeric part may be an integer or an ordinary decimal number. - /// Scientific notation is not supported. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input cannot be parsed as a `u128` byte - /// size. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - parse_u128(src.as_ref()).map(BSize) - } -} - -impl FromStr for BSize { - type Err = ParseError; - - fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) - } -} - -fn parse_u128(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - - parse_number(src, multiply) -} - -fn parse_unit(src: &mut &[u8]) -> Result { - super::strip_b_suffix(src)?; - - if let Some((&infix, before_i)) = src.split_last() { - if infix.eq_ignore_ascii_case(&b'i') { - let Some((&prefix, before_prefix)) = before_i.split_last() else { - return Err(ParseError(())); - }; - let Some(factor) = binary_factor(prefix) else { - return Err(ParseError(())); - }; - - *src = before_prefix; - return Ok(factor); - } - } - - if let Some((&prefix, before_prefix)) = src.split_last() { - if let Some(factor) = decimal_factor(prefix) { - *src = before_prefix; - return Ok(factor); - } - } - - Ok(1) -} - -fn decimal_factor(prefix: u8) -> Option { - Some(match prefix.to_ascii_uppercase() { - b'K' => 1_000, - b'M' => 1_000_000, - b'G' => 1_000_000_000, - b'T' => 1_000_000_000_000, - b'P' => 1_000_000_000_000_000, - b'E' => 1_000_000_000_000_000_000, - _ => return None, - }) -} - -fn binary_factor(prefix: u8) -> Option { - Some(match prefix.to_ascii_uppercase() { - b'K' => 1_u128 << 10, - b'M' => 1_u128 << 20, - b'G' => 1_u128 << 30, - b'T' => 1_u128 << 40, - b'P' => 1_u128 << 50, - b'E' => 1_u128 << 60, - _ => return None, - }) -} - -fn parse_number(src: &[u8], multiply: u128) -> Result { - let mut value = 0_u128; - let mut has_digit = false; - let mut idx = 0; - - while idx < src.len() { - match src[idx] { - digit @ b'0'..=b'9' => { - let Some(next) = value - .checked_mul(10) - .and_then(|value| value.checked_add(u128::from(digit - b'0'))) - else { - return Err(ParseError(())); - }; - value = next; - has_digit = true; - } - b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), - b'_' | b' ' => {} - _ => return Err(ParseError(())), - } - - idx += 1; - } - - if !has_digit { - return Err(ParseError(())); - } - - value.checked_mul(multiply).ok_or(ParseError(())) -} - -fn parse_fraction( - src: &[u8], - start: usize, - integer: u128, - has_integer_digit: bool, - multiply: u128, -) -> Result { - if !has_integer_digit { - return Err(ParseError(())); - } - - let Some(base) = integer.checked_mul(multiply) else { - return Err(ParseError(())); - }; - - let mut fraction = 0_u128; - let mut scale = 1_u128; - let mut digits = 0_u32; - let mut idx = start; - - while idx < src.len() { - match src[idx] { - digit @ b'0'..=b'9' => { - if digits == 19 { - return Err(ParseError(())); - } - - fraction = fraction * 10 + u128::from(digit - b'0'); - scale *= 10; - digits += 1; - } - b'_' | b' ' => {} - _ => return Err(ParseError(())), - } - - idx += 1; - } - - let product = fraction * multiply; - let quotient = product / scale; - let remainder = product % scale; - let rounded = if digits != 0 && remainder >= scale / 2 { - quotient + 1 - } else { - quotient - }; - - base.checked_add(rounded).ok_or(ParseError(())) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parses_u128_max() { - assert_eq!( - BSize::::parse(b"340282366920938463463374607431768211455B").unwrap(), - BSize::(u128::MAX) - ); - } - - #[test] - fn rejects_overflow() { - BSize::::parse(b"340282366920938463463374607431768211456B").unwrap_err(); - } - - #[test] - fn parses_u128_max_with_decimal_unit() { - assert_eq!( - BSize::::parse(b"340282366920938463463.374607431768211455EB").unwrap(), - BSize::(u128::MAX), - ); - } - - #[test] - fn rejects_scientific_notation() { - BSize::::parse(b"1.5e3KiB").unwrap_err(); - } -} +// // Copyright 2026 FastLabs Developers +// // +// // Licensed under the Apache License, Version 2.0 (the "License"); +// // you may not use this file except in compliance with the License. +// // You may obtain a copy of the License at +// // +// // http://www.apache.org/licenses/LICENSE-2.0 +// // +// // Unless required by applicable law or agreed to in writing, software +// // distributed under the License is distributed on an "AS IS" BASIS, +// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// // See the License for the specific language governing permissions and +// // limitations under the License. +// +// use super::ParseError; +// use crate::types::BSize; +// use core::str::FromStr; +// +// impl BSize { +// /// Parses a byte size from a byte slice. +// /// +// /// The input must end with a `B` or `b` byte suffix. Supported units are +// /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, `TiB`, `PB`, `PiB`, +// /// `EB`, and `EiB`, case-insensitively. +// /// +// /// The numeric part may be an integer or an ordinary decimal number. +// /// Scientific notation is not supported. +// /// +// /// # Errors +// /// +// /// Returns [`ParseError`] if the input cannot be parsed as a `u128` byte +// /// size. +// pub fn parse(src: impl AsRef<[u8]>) -> Result { +// parse_u128(src.as_ref()).map(BSize) +// } +// } +// +// impl FromStr for BSize { +// type Err = ParseError; +// +// fn from_str(src: &str) -> Result { +// Self::parse(src.as_bytes()) +// } +// } +// +// fn parse_u128(mut src: &[u8]) -> Result { +// let multiply = parse_unit(&mut src)?; +// +// parse_number(src, multiply) +// } +// +// fn parse_unit(src: &mut &[u8]) -> Result { +// super::strip_unit_suffix(src)?; +// +// if let Some((&infix, before_i)) = src.split_last() { +// if infix.eq_ignore_ascii_case(&b'i') { +// let Some((&prefix, before_prefix)) = before_i.split_last() else { +// return Err(ParseError(())); +// }; +// let Some(factor) = binary_factor(prefix) else { +// return Err(ParseError(())); +// }; +// +// *src = before_prefix; +// return Ok(factor); +// } +// } +// +// if let Some((&prefix, before_prefix)) = src.split_last() { +// if let Some(factor) = decimal_factor(prefix) { +// *src = before_prefix; +// return Ok(factor); +// } +// } +// +// Ok(1) +// } +// +// fn decimal_factor(prefix: u8) -> Option { +// Some(match prefix.to_ascii_uppercase() { +// b'K' => 1_000, +// b'M' => 1_000_000, +// b'G' => 1_000_000_000, +// b'T' => 1_000_000_000_000, +// b'P' => 1_000_000_000_000_000, +// b'E' => 1_000_000_000_000_000_000, +// _ => return None, +// }) +// } +// +// fn binary_factor(prefix: u8) -> Option { +// Some(match prefix.to_ascii_uppercase() { +// b'K' => 1_u128 << 10, +// b'M' => 1_u128 << 20, +// b'G' => 1_u128 << 30, +// b'T' => 1_u128 << 40, +// b'P' => 1_u128 << 50, +// b'E' => 1_u128 << 60, +// _ => return None, +// }) +// } +// +// fn parse_number(src: &[u8], multiply: u128) -> Result { +// let mut value = 0_u128; +// let mut has_digit = false; +// let mut idx = 0; +// +// while idx < src.len() { +// match src[idx] { +// digit @ b'0'..=b'9' => { +// let Some(next) = value +// .checked_mul(10) +// .and_then(|value| value.checked_add(u128::from(digit - b'0'))) +// else { +// return Err(ParseError(())); +// }; +// value = next; +// has_digit = true; +// } +// b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), +// b'_' | b' ' => {} +// _ => return Err(ParseError(())), +// } +// +// idx += 1; +// } +// +// if !has_digit { +// return Err(ParseError(())); +// } +// +// value.checked_mul(multiply).ok_or(ParseError(())) +// } +// +// fn parse_fraction( +// src: &[u8], +// start: usize, +// integer: u128, +// has_integer_digit: bool, +// multiply: u128, +// ) -> Result { +// if !has_integer_digit { +// return Err(ParseError(())); +// } +// +// let Some(base) = integer.checked_mul(multiply) else { +// return Err(ParseError(())); +// }; +// +// let mut fraction = 0_u128; +// let mut scale = 1_u128; +// let mut digits = 0_u32; +// let mut idx = start; +// +// while idx < src.len() { +// match src[idx] { +// digit @ b'0'..=b'9' => { +// if digits == 19 { +// return Err(ParseError(())); +// } +// +// fraction = fraction * 10 + u128::from(digit - b'0'); +// scale *= 10; +// digits += 1; +// } +// b'_' | b' ' => {} +// _ => return Err(ParseError(())), +// } +// +// idx += 1; +// } +// +// let product = fraction * multiply; +// let quotient = product / scale; +// let remainder = product % scale; +// let rounded = if digits != 0 && remainder >= scale / 2 { +// quotient + 1 +// } else { +// quotient +// }; +// +// base.checked_add(rounded).ok_or(ParseError(())) +// } +// +// #[cfg(test)] +// mod tests { +// use super::*; +// +// #[test] +// fn parses_u128_max() { +// assert_eq!( +// BSize::::parse(b"340282366920938463463374607431768211455B").unwrap(), +// BSize::(u128::MAX) +// ); +// } +// +// #[test] +// fn rejects_overflow() { +// BSize::::parse(b"340282366920938463463374607431768211456B").unwrap_err(); +// } +// +// #[test] +// fn parses_u128_max_with_decimal_unit() { +// assert_eq!( +// BSize::::parse(b"340282366920938463463.374607431768211455EB").unwrap(), +// BSize::(u128::MAX), +// ); +// } +// +// #[test] +// fn rejects_scientific_notation() { +// BSize::::parse(b"1.5e3KiB").unwrap_err(); +// } +// } diff --git a/bsize/src/parse/parse_u16.rs b/bsize/src/parse/parse_u16.rs index f43a464..68580dc 100644 --- a/bsize/src/parse/parse_u16.rs +++ b/bsize/src/parse/parse_u16.rs @@ -1,263 +1,260 @@ -// Copyright 2026 FastLabs Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::ParseError; -use crate::types::BSize; -use core::str::FromStr; - -impl BSize { - /// Parses a byte size from a byte slice. - /// - /// The input must end with a `B` or `b` byte suffix. Supported units are - /// `B`, `KB`, and `KiB`, case-insensitively. - /// - /// The numeric part may be an integer or an ordinary decimal number. - /// Scientific notation is not supported. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input cannot be parsed as a `u16` byte - /// size. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - parse_u16(src.as_ref()).map(BSize) - } -} - -impl FromStr for BSize { - type Err = ParseError; - - fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) - } -} - -#[cfg(target_pointer_width = "16")] -impl BSize { - /// Parses a byte size from a byte slice. - /// - /// The input must end with a `B` or `b` byte suffix. On 16-bit targets, - /// supported units are `B`, `KB`, and `KiB`, case-insensitively. - /// - /// The numeric part may be an integer or an ordinary decimal number. - /// Scientific notation is not supported. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte - /// size. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - parse_usize(src.as_ref()).map(BSize) - } -} - -#[cfg(target_pointer_width = "16")] -impl FromStr for BSize { - type Err = ParseError; - - fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) - } -} - -fn parse_u16(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - - parse_number(src, multiply).map(|value| value as u16) -} - -#[cfg(target_pointer_width = "16")] -fn parse_usize(src: &[u8]) -> Result { - parse_u16(src).map(|value| value as usize) -} - -fn parse_unit(src: &mut &[u8]) -> Result { - super::strip_b_suffix(src)?; - - if let Some((&infix, before_i)) = src.split_last() { - if infix.eq_ignore_ascii_case(&b'i') { - let Some((&prefix, before_prefix)) = before_i.split_last() else { - return Err(ParseError(())); - }; - if prefix.eq_ignore_ascii_case(&b'K') { - *src = before_prefix; - return Ok(1_024); - } - - return Err(ParseError(())); - } - } - - if let Some((&prefix, before_prefix)) = src.split_last() { - if prefix.eq_ignore_ascii_case(&b'K') { - *src = before_prefix; - return Ok(1_000); - } - if super::is_ascii_unit(prefix) { - return Err(ParseError(())); - } - } - - Ok(1) -} - -fn parse_number(src: &[u8], multiply: u32) -> Result { - let mut value = 0_u32; - let mut has_digit = false; - let mut idx = 0; - - while idx < src.len() { - match src[idx] { - digit @ b'0'..=b'9' => { - let Some(next) = value - .checked_mul(10) - .and_then(|value| value.checked_add(u32::from(digit - b'0'))) - else { - return Err(ParseError(())); - }; - value = next; - has_digit = true; - } - b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), - b'_' | b' ' => {} - _ => return Err(ParseError(())), - } - - idx += 1; - } - - if !has_digit { - return Err(ParseError(())); - } - - let Some(value) = value.checked_mul(multiply) else { - return Err(ParseError(())); - }; - if value > u32::from(u16::MAX) { - Err(ParseError(())) - } else { - Ok(value) - } -} - -fn parse_fraction( - src: &[u8], - start: usize, - integer: u32, - has_integer_digit: bool, - multiply: u32, -) -> Result { - if !has_integer_digit { - return Err(ParseError(())); - } - - let Some(base) = integer.checked_mul(multiply) else { - return Err(ParseError(())); - }; - if base > u32::from(u16::MAX) { - return Err(ParseError(())); - } - - let mut fraction = 0_u64; - let mut scale = 1_u64; - let mut digits = 0_u32; - let mut idx = start; - - while idx < src.len() { - match src[idx] { - digit @ b'0'..=b'9' => { - if digits == 19 { - return Err(ParseError(())); - } - - fraction = fraction * 10 + u64::from(digit - b'0'); - scale *= 10; - digits += 1; - } - b'_' | b' ' => {} - _ => return Err(ParseError(())), - } - - idx += 1; - } - - let product = u128::from(fraction) * u128::from(multiply); - let quotient = product / u128::from(scale); - let remainder = product % u128::from(scale); - let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { - quotient + 1 - } else { - quotient - }; - - let value = u128::from(base) + rounded; - if value > u128::from(u16::MAX) { - Err(ParseError(())) - } else { - Ok(value as u32) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parses_bytes() { - assert_eq!(BSize::::parse(b"1 B").unwrap(), BSize::(1)); - } - - #[test] - fn parses_units() { - assert_eq!(BSize::::parse(b"1KB").unwrap(), BSize::(1_000)); - assert_eq!(BSize::::parse(b"1KiB").unwrap(), BSize::(1_024)); - assert_eq!(BSize::::parse(b"1kb").unwrap(), BSize::(1_000)); - assert_eq!(BSize::::parse(b"1kib").unwrap(), BSize::(1_024)); - assert_eq!(BSize::::parse(b"1KIB").unwrap(), BSize::(1_024)); - } - - #[test] - fn parses_fractional_units() { - assert_eq!( - BSize::::parse(b"65.535KB").unwrap(), - BSize::(u16::MAX) - ); - assert_eq!(BSize::::parse(b"0.5B").unwrap(), BSize::(1)); - assert_eq!(BSize::::parse(b"0.4B").unwrap(), BSize::(0)); - assert_eq!( - BSize::::parse(b"65535.4B").unwrap(), - BSize::(u16::MAX) - ); - } - - #[test] - fn rejects_unsupported_units() { - BSize::::parse(b"1MB").unwrap_err(); - BSize::::parse(b"1MiB").unwrap_err(); - } - - #[test] - fn rejects_overflow() { - BSize::::parse(b"65535.5B").unwrap_err(); - BSize::::parse(b"65.536KB").unwrap_err(); - } - - #[cfg(target_pointer_width = "16")] - #[test] - fn parses_usize() { - assert_eq!( - BSize::::parse(b"1_234B").unwrap(), - BSize::(1_234) - ); - } -} +// // Copyright 2026 FastLabs Developers +// // +// // Licensed under the Apache License, Version 2.0 (the "License"); +// // you may not use this file except in compliance with the License. +// // You may obtain a copy of the License at +// // +// // http://www.apache.org/licenses/LICENSE-2.0 +// // +// // Unless required by applicable law or agreed to in writing, software +// // distributed under the License is distributed on an "AS IS" BASIS, +// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// // See the License for the specific language governing permissions and +// // limitations under the License. +// +// use super::ParseError; +// use crate::types::BSize; +// use core::str::FromStr; +// +// impl BSize { +// /// Parses a byte size from a byte slice. +// /// +// /// The input must end with a `B` or `b` byte suffix. Supported units are +// /// `B`, `KB`, and `KiB`, case-insensitively. +// /// +// /// The numeric part may be an integer or an ordinary decimal number. +// /// Scientific notation is not supported. +// /// +// /// # Errors +// /// +// /// Returns [`ParseError`] if the input cannot be parsed as a `u16` byte +// /// size. +// pub fn parse(src: impl AsRef<[u8]>) -> Result { +// let bytes = src.as_ref(); +// parse_u16(bytes).map(BSize) +// } +// } +// +// impl FromStr for BSize { +// type Err = ParseError; +// +// fn from_str(src: &str) -> Result { +// Self::parse(src.as_bytes()) +// } +// } +// +// #[cfg(target_pointer_width = "16")] +// impl BSize { +// /// Parses a byte size from a byte slice. +// /// +// /// The input must end with a `B` or `b` byte suffix. On 16-bit targets, +// /// supported units are `B`, `KB`, and `KiB`, case-insensitively. +// /// +// /// The numeric part may be an integer or an ordinary decimal number. +// /// Scientific notation is not supported. +// /// +// /// # Errors +// /// +// /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte +// /// size. +// pub fn parse(src: impl AsRef<[u8]>) -> Result { +// let bytes = src.as_ref(); +// parse_u16(bytes).map(|v| BSize(v as usize)) +// } +// } +// +// #[cfg(target_pointer_width = "16")] +// impl FromStr for BSize { +// type Err = ParseError; +// +// fn from_str(src: &str) -> Result { +// Self::parse(src.as_bytes()) +// } +// } +// +// fn parse_u16(mut src: &[u8]) -> Result { +// let multiply = parse_unit(&mut src)?; +// +// parse_number(src, multiply).map(|value| value as u16) +// } +// +// fn parse_unit(src: &mut &[u8]) -> Result { +// super::strip_unit_suffix(src)?; +// +// if let Some((&infix, before_i)) = src.split_last() { +// if infix.eq_ignore_ascii_case(&b'i') { +// let Some((&prefix, before_prefix)) = before_i.split_last() else { +// return Err(ParseError(())); +// }; +// if prefix.eq_ignore_ascii_case(&b'K') { +// *src = before_prefix; +// return Ok(1_024); +// } +// +// return Err(ParseError(())); +// } +// } +// +// if let Some((&prefix, before_prefix)) = src.split_last() { +// if prefix.eq_ignore_ascii_case(&b'K') { +// *src = before_prefix; +// return Ok(1_000); +// } +// if super::is_ascii_unit(prefix) { +// return Err(ParseError(())); +// } +// } +// +// Ok(1) +// } +// +// fn parse_number(src: &[u8], multiply: u32) -> Result { +// let mut value = 0_u32; +// let mut has_digit = false; +// let mut idx = 0; +// +// while idx < src.len() { +// match src[idx] { +// digit @ b'0'..=b'9' => { +// let Some(next) = value +// .checked_mul(10) +// .and_then(|value| value.checked_add(u32::from(digit - b'0'))) +// else { +// return Err(ParseError(())); +// }; +// value = next; +// has_digit = true; +// } +// b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), +// b'_' | b' ' => {} +// _ => return Err(ParseError(())), +// } +// +// idx += 1; +// } +// +// if !has_digit { +// return Err(ParseError(())); +// } +// +// let Some(value) = value.checked_mul(multiply) else { +// return Err(ParseError(())); +// }; +// if value > u32::from(u16::MAX) { +// Err(ParseError(())) +// } else { +// Ok(value) +// } +// } +// +// fn parse_fraction( +// src: &[u8], +// start: usize, +// integer: u32, +// has_integer_digit: bool, +// multiply: u32, +// ) -> Result { +// if !has_integer_digit { +// return Err(ParseError(())); +// } +// +// let Some(base) = integer.checked_mul(multiply) else { +// return Err(ParseError(())); +// }; +// if base > u32::from(u16::MAX) { +// return Err(ParseError(())); +// } +// +// let mut fraction = 0_u64; +// let mut scale = 1_u64; +// let mut digits = 0_u32; +// let mut idx = start; +// +// while idx < src.len() { +// match src[idx] { +// digit @ b'0'..=b'9' => { +// if digits == 19 { +// return Err(ParseError(())); +// } +// +// fraction = fraction * 10 + u64::from(digit - b'0'); +// scale *= 10; +// digits += 1; +// } +// b'_' | b' ' => {} +// _ => return Err(ParseError(())), +// } +// +// idx += 1; +// } +// +// let product = u128::from(fraction) * u128::from(multiply); +// let quotient = product / u128::from(scale); +// let remainder = product % u128::from(scale); +// let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { +// quotient + 1 +// } else { +// quotient +// }; +// +// let value = u128::from(base) + rounded; +// if value > u128::from(u16::MAX) { +// Err(ParseError(())) +// } else { +// Ok(value as u32) +// } +// } +// +// #[cfg(test)] +// mod tests { +// use super::*; +// +// #[test] +// fn parses_bytes() { +// assert_eq!(BSize::::parse(b"1 B").unwrap(), BSize::(1)); +// } +// +// #[test] +// fn parses_units() { +// assert_eq!(BSize::::parse(b"1KB").unwrap(), BSize::(1_000)); +// assert_eq!(BSize::::parse(b"1KiB").unwrap(), BSize::(1_024)); +// assert_eq!(BSize::::parse(b"1kb").unwrap(), BSize::(1_000)); +// assert_eq!(BSize::::parse(b"1kib").unwrap(), BSize::(1_024)); +// assert_eq!(BSize::::parse(b"1KIB").unwrap(), BSize::(1_024)); +// } +// +// #[test] +// fn parses_fractional_units() { +// assert_eq!( +// BSize::::parse(b"65.535KB").unwrap(), +// BSize::(u16::MAX) +// ); +// assert_eq!(BSize::::parse(b"0.5B").unwrap(), BSize::(1)); +// assert_eq!(BSize::::parse(b"0.4B").unwrap(), BSize::(0)); +// assert_eq!( +// BSize::::parse(b"65535.4B").unwrap(), +// BSize::(u16::MAX) +// ); +// } +// +// #[test] +// fn rejects_unsupported_units() { +// BSize::::parse(b"1MB").unwrap_err(); +// BSize::::parse(b"1MiB").unwrap_err(); +// } +// +// #[test] +// fn rejects_overflow() { +// BSize::::parse(b"65535.5B").unwrap_err(); +// BSize::::parse(b"65.536KB").unwrap_err(); +// } +// +// #[cfg(target_pointer_width = "16")] +// #[test] +// fn parses_usize() { +// assert_eq!( +// BSize::::parse(b"1_234B").unwrap(), +// BSize::(1_234) +// ); +// } +// } diff --git a/bsize/src/parse/parse_u32.rs b/bsize/src/parse/parse_u32.rs index e7a5d66..47f2a9e 100644 --- a/bsize/src/parse/parse_u32.rs +++ b/bsize/src/parse/parse_u32.rs @@ -1,282 +1,282 @@ -// Copyright 2026 FastLabs Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::ParseError; -use crate::types::BSize; -use core::str::FromStr; - -impl BSize { - /// Parses a byte size from a byte slice. - /// - /// The input must end with a `B` or `b` byte suffix. Supported units are - /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, and `GiB`, case-insensitively. - /// - /// The numeric part may be an integer or an ordinary decimal number. - /// Scientific notation is not supported. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input cannot be parsed as a `u32` byte - /// size. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - parse_u32(src.as_ref()).map(BSize) - } -} - -impl FromStr for BSize { - type Err = ParseError; - - fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) - } -} - -#[cfg(target_pointer_width = "32")] -impl BSize { - /// Parses a byte size from a byte slice. - /// - /// The input must end with a `B` or `b` byte suffix. On 32-bit targets, - /// supported units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, and `GiB`, - /// case-insensitively. - /// - /// The numeric part may be an integer or an ordinary decimal number. - /// Scientific notation is not supported. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte - /// size. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - parse_usize(src.as_ref()).map(BSize) - } -} - -#[cfg(target_pointer_width = "32")] -impl FromStr for BSize { - type Err = ParseError; - - fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) - } -} - -fn parse_u32(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - - parse_number(src, multiply).map(|value| value as u32) -} - -#[cfg(target_pointer_width = "32")] -fn parse_usize(src: &[u8]) -> Result { - parse_u32(src).map(|value| value as usize) -} - -fn parse_unit(src: &mut &[u8]) -> Result { - super::strip_b_suffix(src)?; - - if let Some((&infix, before_i)) = src.split_last() { - if infix.eq_ignore_ascii_case(&b'i') { - let Some((&prefix, before_prefix)) = before_i.split_last() else { - return Err(ParseError(())); - }; - let Some(factor) = binary_factor(prefix) else { - return Err(ParseError(())); - }; - - *src = before_prefix; - return Ok(factor); - } - } - - if let Some((&prefix, before_prefix)) = src.split_last() { - if let Some(factor) = decimal_factor(prefix) { - *src = before_prefix; - return Ok(factor); - } - if super::is_ascii_unit(prefix) { - return Err(ParseError(())); - } - } - - Ok(1) -} - -fn decimal_factor(prefix: u8) -> Option { - Some(match prefix.to_ascii_uppercase() { - b'K' => 1_000, - b'M' => 1_000_000, - b'G' => 1_000_000_000, - _ => return None, - }) -} - -fn binary_factor(prefix: u8) -> Option { - Some(match prefix.to_ascii_uppercase() { - b'K' => 1_u64 << 10, - b'M' => 1_u64 << 20, - b'G' => 1_u64 << 30, - _ => return None, - }) -} - -fn parse_number(src: &[u8], multiply: u64) -> Result { - let mut value = 0_u64; - let mut has_digit = false; - let mut idx = 0; - - while idx < src.len() { - match src[idx] { - digit @ b'0'..=b'9' => { - let Some(next) = value - .checked_mul(10) - .and_then(|value| value.checked_add(u64::from(digit - b'0'))) - else { - return Err(ParseError(())); - }; - value = next; - has_digit = true; - } - b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), - b'_' | b' ' => {} - _ => return Err(ParseError(())), - } - - idx += 1; - } - - if !has_digit { - return Err(ParseError(())); - } - - let Some(value) = value.checked_mul(multiply) else { - return Err(ParseError(())); - }; - if value > u64::from(u32::MAX) { - Err(ParseError(())) - } else { - Ok(value) - } -} - -fn parse_fraction( - src: &[u8], - start: usize, - integer: u64, - has_integer_digit: bool, - multiply: u64, -) -> Result { - if !has_integer_digit { - return Err(ParseError(())); - } - - let Some(base) = integer.checked_mul(multiply) else { - return Err(ParseError(())); - }; - if base > u64::from(u32::MAX) { - return Err(ParseError(())); - } - - let mut fraction = 0_u64; - let mut scale = 1_u64; - let mut digits = 0_u32; - let mut idx = start; - - while idx < src.len() { - match src[idx] { - digit @ b'0'..=b'9' => { - if digits == 19 { - return Err(ParseError(())); - } - - fraction = fraction * 10 + u64::from(digit - b'0'); - scale *= 10; - digits += 1; - } - b'_' | b' ' => {} - _ => return Err(ParseError(())), - } - - idx += 1; - } - - let product = u128::from(fraction) * u128::from(multiply); - let quotient = product / u128::from(scale); - let remainder = product % u128::from(scale); - let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { - quotient + 1 - } else { - quotient - }; - - let value = u128::from(base) + rounded; - if value > u128::from(u32::MAX) { - Err(ParseError(())) - } else { - Ok(value as u64) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parses_units() { - assert_eq!( - BSize::::parse(b"2MB").unwrap(), - BSize::(2_000_000) - ); - assert_eq!( - BSize::::parse(b"2MiB").unwrap(), - BSize::(2_097_152) - ); - assert_eq!( - BSize::::parse(b"1GB").unwrap(), - BSize::(1_000_000_000) - ); - assert_eq!( - BSize::::parse(b"1GiB").unwrap(), - BSize::(1_073_741_824) - ); - } - - #[test] - fn parses_fractional_units() { - assert_eq!(BSize::::parse(b"1.5KB").unwrap(), BSize::(1_500)); - assert_eq!( - BSize::::parse(b"4294967295.4B").unwrap(), - BSize::(u32::MAX) - ); - } - - #[test] - fn rejects_unsupported_units() { - BSize::::parse(b"1TB").unwrap_err(); - BSize::::parse(b"1TiB").unwrap_err(); - } - - #[test] - fn rejects_overflow() { - BSize::::parse(b"4294967295.5B").unwrap_err(); - } - - #[cfg(target_pointer_width = "32")] - #[test] - fn parses_usize() { - assert_eq!( - BSize::::parse(b"1_234B").unwrap(), - BSize::(1_234) - ); - } -} +// // Copyright 2026 FastLabs Developers +// // +// // Licensed under the Apache License, Version 2.0 (the "License"); +// // you may not use this file except in compliance with the License. +// // You may obtain a copy of the License at +// // +// // http://www.apache.org/licenses/LICENSE-2.0 +// // +// // Unless required by applicable law or agreed to in writing, software +// // distributed under the License is distributed on an "AS IS" BASIS, +// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// // See the License for the specific language governing permissions and +// // limitations under the License. +// +// use super::ParseError; +// use crate::types::BSize; +// use core::str::FromStr; +// +// impl BSize { +// /// Parses a byte size from a byte slice. +// /// +// /// The input must end with a `B` or `b` byte suffix. Supported units are +// /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, and `GiB`, case-insensitively. +// /// +// /// The numeric part may be an integer or an ordinary decimal number. +// /// Scientific notation is not supported. +// /// +// /// # Errors +// /// +// /// Returns [`ParseError`] if the input cannot be parsed as a `u32` byte +// /// size. +// pub fn parse(src: impl AsRef<[u8]>) -> Result { +// parse_u32(src.as_ref()).map(BSize) +// } +// } +// +// impl FromStr for BSize { +// type Err = ParseError; +// +// fn from_str(src: &str) -> Result { +// Self::parse(src.as_bytes()) +// } +// } +// +// #[cfg(target_pointer_width = "32")] +// impl BSize { +// /// Parses a byte size from a byte slice. +// /// +// /// The input must end with a `B` or `b` byte suffix. On 32-bit targets, +// /// supported units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, and `GiB`, +// /// case-insensitively. +// /// +// /// The numeric part may be an integer or an ordinary decimal number. +// /// Scientific notation is not supported. +// /// +// /// # Errors +// /// +// /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte +// /// size. +// pub fn parse(src: impl AsRef<[u8]>) -> Result { +// parse_usize(src.as_ref()).map(BSize) +// } +// } +// +// #[cfg(target_pointer_width = "32")] +// impl FromStr for BSize { +// type Err = ParseError; +// +// fn from_str(src: &str) -> Result { +// Self::parse(src.as_bytes()) +// } +// } +// +// fn parse_u32(mut src: &[u8]) -> Result { +// let multiply = parse_unit(&mut src)?; +// +// parse_number(src, multiply).map(|value| value as u32) +// } +// +// #[cfg(target_pointer_width = "32")] +// fn parse_usize(src: &[u8]) -> Result { +// parse_u32(src).map(|value| value as usize) +// } +// +// fn parse_unit(src: &mut &[u8]) -> Result { +// super::strip_unit_suffix(src)?; +// +// if let Some((&infix, before_i)) = src.split_last() { +// if infix.eq_ignore_ascii_case(&b'i') { +// let Some((&prefix, before_prefix)) = before_i.split_last() else { +// return Err(ParseError(())); +// }; +// let Some(factor) = binary_factor(prefix) else { +// return Err(ParseError(())); +// }; +// +// *src = before_prefix; +// return Ok(factor); +// } +// } +// +// if let Some((&prefix, before_prefix)) = src.split_last() { +// if let Some(factor) = decimal_factor(prefix) { +// *src = before_prefix; +// return Ok(factor); +// } +// if super::is_ascii_unit(prefix) { +// return Err(ParseError(())); +// } +// } +// +// Ok(1) +// } +// +// fn decimal_factor(prefix: u8) -> Option { +// Some(match prefix.to_ascii_uppercase() { +// b'K' => 1_000, +// b'M' => 1_000_000, +// b'G' => 1_000_000_000, +// _ => return None, +// }) +// } +// +// fn binary_factor(prefix: u8) -> Option { +// Some(match prefix.to_ascii_uppercase() { +// b'K' => 1_u64 << 10, +// b'M' => 1_u64 << 20, +// b'G' => 1_u64 << 30, +// _ => return None, +// }) +// } +// +// fn parse_number(src: &[u8], multiply: u64) -> Result { +// let mut value = 0_u64; +// let mut has_digit = false; +// let mut idx = 0; +// +// while idx < src.len() { +// match src[idx] { +// digit @ b'0'..=b'9' => { +// let Some(next) = value +// .checked_mul(10) +// .and_then(|value| value.checked_add(u64::from(digit - b'0'))) +// else { +// return Err(ParseError(())); +// }; +// value = next; +// has_digit = true; +// } +// b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), +// b'_' | b' ' => {} +// _ => return Err(ParseError(())), +// } +// +// idx += 1; +// } +// +// if !has_digit { +// return Err(ParseError(())); +// } +// +// let Some(value) = value.checked_mul(multiply) else { +// return Err(ParseError(())); +// }; +// if value > u64::from(u32::MAX) { +// Err(ParseError(())) +// } else { +// Ok(value) +// } +// } +// +// fn parse_fraction( +// src: &[u8], +// start: usize, +// integer: u64, +// has_integer_digit: bool, +// multiply: u64, +// ) -> Result { +// if !has_integer_digit { +// return Err(ParseError(())); +// } +// +// let Some(base) = integer.checked_mul(multiply) else { +// return Err(ParseError(())); +// }; +// if base > u64::from(u32::MAX) { +// return Err(ParseError(())); +// } +// +// let mut fraction = 0_u64; +// let mut scale = 1_u64; +// let mut digits = 0_u32; +// let mut idx = start; +// +// while idx < src.len() { +// match src[idx] { +// digit @ b'0'..=b'9' => { +// if digits == 19 { +// return Err(ParseError(())); +// } +// +// fraction = fraction * 10 + u64::from(digit - b'0'); +// scale *= 10; +// digits += 1; +// } +// b'_' | b' ' => {} +// _ => return Err(ParseError(())), +// } +// +// idx += 1; +// } +// +// let product = u128::from(fraction) * u128::from(multiply); +// let quotient = product / u128::from(scale); +// let remainder = product % u128::from(scale); +// let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { +// quotient + 1 +// } else { +// quotient +// }; +// +// let value = u128::from(base) + rounded; +// if value > u128::from(u32::MAX) { +// Err(ParseError(())) +// } else { +// Ok(value as u64) +// } +// } +// +// #[cfg(test)] +// mod tests { +// use super::*; +// +// #[test] +// fn parses_units() { +// assert_eq!( +// BSize::::parse(b"2MB").unwrap(), +// BSize::(2_000_000) +// ); +// assert_eq!( +// BSize::::parse(b"2MiB").unwrap(), +// BSize::(2_097_152) +// ); +// assert_eq!( +// BSize::::parse(b"1GB").unwrap(), +// BSize::(1_000_000_000) +// ); +// assert_eq!( +// BSize::::parse(b"1GiB").unwrap(), +// BSize::(1_073_741_824) +// ); +// } +// +// #[test] +// fn parses_fractional_units() { +// assert_eq!(BSize::::parse(b"1.5KB").unwrap(), BSize::(1_500)); +// assert_eq!( +// BSize::::parse(b"4294967295.4B").unwrap(), +// BSize::(u32::MAX) +// ); +// } +// +// #[test] +// fn rejects_unsupported_units() { +// BSize::::parse(b"1TB").unwrap_err(); +// BSize::::parse(b"1TiB").unwrap_err(); +// } +// +// #[test] +// fn rejects_overflow() { +// BSize::::parse(b"4294967295.5B").unwrap_err(); +// } +// +// #[cfg(target_pointer_width = "32")] +// #[test] +// fn parses_usize() { +// assert_eq!( +// BSize::::parse(b"1_234B").unwrap(), +// BSize::(1_234) +// ); +// } +// } diff --git a/bsize/src/parse/parse_u64.rs b/bsize/src/parse/parse_u64.rs index 7bd680a..277f86c 100644 --- a/bsize/src/parse/parse_u64.rs +++ b/bsize/src/parse/parse_u64.rs @@ -1,286 +1,286 @@ -// Copyright 2026 FastLabs Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::ParseError; -use crate::types::BSize; -use core::str::FromStr; - -impl BSize { - /// Parses a byte size from a byte slice. - /// - /// The input must end with a `B` or `b` byte suffix. Supported units are - /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, `TiB`, `PB`, `PiB`, - /// `EB`, and `EiB`, case-insensitively. - /// - /// The numeric part may be an integer or an ordinary decimal number. - /// Scientific notation is not supported. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input cannot be parsed as a `u64` byte - /// size. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - parse_u64(src.as_ref()).map(BSize) - } -} - -impl FromStr for BSize { - type Err = ParseError; - - fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) - } -} - -#[cfg(target_pointer_width = "64")] -impl BSize { - /// Parses a byte size from a byte slice. - /// - /// The input must end with a `B` or `b` byte suffix. On 64-bit targets, - /// supported units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, - /// `TiB`, `PB`, `PiB`, `EB`, and `EiB`, case-insensitively. - /// - /// The numeric part may be an integer or an ordinary decimal number. - /// Scientific notation is not supported. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte - /// size. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - parse_usize(src.as_ref()).map(BSize) - } -} - -#[cfg(target_pointer_width = "64")] -impl FromStr for BSize { - type Err = ParseError; - - fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) - } -} - -fn parse_u64(mut src: &[u8]) -> Result { - let multiply = parse_unit(&mut src)?; - - parse_number(src, multiply) -} - -#[cfg(target_pointer_width = "64")] -fn parse_usize(src: &[u8]) -> Result { - parse_u64(src).map(|value| value as usize) -} - -fn parse_unit(src: &mut &[u8]) -> Result { - super::strip_b_suffix(src)?; - - if let Some((&infix, before_i)) = src.split_last() { - if infix.eq_ignore_ascii_case(&b'i') { - let Some((&prefix, before_prefix)) = before_i.split_last() else { - return Err(ParseError(())); - }; - let Some(factor) = binary_factor(prefix) else { - return Err(ParseError(())); - }; - - *src = before_prefix; - return Ok(factor); - } - } - - if let Some((&prefix, before_prefix)) = src.split_last() { - if let Some(factor) = decimal_factor(prefix) { - *src = before_prefix; - return Ok(factor); - } - } - - Ok(1) -} - -fn decimal_factor(prefix: u8) -> Option { - Some(match prefix.to_ascii_uppercase() { - b'K' => 1_000, - b'M' => 1_000_000, - b'G' => 1_000_000_000, - b'T' => 1_000_000_000_000, - b'P' => 1_000_000_000_000_000, - b'E' => 1_000_000_000_000_000_000, - _ => return None, - }) -} - -fn binary_factor(prefix: u8) -> Option { - Some(match prefix.to_ascii_uppercase() { - b'K' => 1_u64 << 10, - b'M' => 1_u64 << 20, - b'G' => 1_u64 << 30, - b'T' => 1_u64 << 40, - b'P' => 1_u64 << 50, - b'E' => 1_u64 << 60, - _ => return None, - }) -} - -fn parse_number(src: &[u8], multiply: u64) -> Result { - let mut value = 0_u64; - let mut has_digit = false; - let mut idx = 0; - - while idx < src.len() { - match src[idx] { - digit @ b'0'..=b'9' => { - let Some(next) = value - .checked_mul(10) - .and_then(|value| value.checked_add(u64::from(digit - b'0'))) - else { - return Err(ParseError(())); - }; - value = next; - has_digit = true; - } - b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), - b'_' | b' ' => {} - _ => return Err(ParseError(())), - } - - idx += 1; - } - - if !has_digit { - return Err(ParseError(())); - } - - value.checked_mul(multiply).ok_or(ParseError(())) -} - -fn parse_fraction( - src: &[u8], - start: usize, - integer: u64, - has_integer_digit: bool, - multiply: u64, -) -> Result { - if !has_integer_digit { - return Err(ParseError(())); - } - - let Some(base) = integer.checked_mul(multiply) else { - return Err(ParseError(())); - }; - - let mut fraction = 0_u64; - let mut scale = 1_u64; - let mut digits = 0_u32; - let mut idx = start; - - while idx < src.len() { - match src[idx] { - digit @ b'0'..=b'9' => { - if digits == 19 { - return Err(ParseError(())); - } - - fraction = fraction * 10 + u64::from(digit - b'0'); - scale *= 10; - digits += 1; - } - b'_' | b' ' => {} - _ => return Err(ParseError(())), - } - - idx += 1; - } - - let product = u128::from(fraction) * u128::from(multiply); - let quotient = product / u128::from(scale); - let remainder = product % u128::from(scale); - let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { - quotient + 1 - } else { - quotient - }; - - let value = u128::from(base) + rounded; - if value > u128::from(u64::MAX) { - Err(ParseError(())) - } else { - Ok(value as u64) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parses_bytes() { - assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize::(1_234)); - } - - #[test] - fn parses_units() { - assert_eq!( - BSize::::parse(b"1TB").unwrap(), - BSize::(1_000_000_000_000) - ); - assert_eq!( - BSize::::parse(b"1TiB").unwrap(), - BSize::(1_099_511_627_776) - ); - assert_eq!( - BSize::::parse(b"1PB").unwrap(), - BSize::(1_000_000_000_000_000) - ); - assert_eq!( - BSize::::parse(b"1PiB").unwrap(), - BSize::(1_125_899_906_842_624) - ); - } - - #[test] - fn parses_fractional_units() { - assert_eq!(BSize::::parse(b"1.5KiB").unwrap(), BSize::(1_536)); - assert_eq!( - BSize::::parse(b"18.446744073709551615EB").unwrap(), - BSize::(u64::MAX) - ); - } - - #[test] - fn rejects_invalid_input() { - BSize::::parse(b"").unwrap_err(); - BSize::::parse(b"1").unwrap_err(); - BSize::::parse(b"1K").unwrap_err(); - BSize::::parse(b"1XB").unwrap_err(); - BSize::::parse(b"1iB").unwrap_err(); - BSize::::parse(b"1e+B").unwrap_err(); - BSize::::parse(b"1.5e3KiB").unwrap_err(); - } - - #[test] - fn rejects_overflow() { - BSize::::parse(b"0.00000000000000000001B").unwrap_err(); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn parses_usize() { - assert_eq!( - BSize::::parse(b"1_234B").unwrap(), - BSize::(1_234) - ); - } -} +// // Copyright 2026 FastLabs Developers +// // +// // Licensed under the Apache License, Version 2.0 (the "License"); +// // you may not use this file except in compliance with the License. +// // You may obtain a copy of the License at +// // +// // http://www.apache.org/licenses/LICENSE-2.0 +// // +// // Unless required by applicable law or agreed to in writing, software +// // distributed under the License is distributed on an "AS IS" BASIS, +// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// // See the License for the specific language governing permissions and +// // limitations under the License. +// +// use super::ParseError; +// use crate::types::BSize; +// use core::str::FromStr; +// +// impl BSize { +// /// Parses a byte size from a byte slice. +// /// +// /// The input must end with a `B` or `b` byte suffix. Supported units are +// /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, `TiB`, `PB`, `PiB`, +// /// `EB`, and `EiB`, case-insensitively. +// /// +// /// The numeric part may be an integer or an ordinary decimal number. +// /// Scientific notation is not supported. +// /// +// /// # Errors +// /// +// /// Returns [`ParseError`] if the input cannot be parsed as a `u64` byte +// /// size. +// pub fn parse(src: impl AsRef<[u8]>) -> Result { +// parse_u64(src.as_ref()).map(BSize) +// } +// } +// +// impl FromStr for BSize { +// type Err = ParseError; +// +// fn from_str(src: &str) -> Result { +// Self::parse(src.as_bytes()) +// } +// } +// +// #[cfg(target_pointer_width = "64")] +// impl BSize { +// /// Parses a byte size from a byte slice. +// /// +// /// The input must end with a `B` or `b` byte suffix. On 64-bit targets, +// /// supported units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, +// /// `TiB`, `PB`, `PiB`, `EB`, and `EiB`, case-insensitively. +// /// +// /// The numeric part may be an integer or an ordinary decimal number. +// /// Scientific notation is not supported. +// /// +// /// # Errors +// /// +// /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte +// /// size. +// pub fn parse(src: impl AsRef<[u8]>) -> Result { +// parse_usize(src.as_ref()).map(BSize) +// } +// } +// +// #[cfg(target_pointer_width = "64")] +// impl FromStr for BSize { +// type Err = ParseError; +// +// fn from_str(src: &str) -> Result { +// Self::parse(src.as_bytes()) +// } +// } +// +// fn parse_u64(mut src: &[u8]) -> Result { +// let multiply = parse_unit(&mut src)?; +// +// parse_number(src, multiply) +// } +// +// #[cfg(target_pointer_width = "64")] +// fn parse_usize(src: &[u8]) -> Result { +// parse_u64(src).map(|value| value as usize) +// } +// +// fn parse_unit(src: &mut &[u8]) -> Result { +// super::strip_unit_suffix(src)?; +// +// if let Some((&infix, before_i)) = src.split_last() { +// if infix.eq_ignore_ascii_case(&b'i') { +// let Some((&prefix, before_prefix)) = before_i.split_last() else { +// return Err(ParseError(())); +// }; +// let Some(factor) = binary_factor(prefix) else { +// return Err(ParseError(())); +// }; +// +// *src = before_prefix; +// return Ok(factor); +// } +// } +// +// if let Some((&prefix, before_prefix)) = src.split_last() { +// if let Some(factor) = decimal_factor(prefix) { +// *src = before_prefix; +// return Ok(factor); +// } +// } +// +// Ok(1) +// } +// +// fn decimal_factor(prefix: u8) -> Option { +// Some(match prefix.to_ascii_uppercase() { +// b'K' => 1_000, +// b'M' => 1_000_000, +// b'G' => 1_000_000_000, +// b'T' => 1_000_000_000_000, +// b'P' => 1_000_000_000_000_000, +// b'E' => 1_000_000_000_000_000_000, +// _ => return None, +// }) +// } +// +// fn binary_factor(prefix: u8) -> Option { +// Some(match prefix.to_ascii_uppercase() { +// b'K' => 1_u64 << 10, +// b'M' => 1_u64 << 20, +// b'G' => 1_u64 << 30, +// b'T' => 1_u64 << 40, +// b'P' => 1_u64 << 50, +// b'E' => 1_u64 << 60, +// _ => return None, +// }) +// } +// +// fn parse_number(src: &[u8], multiply: u64) -> Result { +// let mut value = 0_u64; +// let mut has_digit = false; +// let mut idx = 0; +// +// while idx < src.len() { +// match src[idx] { +// digit @ b'0'..=b'9' => { +// let Some(next) = value +// .checked_mul(10) +// .and_then(|value| value.checked_add(u64::from(digit - b'0'))) +// else { +// return Err(ParseError(())); +// }; +// value = next; +// has_digit = true; +// } +// b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), +// b'_' | b' ' => {} +// _ => return Err(ParseError(())), +// } +// +// idx += 1; +// } +// +// if !has_digit { +// return Err(ParseError(())); +// } +// +// value.checked_mul(multiply).ok_or(ParseError(())) +// } +// +// fn parse_fraction( +// src: &[u8], +// start: usize, +// integer: u64, +// has_integer_digit: bool, +// multiply: u64, +// ) -> Result { +// if !has_integer_digit { +// return Err(ParseError(())); +// } +// +// let Some(base) = integer.checked_mul(multiply) else { +// return Err(ParseError(())); +// }; +// +// let mut fraction = 0_u64; +// let mut scale = 1_u64; +// let mut digits = 0_u32; +// let mut idx = start; +// +// while idx < src.len() { +// match src[idx] { +// digit @ b'0'..=b'9' => { +// if digits == 19 { +// return Err(ParseError(())); +// } +// +// fraction = fraction * 10 + u64::from(digit - b'0'); +// scale *= 10; +// digits += 1; +// } +// b'_' | b' ' => {} +// _ => return Err(ParseError(())), +// } +// +// idx += 1; +// } +// +// let product = u128::from(fraction) * u128::from(multiply); +// let quotient = product / u128::from(scale); +// let remainder = product % u128::from(scale); +// let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { +// quotient + 1 +// } else { +// quotient +// }; +// +// let value = u128::from(base) + rounded; +// if value > u128::from(u64::MAX) { +// Err(ParseError(())) +// } else { +// Ok(value as u64) +// } +// } +// +// #[cfg(test)] +// mod tests { +// use super::*; +// +// #[test] +// fn parses_bytes() { +// assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize::(1_234)); +// } +// +// #[test] +// fn parses_units() { +// assert_eq!( +// BSize::::parse(b"1TB").unwrap(), +// BSize::(1_000_000_000_000) +// ); +// assert_eq!( +// BSize::::parse(b"1TiB").unwrap(), +// BSize::(1_099_511_627_776) +// ); +// assert_eq!( +// BSize::::parse(b"1PB").unwrap(), +// BSize::(1_000_000_000_000_000) +// ); +// assert_eq!( +// BSize::::parse(b"1PiB").unwrap(), +// BSize::(1_125_899_906_842_624) +// ); +// } +// +// #[test] +// fn parses_fractional_units() { +// assert_eq!(BSize::::parse(b"1.5KiB").unwrap(), BSize::(1_536)); +// assert_eq!( +// BSize::::parse(b"18.446744073709551615EB").unwrap(), +// BSize::(u64::MAX) +// ); +// } +// +// #[test] +// fn rejects_invalid_input() { +// BSize::::parse(b"").unwrap_err(); +// BSize::::parse(b"1").unwrap_err(); +// BSize::::parse(b"1K").unwrap_err(); +// BSize::::parse(b"1XB").unwrap_err(); +// BSize::::parse(b"1iB").unwrap_err(); +// BSize::::parse(b"1e+B").unwrap_err(); +// BSize::::parse(b"1.5e3KiB").unwrap_err(); +// } +// +// #[test] +// fn rejects_overflow() { +// BSize::::parse(b"0.00000000000000000001B").unwrap_err(); +// } +// +// #[cfg(target_pointer_width = "64")] +// #[test] +// fn parses_usize() { +// assert_eq!( +// BSize::::parse(b"1_234B").unwrap(), +// BSize::(1_234) +// ); +// } +// } diff --git a/bsize/src/parse/parse_u8.rs b/bsize/src/parse/parse_u8.rs index b23e0b6..7327300 100644 --- a/bsize/src/parse/parse_u8.rs +++ b/bsize/src/parse/parse_u8.rs @@ -19,12 +19,6 @@ use core::str::FromStr; impl BSize { /// Parses a byte size from a byte slice. /// - /// The input must end with a `B` or `b` byte suffix. No other unit is - /// supported for `u8`. - /// - /// The numeric part may be an integer or an ordinary decimal number. - /// Scientific notation is not supported. - /// /// # Errors /// /// Returns [`ParseError`] if the input cannot be parsed as a `u8` byte @@ -44,48 +38,42 @@ impl FromStr for BSize { } fn parse_u8(mut src: &[u8]) -> Result { - super::strip_b_suffix(&mut src)?; + let multiply = super::strip_unit_suffix(&mut src)?; + if src.is_empty() { + return Err(ParseError::Empty); + } let mut value = 0u8; - let mut valid = false; - - let len = src.len(); - for idx in 0..len { + for idx in 0..src.len() { match src[idx] { b'_' => {} b'.' => { - if idx + 1 < len { - match src[idx + 1] { - b'0'..=b'4' => {} - b'5'..=b'9' => { - value = value.checked_add(1).ok_or(ParseError(()))?; + let mut frac = 1u64; + for idx in idx + 1..src.len() { + match src[idx] { + b'_' => {} + n @ b'0'..=b'9' => { + frac *= 10; + let n = (n - b'0') as u64; + let n = n.checked_mul(multiply).ok_or(ParseError::PosOverflow)?; + // let n = n.div_euclid(division); } - _ => return Err(ParseError(())), + _ => return Err(ParseError::InvalidDigit), } - valid = true; } - - for idx in idx + 2..len { - if !matches!(src[idx], b'_' | b'0'..=b'9') { - return Err(ParseError(())); - } - } - break; } - digit @ b'0'..=b'9' => { - value = value.checked_mul(10).ok_or(ParseError(()))?; - value = value.checked_add(digit - b'0').ok_or(ParseError(()))?; - valid = true; + n @ b'0'..=b'9' => { + value = value.checked_mul(10).ok_or(ParseError::PosOverflow)?; + let n = (n - b'0') as u64; + let n = n.checked_mul(multiply).ok_or(ParseError::PosOverflow)?; + let n = u8::try_from(n).map_err(|_| ParseError::PosOverflow)?; + value = value.checked_add(n).ok_or(ParseError::PosOverflow)?; } - _ => return Err(ParseError(())), + _ => return Err(ParseError::InvalidDigit), } } - if valid { - Ok(value) - } else { - Err(ParseError(())) - } + Ok(value) } #[cfg(test)] @@ -95,6 +83,7 @@ mod tests { #[test] fn parses_bytes() { assert_eq!(BSize::::parse(b"255B").unwrap(), BSize::(255)); + assert_eq!(BSize::::parse(b"255 B").unwrap(), BSize::(255)); } #[test] @@ -103,17 +92,13 @@ mod tests { assert_eq!(BSize::::parse(b"255.4B").unwrap(), BSize::(u8::MAX)); } - #[test] - fn rejects_units() { - BSize::::parse(b"1KB").unwrap_err(); - BSize::::parse(b"1eB").unwrap_err(); - BSize::::parse(b"1EB").unwrap_err(); - } - #[test] fn rejects_overflow() { - BSize::::parse(b"256B").unwrap_err(); - BSize::::parse(b"0.001KB").unwrap_err(); - BSize::::parse(b"255.5B").unwrap_err(); + assert_eq!(BSize::::parse(b"1KB"), Err(ParseError::PosOverflow)); + assert_eq!(BSize::::parse(b"1eB"), Err(ParseError::PosOverflow)); + assert_eq!(BSize::::parse(b"1EB"), Err(ParseError::PosOverflow)); + assert_eq!(BSize::::parse(b"256B"), Err(ParseError::PosOverflow)); + assert_eq!(BSize::::parse(b"0.001KB"), Err(ParseError::PosOverflow)); + assert_eq!(BSize::::parse(b"255.5B"), Err(ParseError::PosOverflow)); } } From 35b477cefa895cc02557608ec68e0cd551ec657c Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 9 Jun 2026 22:48:39 +0800 Subject: [PATCH 08/10] no u128 Signed-off-by: tison --- bsize/src/ops.rs | 4 +- bsize/src/parse/mod.rs | 49 ++++---- bsize/src/parse/parse_u128.rs | 214 ---------------------------------- bsize/src/parse/parse_u8.rs | 98 ++++++++-------- bsize/src/types.rs | 70 ----------- bsize/src/unsigned.rs | 2 - 6 files changed, 72 insertions(+), 365 deletions(-) delete mode 100644 bsize/src/parse/parse_u128.rs diff --git a/bsize/src/ops.rs b/bsize/src/ops.rs index 61f71b2..015f579 100644 --- a/bsize/src/ops.rs +++ b/bsize/src/ops.rs @@ -54,7 +54,7 @@ macro_rules! impl_ops { }; } -impl_ops!(u8, u16, u32, u64, u128, usize); +impl_ops!(u8, u16, u32, u64, usize); #[cfg(test)] mod tests { @@ -66,7 +66,6 @@ mod tests { assert_eq!((BSize::(3) + BSize(5)).0, 8); assert_eq!((BSize::(3) + BSize(5)).0, 8); assert_eq!((BSize::(3) + BSize(5)).0, 8); - assert_eq!((BSize::(3) + BSize(5)).0, 8); assert_eq!((BSize::(3) + BSize(5)).0, 8); } @@ -83,7 +82,6 @@ mod tests { assert_eq!((BSize::(8) - BSize(5)).0, 3); assert_eq!((BSize::(8) - BSize(5)).0, 3); assert_eq!((BSize::(8) - BSize(5)).0, 3); - assert_eq!((BSize::(8) - BSize(5)).0, 3); assert_eq!((BSize::(8) - BSize(5)).0, 3); } diff --git a/bsize/src/parse/mod.rs b/bsize/src/parse/mod.rs index 5df4e82..c63aae0 100644 --- a/bsize/src/parse/mod.rs +++ b/bsize/src/parse/mod.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod parse_u128; mod parse_u16; mod parse_u32; mod parse_u64; @@ -26,34 +25,37 @@ use core::fmt; pub enum ParseError { /// The input contains no number. Empty, - /// The input contains an invalid byte. - InvalidDigit, + /// The input contains malformed bytes. + Malformed, /// The parsed byte count is too large for the target integer type. - PosOverflow, + Overflow, } impl fmt::Display for ParseError { 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", + Self::Malformed => "malformed bytes found in string", + Self::Overflow => "number too large to fit in target type", }) } } impl core::error::Error for ParseError {} -fn strip_unit_suffix(src: &mut &[u8]) -> Result { - if let [init @ .., b'b' | b'B'] = src { - *src = init; - }; - +fn strip_unit_suffix(src: &mut &str) -> Result { + let mut strip = src.len(); let mut multiply = 1; - if let [init @ .., b'i' | b'I'] = src { - *src = init; - if let [init @ .., prefix] = src { + let mut unit = src.as_bytes(); + if let [init @ .., b'b' | b'B'] = unit { + unit = init; + strip -= 1; + }; + if let [init @ .., b'i' | b'I'] = unit { + unit = init; + strip -= 1; + if let [.., prefix] = unit { match prefix { b'k' | b'K' => multiply = 1 << 10, b'm' | b'M' => multiply = 1 << 20, @@ -61,17 +63,15 @@ fn strip_unit_suffix(src: &mut &[u8]) -> Result { b't' | b'T' => multiply = 1 << 40, b'p' | b'P' => multiply = 1 << 50, b'e' | b'E' => multiply = 1 << 60, - _ => return Err(ParseError::InvalidDigit), + _ => return Err(ParseError::Malformed), } - - *src = init; } else { // [iI][bB] is not a valid suffix. - return Err(ParseError::InvalidDigit); + return Err(ParseError::Malformed); } } else { - if let [init @ .., prefix] = src { - 'skip: { + if let [.., prefix] = unit { + 'outer: { match prefix { b'k' | b'K' => multiply = 1_000, b'm' | b'M' => multiply = 1_000_000, @@ -79,16 +79,13 @@ fn strip_unit_suffix(src: &mut &[u8]) -> Result { 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, + _ => break 'outer, } - *src = init; + strip -= 1; } } } - while let [init @ .., b' '] = src { - *src = init; - } - + *src = &src[..strip]; Ok(multiply) } diff --git a/bsize/src/parse/parse_u128.rs b/bsize/src/parse/parse_u128.rs deleted file mode 100644 index 4b01c6f..0000000 --- a/bsize/src/parse/parse_u128.rs +++ /dev/null @@ -1,214 +0,0 @@ -// // Copyright 2026 FastLabs Developers -// // -// // Licensed under the Apache License, Version 2.0 (the "License"); -// // you may not use this file except in compliance with the License. -// // You may obtain a copy of the License at -// // -// // http://www.apache.org/licenses/LICENSE-2.0 -// // -// // Unless required by applicable law or agreed to in writing, software -// // distributed under the License is distributed on an "AS IS" BASIS, -// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// // See the License for the specific language governing permissions and -// // limitations under the License. -// -// use super::ParseError; -// use crate::types::BSize; -// use core::str::FromStr; -// -// impl BSize { -// /// Parses a byte size from a byte slice. -// /// -// /// The input must end with a `B` or `b` byte suffix. Supported units are -// /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, `TiB`, `PB`, `PiB`, -// /// `EB`, and `EiB`, case-insensitively. -// /// -// /// The numeric part may be an integer or an ordinary decimal number. -// /// Scientific notation is not supported. -// /// -// /// # Errors -// /// -// /// Returns [`ParseError`] if the input cannot be parsed as a `u128` byte -// /// size. -// pub fn parse(src: impl AsRef<[u8]>) -> Result { -// parse_u128(src.as_ref()).map(BSize) -// } -// } -// -// impl FromStr for BSize { -// type Err = ParseError; -// -// fn from_str(src: &str) -> Result { -// Self::parse(src.as_bytes()) -// } -// } -// -// fn parse_u128(mut src: &[u8]) -> Result { -// let multiply = parse_unit(&mut src)?; -// -// parse_number(src, multiply) -// } -// -// fn parse_unit(src: &mut &[u8]) -> Result { -// super::strip_unit_suffix(src)?; -// -// if let Some((&infix, before_i)) = src.split_last() { -// if infix.eq_ignore_ascii_case(&b'i') { -// let Some((&prefix, before_prefix)) = before_i.split_last() else { -// return Err(ParseError(())); -// }; -// let Some(factor) = binary_factor(prefix) else { -// return Err(ParseError(())); -// }; -// -// *src = before_prefix; -// return Ok(factor); -// } -// } -// -// if let Some((&prefix, before_prefix)) = src.split_last() { -// if let Some(factor) = decimal_factor(prefix) { -// *src = before_prefix; -// return Ok(factor); -// } -// } -// -// Ok(1) -// } -// -// fn decimal_factor(prefix: u8) -> Option { -// Some(match prefix.to_ascii_uppercase() { -// b'K' => 1_000, -// b'M' => 1_000_000, -// b'G' => 1_000_000_000, -// b'T' => 1_000_000_000_000, -// b'P' => 1_000_000_000_000_000, -// b'E' => 1_000_000_000_000_000_000, -// _ => return None, -// }) -// } -// -// fn binary_factor(prefix: u8) -> Option { -// Some(match prefix.to_ascii_uppercase() { -// b'K' => 1_u128 << 10, -// b'M' => 1_u128 << 20, -// b'G' => 1_u128 << 30, -// b'T' => 1_u128 << 40, -// b'P' => 1_u128 << 50, -// b'E' => 1_u128 << 60, -// _ => return None, -// }) -// } -// -// fn parse_number(src: &[u8], multiply: u128) -> Result { -// let mut value = 0_u128; -// let mut has_digit = false; -// let mut idx = 0; -// -// while idx < src.len() { -// match src[idx] { -// digit @ b'0'..=b'9' => { -// let Some(next) = value -// .checked_mul(10) -// .and_then(|value| value.checked_add(u128::from(digit - b'0'))) -// else { -// return Err(ParseError(())); -// }; -// value = next; -// has_digit = true; -// } -// b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), -// b'_' | b' ' => {} -// _ => return Err(ParseError(())), -// } -// -// idx += 1; -// } -// -// if !has_digit { -// return Err(ParseError(())); -// } -// -// value.checked_mul(multiply).ok_or(ParseError(())) -// } -// -// fn parse_fraction( -// src: &[u8], -// start: usize, -// integer: u128, -// has_integer_digit: bool, -// multiply: u128, -// ) -> Result { -// if !has_integer_digit { -// return Err(ParseError(())); -// } -// -// let Some(base) = integer.checked_mul(multiply) else { -// return Err(ParseError(())); -// }; -// -// let mut fraction = 0_u128; -// let mut scale = 1_u128; -// let mut digits = 0_u32; -// let mut idx = start; -// -// while idx < src.len() { -// match src[idx] { -// digit @ b'0'..=b'9' => { -// if digits == 19 { -// return Err(ParseError(())); -// } -// -// fraction = fraction * 10 + u128::from(digit - b'0'); -// scale *= 10; -// digits += 1; -// } -// b'_' | b' ' => {} -// _ => return Err(ParseError(())), -// } -// -// idx += 1; -// } -// -// let product = fraction * multiply; -// let quotient = product / scale; -// let remainder = product % scale; -// let rounded = if digits != 0 && remainder >= scale / 2 { -// quotient + 1 -// } else { -// quotient -// }; -// -// base.checked_add(rounded).ok_or(ParseError(())) -// } -// -// #[cfg(test)] -// mod tests { -// use super::*; -// -// #[test] -// fn parses_u128_max() { -// assert_eq!( -// BSize::::parse(b"340282366920938463463374607431768211455B").unwrap(), -// BSize::(u128::MAX) -// ); -// } -// -// #[test] -// fn rejects_overflow() { -// BSize::::parse(b"340282366920938463463374607431768211456B").unwrap_err(); -// } -// -// #[test] -// fn parses_u128_max_with_decimal_unit() { -// assert_eq!( -// BSize::::parse(b"340282366920938463463.374607431768211455EB").unwrap(), -// BSize::(u128::MAX), -// ); -// } -// -// #[test] -// fn rejects_scientific_notation() { -// BSize::::parse(b"1.5e3KiB").unwrap_err(); -// } -// } diff --git a/bsize/src/parse/parse_u8.rs b/bsize/src/parse/parse_u8.rs index 7327300..f3e98e8 100644 --- a/bsize/src/parse/parse_u8.rs +++ b/bsize/src/parse/parse_u8.rs @@ -14,65 +14,63 @@ use super::ParseError; use crate::types::BSize; +use core::num::IntErrorKind; use core::str::FromStr; -impl BSize { - /// Parses a byte size from a byte slice. - /// - /// # Errors - /// - /// Returns [`ParseError`] if the input cannot be parsed as a `u8` byte - /// size. - pub fn parse(src: impl AsRef<[u8]>) -> Result { - let bytes = src.as_ref(); - parse_u8(bytes).map(BSize) - } -} - impl FromStr for BSize { type Err = ParseError; fn from_str(src: &str) -> Result { - Self::parse(src.as_bytes()) + let mut src = src; + let multiply = super::strip_unit_suffix(&mut src)?; + + let mut dot = false; + let mut offset = 0; + for ch in src.chars() { + match ch { + '.' => { + dot = true; + offset += 1; + } + '_' | '0'..='9' => offset += 1, + _ => return Err(ParseError::Malformed), + } + } + + if dot { + let n = match f64::from_str(&src[..offset]) { + Ok(n) => n, + Err(_) => return Err(ParseError::Malformed), + }; + + let n = n * (multiply as f64); + let n = core::f64::math::round(); + } else { + let n = match u64::from_str(&src[..offset]) { + Ok(n) => n, + Err(err) => { + return Err(match err.kind() { + IntErrorKind::PosOverflow => ParseError::Overflow, + IntErrorKind::Empty => ParseError::Empty, + _ => ParseError::Malformed, + }); + } + }; + } + + Ok(BSize(0)) } } fn parse_u8(mut src: &[u8]) -> Result { let multiply = super::strip_unit_suffix(&mut src)?; + while let [init @ .., b' '] = src { + src = init; + } if src.is_empty() { return Err(ParseError::Empty); } - let mut value = 0u8; - for idx in 0..src.len() { - match src[idx] { - b'_' => {} - b'.' => { - let mut frac = 1u64; - for idx in idx + 1..src.len() { - match src[idx] { - b'_' => {} - n @ b'0'..=b'9' => { - frac *= 10; - let n = (n - b'0') as u64; - let n = n.checked_mul(multiply).ok_or(ParseError::PosOverflow)?; - // let n = n.div_euclid(division); - } - _ => return Err(ParseError::InvalidDigit), - } - } - } - n @ b'0'..=b'9' => { - value = value.checked_mul(10).ok_or(ParseError::PosOverflow)?; - let n = (n - b'0') as u64; - let n = n.checked_mul(multiply).ok_or(ParseError::PosOverflow)?; - let n = u8::try_from(n).map_err(|_| ParseError::PosOverflow)?; - value = value.checked_add(n).ok_or(ParseError::PosOverflow)?; - } - _ => return Err(ParseError::InvalidDigit), - } - } - Ok(value) } @@ -94,11 +92,11 @@ mod tests { #[test] fn rejects_overflow() { - assert_eq!(BSize::::parse(b"1KB"), Err(ParseError::PosOverflow)); - assert_eq!(BSize::::parse(b"1eB"), Err(ParseError::PosOverflow)); - assert_eq!(BSize::::parse(b"1EB"), Err(ParseError::PosOverflow)); - assert_eq!(BSize::::parse(b"256B"), Err(ParseError::PosOverflow)); - assert_eq!(BSize::::parse(b"0.001KB"), Err(ParseError::PosOverflow)); - assert_eq!(BSize::::parse(b"255.5B"), Err(ParseError::PosOverflow)); + assert_eq!(BSize::::parse(b"1KB"), Err(ParseError::Overflow)); + assert_eq!(BSize::::parse(b"1eB"), Err(ParseError::Overflow)); + assert_eq!(BSize::::parse(b"1EB"), Err(ParseError::Overflow)); + assert_eq!(BSize::::parse(b"256B"), Err(ParseError::Overflow)); + assert_eq!(BSize::::parse(b"0.001KB"), Err(ParseError::Overflow)); + assert_eq!(BSize::::parse(b"255.5B"), Err(ParseError::Overflow)); } } diff --git a/bsize/src/types.rs b/bsize/src/types.rs index cabae88..1795194 100644 --- a/bsize/src/types.rs +++ b/bsize/src/types.rs @@ -93,21 +93,6 @@ impl_constructors!(u64 => { eib = 1_152_921_504_606_846_976, }); -impl_constructors!(u128 => { - kb = 1_000, - kib = 1_024, - mb = 1_000_000, - mib = 1_048_576, - gb = 1_000_000_000, - gib = 1_073_741_824, - tb = 1_000_000_000_000, - tib = 1_099_511_627_776, - pb = 1_000_000_000_000_000, - pib = 1_125_899_906_842_624, - eb = 1_000_000_000_000_000_000, - eib = 1_152_921_504_606_846_976, -}); - impl_constructors!(usize => { kb = 1_000, kib = 1_024, @@ -177,21 +162,6 @@ impl_accessors!(u64 => { as_eib = 1_152_921_504_606_846_976u64 => "exbibytes", }); -impl_accessors!(u128 => { - as_kb = 1_000u128 => "kilobytes", - as_kib = 1_024u128 => "kibibytes", - as_mb = 1_000_000u128 => "megabytes", - as_mib = 1_048_576u128 => "mebibytes", - as_gb = 1_000_000_000u128 => "gigabytes", - as_gib = 1_073_741_824u128 => "gibibytes", - as_tb = 1_000_000_000_000u128 => "terabytes", - as_tib = 1_099_511_627_776u128 => "tebibytes", - as_pb = 1_000_000_000_000_000u128 => "petabytes", - as_pib = 1_125_899_906_842_624u128 => "pebibytes", - as_eb = 1_000_000_000_000_000_000u128 => "exabytes", - as_eib = 1_152_921_504_606_846_976u128 => "exbibytes", -}); - impl_accessors!(usize => { as_kb = 1_000usize => "kilobytes", as_kib = 1_024usize => "kibibytes", @@ -235,7 +205,6 @@ mod tests { assert_eq!(BSize::::default(), BSize::b(0)); assert_eq!(BSize::::default(), BSize::b(0)); assert_eq!(BSize::::default(), BSize::b(0)); - assert_eq!(BSize::::default(), BSize::b(0)); assert_eq!(BSize::::default(), BSize::b(0)); } @@ -318,45 +287,6 @@ mod tests { ); } - #[test] - fn constructs_u128_units() { - assert_eq!(BSize::::eb(20).0, 20_000_000_000_000_000_000); - assert_eq!(BSize::::eib(20).0, 23_058_430_092_136_939_520); - } - - #[test] - fn returns_u128_units() { - assert_close(BSize::::eib(20).as_eib(), 20.0); - } - - #[test] - fn returns_large_fractional_u128_units() { - let bytes = (1_u128 << 80) + (1_u128 << 40); - let eb = 1_000_000_000_000_000_000u128; - let eib = 1_152_921_504_606_846_976u128; - - assert_close( - BSize::::b(bytes).as_eb(), - (bytes as f64) / (eb as f64), - ); - assert_close( - BSize::::b(bytes).as_eib(), - (bytes as f64) / (eib as f64), - ); - } - - #[test] - fn returns_u128_units_with_f64_precision() { - let bytes = (1_u128 << 100) + 123_456_789; - let eib = 1_152_921_504_606_846_976u128; - - assert_eq!(bytes as f64, (1_u128 << 100) as f64); - assert_close( - BSize::::b(bytes).as_eib(), - ((1_u128 << 100) as f64) / (eib as f64), - ); - } - #[test] fn constructs_usize_units() { assert_eq!(BSize::::kb(2).0, 2_000); diff --git a/bsize/src/unsigned.rs b/bsize/src/unsigned.rs index fc13f1c..28c8869 100644 --- a/bsize/src/unsigned.rs +++ b/bsize/src/unsigned.rs @@ -20,7 +20,6 @@ mod private { impl Sealed for u16 {} impl Sealed for u32 {} impl Sealed for u64 {} - impl Sealed for u128 {} } /// A marker trait for all unsigned integers. @@ -31,4 +30,3 @@ impl Unsigned for u8 {} impl Unsigned for u16 {} impl Unsigned for u32 {} impl Unsigned for u64 {} -impl Unsigned for u128 {} From bd2e1ee25364551a8850438b107c193f642002ed Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 9 Jun 2026 23:31:55 +0800 Subject: [PATCH 09/10] rework Signed-off-by: tison --- Cargo.lock | 99 ----------- bsize/Cargo.toml | 8 - bsize/benches/parse.rs | 73 -------- bsize/src/parse.rs | 317 +++++++++++++++++++++++++++++++++++ bsize/src/parse/mod.rs | 91 ---------- bsize/src/parse/parse_u16.rs | 260 ---------------------------- bsize/src/parse/parse_u32.rs | 282 ------------------------------- bsize/src/parse/parse_u64.rs | 286 ------------------------------- bsize/src/parse/parse_u8.rs | 102 ----------- 9 files changed, 317 insertions(+), 1201 deletions(-) delete mode 100644 bsize/benches/parse.rs create mode 100644 bsize/src/parse.rs delete mode 100644 bsize/src/parse/mod.rs delete mode 100644 bsize/src/parse/parse_u16.rs delete mode 100644 bsize/src/parse/parse_u32.rs delete mode 100644 bsize/src/parse/parse_u64.rs delete mode 100644 bsize/src/parse/parse_u8.rs diff --git a/Cargo.lock b/Cargo.lock index ec204fb..85e901d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,25 +52,9 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "bitflags" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" - [[package]] name = "bsize" version = "0.1.0-rc.1" -dependencies = [ - "divan", - "parse-size", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" @@ -92,7 +76,6 @@ dependencies = [ "anstyle", "clap_lex", "strsim", - "terminal_size", ] [[package]] @@ -119,47 +102,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" -[[package]] -name = "condtype" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" - -[[package]] -name = "divan" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a405457ec78b8fe08b0e32b4a3570ab5dff6dd16eb9e76a5ee0a9d9cbd898933" -dependencies = [ - "cfg-if", - "clap", - "condtype", - "divan-macros", - "libc", - "regex-lite", -] - -[[package]] -name = "divan-macros" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9556bc800956545d6420a640173e5ba7dfa82f38d3ea5a167eb555bc69ac3323" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys", -] - [[package]] name = "heck" version = "0.5.0" @@ -178,24 +120,12 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "parse-size" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b" - [[package]] name = "proc-macro2" version = "1.0.106" @@ -214,25 +144,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "regex-lite" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - [[package]] name = "strsim" version = "0.11.1" @@ -250,16 +161,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "terminal_size" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" -dependencies = [ - "rustix", - "windows-sys", -] - [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/bsize/Cargo.toml b/bsize/Cargo.toml index e07f6b1..e3442f6 100644 --- a/bsize/Cargo.toml +++ b/bsize/Cargo.toml @@ -31,13 +31,5 @@ rust-version.workspace = true [features] default = [] -[dev-dependencies] -divan = "0.1.21" -parse-size = "1.1.0" - -[[bench]] -name = "parse" -harness = false - [lints] workspace = true diff --git a/bsize/benches/parse.rs b/bsize/benches/parse.rs deleted file mode 100644 index 94fb695..0000000 --- a/bsize/benches/parse.rs +++ /dev/null @@ -1,73 +0,0 @@ -// // Copyright 2026 FastLabs Developers -// // -// // Licensed under the Apache License, Version 2.0 (the "License"); -// // you may not use this file except in compliance with the License. -// // You may obtain a copy of the License at -// // -// // http://www.apache.org/licenses/LICENSE-2.0 -// // -// // Unless required by applicable law or agreed to in writing, software -// // distributed under the License is distributed on an "AS IS" BASIS, -// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// // See the License for the specific language governing permissions and -// // limitations under the License. - -// use bsize::BSize; -// use divan::black_box; -// use parse_size::parse_size; - -fn main() { - divan::main(); -} -// -// #[divan::bench] -// fn bsize_bytes() -> u64 { -// BSize::::parse(black_box(b"123456789B")).unwrap().0 -// } -// -// #[divan::bench] -// fn parse_size_bytes() -> u64 { -// parse_size(black_box(b"123456789B")).unwrap() -// } -// -// #[divan::bench] -// fn bsize_decimal() -> u64 { -// BSize::::parse(black_box(b"123.456MB")).unwrap().0 -// } -// -// #[divan::bench] -// fn parse_size_decimal() -> u64 { -// parse_size(black_box(b"123.456MB")).unwrap() -// } -// -// #[divan::bench] -// fn bsize_binary_decimal() -> u64 { -// BSize::::parse(black_box(b"1.5KiB")).unwrap().0 -// } -// -// #[divan::bench] -// fn parse_size_binary_decimal() -> u64 { -// parse_size(black_box(b"1.5KiB")).unwrap() -// } -// -// #[divan::bench] -// fn bsize_tiny_decimal() -> u64 { -// BSize::::parse(black_box(b"0.001KB")).unwrap().0 -// } -// -// #[divan::bench] -// fn parse_size_tiny_decimal() -> u64 { -// parse_size(black_box(b"0.001KB")).unwrap() -// } -// -// #[divan::bench] -// fn bsize_u64_max() -> u64 { -// BSize::::parse(black_box(b"18.446744073709551615EB")) -// .unwrap() -// .0 -// } -// -// #[divan::bench] -// fn parse_size_u64_max() -> u64 { -// parse_size(black_box(b"18.446744073709551615EB")).unwrap() -// } diff --git a/bsize/src/parse.rs b/bsize/src/parse.rs new file mode 100644 index 0000000..05e3a1c --- /dev/null +++ b/bsize/src/parse.rs @@ -0,0 +1,317 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::fmt; +use core::str::FromStr; + +use crate::BSize; + +/// The error returned when parsing a byte size fails. +#[derive(Debug, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub enum ParseError { + /// The input contains no number. + Empty, + /// The input contains malformed bytes. + Malformed, + /// The parsed byte count is too large for the target integer type. + Overflow, +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Empty => "cannot parse integer from empty string", + Self::Malformed => "malformed bytes found in string", + Self::Overflow => "number too large to fit in target type", + }) + } +} + +impl core::error::Error for ParseError {} + +macro_rules! impl_from_str { + ($($ty:ty),* $(,)?) => { + $( + impl FromStr for BSize<$ty> { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + let size = parse_size(s.as_bytes())?; + if size <= <$ty>::MAX as u64 { + Ok(BSize(size as $ty)) + } else { + Err(ParseError::Overflow) + } + } + } + )* + }; +} + +impl_from_str!(u8, u16, u32, u64, usize); + +// This is derived from `parse-size` [1]. +// +// [1]: https://github.com/kennytm/parse-size/blob/8f2bc5a8/src/lib.rs#L364-L495 +fn parse_size(mut src: &[u8]) -> Result { + // 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) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn assert_parse_ok(input: &str, expected: u64) { + assert_eq!( + input.parse::>(), + Ok(BSize::b(expected)), + "input: {input:?}", + ); + } + + fn assert_parse_err(input: &str, expected: ParseError) { + assert_eq!( + input.parse::>(), + Err(expected), + "input: {input:?}", + ); + } + + #[test] + fn test_parse_ok() { + for (input, expected) in [ + ("0", 0), + ("3", 3), + ("30", 30), + ("32", 32), + ("500", 500), + ("_5_", 5), + ("1_234_567", 1_234_567), + (" 42 ", 42), + ("1B", 1), + ("1 b", 1), + ("1kB", 1_000), + ("1K", 1_000), + ("1KB", 1_000), + ("2MB", 2_000_000), + ("3GB", 3_000_000_000), + ("4TB", 4_000_000_000_000), + ("5PB", 5_000_000_000_000_000), + ("6EB", 6_000_000_000_000_000_000), + ("8P", 8_000_000_000_000_000), + ("1Ki", 1 << 10), + ("1KiB", 1 << 10), + ("1.5Ki", 1_536), + ("1.5KiB", 1_536), + ("7 KiB", 7 << 10), + ("8 MiB", 8 << 20), + ("9 GiB", 9 << 30), + ("10 TiB", 10 << 40), + ("11 PiB", 11 << 50), + ("12 EiB", 12 << 60), + (" 7 KiB ", 7 << 10), + ("1mib", 1_048_576), + ("1.1 K", 1_100), + ("1.2345 K", 1_235), + ("1.2345m", 1_234_500), + ("5.k", 5_000), + ("0.0024KB", 2), + ("0.0025KB", 3), + ("0.4B", 0), + ("0.5B", 1), + ("18_446_744_073_709_551_581", 18_446_744_073_709_551_581), + ("18_446_744_073_709_551_615", u64::MAX), + ("18.446_744_073_709_551_615 EB", u64::MAX), + ("1.000_000_000_000_000_001 EB", 1_000_000_000_000_000_001), + ] { + assert_parse_ok(input, expected); + } + } + + #[test] + fn test_parse_err() { + for input in ["", " ", " ", "__", "k", "kb", "KiB"] { + assert_parse_err(input, ParseError::Empty); + } + + for input in [ + ".", + ".5k", + "a", + "a124GB", + "-1", + "1,5", + "1 234 567", + "1 000 B", + "1.3 42.0 B", + "1.3 ... B", + "IB", + "iB", + "1iB", + "1 ZiB", + "1 YiB", + "1e2 KIB", + "1E+6", + "0.1234567890123456789012", + "\t1", + "1\tKB", + ] { + assert_parse_err(input, ParseError::Malformed); + } + + for input in [ + "18_446_744_073_709_551_616", + "18_446_744_073_709_551_620", + "18.446_744_073_709_551_616 EB", + "19EB", + "16EiB", + "100000000000000000000", + ] { + assert_parse_err(input, ParseError::Overflow); + } + + assert_eq!("256".parse::>(), Err(ParseError::Overflow)); + assert_eq!("64 KiB".parse::>(), Err(ParseError::Overflow)); + assert_eq!("4GiB".parse::>(), Err(ParseError::Overflow)); + } +} diff --git a/bsize/src/parse/mod.rs b/bsize/src/parse/mod.rs deleted file mode 100644 index c63aae0..0000000 --- a/bsize/src/parse/mod.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2026 FastLabs Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod parse_u16; -mod parse_u32; -mod parse_u64; -mod parse_u8; - -use core::fmt; - -/// The error returned when parsing a byte size fails. -#[derive(Debug, Clone, Eq, PartialEq)] -#[non_exhaustive] -pub enum ParseError { - /// The input contains no number. - Empty, - /// The input contains malformed bytes. - Malformed, - /// The parsed byte count is too large for the target integer type. - Overflow, -} - -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Self::Empty => "cannot parse integer from empty string", - Self::Malformed => "malformed bytes found in string", - Self::Overflow => "number too large to fit in target type", - }) - } -} - -impl core::error::Error for ParseError {} - -fn strip_unit_suffix(src: &mut &str) -> Result { - let mut strip = src.len(); - let mut multiply = 1; - - let mut unit = src.as_bytes(); - if let [init @ .., b'b' | b'B'] = unit { - unit = init; - strip -= 1; - }; - if let [init @ .., b'i' | b'I'] = unit { - unit = init; - strip -= 1; - if let [.., prefix] = unit { - 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), - } - } else { - // [iI][bB] is not a valid suffix. - return Err(ParseError::Malformed); - } - } else { - if let [.., prefix] = unit { - 'outer: { - 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 'outer, - } - strip -= 1; - } - } - } - - *src = &src[..strip]; - Ok(multiply) -} diff --git a/bsize/src/parse/parse_u16.rs b/bsize/src/parse/parse_u16.rs deleted file mode 100644 index 68580dc..0000000 --- a/bsize/src/parse/parse_u16.rs +++ /dev/null @@ -1,260 +0,0 @@ -// // Copyright 2026 FastLabs Developers -// // -// // Licensed under the Apache License, Version 2.0 (the "License"); -// // you may not use this file except in compliance with the License. -// // You may obtain a copy of the License at -// // -// // http://www.apache.org/licenses/LICENSE-2.0 -// // -// // Unless required by applicable law or agreed to in writing, software -// // distributed under the License is distributed on an "AS IS" BASIS, -// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// // See the License for the specific language governing permissions and -// // limitations under the License. -// -// use super::ParseError; -// use crate::types::BSize; -// use core::str::FromStr; -// -// impl BSize { -// /// Parses a byte size from a byte slice. -// /// -// /// The input must end with a `B` or `b` byte suffix. Supported units are -// /// `B`, `KB`, and `KiB`, case-insensitively. -// /// -// /// The numeric part may be an integer or an ordinary decimal number. -// /// Scientific notation is not supported. -// /// -// /// # Errors -// /// -// /// Returns [`ParseError`] if the input cannot be parsed as a `u16` byte -// /// size. -// pub fn parse(src: impl AsRef<[u8]>) -> Result { -// let bytes = src.as_ref(); -// parse_u16(bytes).map(BSize) -// } -// } -// -// impl FromStr for BSize { -// type Err = ParseError; -// -// fn from_str(src: &str) -> Result { -// Self::parse(src.as_bytes()) -// } -// } -// -// #[cfg(target_pointer_width = "16")] -// impl BSize { -// /// Parses a byte size from a byte slice. -// /// -// /// The input must end with a `B` or `b` byte suffix. On 16-bit targets, -// /// supported units are `B`, `KB`, and `KiB`, case-insensitively. -// /// -// /// The numeric part may be an integer or an ordinary decimal number. -// /// Scientific notation is not supported. -// /// -// /// # Errors -// /// -// /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte -// /// size. -// pub fn parse(src: impl AsRef<[u8]>) -> Result { -// let bytes = src.as_ref(); -// parse_u16(bytes).map(|v| BSize(v as usize)) -// } -// } -// -// #[cfg(target_pointer_width = "16")] -// impl FromStr for BSize { -// type Err = ParseError; -// -// fn from_str(src: &str) -> Result { -// Self::parse(src.as_bytes()) -// } -// } -// -// fn parse_u16(mut src: &[u8]) -> Result { -// let multiply = parse_unit(&mut src)?; -// -// parse_number(src, multiply).map(|value| value as u16) -// } -// -// fn parse_unit(src: &mut &[u8]) -> Result { -// super::strip_unit_suffix(src)?; -// -// if let Some((&infix, before_i)) = src.split_last() { -// if infix.eq_ignore_ascii_case(&b'i') { -// let Some((&prefix, before_prefix)) = before_i.split_last() else { -// return Err(ParseError(())); -// }; -// if prefix.eq_ignore_ascii_case(&b'K') { -// *src = before_prefix; -// return Ok(1_024); -// } -// -// return Err(ParseError(())); -// } -// } -// -// if let Some((&prefix, before_prefix)) = src.split_last() { -// if prefix.eq_ignore_ascii_case(&b'K') { -// *src = before_prefix; -// return Ok(1_000); -// } -// if super::is_ascii_unit(prefix) { -// return Err(ParseError(())); -// } -// } -// -// Ok(1) -// } -// -// fn parse_number(src: &[u8], multiply: u32) -> Result { -// let mut value = 0_u32; -// let mut has_digit = false; -// let mut idx = 0; -// -// while idx < src.len() { -// match src[idx] { -// digit @ b'0'..=b'9' => { -// let Some(next) = value -// .checked_mul(10) -// .and_then(|value| value.checked_add(u32::from(digit - b'0'))) -// else { -// return Err(ParseError(())); -// }; -// value = next; -// has_digit = true; -// } -// b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), -// b'_' | b' ' => {} -// _ => return Err(ParseError(())), -// } -// -// idx += 1; -// } -// -// if !has_digit { -// return Err(ParseError(())); -// } -// -// let Some(value) = value.checked_mul(multiply) else { -// return Err(ParseError(())); -// }; -// if value > u32::from(u16::MAX) { -// Err(ParseError(())) -// } else { -// Ok(value) -// } -// } -// -// fn parse_fraction( -// src: &[u8], -// start: usize, -// integer: u32, -// has_integer_digit: bool, -// multiply: u32, -// ) -> Result { -// if !has_integer_digit { -// return Err(ParseError(())); -// } -// -// let Some(base) = integer.checked_mul(multiply) else { -// return Err(ParseError(())); -// }; -// if base > u32::from(u16::MAX) { -// return Err(ParseError(())); -// } -// -// let mut fraction = 0_u64; -// let mut scale = 1_u64; -// let mut digits = 0_u32; -// let mut idx = start; -// -// while idx < src.len() { -// match src[idx] { -// digit @ b'0'..=b'9' => { -// if digits == 19 { -// return Err(ParseError(())); -// } -// -// fraction = fraction * 10 + u64::from(digit - b'0'); -// scale *= 10; -// digits += 1; -// } -// b'_' | b' ' => {} -// _ => return Err(ParseError(())), -// } -// -// idx += 1; -// } -// -// let product = u128::from(fraction) * u128::from(multiply); -// let quotient = product / u128::from(scale); -// let remainder = product % u128::from(scale); -// let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { -// quotient + 1 -// } else { -// quotient -// }; -// -// let value = u128::from(base) + rounded; -// if value > u128::from(u16::MAX) { -// Err(ParseError(())) -// } else { -// Ok(value as u32) -// } -// } -// -// #[cfg(test)] -// mod tests { -// use super::*; -// -// #[test] -// fn parses_bytes() { -// assert_eq!(BSize::::parse(b"1 B").unwrap(), BSize::(1)); -// } -// -// #[test] -// fn parses_units() { -// assert_eq!(BSize::::parse(b"1KB").unwrap(), BSize::(1_000)); -// assert_eq!(BSize::::parse(b"1KiB").unwrap(), BSize::(1_024)); -// assert_eq!(BSize::::parse(b"1kb").unwrap(), BSize::(1_000)); -// assert_eq!(BSize::::parse(b"1kib").unwrap(), BSize::(1_024)); -// assert_eq!(BSize::::parse(b"1KIB").unwrap(), BSize::(1_024)); -// } -// -// #[test] -// fn parses_fractional_units() { -// assert_eq!( -// BSize::::parse(b"65.535KB").unwrap(), -// BSize::(u16::MAX) -// ); -// assert_eq!(BSize::::parse(b"0.5B").unwrap(), BSize::(1)); -// assert_eq!(BSize::::parse(b"0.4B").unwrap(), BSize::(0)); -// assert_eq!( -// BSize::::parse(b"65535.4B").unwrap(), -// BSize::(u16::MAX) -// ); -// } -// -// #[test] -// fn rejects_unsupported_units() { -// BSize::::parse(b"1MB").unwrap_err(); -// BSize::::parse(b"1MiB").unwrap_err(); -// } -// -// #[test] -// fn rejects_overflow() { -// BSize::::parse(b"65535.5B").unwrap_err(); -// BSize::::parse(b"65.536KB").unwrap_err(); -// } -// -// #[cfg(target_pointer_width = "16")] -// #[test] -// fn parses_usize() { -// assert_eq!( -// BSize::::parse(b"1_234B").unwrap(), -// BSize::(1_234) -// ); -// } -// } diff --git a/bsize/src/parse/parse_u32.rs b/bsize/src/parse/parse_u32.rs deleted file mode 100644 index 47f2a9e..0000000 --- a/bsize/src/parse/parse_u32.rs +++ /dev/null @@ -1,282 +0,0 @@ -// // Copyright 2026 FastLabs Developers -// // -// // Licensed under the Apache License, Version 2.0 (the "License"); -// // you may not use this file except in compliance with the License. -// // You may obtain a copy of the License at -// // -// // http://www.apache.org/licenses/LICENSE-2.0 -// // -// // Unless required by applicable law or agreed to in writing, software -// // distributed under the License is distributed on an "AS IS" BASIS, -// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// // See the License for the specific language governing permissions and -// // limitations under the License. -// -// use super::ParseError; -// use crate::types::BSize; -// use core::str::FromStr; -// -// impl BSize { -// /// Parses a byte size from a byte slice. -// /// -// /// The input must end with a `B` or `b` byte suffix. Supported units are -// /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, and `GiB`, case-insensitively. -// /// -// /// The numeric part may be an integer or an ordinary decimal number. -// /// Scientific notation is not supported. -// /// -// /// # Errors -// /// -// /// Returns [`ParseError`] if the input cannot be parsed as a `u32` byte -// /// size. -// pub fn parse(src: impl AsRef<[u8]>) -> Result { -// parse_u32(src.as_ref()).map(BSize) -// } -// } -// -// impl FromStr for BSize { -// type Err = ParseError; -// -// fn from_str(src: &str) -> Result { -// Self::parse(src.as_bytes()) -// } -// } -// -// #[cfg(target_pointer_width = "32")] -// impl BSize { -// /// Parses a byte size from a byte slice. -// /// -// /// The input must end with a `B` or `b` byte suffix. On 32-bit targets, -// /// supported units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, and `GiB`, -// /// case-insensitively. -// /// -// /// The numeric part may be an integer or an ordinary decimal number. -// /// Scientific notation is not supported. -// /// -// /// # Errors -// /// -// /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte -// /// size. -// pub fn parse(src: impl AsRef<[u8]>) -> Result { -// parse_usize(src.as_ref()).map(BSize) -// } -// } -// -// #[cfg(target_pointer_width = "32")] -// impl FromStr for BSize { -// type Err = ParseError; -// -// fn from_str(src: &str) -> Result { -// Self::parse(src.as_bytes()) -// } -// } -// -// fn parse_u32(mut src: &[u8]) -> Result { -// let multiply = parse_unit(&mut src)?; -// -// parse_number(src, multiply).map(|value| value as u32) -// } -// -// #[cfg(target_pointer_width = "32")] -// fn parse_usize(src: &[u8]) -> Result { -// parse_u32(src).map(|value| value as usize) -// } -// -// fn parse_unit(src: &mut &[u8]) -> Result { -// super::strip_unit_suffix(src)?; -// -// if let Some((&infix, before_i)) = src.split_last() { -// if infix.eq_ignore_ascii_case(&b'i') { -// let Some((&prefix, before_prefix)) = before_i.split_last() else { -// return Err(ParseError(())); -// }; -// let Some(factor) = binary_factor(prefix) else { -// return Err(ParseError(())); -// }; -// -// *src = before_prefix; -// return Ok(factor); -// } -// } -// -// if let Some((&prefix, before_prefix)) = src.split_last() { -// if let Some(factor) = decimal_factor(prefix) { -// *src = before_prefix; -// return Ok(factor); -// } -// if super::is_ascii_unit(prefix) { -// return Err(ParseError(())); -// } -// } -// -// Ok(1) -// } -// -// fn decimal_factor(prefix: u8) -> Option { -// Some(match prefix.to_ascii_uppercase() { -// b'K' => 1_000, -// b'M' => 1_000_000, -// b'G' => 1_000_000_000, -// _ => return None, -// }) -// } -// -// fn binary_factor(prefix: u8) -> Option { -// Some(match prefix.to_ascii_uppercase() { -// b'K' => 1_u64 << 10, -// b'M' => 1_u64 << 20, -// b'G' => 1_u64 << 30, -// _ => return None, -// }) -// } -// -// fn parse_number(src: &[u8], multiply: u64) -> Result { -// let mut value = 0_u64; -// let mut has_digit = false; -// let mut idx = 0; -// -// while idx < src.len() { -// match src[idx] { -// digit @ b'0'..=b'9' => { -// let Some(next) = value -// .checked_mul(10) -// .and_then(|value| value.checked_add(u64::from(digit - b'0'))) -// else { -// return Err(ParseError(())); -// }; -// value = next; -// has_digit = true; -// } -// b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), -// b'_' | b' ' => {} -// _ => return Err(ParseError(())), -// } -// -// idx += 1; -// } -// -// if !has_digit { -// return Err(ParseError(())); -// } -// -// let Some(value) = value.checked_mul(multiply) else { -// return Err(ParseError(())); -// }; -// if value > u64::from(u32::MAX) { -// Err(ParseError(())) -// } else { -// Ok(value) -// } -// } -// -// fn parse_fraction( -// src: &[u8], -// start: usize, -// integer: u64, -// has_integer_digit: bool, -// multiply: u64, -// ) -> Result { -// if !has_integer_digit { -// return Err(ParseError(())); -// } -// -// let Some(base) = integer.checked_mul(multiply) else { -// return Err(ParseError(())); -// }; -// if base > u64::from(u32::MAX) { -// return Err(ParseError(())); -// } -// -// let mut fraction = 0_u64; -// let mut scale = 1_u64; -// let mut digits = 0_u32; -// let mut idx = start; -// -// while idx < src.len() { -// match src[idx] { -// digit @ b'0'..=b'9' => { -// if digits == 19 { -// return Err(ParseError(())); -// } -// -// fraction = fraction * 10 + u64::from(digit - b'0'); -// scale *= 10; -// digits += 1; -// } -// b'_' | b' ' => {} -// _ => return Err(ParseError(())), -// } -// -// idx += 1; -// } -// -// let product = u128::from(fraction) * u128::from(multiply); -// let quotient = product / u128::from(scale); -// let remainder = product % u128::from(scale); -// let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { -// quotient + 1 -// } else { -// quotient -// }; -// -// let value = u128::from(base) + rounded; -// if value > u128::from(u32::MAX) { -// Err(ParseError(())) -// } else { -// Ok(value as u64) -// } -// } -// -// #[cfg(test)] -// mod tests { -// use super::*; -// -// #[test] -// fn parses_units() { -// assert_eq!( -// BSize::::parse(b"2MB").unwrap(), -// BSize::(2_000_000) -// ); -// assert_eq!( -// BSize::::parse(b"2MiB").unwrap(), -// BSize::(2_097_152) -// ); -// assert_eq!( -// BSize::::parse(b"1GB").unwrap(), -// BSize::(1_000_000_000) -// ); -// assert_eq!( -// BSize::::parse(b"1GiB").unwrap(), -// BSize::(1_073_741_824) -// ); -// } -// -// #[test] -// fn parses_fractional_units() { -// assert_eq!(BSize::::parse(b"1.5KB").unwrap(), BSize::(1_500)); -// assert_eq!( -// BSize::::parse(b"4294967295.4B").unwrap(), -// BSize::(u32::MAX) -// ); -// } -// -// #[test] -// fn rejects_unsupported_units() { -// BSize::::parse(b"1TB").unwrap_err(); -// BSize::::parse(b"1TiB").unwrap_err(); -// } -// -// #[test] -// fn rejects_overflow() { -// BSize::::parse(b"4294967295.5B").unwrap_err(); -// } -// -// #[cfg(target_pointer_width = "32")] -// #[test] -// fn parses_usize() { -// assert_eq!( -// BSize::::parse(b"1_234B").unwrap(), -// BSize::(1_234) -// ); -// } -// } diff --git a/bsize/src/parse/parse_u64.rs b/bsize/src/parse/parse_u64.rs deleted file mode 100644 index 277f86c..0000000 --- a/bsize/src/parse/parse_u64.rs +++ /dev/null @@ -1,286 +0,0 @@ -// // Copyright 2026 FastLabs Developers -// // -// // Licensed under the Apache License, Version 2.0 (the "License"); -// // you may not use this file except in compliance with the License. -// // You may obtain a copy of the License at -// // -// // http://www.apache.org/licenses/LICENSE-2.0 -// // -// // Unless required by applicable law or agreed to in writing, software -// // distributed under the License is distributed on an "AS IS" BASIS, -// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// // See the License for the specific language governing permissions and -// // limitations under the License. -// -// use super::ParseError; -// use crate::types::BSize; -// use core::str::FromStr; -// -// impl BSize { -// /// Parses a byte size from a byte slice. -// /// -// /// The input must end with a `B` or `b` byte suffix. Supported units are -// /// `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, `TiB`, `PB`, `PiB`, -// /// `EB`, and `EiB`, case-insensitively. -// /// -// /// The numeric part may be an integer or an ordinary decimal number. -// /// Scientific notation is not supported. -// /// -// /// # Errors -// /// -// /// Returns [`ParseError`] if the input cannot be parsed as a `u64` byte -// /// size. -// pub fn parse(src: impl AsRef<[u8]>) -> Result { -// parse_u64(src.as_ref()).map(BSize) -// } -// } -// -// impl FromStr for BSize { -// type Err = ParseError; -// -// fn from_str(src: &str) -> Result { -// Self::parse(src.as_bytes()) -// } -// } -// -// #[cfg(target_pointer_width = "64")] -// impl BSize { -// /// Parses a byte size from a byte slice. -// /// -// /// The input must end with a `B` or `b` byte suffix. On 64-bit targets, -// /// supported units are `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, -// /// `TiB`, `PB`, `PiB`, `EB`, and `EiB`, case-insensitively. -// /// -// /// The numeric part may be an integer or an ordinary decimal number. -// /// Scientific notation is not supported. -// /// -// /// # Errors -// /// -// /// Returns [`ParseError`] if the input cannot be parsed as a `usize` byte -// /// size. -// pub fn parse(src: impl AsRef<[u8]>) -> Result { -// parse_usize(src.as_ref()).map(BSize) -// } -// } -// -// #[cfg(target_pointer_width = "64")] -// impl FromStr for BSize { -// type Err = ParseError; -// -// fn from_str(src: &str) -> Result { -// Self::parse(src.as_bytes()) -// } -// } -// -// fn parse_u64(mut src: &[u8]) -> Result { -// let multiply = parse_unit(&mut src)?; -// -// parse_number(src, multiply) -// } -// -// #[cfg(target_pointer_width = "64")] -// fn parse_usize(src: &[u8]) -> Result { -// parse_u64(src).map(|value| value as usize) -// } -// -// fn parse_unit(src: &mut &[u8]) -> Result { -// super::strip_unit_suffix(src)?; -// -// if let Some((&infix, before_i)) = src.split_last() { -// if infix.eq_ignore_ascii_case(&b'i') { -// let Some((&prefix, before_prefix)) = before_i.split_last() else { -// return Err(ParseError(())); -// }; -// let Some(factor) = binary_factor(prefix) else { -// return Err(ParseError(())); -// }; -// -// *src = before_prefix; -// return Ok(factor); -// } -// } -// -// if let Some((&prefix, before_prefix)) = src.split_last() { -// if let Some(factor) = decimal_factor(prefix) { -// *src = before_prefix; -// return Ok(factor); -// } -// } -// -// Ok(1) -// } -// -// fn decimal_factor(prefix: u8) -> Option { -// Some(match prefix.to_ascii_uppercase() { -// b'K' => 1_000, -// b'M' => 1_000_000, -// b'G' => 1_000_000_000, -// b'T' => 1_000_000_000_000, -// b'P' => 1_000_000_000_000_000, -// b'E' => 1_000_000_000_000_000_000, -// _ => return None, -// }) -// } -// -// fn binary_factor(prefix: u8) -> Option { -// Some(match prefix.to_ascii_uppercase() { -// b'K' => 1_u64 << 10, -// b'M' => 1_u64 << 20, -// b'G' => 1_u64 << 30, -// b'T' => 1_u64 << 40, -// b'P' => 1_u64 << 50, -// b'E' => 1_u64 << 60, -// _ => return None, -// }) -// } -// -// fn parse_number(src: &[u8], multiply: u64) -> Result { -// let mut value = 0_u64; -// let mut has_digit = false; -// let mut idx = 0; -// -// while idx < src.len() { -// match src[idx] { -// digit @ b'0'..=b'9' => { -// let Some(next) = value -// .checked_mul(10) -// .and_then(|value| value.checked_add(u64::from(digit - b'0'))) -// else { -// return Err(ParseError(())); -// }; -// value = next; -// has_digit = true; -// } -// b'.' => return parse_fraction(src, idx + 1, value, has_digit, multiply), -// b'_' | b' ' => {} -// _ => return Err(ParseError(())), -// } -// -// idx += 1; -// } -// -// if !has_digit { -// return Err(ParseError(())); -// } -// -// value.checked_mul(multiply).ok_or(ParseError(())) -// } -// -// fn parse_fraction( -// src: &[u8], -// start: usize, -// integer: u64, -// has_integer_digit: bool, -// multiply: u64, -// ) -> Result { -// if !has_integer_digit { -// return Err(ParseError(())); -// } -// -// let Some(base) = integer.checked_mul(multiply) else { -// return Err(ParseError(())); -// }; -// -// let mut fraction = 0_u64; -// let mut scale = 1_u64; -// let mut digits = 0_u32; -// let mut idx = start; -// -// while idx < src.len() { -// match src[idx] { -// digit @ b'0'..=b'9' => { -// if digits == 19 { -// return Err(ParseError(())); -// } -// -// fraction = fraction * 10 + u64::from(digit - b'0'); -// scale *= 10; -// digits += 1; -// } -// b'_' | b' ' => {} -// _ => return Err(ParseError(())), -// } -// -// idx += 1; -// } -// -// let product = u128::from(fraction) * u128::from(multiply); -// let quotient = product / u128::from(scale); -// let remainder = product % u128::from(scale); -// let rounded = if digits != 0 && remainder >= u128::from(scale / 2) { -// quotient + 1 -// } else { -// quotient -// }; -// -// let value = u128::from(base) + rounded; -// if value > u128::from(u64::MAX) { -// Err(ParseError(())) -// } else { -// Ok(value as u64) -// } -// } -// -// #[cfg(test)] -// mod tests { -// use super::*; -// -// #[test] -// fn parses_bytes() { -// assert_eq!(BSize::::parse(b"1_234B").unwrap(), BSize::(1_234)); -// } -// -// #[test] -// fn parses_units() { -// assert_eq!( -// BSize::::parse(b"1TB").unwrap(), -// BSize::(1_000_000_000_000) -// ); -// assert_eq!( -// BSize::::parse(b"1TiB").unwrap(), -// BSize::(1_099_511_627_776) -// ); -// assert_eq!( -// BSize::::parse(b"1PB").unwrap(), -// BSize::(1_000_000_000_000_000) -// ); -// assert_eq!( -// BSize::::parse(b"1PiB").unwrap(), -// BSize::(1_125_899_906_842_624) -// ); -// } -// -// #[test] -// fn parses_fractional_units() { -// assert_eq!(BSize::::parse(b"1.5KiB").unwrap(), BSize::(1_536)); -// assert_eq!( -// BSize::::parse(b"18.446744073709551615EB").unwrap(), -// BSize::(u64::MAX) -// ); -// } -// -// #[test] -// fn rejects_invalid_input() { -// BSize::::parse(b"").unwrap_err(); -// BSize::::parse(b"1").unwrap_err(); -// BSize::::parse(b"1K").unwrap_err(); -// BSize::::parse(b"1XB").unwrap_err(); -// BSize::::parse(b"1iB").unwrap_err(); -// BSize::::parse(b"1e+B").unwrap_err(); -// BSize::::parse(b"1.5e3KiB").unwrap_err(); -// } -// -// #[test] -// fn rejects_overflow() { -// BSize::::parse(b"0.00000000000000000001B").unwrap_err(); -// } -// -// #[cfg(target_pointer_width = "64")] -// #[test] -// fn parses_usize() { -// assert_eq!( -// BSize::::parse(b"1_234B").unwrap(), -// BSize::(1_234) -// ); -// } -// } diff --git a/bsize/src/parse/parse_u8.rs b/bsize/src/parse/parse_u8.rs deleted file mode 100644 index f3e98e8..0000000 --- a/bsize/src/parse/parse_u8.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2026 FastLabs Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::ParseError; -use crate::types::BSize; -use core::num::IntErrorKind; -use core::str::FromStr; - -impl FromStr for BSize { - type Err = ParseError; - - fn from_str(src: &str) -> Result { - let mut src = src; - let multiply = super::strip_unit_suffix(&mut src)?; - - let mut dot = false; - let mut offset = 0; - for ch in src.chars() { - match ch { - '.' => { - dot = true; - offset += 1; - } - '_' | '0'..='9' => offset += 1, - _ => return Err(ParseError::Malformed), - } - } - - if dot { - let n = match f64::from_str(&src[..offset]) { - Ok(n) => n, - Err(_) => return Err(ParseError::Malformed), - }; - - let n = n * (multiply as f64); - let n = core::f64::math::round(); - } else { - let n = match u64::from_str(&src[..offset]) { - Ok(n) => n, - Err(err) => { - return Err(match err.kind() { - IntErrorKind::PosOverflow => ParseError::Overflow, - IntErrorKind::Empty => ParseError::Empty, - _ => ParseError::Malformed, - }); - } - }; - } - - Ok(BSize(0)) - } -} - -fn parse_u8(mut src: &[u8]) -> Result { - let multiply = super::strip_unit_suffix(&mut src)?; - while let [init @ .., b' '] = src { - src = init; - } - if src.is_empty() { - return Err(ParseError::Empty); - } - - Ok(value) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parses_bytes() { - assert_eq!(BSize::::parse(b"255B").unwrap(), BSize::(255)); - assert_eq!(BSize::::parse(b"255 B").unwrap(), BSize::(255)); - } - - #[test] - fn parses_fractional_bytes() { - assert_eq!(BSize::::parse(b"25.55B").unwrap(), BSize::(26)); - assert_eq!(BSize::::parse(b"255.4B").unwrap(), BSize::(u8::MAX)); - } - - #[test] - fn rejects_overflow() { - assert_eq!(BSize::::parse(b"1KB"), Err(ParseError::Overflow)); - assert_eq!(BSize::::parse(b"1eB"), Err(ParseError::Overflow)); - assert_eq!(BSize::::parse(b"1EB"), Err(ParseError::Overflow)); - assert_eq!(BSize::::parse(b"256B"), Err(ParseError::Overflow)); - assert_eq!(BSize::::parse(b"0.001KB"), Err(ParseError::Overflow)); - assert_eq!(BSize::::parse(b"255.5B"), Err(ParseError::Overflow)); - } -} From ee4641b3d580efc8c9b6e85d847945179750a1c0 Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 9 Jun 2026 23:53:59 +0800 Subject: [PATCH 10/10] impl display Signed-off-by: tison --- bsize/src/display.rs | 135 +++++++++++++++++++++++++++++++++++++++++++ bsize/src/lib.rs | 2 + 2 files changed, 137 insertions(+) create mode 100644 bsize/src/display.rs diff --git a/bsize/src/display.rs b/bsize/src/display.rs new file mode 100644 index 0000000..c05f6fb --- /dev/null +++ b/bsize/src/display.rs @@ -0,0 +1,135 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::fmt; + +use crate::BSize; + +/// Display wrapper for [`BSize`]. +/// +/// Supports various styles, see methods. By default, the [`binary`] style is used. +/// +/// [`binary`]: Display::binary +/// +/// # Examples +/// +/// ``` +/// # use bsize::BSize; +/// assert_eq!( +/// "1.0 MiB", +/// BSize::::mib(1).display().binary().to_string(), +/// ); +/// +/// assert_eq!( +/// "42.0 kB", +/// BSize::::kb(42).display().decimal().to_string(), +/// ); +/// ``` +#[derive(Debug, Clone)] +pub struct Display { + size: u64, + mode: DisplayMode, +} + +#[derive(Debug, Clone)] +enum DisplayMode { + Binary, + Decimal, +} + +impl Display { + /// Format using binary units (e.g., `11.8MiB`) + pub fn binary(mut self) -> Self { + self.mode = DisplayMode::Binary; + self + } + + /// Format using decimal units (e.g., `11.8MB`) + pub fn decimal(mut self) -> Self { + self.mode = DisplayMode::Decimal; + self + } + + fn new(size: u64) -> Self { + Self { + size, + mode: DisplayMode::Binary, + } + } +} + +impl fmt::Display for Display { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let bytes = self.size; + + let unit = match self.mode { + DisplayMode::Binary => 1024, + DisplayMode::Decimal => 1000, + }; + + let unit_prefixes = match self.mode { + DisplayMode::Binary => b"KMGTPE", + DisplayMode::Decimal => b"kMGTPE", + }; + let unit_suffix = match self.mode { + DisplayMode::Binary => "iB", + DisplayMode::Decimal => "B", + }; + let unit_separator = " "; + let precision = f.precision().unwrap_or(1); + + if bytes < unit { + write!(f, "{bytes}{unit_separator}B")?; + } else { + let size = bytes as f64; + + let mut ideal_prefix = 0usize; + let mut ideal_size = size; + loop { + ideal_prefix += 1; + ideal_size /= unit as f64; + + if ideal_size < unit as f64 { + break; + } + } + let exp = ideal_prefix; + + let unit_prefix = unit_prefixes[exp - 1] as char; + + write!( + f, + "{:.precision$}{unit_separator}{unit_prefix}{unit_suffix}", + size / unit.pow(exp as u32) as f64, + )?; + } + + Ok(()) + } +} + +macro_rules! impl_display { + ($($ty:ty),* $(,)?) => { + $( + impl BSize<$ty> { + /// Returns a display wrapper. + pub fn display(self) -> Display { + Display::new(self.0 as u64) + } + } + )* + }; +} + +impl_display!(u8, u16, u32, u64, usize); diff --git a/bsize/src/lib.rs b/bsize/src/lib.rs index a682056..28b1c42 100644 --- a/bsize/src/lib.rs +++ b/bsize/src/lib.rs @@ -18,11 +18,13 @@ #![deny(missing_docs)] #![no_std] +mod display; mod ops; mod parse; mod types; mod unsigned; +pub use self::display::Display; pub use self::parse::ParseError; pub use self::types::BSize; pub use self::unsigned::Unsigned;