From 6ccc3055136d9347e16db5b8d7ec5854cca611df Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 31 May 2021 20:49:02 +0200 Subject: [PATCH 1/3] seq: implement integer sequences If we notice that we can represent all arguments as BigInts, take a different code path. Just like GNU seq this means we can print an infinite amount of numbers in this case. --- Cargo.lock | 2 + src/uu/seq/Cargo.toml | 2 + src/uu/seq/src/seq.rs | 137 ++++++++++++++++++++++++++++++-------- tests/by-util/test_seq.rs | 61 +++++++++++++++++ 4 files changed, 173 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21bd5950b..ca963674a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2402,6 +2402,8 @@ name = "uu_seq" version = "0.0.6" dependencies = [ "clap", + "num-bigint", + "num-traits", "uucore", "uucore_procs", ] diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index 96c629c68..32f2bbac8 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -16,6 +16,8 @@ path = "src/seq.rs" [dependencies] clap = "2.33" +num-bigint = "0.4.0" +num-traits = "0.2.14" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index c3bba1c78..fc72efc5a 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -7,8 +7,13 @@ extern crate uucore; use clap::{App, AppSettings, Arg}; +use num_bigint::BigInt; +use num_traits::One; +use num_traits::Zero; +use num_traits::{Num, ToPrimitive}; use std::cmp; use std::io::{stdout, Write}; +use std::str::FromStr; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT."; @@ -33,16 +38,46 @@ struct SeqOptions { widths: bool, } -fn parse_float(mut s: &str) -> Result { - if s.starts_with('+') { - s = &s[1..]; +enum Number { + BigInt(BigInt), + F64(f64), +} + +impl Number { + fn is_zero(&self) -> bool { + match self { + Number::BigInt(n) => n.is_zero(), + Number::F64(n) => n.is_zero(), + } } - match s.parse() { - Ok(n) => Ok(n), - Err(e) => Err(format!( - "seq: invalid floating point argument `{}`: {}", - s, e - )), + + fn into_f64(self) -> f64 { + match self { + // BigInt::to_f64() can not return None. + Number::BigInt(n) => n.to_f64().unwrap(), + Number::F64(n) => n, + } + } +} + +impl FromStr for Number { + type Err = String; + /// Tries to parse this string as a BigInt, or if that fails as an f64. + fn from_str(mut s: &str) -> Result { + if s.starts_with('+') { + s = &s[1..]; + } + + match s.parse::() { + Ok(n) => Ok(Number::BigInt(n)), + Err(_) => match s.parse::() { + Ok(n) => Ok(Number::F64(n)), + Err(e) => Err(format!( + "seq: invalid floating point argument `{}`: {}", + s, e + )), + }, + } } } @@ -107,7 +142,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let dec = slice.find('.').unwrap_or(len); largest_dec = len - dec; padding = dec; - match parse_float(slice) { + match slice.parse() { Ok(n) => n, Err(s) => { show_error!("{}", s); @@ -115,7 +150,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } } else { - 1.0 + Number::BigInt(BigInt::one()) }; let increment = if numbers.len() > 2 { let slice = numbers[1]; @@ -123,7 +158,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let dec = slice.find('.').unwrap_or(len); largest_dec = cmp::max(largest_dec, len - dec); padding = cmp::max(padding, dec); - match parse_float(slice) { + match slice.parse() { Ok(n) => n, Err(s) => { show_error!("{}", s); @@ -131,16 +166,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } } else { - 1.0 + Number::BigInt(BigInt::one()) }; - if increment == 0.0 { + if increment.is_zero() { show_error!("increment value: '{}'", numbers[1]); return 1; } let last = { let slice = numbers[numbers.len() - 1]; padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len())); - match parse_float(slice) { + match slice.parse::() { Ok(n) => n, Err(s) => { show_error!("{}", s); @@ -156,28 +191,42 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(term) => escape_sequences(&term[..]), None => separator.clone(), }; - print_seq( - first, - increment, - last, - largest_dec, - separator, - terminator, - options.widths, - padding, - ); + match (first, last, increment) { + (Number::BigInt(first), Number::BigInt(last), Number::BigInt(increment)) => { + print_seq_integers( + first, + increment, + last, + separator, + terminator, + options.widths, + padding, + ) + } + (first, last, increment) => print_seq( + first.into_f64(), + increment.into_f64(), + last.into_f64(), + largest_dec, + separator, + terminator, + options.widths, + padding, + ), + } 0 } -fn done_printing(next: f64, increment: f64, last: f64) -> bool { - if increment >= 0f64 { +fn done_printing(next: &T, increment: &T, last: &T) -> bool { + if increment >= &T::zero() { next > last } else { next < last } } +/// Floating point based code path #[allow(clippy::too_many_arguments)] fn print_seq( first: f64, @@ -191,7 +240,7 @@ fn print_seq( ) { let mut i = 0isize; let mut value = first + i as f64 * increment; - while !done_printing(value, increment, last) { + while !done_printing(&value, &increment, &last) { let istr = format!("{:.*}", largest_dec, value); let ilen = istr.len(); let before_dec = istr.find('.').unwrap_or(ilen); @@ -203,7 +252,7 @@ fn print_seq( print!("{}", istr); i += 1; value = first + i as f64 * increment; - if !done_printing(value, increment, last) { + if !done_printing(&value, &increment, &last) { print!("{}", separator); } } @@ -212,3 +261,33 @@ fn print_seq( } crash_if_err!(1, stdout().flush()); } + +/// BigInt based code path +fn print_seq_integers( + first: BigInt, + increment: BigInt, + last: BigInt, + separator: String, + terminator: String, + pad: bool, + padding: usize, +) { + let mut value = first; + let mut is_first_iteration = true; + while !done_printing(&value, &increment, &last) { + if !is_first_iteration { + print!("{}", separator); + } + is_first_iteration = false; + if pad { + print!("{number:>0width$}", number = value, width = padding); + } else { + print!("{}", value); + } + value += &increment; + } + + if !is_first_iteration { + print!("{}", terminator); + } +} diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index a74938377..3da1a84ca 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -1,5 +1,7 @@ use crate::common::util::*; +// ---- Tests for the big integer based path ---- + #[test] fn test_count_up() { new_ucmd!() @@ -45,3 +47,62 @@ fn test_seq_wrong_arg() { fn test_zero_step() { new_ucmd!().args(&["10", "0", "32"]).fails(); } + +#[test] +fn test_big_numbers() { + new_ucmd!() + .args(&[ + "1000000000000000000000000000", + "1000000000000000000000000001", + ]) + .succeeds() + .stdout_only("1000000000000000000000000000\n1000000000000000000000000001\n"); +} + +// ---- Tests for the floating point based path ---- + +#[test] +fn test_count_up_floats() { + new_ucmd!() + .args(&["10.0"]) + .run() + .stdout_is("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"); +} + +#[test] +fn test_count_down_floats() { + new_ucmd!() + .args(&["--", "5", "-1.0", "1"]) + .run() + .stdout_is("5.0\n4.0\n3.0\n2.0\n1.0\n"); + new_ucmd!() + .args(&["5", "-1", "1.0"]) + .run() + .stdout_is("5\n4\n3\n2\n1\n"); +} + +#[test] +fn test_separator_and_terminator_floats() { + new_ucmd!() + .args(&["-s", ",", "-t", "!", "2.0", "6"]) + .run() + .stdout_is("2.0,3.0,4.0,5.0,6.0!"); +} + +#[test] +fn test_equalize_widths_floats() { + new_ucmd!() + .args(&["-w", "5", "10.0"]) + .run() + .stdout_is("05\n06\n07\n08\n09\n10\n"); +} + +#[test] +fn test_seq_wrong_arg_floats() { + new_ucmd!().args(&["-w", "5", "10.0", "33", "32"]).fails(); +} + +#[test] +fn test_zero_step_floats() { + new_ucmd!().args(&["10.0", "0", "32"]).fails(); +} From c78cc65421eb8089dab043079046cfffa6f2820c Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 31 May 2021 20:53:25 +0200 Subject: [PATCH 2/3] seq: make arguments required Right now seq panics when invoked without args. --- src/uu/seq/src/seq.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index fc72efc5a..4737fe872 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -119,7 +119,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .multiple(true) .takes_value(true) .allow_hyphen_values(true) - .max_values(3), + .max_values(3) + .required(true), ) .get_matches_from(args); From 4cf18e96f3113c07d179fee2bf966bc933e3ab80 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 31 May 2021 21:19:19 +0200 Subject: [PATCH 3/3] seq: change default value for -t and remove dubious escape sequences GNU seq does not support -t, but always outputs a newline at the end. Therefore, our default for -t should be \n. Also removes support for escape sequences (interpreting a literal "\n" as a newline). This is not what GNU seq is doing, and unexpected. --- src/uu/seq/src/seq.rs | 32 ++++++++++---------------------- tests/by-util/test_seq.rs | 12 ++++++++++++ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 4737fe872..bdab044c5 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -34,7 +34,7 @@ fn get_usage() -> String { #[derive(Clone)] struct SeqOptions { separator: String, - terminator: Option, + terminator: String, widths: bool, } @@ -81,10 +81,6 @@ impl FromStr for Number { } } -fn escape_sequences(s: &str) -> String { - s.replace("\\n", "\n").replace("\\t", "\t") -} - pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); let matches = App::new(executable!()) @@ -104,7 +100,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(OPT_TERMINATOR) .short("t") .long("terminator") - .help("Terminator character (defaults to separator)") + .help("Terminator character (defaults to \\n)") .takes_value(true) .number_of_values(1), ) @@ -126,14 +122,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let numbers = matches.values_of(ARG_NUMBERS).unwrap().collect::>(); - let mut options = SeqOptions { - separator: "\n".to_owned(), - terminator: None, - widths: false, + let options = SeqOptions { + separator: matches.value_of(OPT_SEPARATOR).unwrap_or("\n").to_string(), + terminator: matches.value_of(OPT_TERMINATOR).unwrap_or("\n").to_string(), + widths: matches.is_present(OPT_WIDTHS), }; - options.separator = matches.value_of(OPT_SEPARATOR).unwrap_or("\n").to_string(); - options.terminator = matches.value_of(OPT_TERMINATOR).map(String::from); - options.widths = matches.is_present(OPT_WIDTHS); let mut largest_dec = 0; let mut padding = 0; @@ -187,11 +180,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if largest_dec > 0 { largest_dec -= 1; } - let separator = escape_sequences(&options.separator[..]); - let terminator = match options.terminator { - Some(term) => escape_sequences(&term[..]), - None => separator.clone(), - }; match (first, last, increment) { (Number::BigInt(first), Number::BigInt(last), Number::BigInt(increment)) => { @@ -199,8 +187,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { first, increment, last, - separator, - terminator, + options.separator, + options.terminator, options.widths, padding, ) @@ -210,8 +198,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { increment.into_f64(), last.into_f64(), largest_dec, - separator, - terminator, + options.separator, + options.terminator, options.widths, padding, ), diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 3da1a84ca..98eb23598 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -28,6 +28,18 @@ fn test_separator_and_terminator() { .args(&["-s", ",", "-t", "!", "2", "6"]) .run() .stdout_is("2,3,4,5,6!"); + new_ucmd!() + .args(&["-s", ",", "2", "6"]) + .run() + .stdout_is("2,3,4,5,6\n"); + new_ucmd!() + .args(&["-s", "\n", "2", "6"]) + .run() + .stdout_is("2\n3\n4\n5\n6\n"); + new_ucmd!() + .args(&["-s", "\\n", "2", "6"]) + .run() + .stdout_is("2\\n3\\n4\\n5\\n6\n"); } #[test]