1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

printf: coerce missing and invalid arguments to 0

This commit is contained in:
Terts Diepraam 2023-11-20 13:25:20 +01:00
parent c43ee01d19
commit 066d8ba73d
2 changed files with 75 additions and 90 deletions

View file

@ -9,84 +9,103 @@ pub enum FormatArgument {
Unparsed(String),
}
impl FormatArgument {
pub fn get_char(&self) -> Option<char> {
match self {
Self::Char(c) => Some(*c),
Self::Unparsed(s) => {
pub trait ArgumentIter<'a>: Iterator<Item = &'a FormatArgument> {
fn get_char(&mut self) -> char;
fn get_i64(&mut self) -> i64;
fn get_u64(&mut self) -> u64;
fn get_f64(&mut self) -> f64;
fn get_str(&mut self) -> &'a str;
}
impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
fn get_char(&mut self) -> char {
let Some(next) = self.next() else {
return '\0';
};
match next {
FormatArgument::Char(c) => *c,
FormatArgument::Unparsed(s) => {
let mut chars = s.chars();
let Some(c) = chars.next() else {
return None;
return '\0';
};
let None = chars.next() else {
return None;
return '\0';
};
Some(c)
c
}
_ => None,
_ => '\0',
}
}
pub fn get_u64(&self) -> Option<u64> {
match self {
Self::UnsignedInt(n) => Some(*n),
Self::Unparsed(s) => {
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('\'') {
Some(s.chars().next()? as u64)
} else {
s.parse().ok()
}
fn get_u64(&mut self) -> u64 {
let Some(next) = self.next() else {
return 0;
};
match next {
FormatArgument::UnsignedInt(n) => *n,
FormatArgument::Unparsed(s) => 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()
}
_ => None,
.unwrap_or(0),
_ => 0,
}
}
pub fn get_i64(&self) -> Option<i64> {
match self {
Self::SignedInt(n) => Some(*n),
Self::Unparsed(s) => {
fn get_i64(&mut self) -> i64 {
let Some(next) = self.next() else {
return 0;
};
match next {
FormatArgument::SignedInt(n) => *n,
FormatArgument::Unparsed(s) => {
// For hex, we parse `u64` because we do not allow another
// minus sign. We might need to do more precise parsing here.
if let Some(s) = s.strip_prefix("-0x") {
Some(- (u64::from_str_radix(s, 16).ok()? as i64))
u64::from_str_radix(s, 16).ok().map(|x| -(x as i64))
} else if let Some(s) = s.strip_prefix("0x") {
Some(u64::from_str_radix(s, 16).ok()? as i64)
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('\'') {
Some(s.chars().next()? as i64)
s.chars().next().map(|x| x as i64)
} else {
s.parse().ok()
}
.unwrap_or(0)
}
_ => None,
_ => 0,
}
}
pub fn get_f64(&self) -> Option<f64> {
match self {
Self::Float(n) => Some(*n),
Self::Unparsed(s) => {
if s.starts_with("0x") || s.starts_with("-0x") {
unimplemented!("Hexadecimal floats are unimplemented!")
} else if let Some(s) = s.strip_prefix('\'') {
Some(s.chars().next()? as u64 as f64)
} else {
s.parse().ok()
}
fn get_f64(&mut self) -> f64 {
let Some(next) = self.next() else {
return 0.0;
};
match next {
FormatArgument::Float(n) => *n,
FormatArgument::Unparsed(s) => 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()
}
_ => None,
.unwrap_or(0.0),
_ => 0.0,
}
}
pub fn get_str(&self) -> Option<&str> {
match self {
Self::Unparsed(s) | Self::String(s) => Some(s),
_ => None,
fn get_str(&mut self) -> &'a str {
match self.next() {
Some(FormatArgument::Unparsed(s) | FormatArgument::String(s)) => s,
_ => "",
}
}
}

View file

@ -5,7 +5,7 @@ use super::{
self, Case, FloatVariant, ForceDecimal, Formatter, NumberAlignment, PositiveSign, Prefix,
UnsignedIntVariant,
},
parse_escape_only, FormatArgument, FormatChar, FormatError,
parse_escape_only, ArgumentIter, FormatChar, FormatError,
};
use std::{fmt::Display, io::Write, ops::ControlFlow};
@ -244,16 +244,12 @@ impl Spec {
pub fn write<'a>(
&self,
writer: impl Write,
mut args: impl Iterator<Item = &'a FormatArgument>,
mut args: impl ArgumentIter<'a>,
) -> Result<(), FormatError> {
match self {
&Spec::Char { width, align_left } => {
let width = resolve_asterisk(width, &mut args)?.unwrap_or(0);
let arg = next_arg(&mut args)?;
match arg.get_char() {
Some(c) => write_padded(writer, c, width, false, align_left),
_ => Err(FormatError::InvalidArgument(arg.clone())),
}
write_padded(writer, args.get_char(), width, false, align_left)
}
&Spec::String {
width,
@ -263,10 +259,7 @@ impl Spec {
} => {
let width = resolve_asterisk(width, &mut args)?.unwrap_or(0);
let precision = resolve_asterisk(precision, &mut args)?;
let arg = next_arg(&mut args)?;
let Some(s) = arg.get_str() else {
return Err(FormatError::InvalidArgument(arg.clone()));
};
let s = args.get_str();
if parse_escape {
let mut parsed = Vec::new();
for c in parse_escape_only(s.as_bytes()) {
@ -311,11 +304,7 @@ impl Spec {
} => {
let width = resolve_asterisk(width, &mut args)?.unwrap_or(0);
let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(0);
let arg = next_arg(&mut args)?;
let Some(i) = arg.get_i64() else {
return Err(FormatError::InvalidArgument(arg.clone()));
};
let i = args.get_i64();
num_format::SignedInt {
width,
@ -334,11 +323,7 @@ impl Spec {
} => {
let width = resolve_asterisk(width, &mut args)?.unwrap_or(0);
let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(0);
let arg = next_arg(args)?;
let Some(i) = arg.get_u64() else {
return Err(FormatError::InvalidArgument(arg.clone()));
};
let i = args.get_u64();
num_format::UnsignedInt {
variant,
@ -360,11 +345,7 @@ impl Spec {
} => {
let width = resolve_asterisk(width, &mut args)?.unwrap_or(0);
let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(6);
let arg = next_arg(args)?;
let Some(f) = arg.get_f64() else {
return Err(FormatError::InvalidArgument(arg.clone()));
};
let f = args.get_f64();
num_format::Float {
variant,
@ -384,30 +365,15 @@ impl Spec {
fn resolve_asterisk<'a>(
option: Option<CanAsterisk<usize>>,
args: impl Iterator<Item = &'a FormatArgument>,
mut args: impl ArgumentIter<'a>,
) -> Result<Option<usize>, FormatError> {
Ok(match option {
None => None,
Some(CanAsterisk::Asterisk) => {
let arg = next_arg(args)?;
match arg.get_u64() {
Some(u) => match usize::try_from(u) {
Ok(u) => Some(u),
Err(_) => return Err(FormatError::InvalidArgument(arg.clone())),
},
_ => return Err(FormatError::InvalidArgument(arg.clone())),
}
}
Some(CanAsterisk::Asterisk) => Some(usize::try_from(args.get_u64()).ok().unwrap_or(0)),
Some(CanAsterisk::Fixed(w)) => Some(w),
})
}
fn next_arg<'a>(
mut arguments: impl Iterator<Item = &'a FormatArgument>,
) -> Result<&'a FormatArgument, FormatError> {
arguments.next().ok_or(FormatError::NoMoreArguments)
}
fn write_padded(
mut writer: impl Write,
text: impl Display,