core: improve f64's FromWStr impl

This avoids converting the string to UTF8 if it can't possibly
be a float
This commit is contained in:
Moulins 2021-10-06 19:30:43 +02:00 committed by kmeisthax
parent ee326e31b7
commit fd8bdeb1a8
1 changed files with 61 additions and 15 deletions

View File

@ -12,20 +12,66 @@ pub trait FromWStr: Sized {
/// Error returned by [`Integer::from_str_radix`]. /// Error returned by [`Integer::from_str_radix`].
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
#[error("failed to parse integer")] #[error("failed to parse integer")]
pub struct ParseIntError(()); pub struct ParseNumError(());
/// Trait implemented for all integer types that can be parsed from a [`WStr<'_>`]. /// Trait implemented for all integer types that can be parsed from a [`WStr<'_>`].
pub trait Integer: FromWStr<Err = ParseIntError> { pub trait Integer: FromWStr<Err = ParseNumError> {
fn from_wstr_radix(s: WStr<'_>, radix: u32) -> Result<Self, Self::Err>; fn from_wstr_radix(s: WStr<'_>, radix: u32) -> Result<Self, Self::Err>;
} }
fn parse_special_floats(s: WStr<'_>) -> Option<f64> {
let nan = WStr::from_units(b"NaN");
let inf = WStr::from_units(b"inf");
let slice = match s.len() {
3 if s == nan => return Some(f64::NAN),
3 if s == inf => return Some(f64::INFINITY),
4 => s.slice(1..),
_ => return None,
};
let is_nan = if slice == nan {
true
} else if slice == inf {
false
} else {
return None;
};
let is_neg = match u8::try_from(s.get(0)) {
Ok(b'+') => false,
Ok(b'-') => true,
_ => return None,
};
Some(match (is_nan, is_neg) {
(false, false) => f64::INFINITY,
(false, true) => f64::NEG_INFINITY,
(true, _) => f64::NAN,
})
}
impl FromWStr for f64 { impl FromWStr for f64 {
type Err = std::num::ParseFloatError; type Err = ParseNumError;
fn from_wstr(s: WStr<'_>) -> Result<Self, Self::Err> { fn from_wstr(s: WStr<'_>) -> Result<Self, Self::Err> {
// TODO(moulins): avoid the utf8 conversion when we know the string can't if let Some(f) = parse_special_floats(s) {
// possibly represent a floating point number. return Ok(f);
s.to_utf8_lossy().parse() }
// Early-reject strings with non-float chars to avoid the utf8 conversion.
let is_valid = s.iter().all(|c| {
if let Ok(c) = u8::try_from(c) {
matches!(c, b'0'..=b'9' | b'.' | b'+' | b'-' | b'e' | b'E')
} else {
false
}
});
if is_valid {
s.to_utf8_lossy().parse().map_err(|_| ParseNumError(()))
} else {
Err(ParseNumError(()))
}
} }
} }
@ -41,7 +87,7 @@ mod int_parse {
fn checked_mul(self, n: u32) -> Option<Self>; fn checked_mul(self, n: u32) -> Option<Self>;
} }
pub fn from_str_radix<T: IntParse>(s: WStr<'_>, radix: u32) -> Option<T> { pub fn from_wstr_radix<T: IntParse>(s: WStr<'_>, radix: u32) -> Option<T> {
assert!( assert!(
radix >= 2 && radix <= 36, radix >= 2 && radix <= 36,
"from_str_radix: radix must be between 2 and 36, got {}", "from_str_radix: radix must be between 2 and 36, got {}",
@ -138,31 +184,31 @@ macro_rules! impl_from_str_int {
($($ty:ty)*) => { $( ($($ty:ty)*) => { $(
impl Integer for $ty { impl Integer for $ty {
#[inline] #[inline]
fn from_wstr_radix(s: WStr<'_>, radix: u32) -> Result<Self, ParseIntError> { fn from_wstr_radix(s: WStr<'_>, radix: u32) -> Result<Self, ParseNumError> {
int_parse::from_str_radix(s, radix).ok_or(ParseIntError(())) int_parse::from_wstr_radix(s, radix).ok_or(ParseNumError(()))
} }
} }
impl Integer for Wrapping<$ty> { impl Integer for Wrapping<$ty> {
#[inline] #[inline]
fn from_wstr_radix(s: WStr<'_>, radix: u32) -> Result<Self, ParseIntError> { fn from_wstr_radix(s: WStr<'_>, radix: u32) -> Result<Self, ParseNumError> {
int_parse::from_str_radix(s, radix).ok_or(ParseIntError(())) int_parse::from_wstr_radix(s, radix).ok_or(ParseNumError(()))
} }
} }
impl FromWStr for $ty { impl FromWStr for $ty {
type Err = ParseIntError; type Err = ParseNumError;
#[inline] #[inline]
fn from_wstr(s: WStr<'_>) -> Result<Self, Self::Err> { fn from_wstr(s: WStr<'_>) -> Result<Self, Self::Err> {
int_parse::from_str_radix(s, 10).ok_or(ParseIntError(())) int_parse::from_wstr_radix(s, 10).ok_or(ParseNumError(()))
} }
} }
impl FromWStr for Wrapping<$ty> { impl FromWStr for Wrapping<$ty> {
type Err = ParseIntError; type Err = ParseNumError;
#[inline] #[inline]
fn from_wstr(s: WStr<'_>) -> Result<Self, Self::Err> { fn from_wstr(s: WStr<'_>) -> Result<Self, Self::Err> {
int_parse::from_str_radix(s, 10).ok_or(ParseIntError(())) int_parse::from_wstr_radix(s, 10).ok_or(ParseNumError(()))
} }
} }
)* } )* }