changed the way durations and filesizes are parsed (#6640)

This commit is contained in:
Darren Schroeder 2022-09-29 13:24:17 -05:00 committed by GitHub
parent 6aa8a0073b
commit 6486364610
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 151 additions and 109 deletions

View File

@ -402,6 +402,18 @@ fn duration_decimal_math_with_all_units() {
assert_eq!(actual.out, "1wk 3day 8hr 10min 16sec 121ms 11µs 12ns"); assert_eq!(actual.out, "1wk 3day 8hr 10min 16sec 121ms 11µs 12ns");
} }
#[test]
fn duration_decimal_dans_test() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
3.14sec
"#
));
assert_eq!(actual.out, "3sec 140ms");
}
#[test] #[test]
fn duration_math_with_negative() { fn duration_math_with_negative() {
let actual = nu!( let actual = nu!(

View File

@ -1538,20 +1538,36 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
val: size * 1000 * 1000 * 1000, val: size * 1000 * 1000 * 1000,
span, span,
}, },
Unit::Minute => Value::Duration { Unit::Minute => match size.checked_mul(1000 * 1000 * 1000 * 60) {
val: size * 1000 * 1000 * 1000 * 60, Some(val) => Value::Duration { val, span },
span, None => Value::Error {
error: ShellError::GenericError(
"duration too large".into(),
"duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
},
Unit::Hour => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"duration too large".into(),
"duration too large".into(),
Some(span),
None,
Vec::new(),
),
}, },
Unit::Hour => Value::Duration {
val: size * 1000 * 1000 * 1000 * 60 * 60,
span,
}, },
Unit::Day => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24) { Unit::Day => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24) {
Some(val) => Value::Duration { val, span }, Some(val) => Value::Duration { val, span },
None => Value::Error { None => Value::Error {
error: ShellError::GenericError( error: ShellError::GenericError(
"day duration too large".into(), "duration too large".into(),
"day duration too large".into(), "duration too large".into(),
Some(span), Some(span),
None, None,
Vec::new(), Vec::new(),
@ -1562,8 +1578,8 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
Some(val) => Value::Duration { val, span }, Some(val) => Value::Duration { val, span },
None => Value::Error { None => Value::Error {
error: ShellError::GenericError( error: ShellError::GenericError(
"week duration too large".into(), "duration too large".into(),
"week duration too large".into(), "duration too large".into(),
Some(span), Some(span),
None, None,
Vec::new(), Vec::new(),

View File

@ -2175,23 +2175,50 @@ pub fn parse_duration(
} }
} }
pub fn parse_duration_bytes(bytes: &[u8], span: Span) -> Option<Expression> { // Borrowed from libm at https://github.com/rust-lang/libm/blob/master/src/math/modf.rs
fn parse_decimal_str_to_number(decimal: &str) -> Option<i64> { pub fn modf(x: f64) -> (f64, f64) {
let string_to_parse = format!("0.{}", decimal); let rv2: f64;
if let Ok(x) = string_to_parse.parse::<f64>() { let mut u = x.to_bits();
return Some((1_f64 / x) as i64); let e = ((u >> 52 & 0x7ff) as i32) - 0x3ff;
/* no fractional part */
if e >= 52 {
rv2 = x;
if e == 0x400 && (u << 12) != 0 {
/* nan */
return (x, rv2);
} }
None u &= 1 << 63;
return (f64::from_bits(u), rv2);
} }
if bytes.is_empty() || (!bytes[0].is_ascii_digit() && bytes[0] != b'-') { /* no integral part*/
if e < 0 {
u &= 1 << 63;
rv2 = f64::from_bits(u);
return (x, rv2);
}
let mask = ((!0) >> 12) >> e;
if (u & mask) == 0 {
rv2 = x;
u &= 1 << 63;
return (f64::from_bits(u), rv2);
}
u &= !mask;
rv2 = f64::from_bits(u);
(x - rv2, rv2)
}
pub fn parse_duration_bytes(num_with_unit_bytes: &[u8], span: Span) -> Option<Expression> {
if num_with_unit_bytes.is_empty()
|| (!num_with_unit_bytes[0].is_ascii_digit() && num_with_unit_bytes[0] != b'-')
{
return None; return None;
} }
let token = String::from_utf8_lossy(bytes).to_string(); let num_with_unit = String::from_utf8_lossy(num_with_unit_bytes).to_string();
let uppercase_num_with_unit = num_with_unit.to_uppercase();
let upper = token.to_uppercase();
let unit_groups = [ let unit_groups = [
(Unit::Nanosecond, "NS", None), (Unit::Nanosecond, "NS", None),
(Unit::Microsecond, "US", Some((Unit::Nanosecond, 1000))), (Unit::Microsecond, "US", Some((Unit::Nanosecond, 1000))),
@ -2202,34 +2229,33 @@ pub fn parse_duration_bytes(bytes: &[u8], span: Span) -> Option<Expression> {
(Unit::Day, "DAY", Some((Unit::Minute, 1440))), (Unit::Day, "DAY", Some((Unit::Minute, 1440))),
(Unit::Week, "WK", Some((Unit::Day, 7))), (Unit::Week, "WK", Some((Unit::Day, 7))),
]; ];
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
let mut lhs = token; if let Some(unit) = unit_groups
.iter()
.find(|&x| uppercase_num_with_unit.ends_with(x.1))
{
let mut lhs = num_with_unit;
for _ in 0..unit.1.len() { for _ in 0..unit.1.len() {
lhs.pop(); lhs.pop();
} }
let input: Vec<&str> = lhs.split('.').collect(); let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
let (value, unit_to_use) = match &input[..] { Ok(x) => x,
[number_str] => (number_str.parse::<i64>().ok(), unit.0), Err(_) => return None,
[number_str, decimal_part_str] => match unit.2 { });
Some(unit_to_convert_to) => match (
number_str.parse::<i64>(), let (num, unit_to_use) = match unit.2 {
parse_decimal_str_to_number(decimal_part_str), Some(unit_to_convert_to) => (
) {
(Ok(number), Some(decimal_part)) => (
Some( Some(
(number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part), ((number_part * unit_to_convert_to.1 as f64)
+ (decimal_part * unit_to_convert_to.1 as f64)) as i64,
), ),
unit_to_convert_to.0, unit_to_convert_to.0,
), ),
_ => (None, unit.0), None => (Some(number_part as i64), unit.0),
},
None => (None, unit.0),
},
_ => (None, unit.0),
}; };
if let Some(x) = value { if let Some(x) = num {
trace!("-- found {} {:?}", x, unit_to_use); trace!("-- found {} {:?}", x, unit_to_use);
let lhs_span = Span::new(span.start, span.start + lhs.len()); let lhs_span = Span::new(span.start, span.start + lhs.len());
@ -2262,33 +2288,32 @@ pub fn parse_filesize(
working_set: &StateWorkingSet, working_set: &StateWorkingSet,
span: Span, span: Span,
) -> (Expression, Option<ParseError>) { ) -> (Expression, Option<ParseError>) {
trace!("parsing: duration"); trace!("parsing: filesize");
fn parse_decimal_str_to_number(decimal: &str) -> Option<i64> {
let string_to_parse = format!("0.{}", decimal);
if let Ok(x) = string_to_parse.parse::<f64>() {
return Some((1_f64 / x) as i64);
}
None
}
let bytes = working_set.get_span_contents(span); let bytes = working_set.get_span_contents(span);
if bytes.is_empty() || (!bytes[0].is_ascii_digit() && bytes[0] != b'-') { match parse_filesize_bytes(bytes, span) {
return ( Some(expression) => (expression, None),
None => (
garbage(span), garbage(span),
Some(ParseError::Mismatch( Some(ParseError::Mismatch(
"filesize".into(), "filesize".into(),
"non-filesize unit".into(), "non-filesize unit".into(),
span, span,
)), )),
); ),
}
} }
let token = String::from_utf8_lossy(bytes).to_string(); pub fn parse_filesize_bytes(num_with_unit_bytes: &[u8], span: Span) -> Option<Expression> {
if num_with_unit_bytes.is_empty()
let upper = token.to_uppercase(); || (!num_with_unit_bytes[0].is_ascii_digit() && num_with_unit_bytes[0] != b'-')
{
return None;
}
let num_with_unit = String::from_utf8_lossy(num_with_unit_bytes).to_string();
let uppercase_num_with_unit = num_with_unit.to_uppercase();
let unit_groups = [ let unit_groups = [
(Unit::Kilobyte, "KB", Some((Unit::Byte, 1000))), (Unit::Kilobyte, "KB", Some((Unit::Byte, 1000))),
(Unit::Megabyte, "MB", Some((Unit::Kilobyte, 1000))), (Unit::Megabyte, "MB", Some((Unit::Kilobyte, 1000))),
@ -2306,40 +2331,38 @@ pub fn parse_filesize(
(Unit::Zebibyte, "ZIB", Some((Unit::Exbibyte, 1024))), (Unit::Zebibyte, "ZIB", Some((Unit::Exbibyte, 1024))),
(Unit::Byte, "B", None), (Unit::Byte, "B", None),
]; ];
if let Some(unit) = unit_groups.iter().find(|&x| upper.ends_with(x.1)) {
let mut lhs = token; if let Some(unit) = unit_groups
.iter()
.find(|&x| uppercase_num_with_unit.ends_with(x.1))
{
let mut lhs = num_with_unit;
for _ in 0..unit.1.len() { for _ in 0..unit.1.len() {
lhs.pop(); lhs.pop();
} }
let input: Vec<&str> = lhs.split('.').collect(); let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
let (value, unit_to_use) = match &input[..] { Ok(x) => x,
[number_str] => (number_str.parse::<i64>().ok(), unit.0), Err(_) => return None,
[number_str, decimal_part_str] => match unit.2 { });
Some(unit_to_convert_to) => match (
number_str.parse::<i64>(), let (num, unit_to_use) = match unit.2 {
parse_decimal_str_to_number(decimal_part_str), Some(unit_to_convert_to) => (
) {
(Ok(number), Some(decimal_part)) => (
Some( Some(
(number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part), ((number_part * unit_to_convert_to.1 as f64)
+ (decimal_part * unit_to_convert_to.1 as f64)) as i64,
), ),
unit_to_convert_to.0, unit_to_convert_to.0,
), ),
_ => (None, unit.0), None => (Some(number_part as i64), unit.0),
},
None => (None, unit.0),
},
_ => (None, unit.0),
}; };
if let Some(x) = value { if let Some(x) = num {
trace!("-- found {} {:?}", x, unit_to_use); trace!("-- found {} {:?}", x, unit_to_use);
let lhs_span = Span::new(span.start, span.start + lhs.len()); let lhs_span = Span::new(span.start, span.start + lhs.len());
let unit_span = Span::new(span.start + lhs.len(), span.end); let unit_span = Span::new(span.start + lhs.len(), span.end);
return ( return Some(Expression {
Expression {
expr: Expr::ValueWithUnit( expr: Expr::ValueWithUnit(
Box::new(Expression { Box::new(Expression {
expr: Expr::Int(x), expr: Expr::Int(x),
@ -2355,20 +2378,11 @@ pub fn parse_filesize(
span, span,
ty: Type::Filesize, ty: Type::Filesize,
custom_completion: None, custom_completion: None,
}, });
None,
);
} }
} }
( None
garbage(span),
Some(ParseError::Mismatch(
"filesize".into(),
"non-filesize unit".into(),
span,
)),
)
} }
pub fn parse_glob_pattern( pub fn parse_glob_pattern(