mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
format: use the new number parser and fix the error messages
The error messages are more compliant with GNU coreutils. Also, floating hexadecimal numbers are now supported in `printf`.
This commit is contained in:
parent
00cd6fa347
commit
a85a792c88
2 changed files with 88 additions and 65 deletions
|
@ -3,9 +3,14 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::set_exit_code,
|
||||||
|
features::format::num_parser::{ParseError, ParsedNumber},
|
||||||
|
quoting_style::{escape_name, Quotes, QuotingStyle},
|
||||||
|
show_error, show_warning,
|
||||||
|
};
|
||||||
use os_display::Quotable;
|
use os_display::Quotable;
|
||||||
|
use std::ffi::OsStr;
|
||||||
use crate::{error::set_exit_code, show_warning};
|
|
||||||
|
|
||||||
/// An argument for formatting
|
/// An argument for formatting
|
||||||
///
|
///
|
||||||
|
@ -40,9 +45,7 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
||||||
};
|
};
|
||||||
match next {
|
match next {
|
||||||
FormatArgument::Char(c) => *c,
|
FormatArgument::Char(c) => *c,
|
||||||
FormatArgument::Unparsed(s) => {
|
FormatArgument::Unparsed(s) => s.chars().next().unwrap_or('\0'),
|
||||||
s.chars().next().unwrap_or('\0')
|
|
||||||
}
|
|
||||||
_ => '\0',
|
_ => '\0',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,25 +56,7 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
||||||
};
|
};
|
||||||
match next {
|
match next {
|
||||||
FormatArgument::UnsignedInt(n) => *n,
|
FormatArgument::UnsignedInt(n) => *n,
|
||||||
FormatArgument::Unparsed(s) => {
|
FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_u64(s), s),
|
||||||
let opt = if let Some(s) = s.strip_prefix("0x") {
|
|
||||||
u64::from_str_radix(s, 16).ok()
|
|
||||||
} else if let Some(s) = s.strip_prefix('0') {
|
|
||||||
u64::from_str_radix(s, 8).ok()
|
|
||||||
} else if let Some(s) = s.strip_prefix('\'') {
|
|
||||||
s.chars().next().map(|c| c as u64)
|
|
||||||
} else {
|
|
||||||
s.parse().ok()
|
|
||||||
};
|
|
||||||
match opt {
|
|
||||||
Some(n) => n,
|
|
||||||
None => {
|
|
||||||
show_warning!("{}: expected a numeric value", s.quote());
|
|
||||||
set_exit_code(1);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,29 +67,7 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
||||||
};
|
};
|
||||||
match next {
|
match next {
|
||||||
FormatArgument::SignedInt(n) => *n,
|
FormatArgument::SignedInt(n) => *n,
|
||||||
FormatArgument::Unparsed(s) => {
|
FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_i64(s), s),
|
||||||
// For hex, we parse `u64` because we do not allow another
|
|
||||||
// minus sign. We might need to do more precise parsing here.
|
|
||||||
let opt = if let Some(s) = s.strip_prefix("-0x") {
|
|
||||||
u64::from_str_radix(s, 16).ok().map(|x| -(x as i64))
|
|
||||||
} else if let Some(s) = s.strip_prefix("0x") {
|
|
||||||
u64::from_str_radix(s, 16).ok().map(|x| x as i64)
|
|
||||||
} else if s.starts_with("-0") || s.starts_with('0') {
|
|
||||||
i64::from_str_radix(s, 8).ok()
|
|
||||||
} else if let Some(s) = s.strip_prefix('\'') {
|
|
||||||
s.chars().next().map(|x| x as i64)
|
|
||||||
} else {
|
|
||||||
s.parse().ok()
|
|
||||||
};
|
|
||||||
match opt {
|
|
||||||
Some(n) => n,
|
|
||||||
None => {
|
|
||||||
show_warning!("{}: expected a numeric value", s.quote());
|
|
||||||
set_exit_code(1);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,23 +78,7 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
||||||
};
|
};
|
||||||
match next {
|
match next {
|
||||||
FormatArgument::Float(n) => *n,
|
FormatArgument::Float(n) => *n,
|
||||||
FormatArgument::Unparsed(s) => {
|
FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_f64(s), s),
|
||||||
let opt = if s.starts_with("0x") || s.starts_with("-0x") {
|
|
||||||
unimplemented!("Hexadecimal floats are unimplemented!")
|
|
||||||
} else if let Some(s) = s.strip_prefix('\'') {
|
|
||||||
s.chars().next().map(|x| x as u64 as f64)
|
|
||||||
} else {
|
|
||||||
s.parse().ok()
|
|
||||||
};
|
|
||||||
match opt {
|
|
||||||
Some(n) => n,
|
|
||||||
None => {
|
|
||||||
show_warning!("{}: expected a numeric value", s.quote());
|
|
||||||
set_exit_code(1);
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => 0.0,
|
_ => 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,3 +90,39 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_value<T: Default>(p: Result<T, ParseError<'_, T>>, input: &str) -> T {
|
||||||
|
match p {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
set_exit_code(1);
|
||||||
|
let input = escape_name(
|
||||||
|
OsStr::new(input),
|
||||||
|
&QuotingStyle::C {
|
||||||
|
quotes: Quotes::None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
match e {
|
||||||
|
ParseError::Overflow => {
|
||||||
|
show_error!("{}: Numerical result out of range", input.quote());
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
ParseError::NotNumeric => {
|
||||||
|
show_error!("{}: expected a numeric value", input.quote());
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
ParseError::PartialMatch(v, rest) => {
|
||||||
|
if input.starts_with('\'') {
|
||||||
|
show_warning!(
|
||||||
|
"{}: character(s) following character constant have been ignored",
|
||||||
|
&rest,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
show_error!("{}: value not completely converted", input.quote());
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -435,7 +435,6 @@ fn sub_float_dec_places() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "hexadecimal floats are unimplemented"]
|
|
||||||
fn sub_float_hex_in() {
|
fn sub_float_hex_in() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["%f", "0xF1.1F"])
|
.args(&["%f", "0xF1.1F"])
|
||||||
|
@ -599,3 +598,44 @@ fn sub_general_round_float_leading_zeroes() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_only("1.00001");
|
.stdout_only("1.00001");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn partial_float() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["%.2f is %s", "42.03x", "a lot"])
|
||||||
|
.fails()
|
||||||
|
.code_is(1)
|
||||||
|
.stdout_is("42.03 is a lot")
|
||||||
|
.stderr_is("printf: '42.03x': value not completely converted\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn partial_integer() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["%d is %s", "42x23", "a lot"])
|
||||||
|
.fails()
|
||||||
|
.code_is(1)
|
||||||
|
.stdout_is("42 is a lot")
|
||||||
|
.stderr_is("printf: '42x23': value not completely converted\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_overflow() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["%d", "36893488147419103232"])
|
||||||
|
.fails()
|
||||||
|
.code_is(1)
|
||||||
|
.stderr_is("printf: '36893488147419103232': Numerical result out of range\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn partial_char() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["%d", "'abc"])
|
||||||
|
.fails()
|
||||||
|
.code_is(1)
|
||||||
|
.stdout_is("97")
|
||||||
|
.stderr_is(
|
||||||
|
"printf: warning: bc: character(s) following character constant have been ignored\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue