From 6ccc3055136d9347e16db5b8d7ec5854cca611df Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 31 May 2021 20:49:02 +0200 Subject: [PATCH] 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(); +}