mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
Merge pull request #7855 from jtracey/printf-nonzero
printf: use non-zero indexes
This commit is contained in:
commit
279629f67e
3 changed files with 49 additions and 33 deletions
|
@ -12,7 +12,7 @@ use crate::{
|
|||
show_error, show_warning,
|
||||
};
|
||||
use os_display::Quotable;
|
||||
use std::ffi::OsStr;
|
||||
use std::{ffi::OsStr, num::NonZero};
|
||||
|
||||
/// An argument for formatting
|
||||
///
|
||||
|
@ -129,8 +129,9 @@ impl<'a> FormatArguments<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_at_relative_position(&mut self, pos: usize) -> Option<&'a FormatArgument> {
|
||||
let pos = pos.saturating_sub(1).saturating_add(self.current_offset);
|
||||
fn get_at_relative_position(&mut self, pos: NonZero<usize>) -> Option<&'a FormatArgument> {
|
||||
let pos: usize = pos.into();
|
||||
let pos = (pos - 1).saturating_add(self.current_offset);
|
||||
self.highest_arg_position = Some(self.highest_arg_position.map_or(pos, |x| x.max(pos)));
|
||||
self.args.get(pos)
|
||||
}
|
||||
|
@ -281,6 +282,10 @@ mod tests {
|
|||
assert!(args.is_exhausted());
|
||||
}
|
||||
|
||||
fn non_zero_pos(n: usize) -> ArgumentLocation {
|
||||
ArgumentLocation::Position(NonZero::new(n).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_position_access_pattern() {
|
||||
// Test with consistent positional access patterns
|
||||
|
@ -297,23 +302,23 @@ mod tests {
|
|||
]);
|
||||
|
||||
// First batch - positional access
|
||||
assert_eq!(b'b', args.next_char(&ArgumentLocation::Position(2))); // Position 2
|
||||
assert_eq!(b'a', args.next_char(&ArgumentLocation::Position(1))); // Position 1
|
||||
assert_eq!(b'c', args.next_char(&ArgumentLocation::Position(3))); // Position 3
|
||||
assert_eq!(b'b', args.next_char(&non_zero_pos(2))); // Position 2
|
||||
assert_eq!(b'a', args.next_char(&non_zero_pos(1))); // Position 1
|
||||
assert_eq!(b'c', args.next_char(&non_zero_pos(3))); // Position 3
|
||||
args.start_next_batch();
|
||||
assert!(!args.is_exhausted());
|
||||
|
||||
// Second batch - same positional pattern
|
||||
assert_eq!(b'e', args.next_char(&ArgumentLocation::Position(2))); // Position 2
|
||||
assert_eq!(b'd', args.next_char(&ArgumentLocation::Position(1))); // Position 1
|
||||
assert_eq!(b'f', args.next_char(&ArgumentLocation::Position(3))); // Position 3
|
||||
assert_eq!(b'e', args.next_char(&non_zero_pos(2))); // Position 2
|
||||
assert_eq!(b'd', args.next_char(&non_zero_pos(1))); // Position 1
|
||||
assert_eq!(b'f', args.next_char(&non_zero_pos(3))); // Position 3
|
||||
args.start_next_batch();
|
||||
assert!(!args.is_exhausted());
|
||||
|
||||
// Third batch - same positional pattern (last batch)
|
||||
assert_eq!(b'h', args.next_char(&ArgumentLocation::Position(2))); // Position 2
|
||||
assert_eq!(b'g', args.next_char(&ArgumentLocation::Position(1))); // Position 1
|
||||
assert_eq!(b'i', args.next_char(&ArgumentLocation::Position(3))); // Position 3
|
||||
assert_eq!(b'h', args.next_char(&non_zero_pos(2))); // Position 2
|
||||
assert_eq!(b'g', args.next_char(&non_zero_pos(1))); // Position 1
|
||||
assert_eq!(b'i', args.next_char(&non_zero_pos(3))); // Position 3
|
||||
args.start_next_batch();
|
||||
assert!(args.is_exhausted());
|
||||
}
|
||||
|
@ -334,19 +339,19 @@ mod tests {
|
|||
|
||||
// First batch - mix of sequential and positional
|
||||
assert_eq!(b'a', args.next_char(&ArgumentLocation::NextArgument)); // Sequential
|
||||
assert_eq!(b'c', args.next_char(&ArgumentLocation::Position(3))); // Positional
|
||||
assert_eq!(b'c', args.next_char(&non_zero_pos(3))); // Positional
|
||||
args.start_next_batch();
|
||||
assert!(!args.is_exhausted());
|
||||
|
||||
// Second batch - same mixed pattern
|
||||
assert_eq!(b'd', args.next_char(&ArgumentLocation::NextArgument)); // Sequential
|
||||
assert_eq!(b'f', args.next_char(&ArgumentLocation::Position(3))); // Positional
|
||||
assert_eq!(b'f', args.next_char(&non_zero_pos(3))); // Positional
|
||||
args.start_next_batch();
|
||||
assert!(!args.is_exhausted());
|
||||
|
||||
// Last batch - same mixed pattern
|
||||
assert_eq!(b'g', args.next_char(&ArgumentLocation::NextArgument)); // Sequential
|
||||
assert_eq!(b'\0', args.next_char(&ArgumentLocation::Position(3))); // Out of bounds
|
||||
assert_eq!(b'\0', args.next_char(&non_zero_pos(3))); // Out of bounds
|
||||
args.start_next_batch();
|
||||
assert!(args.is_exhausted());
|
||||
}
|
||||
|
@ -419,16 +424,16 @@ mod tests {
|
|||
let mut args = FormatArguments::new(&args);
|
||||
|
||||
// First batch - positional access of different types
|
||||
assert_eq!(b'a', args.next_char(&ArgumentLocation::Position(1)));
|
||||
assert_eq!("test", args.next_string(&ArgumentLocation::Position(2)));
|
||||
assert_eq!(42, args.next_u64(&ArgumentLocation::Position(3)));
|
||||
assert_eq!(b'a', args.next_char(&non_zero_pos(1)));
|
||||
assert_eq!("test", args.next_string(&non_zero_pos(2)));
|
||||
assert_eq!(42, args.next_u64(&non_zero_pos(3)));
|
||||
args.start_next_batch();
|
||||
assert!(!args.is_exhausted());
|
||||
|
||||
// Second batch - same pattern
|
||||
assert_eq!(b'b', args.next_char(&ArgumentLocation::Position(1)));
|
||||
assert_eq!("more", args.next_string(&ArgumentLocation::Position(2)));
|
||||
assert_eq!(99, args.next_u64(&ArgumentLocation::Position(3)));
|
||||
assert_eq!(b'b', args.next_char(&non_zero_pos(1)));
|
||||
assert_eq!("more", args.next_string(&non_zero_pos(2)));
|
||||
assert_eq!(99, args.next_u64(&non_zero_pos(3)));
|
||||
args.start_next_batch();
|
||||
assert!(args.is_exhausted());
|
||||
}
|
||||
|
@ -446,13 +451,13 @@ mod tests {
|
|||
|
||||
// First batch
|
||||
assert_eq!(b'a', args.next_char(&ArgumentLocation::NextArgument));
|
||||
assert_eq!(b'c', args.next_char(&ArgumentLocation::Position(3)));
|
||||
assert_eq!(b'c', args.next_char(&non_zero_pos(3)));
|
||||
args.start_next_batch();
|
||||
assert!(!args.is_exhausted());
|
||||
|
||||
// Second batch (partial)
|
||||
assert_eq!(b'd', args.next_char(&ArgumentLocation::NextArgument));
|
||||
assert_eq!(b'\0', args.next_char(&ArgumentLocation::Position(3))); // Out of bounds
|
||||
assert_eq!(b'\0', args.next_char(&non_zero_pos(3))); // Out of bounds
|
||||
args.start_next_batch();
|
||||
assert!(args.is_exhausted());
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ use super::{
|
|||
parse_escape_only,
|
||||
};
|
||||
use crate::format::FormatArguments;
|
||||
use std::{io::Write, ops::ControlFlow};
|
||||
use std::{io::Write, num::NonZero, ops::ControlFlow};
|
||||
|
||||
/// A parsed specification for formatting a value
|
||||
///
|
||||
|
@ -70,7 +70,7 @@ pub enum Spec {
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ArgumentLocation {
|
||||
NextArgument,
|
||||
Position(usize),
|
||||
Position(NonZero<usize>),
|
||||
}
|
||||
|
||||
/// Precision and width specified might use an asterisk to indicate that they are
|
||||
|
@ -156,7 +156,9 @@ impl Spec {
|
|||
let start = *rest;
|
||||
|
||||
// Check for a positional specifier (%m$)
|
||||
let position = eat_argument_position(rest, &mut index);
|
||||
let Some(position) = eat_argument_position(rest, &mut index) else {
|
||||
return Err(&start[..index]);
|
||||
};
|
||||
|
||||
let flags = Flags::parse(rest, &mut index);
|
||||
|
||||
|
@ -566,19 +568,19 @@ fn write_padded(
|
|||
}
|
||||
|
||||
// Check for a number ending with a '$'
|
||||
fn eat_argument_position(rest: &mut &[u8], index: &mut usize) -> ArgumentLocation {
|
||||
fn eat_argument_position(rest: &mut &[u8], index: &mut usize) -> Option<ArgumentLocation> {
|
||||
let original_index = *index;
|
||||
if let Some(pos) = eat_number(rest, index) {
|
||||
if let Some(&b'$') = rest.get(*index) {
|
||||
*index += 1;
|
||||
ArgumentLocation::Position(pos)
|
||||
Some(ArgumentLocation::Position(NonZero::new(pos)?))
|
||||
} else {
|
||||
*index = original_index;
|
||||
ArgumentLocation::NextArgument
|
||||
Some(ArgumentLocation::NextArgument)
|
||||
}
|
||||
} else {
|
||||
*index = original_index;
|
||||
ArgumentLocation::NextArgument
|
||||
Some(ArgumentLocation::NextArgument)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -586,7 +588,7 @@ fn eat_asterisk_or_number(rest: &mut &[u8], index: &mut usize) -> Option<CanAste
|
|||
if let Some(b'*') = rest.get(*index) {
|
||||
*index += 1;
|
||||
// Check for a positional specifier (*m$)
|
||||
Some(CanAsterisk::Asterisk(eat_argument_position(rest, index)))
|
||||
Some(CanAsterisk::Asterisk(eat_argument_position(rest, index)?))
|
||||
} else {
|
||||
eat_number(rest, index).map(CanAsterisk::Fixed)
|
||||
}
|
||||
|
@ -670,7 +672,9 @@ mod tests {
|
|||
assert_eq!(
|
||||
Some((2, false)),
|
||||
resolve_asterisk_width(
|
||||
Some(CanAsterisk::Asterisk(ArgumentLocation::Position(2))),
|
||||
Some(CanAsterisk::Asterisk(ArgumentLocation::Position(
|
||||
NonZero::new(2).unwrap()
|
||||
))),
|
||||
&mut FormatArguments::new(&[
|
||||
FormatArgument::Unparsed("1".to_string()),
|
||||
FormatArgument::Unparsed("2".to_string()),
|
||||
|
@ -738,7 +742,9 @@ mod tests {
|
|||
assert_eq!(
|
||||
Some(2),
|
||||
resolve_asterisk_precision(
|
||||
Some(CanAsterisk::Asterisk(ArgumentLocation::Position(2))),
|
||||
Some(CanAsterisk::Asterisk(ArgumentLocation::Position(
|
||||
NonZero::new(2).unwrap()
|
||||
))),
|
||||
&mut FormatArguments::new(&[
|
||||
FormatArgument::Unparsed("1".to_string()),
|
||||
FormatArgument::Unparsed("2".to_string()),
|
||||
|
|
|
@ -1341,6 +1341,11 @@ fn positional_format_specifiers() {
|
|||
.succeeds()
|
||||
.stdout_only("05-");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["%0$d%d-", "5", "10", "6", "20"])
|
||||
.fails_with_code(1)
|
||||
.stderr_only("printf: %0$: invalid conversion specification\n");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&[
|
||||
"Octal: %6$o, Int: %1$d, Float: %4$f, String: %2$s, Hex: %7$x, Scientific: %5$e, Char: %9$c, Unsigned: %3$u, Integer: %8$i",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue