Merge pull request #5128 from tertsdiepraam/printf-rewrite

`printf` rewrite (with a lot of `seq` changes)
This commit is contained in:
Sylvestre Ledru
2023-11-28 07:52:58 +01:00
committed by GitHub
37 changed files with 1812 additions and 3336 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ path = "src/dd.rs"
clap = { workspace = true }
gcd = { workspace = true }
libc = { workspace = true }
uucore = { workspace = true, features = ["memo", "quoting-style"] }
uucore = { workspace = true, features = ["format", "quoting-style"] }
[target.'cfg(any(target_os = "linux"))'.dependencies]
nix = { workspace = true, features = ["fs"] }
+12 -3
View File
@@ -13,8 +13,10 @@ use std::io::Write;
use std::sync::mpsc;
use std::time::Duration;
use uucore::error::UResult;
use uucore::memo::sprintf;
use uucore::{
error::UResult,
format::num_format::{FloatVariant, Formatter},
};
use crate::numbers::{to_magnitude_and_suffix, SuffixType};
@@ -152,7 +154,14 @@ impl ProgUpdate {
let (carriage_return, newline) = if rewrite { ("\r", "") } else { ("", "\n") };
// The duration should be formatted as in `printf %g`.
let duration_str = sprintf("%g", &[duration.to_string()])?;
let mut duration_str = Vec::new();
uucore::format::num_format::Float {
variant: FloatVariant::Shortest,
..Default::default()
}
.fmt(&mut duration_str, duration)?;
// We assume that printf will output valid UTF-8
let duration_str = std::str::from_utf8(&duration_str).unwrap();
// If the number of bytes written is sufficiently large, then
// print a more concise representation of the number, like
+1 -1
View File
@@ -16,7 +16,7 @@ path = "src/printf.rs"
[dependencies]
clap = { workspace = true }
uucore = { workspace = true, features = ["memo", "quoting-style"] }
uucore = { workspace = true, features = ["format", "quoting-style"] }
[[bin]]
name = "printf"
+23 -4
View File
@@ -6,9 +6,12 @@
// spell-checker:ignore (change!) each's
// spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr
use std::io::stdout;
use std::ops::ControlFlow;
use clap::{crate_version, Arg, ArgAction, Command};
use uucore::error::{UResult, UUsageError};
use uucore::memo::printf;
use uucore::format::{parse_spec_and_escape, FormatArgument};
use uucore::{format_usage, help_about, help_section, help_usage};
const VERSION: &str = "version";
@@ -30,12 +33,28 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let format_string = matches
.get_one::<String>(options::FORMATSTRING)
.ok_or_else(|| UUsageError::new(1, "missing operand"))?;
let values: Vec<String> = match matches.get_many::<String>(options::ARGUMENT) {
Some(s) => s.map(|s| s.to_string()).collect(),
let values: Vec<_> = match matches.get_many::<String>(options::ARGUMENT) {
Some(s) => s.map(|s| FormatArgument::Unparsed(s.to_string())).collect(),
None => vec![],
};
printf(format_string, &values[..])?;
let mut args = values.iter().peekable();
for item in parse_spec_and_escape(format_string.as_ref()) {
match item?.write(stdout(), &mut args)? {
ControlFlow::Continue(()) => {}
ControlFlow::Break(()) => return Ok(()),
};
}
while args.peek().is_some() {
for item in parse_spec_and_escape(format_string.as_ref()) {
match item?.write(stdout(), &mut args)? {
ControlFlow::Continue(()) => {}
ControlFlow::Break(()) => return Ok(()),
};
}
}
Ok(())
}
+1 -1
View File
@@ -20,7 +20,7 @@ bigdecimal = { workspace = true }
clap = { workspace = true }
num-bigint = { workspace = true }
num-traits = { workspace = true }
uucore = { workspace = true, features = ["memo", "quoting-style"] }
uucore = { workspace = true, features = ["format", "quoting-style"] }
[[bin]]
name = "seq"
+5 -49
View File
@@ -25,13 +25,8 @@ use std::fmt::Display;
use std::ops::Add;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_bigint::ToBigInt;
use num_traits::One;
use num_traits::Zero;
use crate::extendedbigint::ExtendedBigInt;
#[derive(Debug, Clone)]
pub enum ExtendedBigDecimal {
/// Arbitrary precision floating point number.
@@ -72,53 +67,14 @@ pub enum ExtendedBigDecimal {
Nan,
}
/// The smallest integer greater than or equal to this number.
fn ceil(x: BigDecimal) -> BigInt {
if x.is_integer() {
// Unwrapping the Option because it always returns Some
x.to_bigint().unwrap()
} else {
(x + BigDecimal::one().half()).round(0).to_bigint().unwrap()
}
}
/// The largest integer less than or equal to this number.
fn floor(x: BigDecimal) -> BigInt {
if x.is_integer() {
// Unwrapping the Option because it always returns Some
x.to_bigint().unwrap()
} else {
(x - BigDecimal::one().half()).round(0).to_bigint().unwrap()
}
}
impl ExtendedBigDecimal {
/// The smallest integer greater than or equal to this number.
pub fn ceil(self) -> ExtendedBigInt {
match self {
Self::BigDecimal(x) => ExtendedBigInt::BigInt(ceil(x)),
other => From::from(other),
}
#[cfg(test)]
pub fn zero() -> Self {
Self::BigDecimal(0.into())
}
/// The largest integer less than or equal to this number.
pub fn floor(self) -> ExtendedBigInt {
match self {
Self::BigDecimal(x) => ExtendedBigInt::BigInt(floor(x)),
other => From::from(other),
}
}
}
impl From<ExtendedBigInt> for ExtendedBigDecimal {
fn from(big_int: ExtendedBigInt) -> Self {
match big_int {
ExtendedBigInt::BigInt(n) => Self::BigDecimal(BigDecimal::from(n)),
ExtendedBigInt::Infinity => Self::Infinity,
ExtendedBigInt::MinusInfinity => Self::MinusInfinity,
ExtendedBigInt::MinusZero => Self::MinusZero,
ExtendedBigInt::Nan => Self::Nan,
}
pub fn one() -> Self {
Self::BigDecimal(1.into())
}
}
-214
View File
@@ -1,214 +0,0 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore bigint extendedbigint extendedbigdecimal
//! An arbitrary precision integer that can also represent infinity, NaN, etc.
//!
//! Usually infinity, NaN, and negative zero are only represented for
//! floating point numbers. The [`ExtendedBigInt`] enumeration provides
//! a representation of those things with the set of integers. The
//! finite values are stored as [`BigInt`] instances.
//!
//! # Examples
//!
//! Addition works for [`ExtendedBigInt`] as it does for floats. For
//! example, adding infinity to any finite value results in infinity:
//!
//! ```rust,ignore
//! let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
//! let summand2 = ExtendedBigInt::Infinity;
//! assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity);
//! ```
use std::cmp::Ordering;
use std::fmt::Display;
use std::ops::Add;
use num_bigint::BigInt;
use num_bigint::ToBigInt;
use num_traits::One;
use num_traits::Zero;
use crate::extendedbigdecimal::ExtendedBigDecimal;
#[derive(Debug, Clone)]
pub enum ExtendedBigInt {
BigInt(BigInt),
Infinity,
MinusInfinity,
MinusZero,
Nan,
}
impl ExtendedBigInt {
/// The integer number one.
pub fn one() -> Self {
// We would like to implement `num_traits::One`, but it requires
// a multiplication implementation, and we don't want to
// implement that here.
Self::BigInt(BigInt::one())
}
}
impl From<ExtendedBigDecimal> for ExtendedBigInt {
fn from(big_decimal: ExtendedBigDecimal) -> Self {
match big_decimal {
// TODO When can this fail?
ExtendedBigDecimal::BigDecimal(x) => Self::BigInt(x.to_bigint().unwrap()),
ExtendedBigDecimal::Infinity => Self::Infinity,
ExtendedBigDecimal::MinusInfinity => Self::MinusInfinity,
ExtendedBigDecimal::MinusZero => Self::MinusZero,
ExtendedBigDecimal::Nan => Self::Nan,
}
}
}
impl Display for ExtendedBigInt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BigInt(n) => n.fmt(f),
Self::Infinity => f32::INFINITY.fmt(f),
Self::MinusInfinity => f32::NEG_INFINITY.fmt(f),
Self::MinusZero => "-0".fmt(f),
Self::Nan => "nan".fmt(f),
}
}
}
impl Zero for ExtendedBigInt {
fn zero() -> Self {
Self::BigInt(BigInt::zero())
}
fn is_zero(&self) -> bool {
match self {
Self::BigInt(n) => n.is_zero(),
Self::MinusZero => true,
_ => false,
}
}
}
impl Add for ExtendedBigInt {
type Output = Self;
fn add(self, other: Self) -> Self {
match (self, other) {
(Self::BigInt(m), Self::BigInt(n)) => Self::BigInt(m.add(n)),
(Self::BigInt(_), Self::MinusInfinity) => Self::MinusInfinity,
(Self::BigInt(_), Self::Infinity) => Self::Infinity,
(Self::BigInt(_), Self::Nan) => Self::Nan,
(Self::BigInt(m), Self::MinusZero) => Self::BigInt(m),
(Self::Infinity, Self::BigInt(_)) => Self::Infinity,
(Self::Infinity, Self::Infinity) => Self::Infinity,
(Self::Infinity, Self::MinusZero) => Self::Infinity,
(Self::Infinity, Self::MinusInfinity) => Self::Nan,
(Self::Infinity, Self::Nan) => Self::Nan,
(Self::MinusInfinity, Self::BigInt(_)) => Self::MinusInfinity,
(Self::MinusInfinity, Self::MinusInfinity) => Self::MinusInfinity,
(Self::MinusInfinity, Self::MinusZero) => Self::MinusInfinity,
(Self::MinusInfinity, Self::Infinity) => Self::Nan,
(Self::MinusInfinity, Self::Nan) => Self::Nan,
(Self::Nan, _) => Self::Nan,
(Self::MinusZero, other) => other,
}
}
}
impl PartialEq for ExtendedBigInt {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::BigInt(m), Self::BigInt(n)) => m.eq(n),
(Self::BigInt(_), Self::MinusInfinity) => false,
(Self::BigInt(_), Self::Infinity) => false,
(Self::BigInt(_), Self::Nan) => false,
(Self::BigInt(_), Self::MinusZero) => false,
(Self::Infinity, Self::BigInt(_)) => false,
(Self::Infinity, Self::Infinity) => true,
(Self::Infinity, Self::MinusZero) => false,
(Self::Infinity, Self::MinusInfinity) => false,
(Self::Infinity, Self::Nan) => false,
(Self::MinusInfinity, Self::BigInt(_)) => false,
(Self::MinusInfinity, Self::Infinity) => false,
(Self::MinusInfinity, Self::MinusZero) => false,
(Self::MinusInfinity, Self::MinusInfinity) => true,
(Self::MinusInfinity, Self::Nan) => false,
(Self::Nan, _) => false,
(Self::MinusZero, Self::BigInt(_)) => false,
(Self::MinusZero, Self::Infinity) => false,
(Self::MinusZero, Self::MinusZero) => true,
(Self::MinusZero, Self::MinusInfinity) => false,
(Self::MinusZero, Self::Nan) => false,
}
}
}
impl PartialOrd for ExtendedBigInt {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Self::BigInt(m), Self::BigInt(n)) => m.partial_cmp(n),
(Self::BigInt(_), Self::MinusInfinity) => Some(Ordering::Greater),
(Self::BigInt(_), Self::Infinity) => Some(Ordering::Less),
(Self::BigInt(_), Self::Nan) => None,
(Self::BigInt(m), Self::MinusZero) => m.partial_cmp(&BigInt::zero()),
(Self::Infinity, Self::BigInt(_)) => Some(Ordering::Greater),
(Self::Infinity, Self::Infinity) => Some(Ordering::Equal),
(Self::Infinity, Self::MinusZero) => Some(Ordering::Greater),
(Self::Infinity, Self::MinusInfinity) => Some(Ordering::Greater),
(Self::Infinity, Self::Nan) => None,
(Self::MinusInfinity, Self::BigInt(_)) => Some(Ordering::Less),
(Self::MinusInfinity, Self::Infinity) => Some(Ordering::Less),
(Self::MinusInfinity, Self::MinusZero) => Some(Ordering::Less),
(Self::MinusInfinity, Self::MinusInfinity) => Some(Ordering::Equal),
(Self::MinusInfinity, Self::Nan) => None,
(Self::Nan, _) => None,
(Self::MinusZero, Self::BigInt(n)) => BigInt::zero().partial_cmp(n),
(Self::MinusZero, Self::Infinity) => Some(Ordering::Less),
(Self::MinusZero, Self::MinusZero) => Some(Ordering::Equal),
(Self::MinusZero, Self::MinusInfinity) => Some(Ordering::Greater),
(Self::MinusZero, Self::Nan) => None,
}
}
}
#[cfg(test)]
mod tests {
use num_bigint::BigInt;
use num_traits::Zero;
use crate::extendedbigint::ExtendedBigInt;
#[test]
fn test_addition_infinity() {
let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
let summand2 = ExtendedBigInt::Infinity;
assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity);
}
#[test]
fn test_addition_minus_infinity() {
let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
let summand2 = ExtendedBigInt::MinusInfinity;
assert_eq!(summand1 + summand2, ExtendedBigInt::MinusInfinity);
}
#[test]
fn test_addition_nan() {
let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
let summand2 = ExtendedBigInt::Nan;
let sum = summand1 + summand2;
match sum {
ExtendedBigInt::Nan => (),
_ => unreachable!(),
}
}
#[test]
fn test_display() {
assert_eq!(format!("{}", ExtendedBigInt::BigInt(BigInt::zero())), "0");
assert_eq!(format!("{}", ExtendedBigInt::MinusZero), "-0");
assert_eq!(format!("{}", ExtendedBigInt::Infinity), "inf");
assert_eq!(format!("{}", ExtendedBigInt::MinusInfinity), "-inf");
assert_eq!(format!("{}", ExtendedBigInt::Nan), "nan");
}
}
+8 -74
View File
@@ -2,80 +2,10 @@
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore extendedbigdecimal extendedbigint
//! A type to represent the possible start, increment, and end values for seq.
//!
//! The [`Number`] enumeration represents the possible values for the
//! start, increment, and end values for `seq`. These may be integers,
//! floating point numbers, negative zero, etc. A [`Number`] can be
//! parsed from a string by calling [`str::parse`].
// spell-checker:ignore extendedbigdecimal
use num_traits::Zero;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
/// An integral or floating point number.
#[derive(Debug, PartialEq)]
pub enum Number {
Int(ExtendedBigInt),
Float(ExtendedBigDecimal),
}
impl Number {
/// Decide whether this number is zero (either positive or negative).
pub fn is_zero(&self) -> bool {
// We would like to implement `num_traits::Zero`, but it
// requires an addition implementation, and we don't want to
// implement that here.
match self {
Self::Int(n) => n.is_zero(),
Self::Float(x) => x.is_zero(),
}
}
/// Convert this number into an `ExtendedBigDecimal`.
pub fn into_extended_big_decimal(self) -> ExtendedBigDecimal {
match self {
Self::Int(n) => ExtendedBigDecimal::from(n),
Self::Float(x) => x,
}
}
/// The integer number one.
pub fn one() -> Self {
// We would like to implement `num_traits::One`, but it requires
// a multiplication implementation, and we don't want to
// implement that here.
Self::Int(ExtendedBigInt::one())
}
/// Round this number towards the given other number.
///
/// If `other` is greater, then round up. If `other` is smaller,
/// then round down.
pub fn round_towards(self, other: &ExtendedBigInt) -> ExtendedBigInt {
match self {
// If this number is already an integer, it is already
// rounded to the nearest integer in the direction of
// `other`.
Self::Int(num) => num,
// Otherwise, if this number is a float, we need to decide
// whether `other` is larger or smaller than it, and thus
// whether to round up or round down, respectively.
Self::Float(num) => {
let other: ExtendedBigDecimal = From::from(other.clone());
if other > num {
num.ceil()
} else {
// If they are equal, then `self` is already an
// integer, so calling `floor()` does no harm and
// will just return that integer anyway.
num.floor()
}
}
}
}
}
/// A number with a specified number of integer and fractional digits.
///
@@ -87,13 +17,17 @@ impl Number {
/// You can get an instance of this struct by calling [`str::parse`].
#[derive(Debug)]
pub struct PreciseNumber {
pub number: Number,
pub number: ExtendedBigDecimal,
pub num_integral_digits: usize,
pub num_fractional_digits: usize,
}
impl PreciseNumber {
pub fn new(number: Number, num_integral_digits: usize, num_fractional_digits: usize) -> Self {
pub fn new(
number: ExtendedBigDecimal,
num_integral_digits: usize,
num_fractional_digits: usize,
) -> Self {
Self {
number,
num_integral_digits,
@@ -106,7 +40,7 @@ impl PreciseNumber {
// We would like to implement `num_traits::One`, but it requires
// a multiplication implementation, and we don't want to
// implement that here.
Self::new(Number::one(), 1, 0)
Self::new(ExtendedBigDecimal::one(), 1, 0)
}
/// Decide whether this number is zero (either positive or negative).
+47 -82
View File
@@ -2,7 +2,7 @@
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore extendedbigdecimal extendedbigint bigdecimal numberparse
// spell-checker:ignore extendedbigdecimal bigdecimal numberparse
//! Parsing numbers for use in `seq`.
//!
//! This module provides an implementation of [`FromStr`] for the
@@ -16,8 +16,6 @@ use num_traits::Num;
use num_traits::Zero;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
use crate::number::Number;
use crate::number::PreciseNumber;
/// An error returned when parsing a number fails.
@@ -29,8 +27,8 @@ pub enum ParseNumberError {
}
/// Decide whether a given string and its parsed `BigInt` is negative zero.
fn is_minus_zero_int(s: &str, n: &BigInt) -> bool {
s.starts_with('-') && n == &BigInt::zero()
fn is_minus_zero_int(s: &str, n: &BigDecimal) -> bool {
s.starts_with('-') && n == &BigDecimal::zero()
}
/// Decide whether a given string and its parsed `BigDecimal` is negative zero.
@@ -53,19 +51,19 @@ fn is_minus_zero_float(s: &str, x: &BigDecimal) -> bool {
/// assert_eq!(actual, expected);
/// ```
fn parse_no_decimal_no_exponent(s: &str) -> Result<PreciseNumber, ParseNumberError> {
match s.parse::<BigInt>() {
match s.parse::<BigDecimal>() {
Ok(n) => {
// If `s` is '-0', then `parse()` returns `BigInt::zero()`,
// but we need to return `Number::MinusZeroInt` instead.
if is_minus_zero_int(s, &n) {
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::MinusZero),
ExtendedBigDecimal::MinusZero,
s.len(),
0,
))
} else {
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(n)),
ExtendedBigDecimal::BigDecimal(n),
s.len(),
0,
))
@@ -79,7 +77,7 @@ fn parse_no_decimal_no_exponent(s: &str) -> Result<PreciseNumber, ParseNumberErr
"nan" | "-nan" => return Err(ParseNumberError::Nan),
_ => return Err(ParseNumberError::Float),
};
Ok(PreciseNumber::new(Number::Float(float_val), 0, 0))
Ok(PreciseNumber::new(float_val, 0, 0))
}
}
}
@@ -125,13 +123,13 @@ fn parse_exponent_no_decimal(s: &str, j: usize) -> Result<PreciseNumber, ParseNu
if exponent < 0 {
if is_minus_zero_float(s, &x) {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::MinusZero),
ExtendedBigDecimal::MinusZero,
num_integral_digits,
num_fractional_digits,
))
} else {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::BigDecimal(x)),
ExtendedBigDecimal::BigDecimal(x),
num_integral_digits,
num_fractional_digits,
))
@@ -169,13 +167,13 @@ fn parse_decimal_no_exponent(s: &str, i: usize) -> Result<PreciseNumber, ParseNu
let num_fractional_digits = s.len() - (i + 1);
if is_minus_zero_float(s, &x) {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::MinusZero),
ExtendedBigDecimal::MinusZero,
num_integral_digits,
num_fractional_digits,
))
} else {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::BigDecimal(x)),
ExtendedBigDecimal::BigDecimal(x),
num_integral_digits,
num_fractional_digits,
))
@@ -239,7 +237,7 @@ fn parse_decimal_and_exponent(
if num_digits_between_decimal_point_and_e <= exponent {
if is_minus_zero_float(s, &val) {
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::MinusZero),
ExtendedBigDecimal::MinusZero,
num_integral_digits,
num_fractional_digits,
))
@@ -251,23 +249,23 @@ fn parse_decimal_and_exponent(
);
let expanded = [&s[0..i], &s[i + 1..j], &zeros].concat();
let n = expanded
.parse::<BigInt>()
.parse::<BigDecimal>()
.map_err(|_| ParseNumberError::Float)?;
Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(n)),
ExtendedBigDecimal::BigDecimal(n),
num_integral_digits,
num_fractional_digits,
))
}
} else if is_minus_zero_float(s, &val) {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::MinusZero),
ExtendedBigDecimal::MinusZero,
num_integral_digits,
num_fractional_digits,
))
} else {
Ok(PreciseNumber::new(
Number::Float(ExtendedBigDecimal::BigDecimal(val)),
ExtendedBigDecimal::BigDecimal(val),
num_integral_digits,
num_fractional_digits,
))
@@ -303,20 +301,17 @@ fn parse_hexadecimal(s: &str) -> Result<PreciseNumber, ParseNumberError> {
}
let num = BigInt::from_str_radix(s, 16).map_err(|_| ParseNumberError::Hex)?;
let num = BigDecimal::from(num);
match (is_neg, num == BigInt::zero()) {
(true, true) => Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::MinusZero),
2,
0,
)),
match (is_neg, num == BigDecimal::zero()) {
(true, true) => Ok(PreciseNumber::new(ExtendedBigDecimal::MinusZero, 2, 0)),
(true, false) => Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(-num)),
ExtendedBigDecimal::BigDecimal(-num),
0,
0,
)),
(false, _) => Ok(PreciseNumber::new(
Number::Int(ExtendedBigInt::BigInt(num)),
ExtendedBigDecimal::BigDecimal(num),
0,
0,
)),
@@ -364,19 +359,14 @@ impl FromStr for PreciseNumber {
#[cfg(test)]
mod tests {
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::Zero;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
use crate::number::Number;
use crate::number::PreciseNumber;
use crate::numberparse::ParseNumberError;
/// Convenience function for parsing a [`Number`] and unwrapping.
fn parse(s: &str) -> Number {
fn parse(s: &str) -> ExtendedBigDecimal {
s.parse::<PreciseNumber>().unwrap().number
}
@@ -392,40 +382,37 @@ mod tests {
#[test]
fn test_parse_minus_zero_int() {
assert_eq!(parse("-0e0"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0e-0"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0e1"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0e+1"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0.0e1"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0x0"), Number::Int(ExtendedBigInt::MinusZero));
assert_eq!(parse("-0e0"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0e-0"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0e1"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0e+1"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0.0e1"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0x0"), ExtendedBigDecimal::MinusZero);
}
#[test]
fn test_parse_minus_zero_float() {
assert_eq!(parse("-0.0"), Number::Float(ExtendedBigDecimal::MinusZero));
assert_eq!(parse("-0e-1"), Number::Float(ExtendedBigDecimal::MinusZero));
assert_eq!(
parse("-0.0e-1"),
Number::Float(ExtendedBigDecimal::MinusZero)
);
assert_eq!(parse("-0.0"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0e-1"), ExtendedBigDecimal::MinusZero);
assert_eq!(parse("-0.0e-1"), ExtendedBigDecimal::MinusZero);
}
#[test]
fn test_parse_big_int() {
assert_eq!(parse("0"), Number::Int(ExtendedBigInt::zero()));
assert_eq!(parse("0.1e1"), Number::Int(ExtendedBigInt::one()));
assert_eq!(parse("0"), ExtendedBigDecimal::zero());
assert_eq!(parse("0.1e1"), ExtendedBigDecimal::one());
assert_eq!(
parse("1.0e1"),
Number::Int(ExtendedBigInt::BigInt("10".parse::<BigInt>().unwrap()))
ExtendedBigDecimal::BigDecimal("10".parse::<BigDecimal>().unwrap())
);
}
#[test]
fn test_parse_hexadecimal_big_int() {
assert_eq!(parse("0x0"), Number::Int(ExtendedBigInt::zero()));
assert_eq!(parse("0x0"), ExtendedBigDecimal::zero());
assert_eq!(
parse("0x10"),
Number::Int(ExtendedBigInt::BigInt("16".parse::<BigInt>().unwrap()))
ExtendedBigDecimal::BigDecimal("16".parse::<BigDecimal>().unwrap())
);
}
@@ -433,56 +420,34 @@ mod tests {
fn test_parse_big_decimal() {
assert_eq!(
parse("0.0"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"0.0".parse::<BigDecimal>().unwrap()
))
ExtendedBigDecimal::BigDecimal("0.0".parse::<BigDecimal>().unwrap())
);
assert_eq!(
parse(".0"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"0.0".parse::<BigDecimal>().unwrap()
))
ExtendedBigDecimal::BigDecimal("0.0".parse::<BigDecimal>().unwrap())
);
assert_eq!(
parse("1.0"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"1.0".parse::<BigDecimal>().unwrap()
))
ExtendedBigDecimal::BigDecimal("1.0".parse::<BigDecimal>().unwrap())
);
assert_eq!(
parse("10e-1"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"1.0".parse::<BigDecimal>().unwrap()
))
ExtendedBigDecimal::BigDecimal("1.0".parse::<BigDecimal>().unwrap())
);
assert_eq!(
parse("-1e-3"),
Number::Float(ExtendedBigDecimal::BigDecimal(
"-0.001".parse::<BigDecimal>().unwrap()
))
ExtendedBigDecimal::BigDecimal("-0.001".parse::<BigDecimal>().unwrap())
);
}
#[test]
fn test_parse_inf() {
assert_eq!(parse("inf"), Number::Float(ExtendedBigDecimal::Infinity));
assert_eq!(
parse("infinity"),
Number::Float(ExtendedBigDecimal::Infinity)
);
assert_eq!(parse("+inf"), Number::Float(ExtendedBigDecimal::Infinity));
assert_eq!(
parse("+infinity"),
Number::Float(ExtendedBigDecimal::Infinity)
);
assert_eq!(
parse("-inf"),
Number::Float(ExtendedBigDecimal::MinusInfinity)
);
assert_eq!(
parse("-infinity"),
Number::Float(ExtendedBigDecimal::MinusInfinity)
);
assert_eq!(parse("inf"), ExtendedBigDecimal::Infinity);
assert_eq!(parse("infinity"), ExtendedBigDecimal::Infinity);
assert_eq!(parse("+inf"), ExtendedBigDecimal::Infinity);
assert_eq!(parse("+infinity"), ExtendedBigDecimal::Infinity);
assert_eq!(parse("-inf"), ExtendedBigDecimal::MinusInfinity);
assert_eq!(parse("-infinity"), ExtendedBigDecimal::MinusInfinity);
}
#[test]
+33 -128
View File
@@ -2,28 +2,22 @@
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse
// spell-checker:ignore (ToDO) extendedbigdecimal numberparse
use std::io::{stdout, ErrorKind, Write};
use std::process::exit;
use clap::{crate_version, Arg, ArgAction, Command};
use num_traits::Zero;
use num_traits::{ToPrimitive, Zero};
use uucore::error::FromIo;
use uucore::error::UResult;
use uucore::memo::printf;
use uucore::show;
use uucore::error::{FromIo, UResult};
use uucore::format::{num_format, Format};
use uucore::{format_usage, help_about, help_usage};
mod error;
mod extendedbigdecimal;
mod extendedbigint;
mod number;
mod numberparse;
use crate::error::SeqError;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
use crate::number::Number;
use crate::number::PreciseNumber;
const ABOUT: &str = help_about!("seq.md");
@@ -44,11 +38,6 @@ struct SeqOptions<'a> {
format: Option<&'a str>,
}
/// A range of integers.
///
/// The elements are (first, increment, last).
type RangeInt = (ExtendedBigInt, ExtendedBigInt, ExtendedBigInt);
/// A range of floats.
///
/// The elements are (first, increment, last).
@@ -119,32 +108,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.num_fractional_digits
.max(increment.num_fractional_digits);
let result = match (first.number, increment.number, last.number) {
(Number::Int(first), Number::Int(increment), last) => {
let last = last.round_towards(&first);
print_seq_integers(
(first, increment, last),
&options.separator,
&options.terminator,
options.equal_width,
padding,
options.format,
)
let format = match options.format {
Some(f) => {
let f = Format::<num_format::Float>::parse(f)?;
Some(f)
}
(first, increment, last) => print_seq(
(
first.into_extended_big_decimal(),
increment.into_extended_big_decimal(),
last.into_extended_big_decimal(),
),
largest_dec,
&options.separator,
&options.terminator,
options.equal_width,
padding,
options.format,
),
None => None,
};
let result = print_seq(
(first.number, increment.number, last.number),
largest_dec,
&options.separator,
&options.terminator,
options.equal_width,
padding,
&format,
);
match result {
Ok(_) => Ok(()),
Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()),
@@ -216,28 +195,6 @@ fn write_value_float(
write!(writer, "{value_as_str}")
}
/// Write a big int formatted according to the given parameters.
fn write_value_int(
writer: &mut impl Write,
value: &ExtendedBigInt,
width: usize,
pad: bool,
) -> std::io::Result<()> {
let value_as_str = if pad {
if *value == ExtendedBigInt::MinusZero {
format!("{value:0<width$}")
} else {
format!("{value:>0width$}")
}
} else {
format!("{value}")
};
write!(writer, "{value_as_str}")
}
// TODO `print_seq()` and `print_seq_integers()` are nearly identical,
// they could be refactored into a single more general function.
/// Floating point based code path
fn print_seq(
range: RangeFloat,
@@ -246,13 +203,17 @@ fn print_seq(
terminator: &str,
pad: bool,
padding: usize,
format: Option<&str>,
format: &Option<Format<num_format::Float>>,
) -> std::io::Result<()> {
let stdout = stdout();
let mut stdout = stdout.lock();
let (first, increment, last) = range;
let mut value = first;
let padding = if pad { padding + 1 + largest_dec } else { 0 };
let padding = if pad {
padding + if largest_dec > 0 { largest_dec + 1 } else { 0 }
} else {
0
};
let mut is_first_iteration = true;
while !done_printing(&value, &increment, &last) {
if !is_first_iteration {
@@ -270,13 +231,16 @@ fn print_seq(
// it as a string and ultimately writing to `stdout`. We
// shouldn't have to do so much converting back and forth via
// strings.
match format {
match &format {
Some(f) => {
let s = format!("{value}");
if let Err(x) = printf(f, &[s]) {
show!(x);
exit(1);
}
let float = match &value {
ExtendedBigDecimal::BigDecimal(bd) => bd.to_f64().unwrap(),
ExtendedBigDecimal::Infinity => f64::INFINITY,
ExtendedBigDecimal::MinusInfinity => f64::NEG_INFINITY,
ExtendedBigDecimal::MinusZero => -0.0,
ExtendedBigDecimal::Nan => f64::NAN,
};
f.fmt(&mut stdout, float)?;
}
None => write_value_float(&mut stdout, &value, padding, largest_dec)?,
}
@@ -290,62 +254,3 @@ fn print_seq(
stdout.flush()?;
Ok(())
}
/// Print an integer sequence.
///
/// This function prints a sequence of integers defined by `range`,
/// which defines the first integer, last integer, and increment of the
/// range. The `separator` is inserted between each integer and
/// `terminator` is inserted at the end.
///
/// The `pad` parameter indicates whether to pad numbers to the width
/// given in `padding`.
///
/// If `is_first_minus_zero` is `true`, then the `first` parameter is
/// printed as if it were negative zero, even though no such number
/// exists as an integer (negative zero only exists for floating point
/// numbers). Only set this to `true` if `first` is actually zero.
fn print_seq_integers(
range: RangeInt,
separator: &str,
terminator: &str,
pad: bool,
padding: usize,
format: Option<&str>,
) -> std::io::Result<()> {
let stdout = stdout();
let mut stdout = stdout.lock();
let (first, increment, last) = range;
let mut value = first;
let mut is_first_iteration = true;
while !done_printing(&value, &increment, &last) {
if !is_first_iteration {
write!(stdout, "{separator}")?;
}
// If there was an argument `-f FORMAT`, then use that format
// template instead of the default formatting strategy.
//
// The `printf()` function takes in the template and
// the current value and writes the result to `stdout`.
//
// TODO See similar comment about formatting in `print_seq()`.
match format {
Some(f) => {
let s = format!("{value}");
if let Err(x) = printf(f, &[s]) {
show!(x);
exit(1);
}
}
None => write_value_int(&mut stdout, &value, padding, pad)?,
}
// TODO Implement augmenting addition.
value = value + increment.clone();
is_first_iteration = false;
}
if !is_first_iteration {
write!(stdout, "{terminator}")?;
}
Ok(())
}
+1 -1
View File
@@ -77,7 +77,7 @@ entries = ["libc"]
fs = ["dunce", "libc", "winapi-util", "windows-sys"]
fsext = ["libc", "time", "windows-sys"]
lines = []
memo = ["itertools"]
format = ["itertools"]
mode = ["libc"]
perms = ["libc", "walkdir"]
pipes = []
+2 -4
View File
@@ -8,14 +8,14 @@
pub mod backup_control;
#[cfg(feature = "encoding")]
pub mod encoding;
#[cfg(feature = "format")]
pub mod format;
#[cfg(feature = "fs")]
pub mod fs;
#[cfg(feature = "fsext")]
pub mod fsext;
#[cfg(feature = "lines")]
pub mod lines;
#[cfg(feature = "memo")]
pub mod memo;
#[cfg(feature = "quoting-style")]
pub mod quoting_style;
#[cfg(feature = "ranges")]
@@ -24,8 +24,6 @@ pub mod ranges;
pub mod ringbuffer;
#[cfg(feature = "sum")]
pub mod sum;
#[cfg(feature = "memo")]
mod tokenize;
#[cfg(feature = "update-control")]
pub mod update_control;
#[cfg(feature = "version-cmp")]
@@ -0,0 +1,152 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use os_display::Quotable;
use crate::{error::set_exit_code, show_warning};
/// An argument for formatting
///
/// Each of these variants is only accepted by their respective directives. For
/// example, [`FormatArgument::Char`] requires a `%c` directive.
///
/// The [`FormatArgument::Unparsed`] variant contains a string that can be
/// parsed into other types. This is used by the `printf` utility.
#[derive(Clone, Debug)]
pub enum FormatArgument {
Char(char),
String(String),
UnsignedInt(u64),
SignedInt(i64),
Float(f64),
/// Special argument that gets coerced into the other variants
Unparsed(String),
}
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 '\0';
};
let None = chars.next() else {
return '\0';
};
c
}
_ => '\0',
}
}
fn get_u64(&mut self) -> u64 {
let Some(next) = self.next() else {
return 0;
};
match next {
FormatArgument::UnsignedInt(n) => *n,
FormatArgument::Unparsed(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,
}
}
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.
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,
}
}
fn get_f64(&mut self) -> f64 {
let Some(next) = self.next() else {
return 0.0;
};
match next {
FormatArgument::Float(n) => *n,
FormatArgument::Unparsed(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,
}
}
fn get_str(&mut self) -> &'a str {
match self.next() {
Some(FormatArgument::Unparsed(s) | FormatArgument::String(s)) => s,
_ => "",
}
}
}
@@ -0,0 +1,135 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//! Parsing of escape sequences
#[derive(Debug)]
pub enum EscapedChar {
/// A single byte
Byte(u8),
/// A unicode character
Char(char),
/// A character prefixed with a backslash (i.e. an invalid escape sequence)
Backslash(u8),
/// Specifies that the string should stop (`\c`)
End,
}
#[repr(u8)]
#[derive(Clone, Copy)]
enum Base {
Oct = 8,
Hex = 16,
}
impl Base {
fn max_digits(&self) -> u8 {
match self {
Self::Oct => 3,
Self::Hex => 2,
}
}
fn convert_digit(&self, c: u8) -> Option<u8> {
match self {
Self::Oct => {
if matches!(c, b'0'..=b'7') {
Some(c - b'0')
} else {
None
}
}
Self::Hex => match c {
b'0'..=b'9' => Some(c - b'0'),
b'A'..=b'F' => Some(c - b'A' + 10),
b'a'..=b'f' => Some(c - b'a' + 10),
_ => None,
},
}
}
}
/// Parse the numeric part of the `\xHHH` and `\0NNN` escape sequences
fn parse_code(input: &mut &[u8], base: Base) -> Option<u8> {
// All arithmetic on `ret` needs to be wrapping, because octal input can
// take 3 digits, which is 9 bits, and therefore more than what fits in a
// `u8`. GNU just seems to wrap these values.
// Note that if we instead make `ret` a `u32` and use `char::from_u32` will
// yield incorrect results because it will interpret values larger than
// `u8::MAX` as unicode.
let [c, rest @ ..] = input else { return None };
let mut ret = base.convert_digit(*c)?;
*input = rest;
for _ in 1..base.max_digits() {
let [c, rest @ ..] = input else { break };
let Some(n) = base.convert_digit(*c) else {
break;
};
ret = ret.wrapping_mul(base as u8).wrapping_add(n);
*input = rest;
}
Some(ret)
}
// spell-checker:disable-next
/// Parse `\uHHHH` and `\UHHHHHHHH`
// TODO: This should print warnings and possibly halt execution when it fails to parse
// TODO: If the character cannot be converted to u32, the input should be printed.
fn parse_unicode(input: &mut &[u8], digits: u8) -> Option<char> {
let (c, rest) = input.split_first()?;
let mut ret = Base::Hex.convert_digit(*c)? as u32;
*input = rest;
for _ in 1..digits {
let (c, rest) = input.split_first()?;
let n = Base::Hex.convert_digit(*c)?;
ret = ret.wrapping_mul(Base::Hex as u32).wrapping_add(n as u32);
*input = rest;
}
char::from_u32(ret)
}
pub fn parse_escape_code(rest: &mut &[u8]) -> EscapedChar {
if let [c, new_rest @ ..] = rest {
// This is for the \NNN syntax for octal sequences.
// Note that '0' is intentionally omitted because that
// would be the \0NNN syntax.
if let b'1'..=b'7' = c {
if let Some(parsed) = parse_code(rest, Base::Oct) {
return EscapedChar::Byte(parsed);
}
}
*rest = new_rest;
match c {
b'\\' => EscapedChar::Byte(b'\\'),
b'a' => EscapedChar::Byte(b'\x07'),
b'b' => EscapedChar::Byte(b'\x08'),
b'c' => EscapedChar::End,
b'e' => EscapedChar::Byte(b'\x1b'),
b'f' => EscapedChar::Byte(b'\x0c'),
b'n' => EscapedChar::Byte(b'\n'),
b'r' => EscapedChar::Byte(b'\r'),
b't' => EscapedChar::Byte(b'\t'),
b'v' => EscapedChar::Byte(b'\x0b'),
b'x' => {
if let Some(c) = parse_code(rest, Base::Hex) {
EscapedChar::Byte(c)
} else {
EscapedChar::Backslash(b'x')
}
}
b'0' => EscapedChar::Byte(parse_code(rest, Base::Oct).unwrap_or(b'\0')),
b'u' => EscapedChar::Char(parse_unicode(rest, 4).unwrap_or('\0')),
b'U' => EscapedChar::Char(parse_unicode(rest, 8).unwrap_or('\0')),
c => EscapedChar::Backslash(*c),
}
} else {
EscapedChar::Byte(b'\\')
}
}
+334
View File
@@ -0,0 +1,334 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//! `printf`-style formatting
//!
//! Rust has excellent formatting capabilities, but the coreutils require very
//! specific formatting that needs to work exactly like the GNU utilities.
//! Naturally, the GNU behavior is based on the C `printf` functionality.
//!
//! Additionally, we need support for escape sequences for the `printf` utility.
//!
//! The [`printf`] and [`sprintf`] functions closely match the behavior of the
//! corresponding C functions: the former renders a formatted string
//! to stdout, the latter renders to a new [`String`] object.
//!
//! There are three kinds of parsing that we might want to do:
//!
//! 1. Parse only `printf` directives (for e.g. `seq`, `dd`)
//! 2. Parse only escape sequences (for e.g. `echo`)
//! 3. Parse both `printf` specifiers and escape sequences (for e.g. `printf`)
//!
//! This module aims to combine all three use cases. An iterator parsing each
//! of these cases is provided by [`parse_escape_only`], [`parse_spec_only`]
//! and [`parse_spec_and_escape`], respectively.
//!
//! There is a special [`Format`] type, which can be used to parse a format
//! string containing exactly one directive and does not use any `*` in that
//! directive. This format can be printed in a type-safe manner without failing
//! (modulo IO errors).
mod argument;
mod escape;
pub mod num_format;
mod spec;
pub use argument::*;
use spec::Spec;
use std::{
error::Error,
fmt::Display,
io::{stdout, Write},
ops::ControlFlow,
};
use crate::error::UError;
use self::{
escape::{parse_escape_code, EscapedChar},
num_format::Formatter,
};
#[derive(Debug)]
pub enum FormatError {
SpecError(Vec<u8>),
IoError(std::io::Error),
NoMoreArguments,
InvalidArgument(FormatArgument),
TooManySpecs,
NeedAtLeastOneSpec,
WrongSpecType,
}
impl Error for FormatError {}
impl UError for FormatError {}
impl From<std::io::Error> for FormatError {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
impl Display for FormatError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SpecError(s) => write!(
f,
"%{}: invalid conversion specification",
String::from_utf8_lossy(s)
),
// TODO: The next two should print the spec as well
Self::TooManySpecs => write!(f, "format has too many % directives"),
Self::NeedAtLeastOneSpec => write!(f, "format has no % directive"),
// TODO: Error message below needs some work
Self::WrongSpecType => write!(f, "wrong % directive type was given"),
Self::IoError(_) => write!(f, "io error"),
Self::NoMoreArguments => write!(f, "no more arguments"),
Self::InvalidArgument(_) => write!(f, "invalid argument"),
}
}
}
/// A single item to format
pub enum FormatItem<C: FormatChar> {
/// A format specifier
Spec(Spec),
/// A single character
Char(C),
}
pub trait FormatChar {
fn write(&self, writer: impl Write) -> std::io::Result<ControlFlow<()>>;
}
impl FormatChar for u8 {
fn write(&self, mut writer: impl Write) -> std::io::Result<ControlFlow<()>> {
writer.write_all(&[*self])?;
Ok(ControlFlow::Continue(()))
}
}
impl FormatChar for EscapedChar {
fn write(&self, mut writer: impl Write) -> std::io::Result<ControlFlow<()>> {
match self {
Self::Byte(c) => {
writer.write_all(&[*c])?;
}
Self::Char(c) => {
write!(writer, "{c}")?;
}
Self::Backslash(c) => {
writer.write_all(&[b'\\', *c])?;
}
Self::End => return Ok(ControlFlow::Break(())),
}
Ok(ControlFlow::Continue(()))
}
}
impl<C: FormatChar> FormatItem<C> {
pub fn write<'a>(
&self,
writer: impl Write,
args: &mut impl Iterator<Item = &'a FormatArgument>,
) -> Result<ControlFlow<()>, FormatError> {
match self {
Self::Spec(spec) => spec.write(writer, args)?,
Self::Char(c) => return c.write(writer).map_err(FormatError::IoError),
};
Ok(ControlFlow::Continue(()))
}
}
/// Parse a format string containing % directives and escape sequences
pub fn parse_spec_and_escape(
fmt: &[u8],
) -> impl Iterator<Item = Result<FormatItem<EscapedChar>, FormatError>> + '_ {
let mut current = fmt;
std::iter::from_fn(move || match current {
[] => None,
[b'%', b'%', rest @ ..] => {
current = rest;
Some(Ok(FormatItem::Char(EscapedChar::Byte(b'%'))))
}
[b'%', rest @ ..] => {
current = rest;
let spec = match Spec::parse(&mut current) {
Ok(spec) => spec,
Err(slice) => return Some(Err(FormatError::SpecError(slice.to_vec()))),
};
Some(Ok(FormatItem::Spec(spec)))
}
[b'\\', rest @ ..] => {
current = rest;
Some(Ok(FormatItem::Char(parse_escape_code(&mut current))))
}
[c, rest @ ..] => {
current = rest;
Some(Ok(FormatItem::Char(EscapedChar::Byte(*c))))
}
})
}
/// Parse a format string containing % directives
pub fn parse_spec_only(
fmt: &[u8],
) -> impl Iterator<Item = Result<FormatItem<u8>, FormatError>> + '_ {
let mut current = fmt;
std::iter::from_fn(move || match current {
[] => None,
[b'%', b'%', rest @ ..] => {
current = rest;
Some(Ok(FormatItem::Char(b'%')))
}
[b'%', rest @ ..] => {
current = rest;
let spec = match Spec::parse(&mut current) {
Ok(spec) => spec,
Err(slice) => return Some(Err(FormatError::SpecError(slice.to_vec()))),
};
Some(Ok(FormatItem::Spec(spec)))
}
[c, rest @ ..] => {
current = rest;
Some(Ok(FormatItem::Char(*c)))
}
})
}
/// Parse a format string containing escape sequences
pub fn parse_escape_only(fmt: &[u8]) -> impl Iterator<Item = EscapedChar> + '_ {
let mut current = fmt;
std::iter::from_fn(move || match current {
[] => None,
[b'\\', rest @ ..] => {
current = rest;
Some(parse_escape_code(&mut current))
}
[c, rest @ ..] => {
current = rest;
Some(EscapedChar::Byte(*c))
}
})
}
/// Write a formatted string to stdout.
///
/// `format_string` contains the template and `args` contains the
/// arguments to render into the template.
///
/// See also [`sprintf`], which creates a new formatted [`String`].
///
/// # Examples
///
/// ```rust
/// use uucore::format::{printf, FormatArgument};
///
/// printf("hello %s", &[FormatArgument::String("world".into())]).unwrap();
/// // prints "hello world"
/// ```
pub fn printf<'a>(
format_string: impl AsRef<[u8]>,
arguments: impl IntoIterator<Item = &'a FormatArgument>,
) -> Result<(), FormatError> {
printf_writer(stdout(), format_string, arguments)
}
fn printf_writer<'a>(
mut writer: impl Write,
format_string: impl AsRef<[u8]>,
args: impl IntoIterator<Item = &'a FormatArgument>,
) -> Result<(), FormatError> {
let mut args = args.into_iter();
for item in parse_spec_only(format_string.as_ref()) {
item?.write(&mut writer, &mut args)?;
}
Ok(())
}
/// Create a new formatted string.
///
/// `format_string` contains the template and `args` contains the
/// arguments to render into the template.
///
/// See also [`printf`], which prints to stdout.
///
/// # Examples
///
/// ```rust
/// use uucore::format::{sprintf, FormatArgument};
///
/// let s = sprintf("hello %s", &[FormatArgument::String("world".into())]).unwrap();
/// let s = std::str::from_utf8(&s).unwrap();
/// assert_eq!(s, "hello world");
/// ```
pub fn sprintf<'a>(
format_string: impl AsRef<[u8]>,
arguments: impl IntoIterator<Item = &'a FormatArgument>,
) -> Result<Vec<u8>, FormatError> {
let mut writer = Vec::new();
printf_writer(&mut writer, format_string, arguments)?;
Ok(writer)
}
/// A parsed format for a single float value
///
/// This is used by `seq`. It can be constructed with [`Format::parse`]
/// and can write a value with [`Format::fmt`].
///
/// It can only accept a single specification without any asterisk parameters.
/// If it does get more specifications, it will return an error.
pub struct Format<F: Formatter> {
prefix: Vec<u8>,
suffix: Vec<u8>,
formatter: F,
}
impl<F: Formatter> Format<F> {
pub fn parse(format_string: impl AsRef<[u8]>) -> Result<Self, FormatError> {
let mut iter = parse_spec_only(format_string.as_ref());
let mut prefix = Vec::new();
let mut spec = None;
for item in &mut iter {
match item? {
FormatItem::Spec(s) => {
spec = Some(s);
break;
}
FormatItem::Char(c) => prefix.push(c),
}
}
let Some(spec) = spec else {
return Err(FormatError::NeedAtLeastOneSpec);
};
let formatter = F::try_from_spec(spec)?;
let mut suffix = Vec::new();
for item in &mut iter {
match item? {
FormatItem::Spec(_) => {
return Err(FormatError::TooManySpecs);
}
FormatItem::Char(c) => suffix.push(c),
}
}
Ok(Self {
prefix,
suffix,
formatter,
})
}
pub fn fmt(&self, mut w: impl Write, f: F::Input) -> std::io::Result<()> {
w.write_all(&self.prefix)?;
self.formatter.fmt(&mut w, f)?;
w.write_all(&self.suffix)?;
Ok(())
}
}
File diff suppressed because it is too large Load Diff
+462
View File
@@ -0,0 +1,462 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (vars) intmax ptrdiff
use crate::quoting_style::{escape_name, QuotingStyle};
use super::{
num_format::{
self, Case, FloatVariant, ForceDecimal, Formatter, NumberAlignment, PositiveSign, Prefix,
UnsignedIntVariant,
},
parse_escape_only, ArgumentIter, FormatChar, FormatError,
};
use std::{fmt::Display, io::Write, ops::ControlFlow};
/// A parsed specification for formatting a value
///
/// This might require more than one argument to resolve width or precision
/// values that are given as `*`.
#[derive(Debug)]
pub enum Spec {
Char {
width: Option<CanAsterisk<usize>>,
align_left: bool,
},
String {
precision: Option<CanAsterisk<usize>>,
width: Option<CanAsterisk<usize>>,
align_left: bool,
},
EscapedString,
QuotedString,
SignedInt {
width: Option<CanAsterisk<usize>>,
precision: Option<CanAsterisk<usize>>,
positive_sign: PositiveSign,
alignment: NumberAlignment,
},
UnsignedInt {
variant: UnsignedIntVariant,
width: Option<CanAsterisk<usize>>,
precision: Option<CanAsterisk<usize>>,
alignment: NumberAlignment,
},
Float {
variant: FloatVariant,
case: Case,
force_decimal: ForceDecimal,
width: Option<CanAsterisk<usize>>,
positive_sign: PositiveSign,
alignment: NumberAlignment,
precision: Option<CanAsterisk<usize>>,
},
}
/// Precision and width specified might use an asterisk to indicate that they are
/// determined by an argument.
#[derive(Clone, Copy, Debug)]
pub enum CanAsterisk<T> {
Fixed(T),
Asterisk,
}
/// Size of the expected type (ignored)
///
/// We ignore this parameter entirely, but we do parse it.
/// It could be used in the future if the need arises.
enum Length {
/// signed/unsigned char ("hh")
Char,
/// signed/unsigned short int ("h")
Short,
/// signed/unsigned long int ("l")
Long,
/// signed/unsigned long long int ("ll")
LongLong,
/// intmax_t ("j")
IntMaxT,
/// size_t ("z")
SizeT,
/// ptrdiff_t ("t")
PtfDiffT,
/// long double ("L")
LongDouble,
}
impl Spec {
pub fn parse<'a>(rest: &mut &'a [u8]) -> Result<Self, &'a [u8]> {
// Based on the C++ reference, the spec format looks like:
//
// %[flags][width][.precision][length]specifier
//
// However, we have already parsed the '%'.
let mut index = 0;
let start = *rest;
let mut minus = false;
let mut plus = false;
let mut space = false;
let mut hash = false;
let mut zero = false;
while let Some(x) = rest.get(index) {
match x {
b'-' => minus = true,
b'+' => plus = true,
b' ' => space = true,
b'#' => hash = true,
b'0' => zero = true,
_ => break,
}
index += 1;
}
let alignment = match (minus, zero) {
(true, _) => NumberAlignment::Left,
(false, true) => NumberAlignment::RightZero,
(false, false) => NumberAlignment::RightSpace,
};
let positive_sign = match (plus, space) {
(true, _) => PositiveSign::Plus,
(false, true) => PositiveSign::Space,
(false, false) => PositiveSign::None,
};
let width = eat_asterisk_or_number(rest, &mut index);
let precision = if let Some(b'.') = rest.get(index) {
index += 1;
Some(eat_asterisk_or_number(rest, &mut index).unwrap_or(CanAsterisk::Fixed(0)))
} else {
None
};
// We ignore the length. It's not really relevant to printf
let _ = Self::parse_length(rest, &mut index);
let Some(type_spec) = rest.get(index) else {
return Err(&start[..index]);
};
index += 1;
*rest = &start[index..];
Ok(match type_spec {
// GNU accepts minus, plus and space even though they are not used
b'c' => {
if hash || precision.is_some() {
return Err(&start[..index]);
}
Self::Char {
width,
align_left: minus,
}
}
b's' => {
if hash {
return Err(&start[..index]);
}
Self::String {
precision,
width,
align_left: minus,
}
}
b'b' => {
if hash || minus || plus || space || width.is_some() || precision.is_some() {
return Err(&start[..index]);
}
Self::EscapedString
}
b'q' => {
if hash || minus || plus || space || width.is_some() || precision.is_some() {
return Err(&start[..index]);
}
Self::QuotedString
}
b'd' | b'i' => {
if hash {
return Err(&start[..index]);
}
Self::SignedInt {
width,
precision,
alignment,
positive_sign,
}
}
c @ (b'u' | b'o' | b'x' | b'X') => {
// Normal unsigned integer cannot have a prefix
if *c == b'u' && hash {
return Err(&start[..index]);
}
let prefix = match hash {
false => Prefix::No,
true => Prefix::Yes,
};
let variant = match c {
b'u' => UnsignedIntVariant::Decimal,
b'o' => UnsignedIntVariant::Octal(prefix),
b'x' => UnsignedIntVariant::Hexadecimal(Case::Lowercase, prefix),
b'X' => UnsignedIntVariant::Hexadecimal(Case::Uppercase, prefix),
_ => unreachable!(),
};
Self::UnsignedInt {
variant,
precision,
width,
alignment,
}
}
c @ (b'f' | b'F' | b'e' | b'E' | b'g' | b'G' | b'a' | b'A') => Self::Float {
width,
precision,
variant: match c {
b'f' | b'F' => FloatVariant::Decimal,
b'e' | b'E' => FloatVariant::Scientific,
b'g' | b'G' => FloatVariant::Shortest,
b'a' | b'A' => FloatVariant::Hexadecimal,
_ => unreachable!(),
},
force_decimal: match hash {
false => ForceDecimal::No,
true => ForceDecimal::Yes,
},
case: match c.is_ascii_uppercase() {
false => Case::Lowercase,
true => Case::Uppercase,
},
alignment,
positive_sign,
},
_ => return Err(&start[..index]),
})
}
fn parse_length(rest: &mut &[u8], index: &mut usize) -> Option<Length> {
// Parse 0..N length options, keep the last one
// Even though it is just ignored. We might want to use it later and we
// should parse those characters.
//
// TODO: This needs to be configurable: `seq` accepts only one length
// param
let mut length = None;
loop {
let new_length = rest.get(*index).and_then(|c| {
Some(match c {
b'h' => {
if let Some(b'h') = rest.get(*index + 1) {
*index += 1;
Length::Char
} else {
Length::Short
}
}
b'l' => {
if let Some(b'l') = rest.get(*index + 1) {
*index += 1;
Length::Long
} else {
Length::LongLong
}
}
b'j' => Length::IntMaxT,
b'z' => Length::SizeT,
b't' => Length::PtfDiffT,
b'L' => Length::LongDouble,
_ => return None,
})
});
if new_length.is_some() {
*index += 1;
length = new_length;
} else {
break;
}
}
length
}
pub fn write<'a>(
&self,
mut writer: impl Write,
mut args: impl ArgumentIter<'a>,
) -> Result<(), FormatError> {
match self {
Self::Char { width, align_left } => {
let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0);
write_padded(writer, args.get_char(), width, false, *align_left)
}
Self::String {
width,
align_left,
precision,
} => {
let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0);
// GNU does do this truncation on a byte level, see for instance:
// printf "%.1s" 🙃
// >
// For now, we let printf panic when we truncate within a code point.
// TODO: We need to not use Rust's formatting for aligning the output,
// so that we can just write bytes to stdout without panicking.
let precision = resolve_asterisk(*precision, &mut args)?;
let s = args.get_str();
let truncated = match precision {
Some(p) if p < s.len() => &s[..p],
_ => s,
};
write_padded(writer, truncated, width, false, *align_left)
}
Self::EscapedString => {
let s = args.get_str();
let mut parsed = Vec::new();
for c in parse_escape_only(s.as_bytes()) {
match c.write(&mut parsed)? {
ControlFlow::Continue(()) => {}
ControlFlow::Break(()) => {
// TODO: This should break the _entire execution_ of printf
break;
}
};
}
writer.write_all(&parsed).map_err(FormatError::IoError)
}
Self::QuotedString => {
let s = args.get_str();
writer
.write_all(
escape_name(
s.as_ref(),
&QuotingStyle::Shell {
escape: true,
always_quote: false,
show_control: false,
},
)
.as_bytes(),
)
.map_err(FormatError::IoError)
}
Self::SignedInt {
width,
precision,
positive_sign,
alignment,
} => {
let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0);
let precision = resolve_asterisk(*precision, &mut args)?.unwrap_or(0);
let i = args.get_i64();
num_format::SignedInt {
width,
precision,
positive_sign: *positive_sign,
alignment: *alignment,
}
.fmt(writer, i)
.map_err(FormatError::IoError)
}
Self::UnsignedInt {
variant,
width,
precision,
alignment,
} => {
let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0);
let precision = resolve_asterisk(*precision, &mut args)?.unwrap_or(0);
let i = args.get_u64();
num_format::UnsignedInt {
variant: *variant,
precision,
width,
alignment: *alignment,
}
.fmt(writer, i)
.map_err(FormatError::IoError)
}
Self::Float {
variant,
case,
force_decimal,
width,
positive_sign,
alignment,
precision,
} => {
let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0);
let precision = resolve_asterisk(*precision, &mut args)?.unwrap_or(6);
let f = args.get_f64();
num_format::Float {
width,
precision,
variant: *variant,
case: *case,
force_decimal: *force_decimal,
positive_sign: *positive_sign,
alignment: *alignment,
}
.fmt(writer, f)
.map_err(FormatError::IoError)
}
}
}
}
fn resolve_asterisk<'a>(
option: Option<CanAsterisk<usize>>,
mut args: impl ArgumentIter<'a>,
) -> Result<Option<usize>, FormatError> {
Ok(match option {
None => None,
Some(CanAsterisk::Asterisk) => Some(usize::try_from(args.get_u64()).ok().unwrap_or(0)),
Some(CanAsterisk::Fixed(w)) => Some(w),
})
}
fn write_padded(
mut writer: impl Write,
text: impl Display,
width: usize,
pad_zero: bool,
left: bool,
) -> Result<(), FormatError> {
match (left, pad_zero) {
(false, false) => write!(writer, "{text: >width$}"),
(false, true) => write!(writer, "{text:0>width$}"),
// 0 is ignored if we pad left.
(true, _) => write!(writer, "{text: <width$}"),
}
.map_err(FormatError::IoError)
}
fn eat_asterisk_or_number(rest: &mut &[u8], index: &mut usize) -> Option<CanAsterisk<usize>> {
if let Some(b'*') = rest.get(*index) {
*index += 1;
Some(CanAsterisk::Asterisk)
} else {
eat_number(rest, index).map(CanAsterisk::Fixed)
}
}
fn eat_number(rest: &mut &[u8], index: &mut usize) -> Option<usize> {
match rest[*index..].iter().position(|b| !b.is_ascii_digit()) {
None | Some(0) => None,
Some(i) => {
// TODO: This might need to handle errors better
// For example in case of overflow.
let parsed = std::str::from_utf8(&rest[*index..(*index + i)])
.unwrap()
.parse()
.unwrap();
*index += i;
Some(parsed)
}
}
}
-179
View File
@@ -1,179 +0,0 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//! Main entry point for our implementation of printf.
//!
//! The [`printf`] and [`sprintf`] closely match the behavior of the
//! corresponding C functions: the former renders a formatted string
//! to stdout, the latter renders to a new [`String`] object.
use crate::display::Quotable;
use crate::error::{UResult, USimpleError};
use crate::features::tokenize::sub::SubParser;
use crate::features::tokenize::token::Token;
use crate::features::tokenize::unescaped_text::UnescapedText;
use crate::show_warning;
use itertools::put_back_n;
use std::io::{stdout, Cursor, Write};
use std::iter::Peekable;
use std::slice::Iter;
/// Memo runner of printf
/// Takes a format string and arguments
/// 1. tokenize format string into tokens, consuming
/// any subst. arguments along the way.
/// 2. feeds remaining arguments into function
/// that prints tokens.
struct Memo {
tokens: Vec<Token>,
}
fn warn_excess_args(first_arg: &str) {
show_warning!(
"ignoring excess arguments, starting with {}",
first_arg.quote()
);
}
impl Memo {
fn new<W>(
writer: &mut W,
pf_string: &str,
pf_args_it: &mut Peekable<Iter<String>>,
) -> UResult<Self>
where
W: Write,
{
let mut pm = Self { tokens: Vec::new() };
let mut it = put_back_n(pf_string.chars());
let mut has_sub = false;
loop {
if let Some(x) = UnescapedText::from_it_core(writer, &mut it, false) {
pm.tokens.push(x);
}
if let Some(x) = SubParser::from_it(writer, &mut it, pf_args_it)? {
if !has_sub {
has_sub = true;
}
pm.tokens.push(x);
}
if let Some(x) = it.next() {
it.put_back(x);
} else {
break;
}
}
if !has_sub {
let mut drain = false;
if let Some(first_arg) = pf_args_it.peek() {
warn_excess_args(first_arg);
drain = true;
}
if drain {
loop {
// drain remaining args;
if pf_args_it.next().is_none() {
break;
}
}
}
}
Ok(pm)
}
fn apply<W>(&self, writer: &mut W, pf_args_it: &mut Peekable<Iter<String>>)
where
W: Write,
{
for tkn in &self.tokens {
tkn.write(writer, pf_args_it);
}
}
fn run_all<W>(writer: &mut W, pf_string: &str, pf_args: &[String]) -> UResult<()>
where
W: Write,
{
let mut arg_it = pf_args.iter().peekable();
let pm = Self::new(writer, pf_string, &mut arg_it)?;
loop {
if arg_it.peek().is_none() {
return Ok(());
}
pm.apply(writer, &mut arg_it);
}
}
}
/// Write a formatted string to stdout.
///
/// `format_string` contains the template and `args` contains the
/// arguments to render into the template.
///
/// See also [`sprintf`], which creates a new formatted [`String`].
///
/// # Examples
///
/// ```rust
/// use uucore::memo::printf;
///
/// printf("hello %s", &["world".to_string()]).unwrap();
/// // prints "hello world"
/// ```
pub fn printf(format_string: &str, args: &[String]) -> UResult<()> {
let mut writer = stdout();
Memo::run_all(&mut writer, format_string, args)
}
/// Create a new formatted string.
///
/// `format_string` contains the template and `args` contains the
/// arguments to render into the template.
///
/// See also [`printf`], which prints to stdout.
///
/// # Examples
///
/// ```rust
/// use uucore::memo::sprintf;
///
/// let s = sprintf("hello %s", &["world".to_string()]).unwrap();
/// assert_eq!(s, "hello world".to_string());
/// ```
pub fn sprintf(format_string: &str, args: &[String]) -> UResult<String> {
let mut writer = Cursor::new(vec![]);
Memo::run_all(&mut writer, format_string, args)?;
let buf = writer.into_inner();
match String::from_utf8(buf) {
Ok(s) => Ok(s),
Err(e) => Err(USimpleError::new(
1,
format!("failed to parse formatted string as UTF-8: {e}"),
)),
}
}
#[cfg(test)]
mod tests {
use crate::memo::sprintf;
#[test]
fn test_sprintf_smoke() {
assert_eq!(sprintf("", &[]).unwrap(), "".to_string());
}
#[test]
fn test_sprintf_no_args() {
assert_eq!(
sprintf("hello world", &[]).unwrap(),
"hello world".to_string()
);
}
#[test]
fn test_sprintf_string() {
assert_eq!(
sprintf("hello %s", &["world".to_string()]).unwrap(),
"hello world".to_string()
);
}
}
@@ -1,9 +0,0 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[allow(clippy::module_inception)]
mod num_format;
pub mod sub;
pub mod token;
pub mod unescaped_text;
@@ -1,30 +0,0 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety
//! Primitives used by Sub Tokenizer
//! and num_format modules
#[derive(Clone)]
pub enum FieldType {
Strf,
Floatf,
CninetyNineHexFloatf,
Scif,
Decf,
Intf,
Charf,
}
// a Sub Tokens' fields are stored
// as a single object so they can be more simply
// passed by ref to num_format in a Sub method
#[derive(Clone)]
pub struct FormatField<'a> {
pub min_width: Option<isize>,
pub second_field: Option<u32>,
pub field_char: &'a char,
pub field_type: &'a FieldType,
pub orig: &'a String,
}

Some files were not shown because too many files have changed in this diff Show More