diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs index 08111bca8..fca402378 100644 --- a/src/uucore/src/lib/features/format/argument.rs +++ b/src/uucore/src/lib/features/format/argument.rs @@ -5,7 +5,7 @@ use crate::{ error::set_exit_code, - features::format::num_parser::{ParseError, ParsedNumber}, + features::format::num_parser::{ExtendedParser, ExtendedParserError}, quoting_style::{Quotes, QuotingStyle, escape_name}, show_error, show_warning, }; @@ -56,7 +56,7 @@ impl<'a, T: Iterator> ArgumentIter<'a> for T { }; match next { FormatArgument::UnsignedInt(n) => *n, - FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_u64(s), s), + FormatArgument::Unparsed(s) => extract_value(u64::extended_parse(s), s), _ => 0, } } @@ -67,7 +67,7 @@ impl<'a, T: Iterator> ArgumentIter<'a> for T { }; match next { FormatArgument::SignedInt(n) => *n, - FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_i64(s), s), + FormatArgument::Unparsed(s) => extract_value(i64::extended_parse(s), s), _ => 0, } } @@ -78,7 +78,7 @@ impl<'a, T: Iterator> ArgumentIter<'a> for T { }; match next { FormatArgument::Float(n) => *n, - FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_f64(s), s), + FormatArgument::Unparsed(s) => extract_value(f64::extended_parse(s), s), _ => 0.0, } } @@ -91,7 +91,7 @@ impl<'a, T: Iterator> ArgumentIter<'a> for T { } } -fn extract_value(p: Result>, input: &str) -> T { +fn extract_value(p: Result>, input: &str) -> T { match p { Ok(v) => v, Err(e) => { @@ -103,15 +103,15 @@ fn extract_value(p: Result>, input: &str) -> T }, ); match e { - ParseError::Overflow => { + ExtendedParserError::Overflow => { show_error!("{}: Numerical result out of range", input.quote()); Default::default() } - ParseError::NotNumeric => { + ExtendedParserError::NotNumeric => { show_error!("{}: expected a numeric value", input.quote()); Default::default() } - ParseError::PartialMatch(v, rest) => { + ExtendedParserError::PartialMatch(v, rest) => { let bytes = input.as_encoded_bytes(); if !bytes.is_empty() && bytes[0] == b'\'' { show_warning!( diff --git a/src/uucore/src/lib/features/format/num_parser.rs b/src/uucore/src/lib/features/format/num_parser.rs index 67cb457ba..396bdfa72 100644 --- a/src/uucore/src/lib/features/format/num_parser.rs +++ b/src/uucore/src/lib/features/format/num_parser.rs @@ -8,8 +8,8 @@ // spell-checker:ignore powf copysign prec inity bigdecimal extendedbigdecimal biguint use bigdecimal::{ - num_bigint::{BigInt, BigUint, Sign}, BigDecimal, + num_bigint::{BigInt, BigUint, Sign}, }; use num_traits::ToPrimitive; use num_traits::Zero; @@ -18,7 +18,7 @@ use crate::format::extendedbigdecimal::ExtendedBigDecimal; /// Base for number parsing #[derive(Clone, Copy, PartialEq)] -pub enum Base { +enum Base { /// Binary base Binary = 2, @@ -53,7 +53,7 @@ impl Base { /// Type returned if a number could not be parsed in its entirety #[derive(Debug, PartialEq)] -pub enum ParseError<'a, T> { +pub enum ExtendedParserError<'a, T> { /// The input as a whole makes no sense NotNumeric, /// The beginning of the input made sense and has been parsed, @@ -65,11 +65,14 @@ pub enum ParseError<'a, T> { Overflow, } -impl<'a, T> ParseError<'a, T> { - fn map(self, f: impl FnOnce(T, &'a str) -> ParseError<'a, U>) -> ParseError<'a, U> { +impl<'a, T> ExtendedParserError<'a, T> { + fn map( + self, + f: impl FnOnce(T, &'a str) -> ExtendedParserError<'a, U>, + ) -> ExtendedParserError<'a, U> { match self { - Self::NotNumeric => ParseError::NotNumeric, - Self::Overflow => ParseError::Overflow, + Self::NotNumeric => ExtendedParserError::NotNumeric, + Self::Overflow => ExtendedParserError::Overflow, Self::PartialMatch(v, s) => f(v, s), } } @@ -77,375 +80,377 @@ impl<'a, T> ParseError<'a, T> { /// A number parser for binary, octal, decimal, hexadecimal and single characters. /// -/// TODO: we just keep an ExtendedBigDecimal internally, so we don't really need this -/// struct actually. -/// -/// If the fractional part cannot be represented on a `u64`, parsing continues -/// silently by ignoring non-significant digits. -pub struct ParsedNumber { - number: ExtendedBigDecimal, +/// It is implemented for `u64`/`i64`, where no fractional part is parsed, +/// and `f64` float, where octal is not allowed. +pub trait ExtendedParser { + // We pick a hopefully different name for our parser, to avoid clash with standard traits. + fn extended_parse(input: &str) -> Result> + where + Self: Sized; } -impl ParsedNumber { - fn into_i64(self) -> Option { - match self.number { - ExtendedBigDecimal::BigDecimal(bd) => { - let (digits, scale) = bd.into_bigint_and_scale(); - if scale == 0 { - i64::try_from(digits).ok() - } else { - None - } - } - ExtendedBigDecimal::MinusZero => Some(0), - _ => None, - } - } - +impl ExtendedParser for i64 { /// Parse a number as i64. No fractional part is allowed. - pub fn parse_i64(input: &str) -> Result> { - match Self::parse(input, true) { - Ok(v) => v.into_i64().ok_or(ParseError::Overflow), + fn extended_parse(input: &str) -> Result> { + fn into_i64(ebd: ExtendedBigDecimal) -> Option { + match ebd { + ExtendedBigDecimal::BigDecimal(bd) => { + let (digits, scale) = bd.into_bigint_and_scale(); + if scale == 0 { + i64::try_from(digits).ok() + } else { + None + } + } + ExtendedBigDecimal::MinusZero => Some(0), + _ => None, + } + } + + match parse(input, true) { + Ok(v) => into_i64(v).ok_or(ExtendedParserError::Overflow), Err(e) => Err(e.map(|v, rest| { - v.into_i64() - .map(|v| ParseError::PartialMatch(v, rest)) - .unwrap_or(ParseError::Overflow) + into_i64(v) + .map(|v| ExtendedParserError::PartialMatch(v, rest)) + .unwrap_or(ExtendedParserError::Overflow) })), } } +} - fn into_u64(self) -> Option { - match self.number { - ExtendedBigDecimal::BigDecimal(bd) => { - let (digits, scale) = bd.into_bigint_and_scale(); - if scale == 0 { - u64::try_from(digits).ok() - } else { - None - } - } - _ => None, - } - } - +impl ExtendedParser for u64 { /// Parse a number as u64. No fractional part is allowed. - pub fn parse_u64(input: &str) -> Result> { - match Self::parse(input, true) { - Ok(v) => v.into_u64().ok_or(ParseError::Overflow), + fn extended_parse(input: &str) -> Result> { + fn into_u64(ebd: ExtendedBigDecimal) -> Option { + match ebd { + ExtendedBigDecimal::BigDecimal(bd) => { + let (digits, scale) = bd.into_bigint_and_scale(); + if scale == 0 { + u64::try_from(digits).ok() + } else { + None + } + } + _ => None, + } + } + + match parse(input, true) { + Ok(v) => into_u64(v).ok_or(ExtendedParserError::Overflow), Err(e) => Err(e.map(|v, rest| { - v.into_u64() - .map(|v| ParseError::PartialMatch(v, rest)) - .unwrap_or(ParseError::Overflow) + into_u64(v) + .map(|v| ExtendedParserError::PartialMatch(v, rest)) + .unwrap_or(ExtendedParserError::Overflow) })), } } +} - fn into_f64(self) -> f64 { - match self.number { - ExtendedBigDecimal::BigDecimal(bd) => bd.to_f64().unwrap(), - ExtendedBigDecimal::MinusZero => -0.0, - ExtendedBigDecimal::Nan => f64::NAN, - ExtendedBigDecimal::MinusNan => -f64::NAN, - ExtendedBigDecimal::Infinity => f64::INFINITY, - ExtendedBigDecimal::MinusInfinity => -f64::INFINITY, - } - } - +impl ExtendedParser for f64 { /// Parse a number as f64 - pub fn parse_f64(input: &str) -> Result> { - match Self::parse(input, false) { - Ok(v) => Ok(v.into_f64()), - Err(e) => Err(e.map(|v, rest| ParseError::PartialMatch(v.into_f64(), rest))), - } - } - - fn parse_special_value(input: &str, negative: bool) -> Result> { - let prefix = input - .chars() - .take(3) - .map(|c| c.to_ascii_lowercase()) - .collect::(); - let special = Self { - number: match prefix.as_str() { - "inf" => { - if negative { - ExtendedBigDecimal::MinusInfinity - } else { - ExtendedBigDecimal::Infinity - } - } - "nan" => { - if negative { - ExtendedBigDecimal::MinusNan - } else { - ExtendedBigDecimal::Nan - } - } - _ => return Err(ParseError::NotNumeric), - }, - }; - if input.len() == 3 { - Ok(special) - } else { - Err(ParseError::PartialMatch(special, &input[3..])) - } - } - - #[allow(clippy::cognitive_complexity)] - fn parse(input: &str, integral_only: bool) -> Result> { - // Parse the "'" prefix separately - if let Some(rest) = input.strip_prefix('\'') { - let mut chars = rest.char_indices().fuse(); - let v = chars.next().map(|(_, c)| Self { - number: ExtendedBigDecimal::BigDecimal(u32::from(c).into()), - }); - return match (v, chars.next()) { - (Some(v), None) => Ok(v), - (Some(v), Some((i, _))) => Err(ParseError::PartialMatch(v, &rest[i..])), - (None, _) => Err(ParseError::NotNumeric), - }; - } - - let trimmed_input = input.trim_ascii_start(); - - // Initial minus sign - let (negative, unsigned) = if let Some(trimmed_input) = trimmed_input.strip_prefix('-') { - (true, trimmed_input) - } else { - (false, trimmed_input) - }; - - // Parse an optional base prefix ("0b" / "0B" / "0" / "0x" / "0X"). "0" is octal unless a - // fractional part is allowed in which case it is an insignificant leading 0. A "0" prefix - // will not be consumed in case the parsable string contains only "0": the leading extra "0" - // will have no influence on the result. - let (base, rest) = if let Some(rest) = unsigned.strip_prefix('0') { - if let Some(rest) = rest.strip_prefix(['b', 'B']) { - (Base::Binary, rest) - } else if let Some(rest) = rest.strip_prefix(['x', 'X']) { - (Base::Hexadecimal, rest) - } else if integral_only { - (Base::Octal, unsigned) - } else { - (Base::Decimal, unsigned) + fn extended_parse(input: &str) -> Result> { + // TODO: This is generic, so this should probably be implemented as an ExtendedBigDecimal trait (ToPrimitive). + fn into_f64(ebd: ExtendedBigDecimal) -> f64 { + match ebd { + ExtendedBigDecimal::BigDecimal(bd) => bd.to_f64().unwrap(), + ExtendedBigDecimal::MinusZero => -0.0, + ExtendedBigDecimal::Nan => f64::NAN, + ExtendedBigDecimal::MinusNan => -f64::NAN, + ExtendedBigDecimal::Infinity => f64::INFINITY, + ExtendedBigDecimal::MinusInfinity => -f64::INFINITY, } + } + + match parse(input, false) { + Ok(v) => Ok(into_f64(v)), + Err(e) => Err(e.map(|v, rest| ExtendedParserError::PartialMatch(into_f64(v), rest))), + } + } +} + +fn parse_special_value( + input: &str, + negative: bool, +) -> Result> { + let prefix = input + .chars() + .take(3) + .map(|c| c.to_ascii_lowercase()) + .collect::(); + let special = match prefix.as_str() { + "inf" => { + if negative { + ExtendedBigDecimal::MinusInfinity + } else { + ExtendedBigDecimal::Infinity + } + } + "nan" => { + if negative { + ExtendedBigDecimal::MinusNan + } else { + ExtendedBigDecimal::Nan + } + } + _ => return Err(ExtendedParserError::NotNumeric), + }; + if input.len() == 3 { + Ok(special) + } else { + Err(ExtendedParserError::PartialMatch(special, &input[3..])) + } +} + +#[allow(clippy::cognitive_complexity)] +fn parse( + input: &str, + integral_only: bool, +) -> Result> { + // Parse the "'" prefix separately + if let Some(rest) = input.strip_prefix('\'') { + let mut chars = rest.char_indices().fuse(); + let v = chars + .next() + .map(|(_, c)| ExtendedBigDecimal::BigDecimal(u32::from(c).into())); + return match (v, chars.next()) { + (Some(v), None) => Ok(v), + (Some(v), Some((i, _))) => Err(ExtendedParserError::PartialMatch(v, &rest[i..])), + (None, _) => Err(ExtendedParserError::NotNumeric), + }; + } + + let trimmed_input = input.trim_ascii_start(); + + // Initial minus sign + let (negative, unsigned) = if let Some(trimmed_input) = trimmed_input.strip_prefix('-') { + (true, trimmed_input) + } else { + (false, trimmed_input) + }; + + // Parse an optional base prefix ("0b" / "0B" / "0" / "0x" / "0X"). "0" is octal unless a + // fractional part is allowed in which case it is an insignificant leading 0. A "0" prefix + // will not be consumed in case the parsable string contains only "0": the leading extra "0" + // will have no influence on the result. + let (base, rest) = if let Some(rest) = unsigned.strip_prefix('0') { + if let Some(rest) = rest.strip_prefix(['b', 'B']) { + (Base::Binary, rest) + } else if let Some(rest) = rest.strip_prefix(['x', 'X']) { + (Base::Hexadecimal, rest) + } else if integral_only { + (Base::Octal, unsigned) } else { (Base::Decimal, unsigned) - }; - if rest.is_empty() { - return Err(ParseError::NotNumeric); } + } else { + (Base::Decimal, unsigned) + }; + if rest.is_empty() { + return Err(ExtendedParserError::NotNumeric); + } - // Parse the integral part of the number - let mut chars = rest.chars().enumerate().fuse().peekable(); - let mut digits = BigUint::zero(); - let mut scale = 0i64; + // Parse the integral part of the number + let mut chars = rest.chars().enumerate().fuse().peekable(); + let mut digits = BigUint::zero(); + let mut scale = 0i64; + while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) { + chars.next(); + digits = digits * base as u8 + d; + } + + // Parse the fractional part of the number if there can be one and the input contains + // a '.' decimal separator. + if matches!(chars.peek(), Some(&(_, '.'))) + && matches!(base, Base::Decimal | Base::Hexadecimal) + && !integral_only + { + chars.next(); while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) { chars.next(); - digits = digits * base as u8 + d; + (digits, scale) = (digits * base as u8 + d, scale + 1); } + } - // Parse the fractional part of the number if there can be one and the input contains - // a '.' decimal separator. - if matches!(chars.peek(), Some(&(_, '.'))) - && matches!(base, Base::Decimal | Base::Hexadecimal) - && !integral_only - { - chars.next(); - while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) { - chars.next(); - (digits, scale) = (digits * base as u8 + d, scale + 1); - } - } - - // If nothing has been parsed, check if this is a special value, or declare the parsing unsuccessful - if let Some((0, _)) = chars.peek() { - if integral_only { - return Err(ParseError::NotNumeric); - } else { - return Self::parse_special_value(unsigned, negative); - } - } - - // TODO: Might be nice to implement a ExtendedBigDecimal copysign or negation function to move away some of this logic... - let ebd = if digits == BigUint::zero() && negative { - ExtendedBigDecimal::MinusZero + // If nothing has been parsed, check if this is a special value, or declare the parsing unsuccessful + if let Some((0, _)) = chars.peek() { + if integral_only { + return Err(ExtendedParserError::NotNumeric); } else { - let sign = if negative { Sign::Minus } else { Sign::Plus }; - let signed_digits = BigInt::from_biguint(sign, digits); - let bd = if scale == 0 { - BigDecimal::from_bigint(signed_digits, 0) - } else if base == Base::Decimal { - BigDecimal::from_bigint(signed_digits, scale) - } else { - // Base is not 10, init at scale 0 then divide by base**scale. - BigDecimal::from_bigint(signed_digits, 0) / (base as u64).pow(scale as u32) - }; - ExtendedBigDecimal::BigDecimal(bd) + return parse_special_value(unsigned, negative); + } + } + + // TODO: Might be nice to implement a ExtendedBigDecimal copysign or negation function to move away some of this logic... + let ebd = if digits == BigUint::zero() && negative { + ExtendedBigDecimal::MinusZero + } else { + let sign = if negative { Sign::Minus } else { Sign::Plus }; + let signed_digits = BigInt::from_biguint(sign, digits); + let bd = if scale == 0 { + BigDecimal::from_bigint(signed_digits, 0) + } else if base == Base::Decimal { + BigDecimal::from_bigint(signed_digits, scale) + } else { + // Base is not 10, init at scale 0 then divide by base**scale. + BigDecimal::from_bigint(signed_digits, 0) / (base as u64).pow(scale as u32) }; + ExtendedBigDecimal::BigDecimal(bd) + }; - // Return what has been parsed so far. It there are extra characters, mark the - // parsing as a partial match. - let parsed = Self { number: ebd }; - if let Some((first_unparsed, _)) = chars.next() { - Err(ParseError::PartialMatch(parsed, &rest[first_unparsed..])) - } else { - Ok(parsed) - } + // Return what has been parsed so far. It there are extra characters, mark the + // parsing as a partial match. + if let Some((first_unparsed, _)) = chars.next() { + Err(ExtendedParserError::PartialMatch( + ebd, + &rest[first_unparsed..], + )) + } else { + Ok(ebd) } } #[cfg(test)] mod tests { - use super::{ParseError, ParsedNumber}; + use super::{ExtendedParser, ExtendedParserError}; #[test] fn test_decimal_u64() { - assert_eq!(Ok(123), ParsedNumber::parse_u64("123")); - assert_eq!( - Ok(u64::MAX), - ParsedNumber::parse_u64(&format!("{}", u64::MAX)) - ); + assert_eq!(Ok(123), u64::extended_parse("123")); + assert_eq!(Ok(u64::MAX), u64::extended_parse(&format!("{}", u64::MAX))); assert!(matches!( - ParsedNumber::parse_u64("-123"), - Err(ParseError::Overflow) + u64::extended_parse("-123"), + Err(ExtendedParserError::Overflow) )); assert!(matches!( - ParsedNumber::parse_u64(""), - Err(ParseError::NotNumeric) + u64::extended_parse(""), + Err(ExtendedParserError::NotNumeric) )); assert!(matches!( - ParsedNumber::parse_u64("123.15"), - Err(ParseError::PartialMatch(123, ".15")) + u64::extended_parse("123.15"), + Err(ExtendedParserError::PartialMatch(123, ".15")) )); } #[test] fn test_decimal_i64() { - assert_eq!(Ok(123), ParsedNumber::parse_i64("123")); - assert_eq!(Ok(-123), ParsedNumber::parse_i64("-123")); + assert_eq!(Ok(123), i64::extended_parse("123")); + assert_eq!(Ok(-123), i64::extended_parse("-123")); assert!(matches!( - ParsedNumber::parse_i64("--123"), - Err(ParseError::NotNumeric) + i64::extended_parse("--123"), + Err(ExtendedParserError::NotNumeric) )); - assert_eq!( - Ok(i64::MAX), - ParsedNumber::parse_i64(&format!("{}", i64::MAX)) - ); - assert_eq!( - Ok(i64::MIN), - ParsedNumber::parse_i64(&format!("{}", i64::MIN)) - ); + assert_eq!(Ok(i64::MAX), i64::extended_parse(&format!("{}", i64::MAX))); + assert_eq!(Ok(i64::MIN), i64::extended_parse(&format!("{}", i64::MIN))); assert!(matches!( - ParsedNumber::parse_i64(&format!("{}", u64::MAX)), - Err(ParseError::Overflow) + i64::extended_parse(&format!("{}", u64::MAX)), + Err(ExtendedParserError::Overflow) )); assert!(matches!( - ParsedNumber::parse_i64(&format!("{}", i64::MAX as u64 + 1)), - Err(ParseError::Overflow) + i64::extended_parse(&format!("{}", i64::MAX as u64 + 1)), + Err(ExtendedParserError::Overflow) )); } #[test] fn test_decimal_f64() { - assert_eq!(Ok(123.0), ParsedNumber::parse_f64("123")); - assert_eq!(Ok(-123.0), ParsedNumber::parse_f64("-123")); - assert_eq!(Ok(123.0), ParsedNumber::parse_f64("123.")); - assert_eq!(Ok(-123.0), ParsedNumber::parse_f64("-123.")); - assert_eq!(Ok(123.0), ParsedNumber::parse_f64("123.0")); - assert_eq!(Ok(-123.0), ParsedNumber::parse_f64("-123.0")); - assert_eq!(Ok(123.15), ParsedNumber::parse_f64("123.15")); - assert_eq!(Ok(-123.15), ParsedNumber::parse_f64("-123.15")); - assert_eq!(Ok(0.15), ParsedNumber::parse_f64(".15")); - assert_eq!(Ok(-0.15), ParsedNumber::parse_f64("-.15")); + assert_eq!(Ok(123.0), f64::extended_parse("123")); + assert_eq!(Ok(-123.0), f64::extended_parse("-123")); + assert_eq!(Ok(123.0), f64::extended_parse("123.")); + assert_eq!(Ok(-123.0), f64::extended_parse("-123.")); + assert_eq!(Ok(123.0), f64::extended_parse("123.0")); + assert_eq!(Ok(-123.0), f64::extended_parse("-123.0")); + assert_eq!(Ok(123.15), f64::extended_parse("123.15")); + assert_eq!(Ok(-123.15), f64::extended_parse("-123.15")); + assert_eq!(Ok(0.15), f64::extended_parse(".15")); + assert_eq!(Ok(-0.15), f64::extended_parse("-.15")); assert_eq!( Ok(0.15), - ParsedNumber::parse_f64(".150000000000000000000000000231313") + f64::extended_parse(".150000000000000000000000000231313") ); - assert!(matches!(ParsedNumber::parse_f64("1.2.3"), - Err(ParseError::PartialMatch(f, ".3")) if f == 1.2)); - assert_eq!(Ok(f64::INFINITY), ParsedNumber::parse_f64("inf")); - assert_eq!(Ok(f64::NEG_INFINITY), ParsedNumber::parse_f64("-inf")); - assert!(ParsedNumber::parse_f64("NaN").unwrap().is_nan()); - assert!(ParsedNumber::parse_f64("NaN").unwrap().is_sign_positive()); - assert!(ParsedNumber::parse_f64("-NaN").unwrap().is_nan()); - assert!(ParsedNumber::parse_f64("-NaN").unwrap().is_sign_negative()); - assert!(matches!(ParsedNumber::parse_f64("-infinity"), - Err(ParseError::PartialMatch(f, "inity")) if f == f64::NEG_INFINITY)); - assert!(ParsedNumber::parse_f64(&format!("{}", u64::MAX)).is_ok()); - assert!(ParsedNumber::parse_f64(&format!("{}", i64::MIN)).is_ok()); + assert!(matches!(f64::extended_parse("1.2.3"), + Err(ExtendedParserError::PartialMatch(f, ".3")) if f == 1.2)); + assert_eq!(Ok(f64::INFINITY), f64::extended_parse("inf")); + assert_eq!(Ok(f64::NEG_INFINITY), f64::extended_parse("-inf")); + assert!(f64::extended_parse("NaN").unwrap().is_nan()); + assert!(f64::extended_parse("NaN").unwrap().is_sign_positive()); + assert!(f64::extended_parse("-NaN").unwrap().is_nan()); + assert!(f64::extended_parse("-NaN").unwrap().is_sign_negative()); + assert!(matches!(f64::extended_parse("-infinity"), + Err(ExtendedParserError::PartialMatch(f, "inity")) if f == f64::NEG_INFINITY)); + assert!(f64::extended_parse(&format!("{}", u64::MAX)).is_ok()); + assert!(f64::extended_parse(&format!("{}", i64::MIN)).is_ok()); } #[test] fn test_hexadecimal() { - assert_eq!(Ok(0x123), ParsedNumber::parse_u64("0x123")); - assert_eq!(Ok(0x123), ParsedNumber::parse_u64("0X123")); - assert_eq!(Ok(0xfe), ParsedNumber::parse_u64("0xfE")); - assert_eq!(Ok(-0x123), ParsedNumber::parse_i64("-0x123")); + assert_eq!(Ok(0x123), u64::extended_parse("0x123")); + assert_eq!(Ok(0x123), u64::extended_parse("0X123")); + assert_eq!(Ok(0xfe), u64::extended_parse("0xfE")); + assert_eq!(Ok(-0x123), i64::extended_parse("-0x123")); - assert_eq!(Ok(0.5), ParsedNumber::parse_f64("0x.8")); - assert_eq!(Ok(0.0625), ParsedNumber::parse_f64("0x.1")); - assert_eq!(Ok(15.007_812_5), ParsedNumber::parse_f64("0xf.02")); + assert_eq!(Ok(0.5), f64::extended_parse("0x.8")); + assert_eq!(Ok(0.0625), f64::extended_parse("0x.1")); + assert_eq!(Ok(15.007_812_5), f64::extended_parse("0xf.02")); } #[test] fn test_octal() { - assert_eq!(Ok(0), ParsedNumber::parse_u64("0")); - assert_eq!(Ok(0o123), ParsedNumber::parse_u64("0123")); - assert_eq!(Ok(0o123), ParsedNumber::parse_u64("00123")); - assert_eq!(Ok(0), ParsedNumber::parse_u64("00")); + assert_eq!(Ok(0), u64::extended_parse("0")); + assert_eq!(Ok(0o123), u64::extended_parse("0123")); + assert_eq!(Ok(0o123), u64::extended_parse("00123")); + assert_eq!(Ok(0), u64::extended_parse("00")); assert!(matches!( - ParsedNumber::parse_u64("008"), - Err(ParseError::PartialMatch(0, "8")) + u64::extended_parse("008"), + Err(ExtendedParserError::PartialMatch(0, "8")) )); assert!(matches!( - ParsedNumber::parse_u64("08"), - Err(ParseError::PartialMatch(0, "8")) + u64::extended_parse("08"), + Err(ExtendedParserError::PartialMatch(0, "8")) )); assert!(matches!( - ParsedNumber::parse_u64("0."), - Err(ParseError::PartialMatch(0, ".")) + u64::extended_parse("0."), + Err(ExtendedParserError::PartialMatch(0, ".")) )); } #[test] fn test_binary() { - assert_eq!(Ok(0b1011), ParsedNumber::parse_u64("0b1011")); - assert_eq!(Ok(0b1011), ParsedNumber::parse_u64("0B1011")); + assert_eq!(Ok(0b1011), u64::extended_parse("0b1011")); + assert_eq!(Ok(0b1011), u64::extended_parse("0B1011")); } #[test] fn test_parsing_with_leading_whitespace() { - assert_eq!(Ok(1), ParsedNumber::parse_u64(" 0x1")); - assert_eq!(Ok(-2), ParsedNumber::parse_i64(" -0x2")); - assert_eq!(Ok(-3), ParsedNumber::parse_i64(" \t-0x3")); - assert_eq!(Ok(-4), ParsedNumber::parse_i64(" \n-0x4")); - assert_eq!(Ok(-5), ParsedNumber::parse_i64(" \n\t\u{000d}-0x5")); + assert_eq!(Ok(1), u64::extended_parse(" 0x1")); + assert_eq!(Ok(-2), i64::extended_parse(" -0x2")); + assert_eq!(Ok(-3), i64::extended_parse(" \t-0x3")); + assert_eq!(Ok(-4), i64::extended_parse(" \n-0x4")); + assert_eq!(Ok(-5), i64::extended_parse(" \n\t\u{000d}-0x5")); // Ensure that trailing whitespace is still a partial match assert_eq!( - Err(ParseError::PartialMatch(6, " ")), - ParsedNumber::parse_u64("0x6 ") + Err(ExtendedParserError::PartialMatch(6, " ")), + u64::extended_parse("0x6 ") ); assert_eq!( - Err(ParseError::PartialMatch(7, "\t")), - ParsedNumber::parse_u64("0x7\t") + Err(ExtendedParserError::PartialMatch(7, "\t")), + u64::extended_parse("0x7\t") ); assert_eq!( - Err(ParseError::PartialMatch(8, "\n")), - ParsedNumber::parse_u64("0x8\n") + Err(ExtendedParserError::PartialMatch(8, "\n")), + u64::extended_parse("0x8\n") ); // Ensure that unicode non-ascii whitespace is a partial match assert_eq!( - Err(ParseError::NotNumeric), - ParsedNumber::parse_i64("\u{2029}-0x9") + Err(ExtendedParserError::NotNumeric), + i64::extended_parse("\u{2029}-0x9") ); // Ensure that whitespace after the number has "started" is not allowed assert_eq!( - Err(ParseError::NotNumeric), - ParsedNumber::parse_i64("- 0x9") + Err(ExtendedParserError::NotNumeric), + i64::extended_parse("- 0x9") ); } }