mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Merge pull request #7694 from drinkcat/printf-fix-empty
uucore: parser: num_parser: Return error if no digit has been parsed uucore: parser: num_parser: Ignore empty exponents uucore: parser: num_parser: Parse "0x"/"0b" as PartialMatch
This commit is contained in:
commit
349e56897c
2 changed files with 246 additions and 13 deletions
|
@ -450,18 +450,15 @@ pub(crate) fn parse<'a>(
|
|||
} 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 digits: Option<BigUint> = None;
|
||||
let mut scale = 0u64;
|
||||
let mut exponent = BigInt::zero();
|
||||
let mut exponent: Option<BigInt> = None;
|
||||
while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) {
|
||||
chars.next();
|
||||
digits = digits * base as u8 + d;
|
||||
digits = Some(digits.unwrap_or_default() * base as u8 + d);
|
||||
}
|
||||
|
||||
// Parse fractional/exponent part of the number for supported bases.
|
||||
|
@ -472,7 +469,7 @@ pub(crate) fn parse<'a>(
|
|||
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);
|
||||
(digits, scale) = (Some(digits.unwrap_or_default() * base as u8 + d), scale + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -487,6 +484,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,16 +500,31 @@ 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 let Some(exp) = &exponent {
|
||||
if exp_negative {
|
||||
exponent = -exponent;
|
||||
exponent = Some(-exp);
|
||||
}
|
||||
} else {
|
||||
// No exponent actually parsed, reset iterator to return partial match.
|
||||
chars = save_chars;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing has been parsed, check if this is a special value, or declare the parsing unsuccessful
|
||||
if let Some((0, _)) = chars.peek() {
|
||||
// If no digit has been parsed, check if this is a special value, or declare the parsing unsuccessful
|
||||
if digits.is_none() {
|
||||
// If we trimmed an initial `0x`/`0b`, return a partial match.
|
||||
if rest != unsigned {
|
||||
let ebd = if negative {
|
||||
ExtendedBigDecimal::MinusZero
|
||||
} else {
|
||||
ExtendedBigDecimal::zero()
|
||||
};
|
||||
return Err(ExtendedParserError::PartialMatch(ebd, &unsigned[1..]));
|
||||
}
|
||||
|
||||
return if target == ParseTarget::Integral {
|
||||
Err(ExtendedParserError::NotNumeric)
|
||||
} else {
|
||||
|
@ -518,6 +532,8 @@ pub(crate) fn parse<'a>(
|
|||
};
|
||||
}
|
||||
|
||||
let mut digits = digits.unwrap();
|
||||
|
||||
if let Some((_, ch)) = chars.peek() {
|
||||
if let Some(times) = allowed_suffixes
|
||||
.iter()
|
||||
|
@ -529,7 +545,8 @@ pub(crate) fn parse<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
let ebd_result = construct_extended_big_decimal(digits, negative, base, scale, exponent);
|
||||
let ebd_result =
|
||||
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.
|
||||
|
@ -625,6 +642,15 @@ mod tests {
|
|||
i64::extended_parse(&format!("{}", i64::MIN as i128 - 1)),
|
||||
Err(ExtendedParserError::Overflow(i64::MIN))
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
i64::extended_parse(""),
|
||||
Err(ExtendedParserError::NotNumeric)
|
||||
));
|
||||
assert!(matches!(
|
||||
i64::extended_parse("."),
|
||||
Err(ExtendedParserError::NotNumeric)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -659,6 +685,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"),
|
||||
|
@ -811,6 +847,48 @@ mod tests {
|
|||
ExtendedBigDecimal::extended_parse(&format!("-0e{}", i64::MIN + 2)),
|
||||
Ok(ExtendedBigDecimal::MinusZero)
|
||||
);
|
||||
|
||||
/* Invalid numbers */
|
||||
assert_eq!(
|
||||
Err(ExtendedParserError::NotNumeric),
|
||||
ExtendedBigDecimal::extended_parse("")
|
||||
);
|
||||
assert_eq!(
|
||||
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]
|
||||
|
@ -831,6 +909,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()
|
||||
|
@ -887,6 +974,42 @@ mod tests {
|
|||
ExtendedBigDecimal::MinusZero
|
||||
))
|
||||
));
|
||||
|
||||
// Not actually hex numbers, but the prefixes look like it.
|
||||
assert!(matches!(f64::extended_parse("0x"),
|
||||
Err(ExtendedParserError::PartialMatch(f, "x")) if f == 0.0));
|
||||
assert!(matches!(f64::extended_parse("0x."),
|
||||
Err(ExtendedParserError::PartialMatch(f, "x.")) if f == 0.0));
|
||||
assert!(matches!(f64::extended_parse("0xp"),
|
||||
Err(ExtendedParserError::PartialMatch(f, "xp")) if f == 0.0));
|
||||
assert!(matches!(f64::extended_parse("0xp-2"),
|
||||
Err(ExtendedParserError::PartialMatch(f, "xp-2")) if f == 0.0));
|
||||
assert!(matches!(f64::extended_parse("0x.p-2"),
|
||||
Err(ExtendedParserError::PartialMatch(f, "x.p-2")) if f == 0.0));
|
||||
assert!(matches!(f64::extended_parse("0X"),
|
||||
Err(ExtendedParserError::PartialMatch(f, "X")) if f == 0.0));
|
||||
assert!(matches!(f64::extended_parse("-0x"),
|
||||
Err(ExtendedParserError::PartialMatch(f, "x")) if f == -0.0));
|
||||
assert!(matches!(f64::extended_parse("+0x"),
|
||||
Err(ExtendedParserError::PartialMatch(f, "x")) if f == 0.0));
|
||||
assert!(matches!(f64::extended_parse("-0x."),
|
||||
Err(ExtendedParserError::PartialMatch(f, "x.")) if f == -0.0));
|
||||
assert!(matches!(
|
||||
u64::extended_parse("0x"),
|
||||
Err(ExtendedParserError::PartialMatch(0, "x"))
|
||||
));
|
||||
assert!(matches!(
|
||||
u64::extended_parse("-0x"),
|
||||
Err(ExtendedParserError::PartialMatch(0, "x"))
|
||||
));
|
||||
assert!(matches!(
|
||||
i64::extended_parse("0x"),
|
||||
Err(ExtendedParserError::PartialMatch(0, "x"))
|
||||
));
|
||||
assert!(matches!(
|
||||
i64::extended_parse("-0x"),
|
||||
Err(ExtendedParserError::PartialMatch(0, "x"))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -920,6 +1043,27 @@ mod tests {
|
|||
assert_eq!(Ok(0b1011), u64::extended_parse("+0b1011"));
|
||||
assert_eq!(Ok(-0b1011), i64::extended_parse("-0b1011"));
|
||||
|
||||
assert!(matches!(
|
||||
u64::extended_parse("0b"),
|
||||
Err(ExtendedParserError::PartialMatch(0, "b"))
|
||||
));
|
||||
assert!(matches!(
|
||||
u64::extended_parse("0b."),
|
||||
Err(ExtendedParserError::PartialMatch(0, "b."))
|
||||
));
|
||||
assert!(matches!(
|
||||
u64::extended_parse("-0b"),
|
||||
Err(ExtendedParserError::PartialMatch(0, "b"))
|
||||
));
|
||||
assert!(matches!(
|
||||
i64::extended_parse("0b"),
|
||||
Err(ExtendedParserError::PartialMatch(0, "b"))
|
||||
));
|
||||
assert!(matches!(
|
||||
i64::extended_parse("-0b"),
|
||||
Err(ExtendedParserError::PartialMatch(0, "b"))
|
||||
));
|
||||
|
||||
// Binary not allowed for floats
|
||||
assert!(matches!(
|
||||
f64::extended_parse("0b100"),
|
||||
|
@ -935,6 +1079,15 @@ mod tests {
|
|||
ebd == ExtendedBigDecimal::zero(),
|
||||
_ => false,
|
||||
});
|
||||
|
||||
assert!(match ExtendedBigDecimal::extended_parse("0b") {
|
||||
Err(ExtendedParserError::PartialMatch(ebd, "b")) => ebd == ExtendedBigDecimal::zero(),
|
||||
_ => false,
|
||||
});
|
||||
assert!(match ExtendedBigDecimal::extended_parse("0b.") {
|
||||
Err(ExtendedParserError::PartialMatch(ebd, "b.")) => ebd == ExtendedBigDecimal::zero(),
|
||||
_ => false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -826,6 +826,12 @@ fn partial_integer() {
|
|||
.fails_with_code(1)
|
||||
.stdout_is("42 is a lot")
|
||||
.stderr_is("printf: '42x23': value not completely converted\n");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["%d is not %s", "0xwa", "a lot"])
|
||||
.fails_with_code(1)
|
||||
.stdout_is("0 is not a lot")
|
||||
.stderr_is("printf: '0xwa': value not completely converted\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1280,6 +1286,80 @@ fn float_switch_switch_decimal_scientific() {
|
|||
.stdout_only("1e-05");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_arg_zero() {
|
||||
new_ucmd!()
|
||||
.args(&["%f", "0."])
|
||||
.succeeds()
|
||||
.stdout_only("0.000000");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["%f", ".0"])
|
||||
.succeeds()
|
||||
.stdout_only("0.000000");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["%f", ".0e100000"])
|
||||
.succeeds()
|
||||
.stdout_only("0.000000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_arg_invalid() {
|
||||
// Just a dot fails.
|
||||
new_ucmd!()
|
||||
.args(&["%f", "."])
|
||||
.fails()
|
||||
.stdout_is("0.000000")
|
||||
.stderr_contains("expected a numeric value");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["%f", "-."])
|
||||
.fails()
|
||||
.stdout_is("0.000000")
|
||||
.stderr_contains("expected a numeric value");
|
||||
|
||||
// Just an exponent indicator fails.
|
||||
new_ucmd!()
|
||||
.args(&["%f", "e"])
|
||||
.fails()
|
||||
.stdout_is("0.000000")
|
||||
.stderr_contains("expected a numeric value");
|
||||
|
||||
// No digit but only exponent fails
|
||||
new_ucmd!()
|
||||
.args(&["%f", ".e12"])
|
||||
.fails()
|
||||
.stdout_is("0.000000")
|
||||
.stderr_contains("expected a numeric value");
|
||||
|
||||
// No exponent partially fails
|
||||
new_ucmd!()
|
||||
.args(&["%f", "123e"])
|
||||
.fails()
|
||||
.stdout_is("123.000000")
|
||||
.stderr_contains("value not completely converted");
|
||||
|
||||
// Nothing past `0x` parses as zero
|
||||
new_ucmd!()
|
||||
.args(&["%f", "0x"])
|
||||
.fails()
|
||||
.stdout_is("0.000000")
|
||||
.stderr_contains("value not completely converted");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["%f", "0x."])
|
||||
.fails()
|
||||
.stdout_is("0.000000")
|
||||
.stderr_contains("value not completely converted");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["%f", "0xp12"])
|
||||
.fails()
|
||||
.stdout_is("0.000000")
|
||||
.stderr_contains("value not completely converted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_arg_with_whitespace() {
|
||||
new_ucmd!()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue