Skip to content

Commit 395da2e

Browse files
committed
uucore: parser: num_parser: Ignore empty exponents
Numbers like 123.15e or 123.15e- should return PartialMatch. Numbers like `e`, `.e` are not valid. Fixes #7685.
1 parent 153f464 commit 395da2e

File tree

1 file changed

+72
-6
lines changed

1 file changed

+72
-6
lines changed

src/uucore/src/lib/features/parser/num_parser.rs

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ pub(crate) fn parse<'a>(
458458
let mut chars = rest.chars().enumerate().fuse().peekable();
459459
let mut digits: Option<BigUint> = None;
460460
let mut scale = 0u64;
461-
let mut exponent = BigInt::zero();
461+
let mut exponent: Option<BigInt> = None;
462462
while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) {
463463
chars.next();
464464
digits = Some(digits.unwrap_or_default() * base as u8 + d);
@@ -487,6 +487,8 @@ pub(crate) fn parse<'a>(
487487
.peek()
488488
.is_some_and(|&(_, c)| c.to_ascii_lowercase() == exp_char)
489489
{
490+
// Save the iterator position in case we do not parse any exponent.
491+
let save_chars = chars.clone();
490492
chars.next();
491493
let exp_negative = match chars.peek() {
492494
Some((_, '-')) => {
@@ -501,10 +503,15 @@ pub(crate) fn parse<'a>(
501503
};
502504
while let Some(d) = chars.peek().and_then(|&(_, c)| Base::Decimal.digit(c)) {
503505
chars.next();
504-
exponent = exponent * 10 + d as i64;
506+
exponent = Some(exponent.unwrap_or_default() * 10 + d as i64);
505507
}
506-
if exp_negative {
507-
exponent = -exponent;
508+
if let Some(exp) = &exponent {
509+
if exp_negative {
510+
exponent = Some(-exp);
511+
}
512+
} else {
513+
// No exponent actually parsed, reset iterator to return partial match.
514+
chars = save_chars;
508515
}
509516
}
510517
}
@@ -532,7 +539,7 @@ pub(crate) fn parse<'a>(
532539
}
533540

534541
let ebd_result =
535-
construct_extended_big_decimal(digits, negative, base, scale, exponent);
542+
construct_extended_big_decimal(digits, negative, base, scale, exponent.unwrap_or_default());
536543

537544
// Return what has been parsed so far. If there are extra characters, mark the
538545
// parsing as a partial match.
@@ -671,6 +678,16 @@ mod tests {
671678
Ok(0.15),
672679
f64::extended_parse(".150000000000000000000000000231313")
673680
);
681+
assert!(matches!(f64::extended_parse("123.15e"),
682+
Err(ExtendedParserError::PartialMatch(f, "e")) if f == 123.15));
683+
assert!(matches!(f64::extended_parse("123.15E"),
684+
Err(ExtendedParserError::PartialMatch(f, "E")) if f == 123.15));
685+
assert!(matches!(f64::extended_parse("123.15e-"),
686+
Err(ExtendedParserError::PartialMatch(f, "e-")) if f == 123.15));
687+
assert!(matches!(f64::extended_parse("123.15e+"),
688+
Err(ExtendedParserError::PartialMatch(f, "e+")) if f == 123.15));
689+
assert!(matches!(f64::extended_parse("123.15e."),
690+
Err(ExtendedParserError::PartialMatch(f, "e.")) if f == 123.15));
674691
assert!(matches!(f64::extended_parse("1.2.3"),
675692
Err(ExtendedParserError::PartialMatch(f, ".3")) if f == 1.2));
676693
assert!(matches!(f64::extended_parse("123.15p5"),
@@ -833,6 +850,38 @@ mod tests {
833850
Err(ExtendedParserError::NotNumeric),
834851
ExtendedBigDecimal::extended_parse(".")
835852
);
853+
assert_eq!(
854+
Err(ExtendedParserError::NotNumeric),
855+
ExtendedBigDecimal::extended_parse("e")
856+
);
857+
assert_eq!(
858+
Err(ExtendedParserError::NotNumeric),
859+
ExtendedBigDecimal::extended_parse(".e")
860+
);
861+
assert_eq!(
862+
Err(ExtendedParserError::NotNumeric),
863+
ExtendedBigDecimal::extended_parse("-e")
864+
);
865+
assert_eq!(
866+
Err(ExtendedParserError::NotNumeric),
867+
ExtendedBigDecimal::extended_parse("+.e")
868+
);
869+
assert_eq!(
870+
Err(ExtendedParserError::NotNumeric),
871+
ExtendedBigDecimal::extended_parse("e10")
872+
);
873+
assert_eq!(
874+
Err(ExtendedParserError::NotNumeric),
875+
ExtendedBigDecimal::extended_parse("e-10")
876+
);
877+
assert_eq!(
878+
Err(ExtendedParserError::NotNumeric),
879+
ExtendedBigDecimal::extended_parse("-e10")
880+
);
881+
assert_eq!(
882+
Err(ExtendedParserError::NotNumeric),
883+
ExtendedBigDecimal::extended_parse("+e10")
884+
);
836885
}
837886

838887
#[test]
@@ -853,6 +902,15 @@ mod tests {
853902
// but we can check that the number still gets parsed properly: 0x0.8e5 is 0x8e5 / 16**3
854903
assert_eq!(Ok(0.555908203125), f64::extended_parse("0x0.8e5"));
855904

905+
assert!(matches!(f64::extended_parse("0x0.1p"),
906+
Err(ExtendedParserError::PartialMatch(f, "p")) if f == 0.0625));
907+
assert!(matches!(f64::extended_parse("0x0.1p-"),
908+
Err(ExtendedParserError::PartialMatch(f, "p-")) if f == 0.0625));
909+
assert!(matches!(f64::extended_parse("0x.1p+"),
910+
Err(ExtendedParserError::PartialMatch(f, "p+")) if f == 0.0625));
911+
assert!(matches!(f64::extended_parse("0x.1p."),
912+
Err(ExtendedParserError::PartialMatch(f, "p.")) if f == 0.0625));
913+
856914
assert_eq!(
857915
Ok(ExtendedBigDecimal::BigDecimal(
858916
BigDecimal::from_str("0.0625").unwrap()
@@ -910,7 +968,7 @@ mod tests {
910968
))
911969
));
912970

913-
// TODO: GNU coreutils treats these 2 as partial match.
971+
// TODO: GNU coreutils treats these as partial matches.
914972
assert_eq!(
915973
Err(ExtendedParserError::NotNumeric),
916974
ExtendedBigDecimal::extended_parse("0x")
@@ -919,6 +977,14 @@ mod tests {
919977
Err(ExtendedParserError::NotNumeric),
920978
ExtendedBigDecimal::extended_parse("0x.")
921979
);
980+
assert_eq!(
981+
Err(ExtendedParserError::NotNumeric),
982+
ExtendedBigDecimal::extended_parse("0xp")
983+
);
984+
assert_eq!(
985+
Err(ExtendedParserError::NotNumeric),
986+
ExtendedBigDecimal::extended_parse("0xp-2")
987+
);
922988
}
923989

924990
#[test]

0 commit comments

Comments
 (0)