mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-08-02 05:57:46 +00:00
seq: simplify and use new printf implementation
This commit is contained in:
parent
6481d63ea4
commit
e7d58f673f
8 changed files with 104 additions and 569 deletions
|
@ -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(1.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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -12,70 +12,6 @@
|
|||
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 +23,13 @@ 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 +42,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).
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -3,24 +3,21 @@
|
|||
// 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
|
||||
use std::io::{stdout, Write};
|
||||
use std::io::{stdout, ErrorKind, Write};
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use num_traits::{Zero, ToPrimitive};
|
||||
use num_traits::{ToPrimitive, Zero};
|
||||
|
||||
use uucore::error::UResult;
|
||||
use uucore::format::{printf, FormatArgument, Format, num_format};
|
||||
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");
|
||||
|
@ -41,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).
|
||||
|
@ -116,26 +108,6 @@ 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);
|
||||
let format = match options.format {
|
||||
Some(f) => {
|
||||
let f = Format::<num_format::SignedInt>::parse(f)?;
|
||||
Some(f)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
print_seq_integers(
|
||||
(first, increment, last),
|
||||
&options.separator,
|
||||
&options.terminator,
|
||||
options.equal_width,
|
||||
padding,
|
||||
format,
|
||||
)
|
||||
}
|
||||
(first, increment, last) => {
|
||||
let format = match options.format {
|
||||
Some(f) => {
|
||||
let f = Format::<num_format::Float>::parse(f)?;
|
||||
|
@ -143,26 +115,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
}
|
||||
None => None,
|
||||
};
|
||||
print_seq(
|
||||
(
|
||||
first.into_extended_big_decimal(),
|
||||
increment.into_extended_big_decimal(),
|
||||
last.into_extended_big_decimal(),
|
||||
),
|
||||
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(()),
|
||||
_ => todo!(),
|
||||
// Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()),
|
||||
// Err(e) => Err(e.map_err_context(|| "write error".into())),
|
||||
Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()),
|
||||
Err(e) => Err(e.map_err_context(|| "write error".into())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,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,
|
||||
|
@ -261,12 +204,16 @@ fn print_seq(
|
|||
pad: bool,
|
||||
padding: usize,
|
||||
format: Option<Format<num_format::Float>>,
|
||||
) -> UResult<()> {
|
||||
) -> 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 {
|
||||
|
@ -307,65 +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<Format<num_format::SignedInt>>,
|
||||
) -> UResult<()> {
|
||||
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 int = match &value {
|
||||
ExtendedBigInt::BigInt(bi) => bi.to_i64().unwrap(),
|
||||
ExtendedBigInt::Infinity => todo!(),
|
||||
ExtendedBigInt::MinusInfinity => todo!(),
|
||||
ExtendedBigInt::MinusZero => todo!(),
|
||||
ExtendedBigInt::Nan => todo!(),
|
||||
};
|
||||
f.fmt(&mut stdout, int)?;
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ fn parse_iter(fmt: &[u8]) -> impl Iterator<Item = Result<FormatItem, FormatError
|
|||
Some(_) => {
|
||||
let spec = match Spec::parse(&mut rest) {
|
||||
Some(spec) => spec,
|
||||
None => return Some(Err(FormatError::SpecError)),
|
||||
None => return Some(Err(dbg!(FormatError::SpecError))),
|
||||
};
|
||||
Some(Ok(FormatItem::Spec(spec)))
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ impl<F: Formatter> Format<F> {
|
|||
for item in &mut iter {
|
||||
match item? {
|
||||
FormatItem::Spec(_) => {
|
||||
return Err(FormatError::SpecError);
|
||||
return Err(dbg!(FormatError::SpecError));
|
||||
}
|
||||
FormatItem::Text(t) => suffix.extend_from_slice(&t),
|
||||
FormatItem::Char(c) => suffix.push(c),
|
||||
|
|
|
@ -13,14 +13,14 @@ pub trait Formatter {
|
|||
Self: Sized;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum UnsignedIntVariant {
|
||||
Decimal,
|
||||
Octal(Prefix),
|
||||
Hexadecimal(Case, Prefix),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
pub enum FloatVariant {
|
||||
Decimal,
|
||||
|
@ -29,32 +29,32 @@ pub enum FloatVariant {
|
|||
Hexadecimal,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Case {
|
||||
Lowercase,
|
||||
Uppercase,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Prefix {
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ForceDecimal {
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PositiveSign {
|
||||
None,
|
||||
Plus,
|
||||
Space,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum NumberAlignment {
|
||||
Left,
|
||||
RightSpace,
|
||||
|
@ -93,7 +93,7 @@ impl Formatter for SignedInt {
|
|||
alignment,
|
||||
} = s
|
||||
else {
|
||||
return Err(FormatError::SpecError);
|
||||
return Err(dbg!(FormatError::SpecError));
|
||||
};
|
||||
|
||||
let width = match width {
|
||||
|
@ -152,7 +152,7 @@ impl Formatter for UnsignedInt {
|
|||
alignment,
|
||||
} = s
|
||||
else {
|
||||
return Err(FormatError::SpecError);
|
||||
return Err(dbg!(FormatError::SpecError));
|
||||
};
|
||||
|
||||
let width = match width {
|
||||
|
@ -241,19 +241,19 @@ impl Formatter for Float {
|
|||
precision,
|
||||
} = s
|
||||
else {
|
||||
return Err(FormatError::SpecError);
|
||||
return Err(dbg!(FormatError::SpecError));
|
||||
};
|
||||
|
||||
let width = match width {
|
||||
Some(CanAsterisk::Fixed(x)) => x,
|
||||
None => 0,
|
||||
Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError),
|
||||
Some(CanAsterisk::Asterisk) => return Err(dbg!(FormatError::SpecError)),
|
||||
};
|
||||
|
||||
let precision = match precision {
|
||||
Some(CanAsterisk::Fixed(x)) => x,
|
||||
None => 0,
|
||||
Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError),
|
||||
Some(CanAsterisk::Asterisk) => return Err(dbg!(FormatError::SpecError)),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
|
|
|
@ -9,6 +9,7 @@ use super::{
|
|||
};
|
||||
use std::{fmt::Display, io::Write};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Spec {
|
||||
Char {
|
||||
width: Option<CanAsterisk<usize>>,
|
||||
|
@ -41,7 +42,7 @@ pub enum Spec {
|
|||
|
||||
/// Precision and width specified might use an asterisk to indicate that they are
|
||||
/// determined by an argument.
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum CanAsterisk<T> {
|
||||
Fixed(T),
|
||||
Asterisk,
|
||||
|
@ -99,6 +100,7 @@ impl Spec {
|
|||
let width = eat_asterisk_or_number(rest);
|
||||
|
||||
let precision = if let Some(b'.') = rest.get(0) {
|
||||
*rest = &rest[1..];
|
||||
Some(eat_asterisk_or_number(rest).unwrap_or(CanAsterisk::Fixed(0)))
|
||||
} else {
|
||||
None
|
||||
|
@ -134,7 +136,9 @@ impl Spec {
|
|||
*rest = &rest[1..];
|
||||
}
|
||||
|
||||
Some(match rest.get(0)? {
|
||||
let type_spec = rest.get(0)?;
|
||||
*rest = &rest[1..];
|
||||
Some(match type_spec {
|
||||
b'c' => Spec::Char {
|
||||
width,
|
||||
align_left: minus,
|
||||
|
@ -208,7 +212,10 @@ impl Spec {
|
|||
(false, false) => PositiveSign::None,
|
||||
},
|
||||
},
|
||||
_ => return None,
|
||||
x => {
|
||||
dbg!("{:b}", x);
|
||||
return dbg!(None)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue