diff --git a/crates/nu-command/src/database/values/dsl/expression.rs b/crates/nu-command/src/database/values/dsl/expression.rs index d5505de010..fbf1fa0825 100644 --- a/crates/nu-command/src/database/values/dsl/expression.rs +++ b/crates/nu-command/src/database/values/dsl/expression.rs @@ -112,6 +112,7 @@ impl CustomValue for ExprDb { Operator::Multiply => Ok(BinaryOperator::Multiply), Operator::Divide => Ok(BinaryOperator::Divide), Operator::Modulo => Ok(BinaryOperator::Modulo), + Operator::FloorDivision => Ok(BinaryOperator::Divide), Operator::And => Ok(BinaryOperator::And), Operator::Or => Ok(BinaryOperator::Or), Operator::In diff --git a/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs b/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs index e9a16d453a..f856598e41 100644 --- a/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs +++ b/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs @@ -100,6 +100,7 @@ fn with_operator( Operator::Multiply => apply_arithmetic(left, right, lhs_span, Mul::mul), Operator::Divide => apply_arithmetic(left, right, lhs_span, Div::div), Operator::Modulo => apply_arithmetic(left, right, lhs_span, Rem::rem), + Operator::FloorDivision => apply_arithmetic(left, right, lhs_span, Div::div), Operator::Equal => Ok(left .clone() .apply_with_expr(right.clone(), Expr::eq) diff --git a/crates/nu-command/tests/commands/math/mod.rs b/crates/nu-command/tests/commands/math/mod.rs index d8e8109745..3627018227 100644 --- a/crates/nu-command/tests/commands/math/mod.rs +++ b/crates/nu-command/tests/commands/math/mod.rs @@ -163,6 +163,89 @@ fn error_zero_division_decimal_decimal() { assert!(actual.err.contains("division by zero")); } +#[test] +fn floor_division_of_ints() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 5 // 2 + "# + )); + + assert_eq!(actual.out, "2"); +} + +#[test] +fn floor_division_of_ints2() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + -3 // 2 + "# + )); + + assert_eq!(actual.out, "-2"); +} + +#[test] +fn floor_division_of_floats() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + -3.0 // 2.0 + "# + )); + + assert_eq!(actual.out, "-2"); +} + +#[test] +fn error_zero_floor_division_int_int() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1 // 0 + "# + )); + + assert!(actual.err.contains("division by zero")); +} + +#[test] +fn error_zero_floor_division_decimal_int() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1.0 // 0 + "# + )); + + assert!(actual.err.contains("division by zero")); +} + +#[test] +fn error_zero_floor_division_int_decimal() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1 // 0.0 + "# + )); + + assert!(actual.err.contains("division by zero")); +} + +#[test] +fn error_zero_floor_division_decimal_decimal() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1.0 // 0.0 + "# + )); + + assert!(actual.err.contains("division by zero")); +} #[test] fn proper_precedence_history() { let actual = nu!( diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index b9dad9168b..0013cefc77 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -422,6 +422,10 @@ pub fn eval_expression( let rhs = eval_expression(engine_state, stack, rhs)?; lhs.modulo(op_span, &rhs, expr.span) } + Operator::FloorDivision => { + let rhs = eval_expression(engine_state, stack, rhs)?; + lhs.floor_div(op_span, &rhs, expr.span) + } Operator::Pow => { let rhs = eval_expression(engine_state, stack, rhs)?; lhs.pow(op_span, &rhs, expr.span) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 0178930d7a..277beaf11d 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -4086,6 +4086,7 @@ pub fn parse_operator( b"-" => Operator::Minus, b"*" => Operator::Multiply, b"/" => Operator::Divide, + b"//" => Operator::FloorDivision, b"in" => Operator::In, b"not-in" => Operator::NotIn, b"mod" => Operator::Modulo, diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index f7365e29c9..dd0a7449e5 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -181,6 +181,33 @@ pub fn math_result_type( ) } }, + Operator::FloorDivision => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Int, None), + (Type::Int, Type::Float) => (Type::Int, None), + (Type::Float, Type::Float) => (Type::Int, None), + (Type::Filesize, Type::Filesize) => (Type::Int, None), + (Type::Duration, Type::Duration) => (Type::Int, None), + + (Type::Filesize, Type::Int) => (Type::Filesize, None), + (Type::Duration, Type::Int) => (Type::Duration, None), + + (Type::Any, _) => (Type::Any, None), + (_, Type::Any) => (Type::Any, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Any, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, Operator::And | Operator::Or => match (&lhs.ty, &rhs.ty) { (Type::Bool, Type::Bool) => (Type::Bool, None), diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 4a93841ef2..929a6f3536 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -30,7 +30,10 @@ impl Expression { match operator { Operator::Pow => 100, - Operator::Multiply | Operator::Divide | Operator::Modulo => 95, + Operator::Multiply + | Operator::Divide + | Operator::Modulo + | Operator::FloorDivision => 95, Operator::Plus | Operator::Minus => 90, Operator::NotRegexMatch | Operator::RegexMatch diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index ad825e0113..1a2d6acb7c 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -20,6 +20,7 @@ pub enum Operator { In, NotIn, Modulo, + FloorDivision, And, Or, Pow, @@ -43,6 +44,7 @@ impl Display for Operator { Operator::In => write!(f, "in"), Operator::NotIn => write!(f, "not-in"), Operator::Modulo => write!(f, "mod"), + Operator::FloorDivision => write!(f, "fdiv"), Operator::And => write!(f, "&&"), Operator::Or => write!(f, "||"), Operator::Pow => write!(f, "**"), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 155633acb9..c301cd6f87 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -1769,6 +1769,119 @@ impl Value { }), } } + pub fn floor_div(&self, op: Span, rhs: &Value, span: Span) -> Result { + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Int { + val: (*lhs as f64 / *rhs as f64) + .max(std::i64::MIN as f64) + .min(std::i64::MAX as f64) + .floor() as i64, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + if *rhs != 0.0 { + Ok(Value::Int { + val: (*lhs as f64 / *rhs) + .max(std::i64::MIN as f64) + .min(std::i64::MAX as f64) + .floor() as i64, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Int { + val: (*lhs / *rhs as f64) + .max(std::i64::MIN as f64) + .min(std::i64::MAX as f64) + .floor() as i64, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + if *rhs != 0.0 { + Ok(Value::Int { + val: (lhs / rhs) + .max(std::i64::MIN as f64) + .min(std::i64::MAX as f64) + .floor() as i64, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Int { + val: (*lhs as f64 / *rhs as f64) + .max(std::i64::MIN as f64) + .min(std::i64::MAX as f64) + .floor() as i64, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Int { + val: (*lhs as f64 / *rhs as f64) + .max(std::i64::MIN as f64) + .min(std::i64::MAX as f64) + .floor() as i64, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Filesize { + val: lhs / rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Duration { + val: lhs / rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::CustomValue { val: lhs, span }, rhs) => { + lhs.operation(*span, Operator::Divide, op, rhs) + } + + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span()?, + rhs_ty: rhs.get_type(), + rhs_span: rhs.span()?, + }), + } + } pub fn lt(&self, op: Span, rhs: &Value, span: Span) -> Result { if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) { return lhs.operation(*span, Operator::LessThan, op, rhs);