diff --git a/crates/nu-cli/src/types/deduction.rs b/crates/nu-cli/src/types/deduction.rs index 81379d4e43..a5ad92c5eb 100644 --- a/crates/nu-cli/src/types/deduction.rs +++ b/crates/nu-cli/src/types/deduction.rs @@ -81,29 +81,29 @@ lazy_static! { static ref MULT_DIV_LOOKUP_TABLE: HashMap<(Operator, BinarySide, SyntaxShape), Vec> = { vec![ ((Operator::Divide, BinarySide::Left, SyntaxShape::Number), // expr => possible var shapes - vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //$var / number => Unit, Int, Number + vec![SyntaxShape::Filesize, SyntaxShape::Duration, SyntaxShape::Number, SyntaxShape::Int]), //$var / number => Unit, Int, Number ((Operator::Divide, BinarySide::Left, SyntaxShape::Int), - vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //$var / int => Unit, Int, Number - ((Operator::Divide, BinarySide::Left, SyntaxShape::Unit), - vec![SyntaxShape::Unit]), //$var / unit => Unit + vec![SyntaxShape::Filesize, SyntaxShape::Duration, SyntaxShape::Number, SyntaxShape::Int]), //$var / int => Unit, Int, Number + ((Operator::Divide, BinarySide::Left, SyntaxShape::Filesize), + vec![SyntaxShape::Filesize, SyntaxShape::Duration, SyntaxShape::Filesize]), //$var / unit => Unit ((Operator::Divide, BinarySide::Right, SyntaxShape::Number), vec![SyntaxShape::Number, SyntaxShape::Int]), //number / $var => Int, Number ((Operator::Divide, BinarySide::Right, SyntaxShape::Int), vec![SyntaxShape::Number, SyntaxShape::Int]), //int / $var => Int, Number - ((Operator::Divide, BinarySide::Right, SyntaxShape::Unit), - vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //unit / $var => unit, int, number + ((Operator::Divide, BinarySide::Right, SyntaxShape::Filesize), + vec![SyntaxShape::Filesize, SyntaxShape::Number, SyntaxShape::Int]), //unit / $var => unit, int, number ((Operator::Multiply, BinarySide::Left, SyntaxShape::Number), - vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //$var * number => Unit, Int, Number + vec![SyntaxShape::Filesize, SyntaxShape::Number, SyntaxShape::Int]), //$var * number => Unit, Int, Number ((Operator::Multiply, BinarySide::Left, SyntaxShape::Int), - vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //$var * int => Unit, Int, Number - ((Operator::Multiply, BinarySide::Left, SyntaxShape::Unit), + vec![SyntaxShape::Filesize, SyntaxShape::Number, SyntaxShape::Int]), //$var * int => Unit, Int, Number + ((Operator::Multiply, BinarySide::Left, SyntaxShape::Filesize), vec![SyntaxShape::Int, SyntaxShape::Number]), //$var * unit => int, number //TODO this changes as soon as more complex units arrive ((Operator::Multiply, BinarySide::Right, SyntaxShape::Number), - vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //number * $var => Unit, Int, Number + vec![SyntaxShape::Filesize, SyntaxShape::Number, SyntaxShape::Int]), //number * $var => Unit, Int, Number ((Operator::Multiply, BinarySide::Right, SyntaxShape::Int), - vec![SyntaxShape::Unit, SyntaxShape::Number, SyntaxShape::Int]), //int * $var => Unit, Int, Number - ((Operator::Multiply, BinarySide::Right, SyntaxShape::Unit), + vec![SyntaxShape::Filesize, SyntaxShape::Number, SyntaxShape::Int]), //int * $var => Unit, Int, Number + ((Operator::Multiply, BinarySide::Right, SyntaxShape::Filesize), vec![SyntaxShape::Int, SyntaxShape::Number]), //unit * $var => int, number //TODO this changes as soon as more complex units arrive ].into_iter().collect() }; @@ -241,8 +241,8 @@ fn get_result_shape_of( l_shape } Operator::Multiply => { - if l_shape == SyntaxShape::Unit || r_shape == SyntaxShape::Unit { - SyntaxShape::Unit + if l_shape == SyntaxShape::Duration || r_shape == SyntaxShape::Duration { + SyntaxShape::Duration } else { SyntaxShape::Number } @@ -250,7 +250,7 @@ fn get_result_shape_of( Operator::Divide => { if l_shape == r_shape { SyntaxShape::Number - } else if l_shape == SyntaxShape::Unit { + } else if l_shape == SyntaxShape::Duration { l_shape } else { SyntaxShape::Number @@ -277,7 +277,7 @@ fn get_shape_of_expr(expr: &SpannedExpression) -> Option { nu_protocol::hir::Number::Int(_) => Some(SyntaxShape::Int), nu_protocol::hir::Number::Decimal(_) => Some(SyntaxShape::Number), }, - nu_protocol::hir::Literal::Size(_, _) => Some(SyntaxShape::Unit), + nu_protocol::hir::Literal::Size(_, _) => Some(SyntaxShape::Duration), nu_protocol::hir::Literal::String(_) => Some(SyntaxShape::String), //Rest should have failed at parsing stage? nu_protocol::hir::Literal::GlobPattern(_) => Some(SyntaxShape::String), @@ -843,12 +843,21 @@ impl VarSyntaxShapeDeductor { ), )?; } - SyntaxShape::Unit => { + SyntaxShape::Duration => { self.checked_insert( var, VarShapeDeduction::from_usage_with_alternatives( &var.span, - &[SyntaxShape::Unit], + &[SyntaxShape::Duration], + ), + )?; + } + SyntaxShape::Filesize => { + self.checked_insert( + var, + VarShapeDeduction::from_usage_with_alternatives( + &var.span, + &[SyntaxShape::Filesize], ), )?; } diff --git a/crates/nu-command/src/commands/sleep.rs b/crates/nu-command/src/commands/sleep.rs index c53b17ca03..50ac577c0a 100644 --- a/crates/nu-command/src/commands/sleep.rs +++ b/crates/nu-command/src/commands/sleep.rs @@ -26,8 +26,8 @@ impl WholeStreamCommand for Sleep { fn signature(&self) -> Signature { Signature::build("sleep") - .required("duration", SyntaxShape::Unit, "time to sleep") - .rest(SyntaxShape::Unit, "additional time") + .required("duration", SyntaxShape::Duration, "time to sleep") + .rest(SyntaxShape::Duration, "additional time") } fn usage(&self) -> &str { diff --git a/crates/nu-data/src/value.rs b/crates/nu-data/src/value.rs index 43aee2159c..347e2594c9 100644 --- a/crates/nu-data/src/value.rs +++ b/crates/nu-data/src/value.rs @@ -438,6 +438,15 @@ pub fn compute_values( Err(_) => Err(("Date", "Duration overflow")), } } + Operator::Minus => { + match Primitive::into_chrono_duration(rhs.clone(), Span::unknown()) { + Ok(y) => match x.checked_sub_signed(y) { + Some(value) => Ok(value), + None => Err(("Date", "Duration and date addition overflow")), + }, + Err(_) => Err(("Date", "Duration overflow")), + } + } _ => Err((left.type_name(), right.type_name())), }?; Ok(UntaggedValue::Primitive(Primitive::Date(result))) diff --git a/crates/nu-parser/src/parse.rs b/crates/nu-parser/src/parse.rs index ee216b19da..fad8ede653 100644 --- a/crates/nu-parser/src/parse.rs +++ b/crates/nu-parser/src/parse.rs @@ -331,8 +331,76 @@ fn parse_operator(lite_arg: &Spanned) -> (SpannedExpression, Option) -> (SpannedExpression, Option) { + fn parse_decimal_str_to_number(decimal: &str) -> Option { + let string_to_parse = format!("0.{}", decimal); + if let Ok(x) = string_to_parse.parse::() { + return Some((1_f64 / x) as i64); + } + None + } + let unit_groups = [ + (Unit::Nanosecond, "NS", None), + (Unit::Microsecond, "US", Some((Unit::Nanosecond, 1000))), + (Unit::Millisecond, "MS", Some((Unit::Microsecond, 1000))), + (Unit::Second, "SEC", Some((Unit::Millisecond, 1000))), + (Unit::Minute, "MIN", Some((Unit::Second, 60))), + (Unit::Hour, "HR", Some((Unit::Minute, 60))), + (Unit::Day, "DAY", Some((Unit::Minute, 1440))), + (Unit::Week, "WK", Some((Unit::Day, 7))), + ]; + if let Some(unit) = unit_groups + .iter() + .find(|&x| lite_arg.to_uppercase().ends_with(x.1)) + { + let mut lhs = lite_arg.item.clone(); + for _ in 0..unit.1.len() { + lhs.pop(); + } + + let input: Vec<&str> = lhs.split('.').collect(); + let (value, unit_to_use) = match &input[..] { + [number_str] => (number_str.parse::().ok(), unit.0), + [number_str, decimal_part_str] => match unit.2 { + Some(unit_to_convert_to) => match ( + number_str.parse::(), + parse_decimal_str_to_number(decimal_part_str), + ) { + (Ok(number), Some(decimal_part)) => ( + Some( + (number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part), + ), + unit_to_convert_to.0, + ), + _ => (None, unit.0), + }, + None => (None, unit.0), + }, + _ => (None, unit.0), + }; + + if let Some(x) = value { + let lhs_span = Span::new(lite_arg.span.start(), lite_arg.span.start() + lhs.len()); + let unit_span = Span::new(lite_arg.span.start() + lhs.len(), lite_arg.span.end()); + return ( + SpannedExpression::new( + Expression::unit(x.spanned(lhs_span), unit_to_use.spanned(unit_span)), + lite_arg.span, + ), + None, + ); + } + } + + ( + garbage(lite_arg.span), + Some(ParseError::mismatch("duration", lite_arg.clone())), + ) +} + /// Parse a unit type, eg '10kb' -fn parse_unit(lite_arg: &Spanned) -> (SpannedExpression, Option) { +fn parse_filesize(lite_arg: &Spanned) -> (SpannedExpression, Option) { fn parse_decimal_str_to_number(decimal: &str) -> Option { let string_to_parse = format!("0.{}", decimal); if let Ok(x) = string_to_parse.parse::() { @@ -352,14 +420,6 @@ fn parse_unit(lite_arg: &Spanned) -> (SpannedExpression, Option (SpannedExpression, Option) { trace!("Parsing dollar expression: {:?}", lite_arg.item); - if lite_arg.item.starts_with("$\"") && lite_arg.item.len() > 1 && lite_arg.item.ends_with('"') { + if (lite_arg.item.starts_with("$\"") && lite_arg.item.len() > 1 && lite_arg.item.ends_with('"')) + || (lite_arg.item.starts_with("$'") + && lite_arg.item.len() > 1 + && lite_arg.item.ends_with('\'')) + { // This is an interpolated string parse_interpolated_string(&lite_arg, scope) } else if let (expr, None) = parse_range(lite_arg, scope) { @@ -844,7 +908,8 @@ fn parse_arg( SyntaxShape::Range => parse_range(&lite_arg, scope), SyntaxShape::Operator => parse_operator(&lite_arg), - SyntaxShape::Unit => parse_unit(&lite_arg), + SyntaxShape::Filesize => parse_filesize(&lite_arg), + SyntaxShape::Duration => parse_duration(&lite_arg), SyntaxShape::FilePath => { let trimmed = trim_quotes(&lite_arg.item); let expanded = expand_path(&trimmed).to_string(); @@ -861,7 +926,8 @@ fn parse_arg( SyntaxShape::Int, SyntaxShape::Number, SyntaxShape::Range, - SyntaxShape::Unit, + SyntaxShape::Filesize, + SyntaxShape::Duration, SyntaxShape::Block, SyntaxShape::Table, SyntaxShape::String, @@ -2164,7 +2230,7 @@ fn unit_parse_byte_units() { let input_len = case.string.len(); let value_len = case.value.to_string().len(); let input = case.string.clone().spanned(Span::new(0, input_len)); - let result = parse_unit(&input); + let result = parse_filesize(&input); assert_eq!(result.1, None); assert_eq!( result.0.expr, @@ -2252,7 +2318,7 @@ fn unit_parse_byte_units_decimal() { let input_len = case.string.len(); let value_len = case.value_str.to_string().len(); let input = case.string.clone().spanned(Span::new(0, input_len)); - let result = parse_unit(&input); + let result = parse_filesize(&input); assert_eq!(result.1, None); assert_eq!( result.0.expr, diff --git a/crates/nu-parser/src/parse/def/primitives.rs b/crates/nu-parser/src/parse/def/primitives.rs index 90f681a960..4689496de2 100644 --- a/crates/nu-parser/src/parse/def/primitives.rs +++ b/crates/nu-parser/src/parse/def/primitives.rs @@ -166,7 +166,8 @@ pub fn parse_type_token(type_: &Token) -> (SyntaxShape, Option) { "string" => (SyntaxShape::String, None), "path" => (SyntaxShape::FilePath, None), "table" => (SyntaxShape::Table, None), - "unit" => (SyntaxShape::Unit, None), + "duration" => (SyntaxShape::Duration, None), + "filesize" => (SyntaxShape::Filesize, None), "number" => (SyntaxShape::Number, None), "pattern" => (SyntaxShape::GlobPattern, None), "range" => (SyntaxShape::Range, None), diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index b47474fa6f..ab11b77fa4 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -26,8 +26,10 @@ pub enum SyntaxShape { Block, /// A table is allowed, eg `[first second]` Table, - /// A unit value is allowed, eg `10kb` - Unit, + /// A filesize value is allowed, eg `10kb` + Filesize, + /// A duration value is allowed, eg `19day` + Duration, /// An operator Operator, /// A math expression which expands shorthand forms on the lefthand side, eg `foo > 1` @@ -52,7 +54,8 @@ impl PrettyDebug for SyntaxShape { SyntaxShape::GlobPattern => "pattern", SyntaxShape::Block => "block", SyntaxShape::Table => "table", - SyntaxShape::Unit => "unit", + SyntaxShape::Duration => "duration", + SyntaxShape::Filesize => "filesize", SyntaxShape::Operator => "operator", SyntaxShape::RowCondition => "condition", SyntaxShape::MathExpression => "math expression",