diff --git a/src/uucore/src/lib/features/parser/num_parser.rs b/src/uucore/src/lib/features/parser/num_parser.rs index 3b520a977..77c6aa82d 100644 --- a/src/uucore/src/lib/features/parser/num_parser.rs +++ b/src/uucore/src/lib/features/parser/num_parser.rs @@ -458,7 +458,7 @@ pub(crate) fn parse<'a>( let mut chars = rest.chars().enumerate().fuse().peekable(); let mut digits: Option = None; let mut scale = 0u64; - let mut exponent = BigInt::zero(); + let mut exponent: Option = None; while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) { chars.next(); digits = Some(digits.unwrap_or_default() * base as u8 + d); @@ -487,6 +487,8 @@ pub(crate) fn parse<'a>( .peek() .is_some_and(|&(_, c)| c.to_ascii_lowercase() == exp_char) { + // Save the iterator position in case we do not parse any exponent. + let save_chars = chars.clone(); chars.next(); let exp_negative = match chars.peek() { Some((_, '-')) => { @@ -501,10 +503,15 @@ pub(crate) fn parse<'a>( }; while let Some(d) = chars.peek().and_then(|&(_, c)| Base::Decimal.digit(c)) { chars.next(); - exponent = exponent * 10 + d as i64; + exponent = Some(exponent.unwrap_or_default() * 10 + d as i64); } - if exp_negative { - exponent = -exponent; + if let Some(exp) = &exponent { + if exp_negative { + exponent = Some(-exp); + } + } else { + // No exponent actually parsed, reset iterator to return partial match. + chars = save_chars; } } } @@ -532,7 +539,7 @@ pub(crate) fn parse<'a>( } let ebd_result = - construct_extended_big_decimal(digits, negative, base, scale, exponent); + construct_extended_big_decimal(digits, negative, base, scale, exponent.unwrap_or_default()); // Return what has been parsed so far. If there are extra characters, mark the // parsing as a partial match. @@ -671,6 +678,16 @@ mod tests { Ok(0.15), f64::extended_parse(".150000000000000000000000000231313") ); + assert!(matches!(f64::extended_parse("123.15e"), + Err(ExtendedParserError::PartialMatch(f, "e")) if f == 123.15)); + assert!(matches!(f64::extended_parse("123.15E"), + Err(ExtendedParserError::PartialMatch(f, "E")) if f == 123.15)); + assert!(matches!(f64::extended_parse("123.15e-"), + Err(ExtendedParserError::PartialMatch(f, "e-")) if f == 123.15)); + assert!(matches!(f64::extended_parse("123.15e+"), + Err(ExtendedParserError::PartialMatch(f, "e+")) if f == 123.15)); + assert!(matches!(f64::extended_parse("123.15e."), + Err(ExtendedParserError::PartialMatch(f, "e.")) if f == 123.15)); assert!(matches!(f64::extended_parse("1.2.3"), Err(ExtendedParserError::PartialMatch(f, ".3")) if f == 1.2)); assert!(matches!(f64::extended_parse("123.15p5"), @@ -833,6 +850,38 @@ mod tests { Err(ExtendedParserError::NotNumeric), ExtendedBigDecimal::extended_parse(".") ); + assert_eq!( + Err(ExtendedParserError::NotNumeric), + ExtendedBigDecimal::extended_parse("e") + ); + assert_eq!( + Err(ExtendedParserError::NotNumeric), + ExtendedBigDecimal::extended_parse(".e") + ); + assert_eq!( + Err(ExtendedParserError::NotNumeric), + ExtendedBigDecimal::extended_parse("-e") + ); + assert_eq!( + Err(ExtendedParserError::NotNumeric), + ExtendedBigDecimal::extended_parse("+.e") + ); + assert_eq!( + Err(ExtendedParserError::NotNumeric), + ExtendedBigDecimal::extended_parse("e10") + ); + assert_eq!( + Err(ExtendedParserError::NotNumeric), + ExtendedBigDecimal::extended_parse("e-10") + ); + assert_eq!( + Err(ExtendedParserError::NotNumeric), + ExtendedBigDecimal::extended_parse("-e10") + ); + assert_eq!( + Err(ExtendedParserError::NotNumeric), + ExtendedBigDecimal::extended_parse("+e10") + ); } #[test] @@ -853,6 +902,15 @@ mod tests { // but we can check that the number still gets parsed properly: 0x0.8e5 is 0x8e5 / 16**3 assert_eq!(Ok(0.555908203125), f64::extended_parse("0x0.8e5")); + assert!(matches!(f64::extended_parse("0x0.1p"), + Err(ExtendedParserError::PartialMatch(f, "p")) if f == 0.0625)); + assert!(matches!(f64::extended_parse("0x0.1p-"), + Err(ExtendedParserError::PartialMatch(f, "p-")) if f == 0.0625)); + assert!(matches!(f64::extended_parse("0x.1p+"), + Err(ExtendedParserError::PartialMatch(f, "p+")) if f == 0.0625)); + assert!(matches!(f64::extended_parse("0x.1p."), + Err(ExtendedParserError::PartialMatch(f, "p.")) if f == 0.0625)); + assert_eq!( Ok(ExtendedBigDecimal::BigDecimal( BigDecimal::from_str("0.0625").unwrap() @@ -910,7 +968,7 @@ mod tests { )) )); - // TODO: GNU coreutils treats these 2 as partial match. + // TODO: GNU coreutils treats these as partial matches. assert_eq!( Err(ExtendedParserError::NotNumeric), ExtendedBigDecimal::extended_parse("0x") @@ -919,6 +977,14 @@ mod tests { Err(ExtendedParserError::NotNumeric), ExtendedBigDecimal::extended_parse("0x.") ); + assert_eq!( + Err(ExtendedParserError::NotNumeric), + ExtendedBigDecimal::extended_parse("0xp") + ); + assert_eq!( + Err(ExtendedParserError::NotNumeric), + ExtendedBigDecimal::extended_parse("0xp-2") + ); } #[test]