diff --git a/crates/nu-command/src/database/values/dsl/expression.rs b/crates/nu-command/src/database/values/dsl/expression.rs index 9489f8c657..c22b1267eb 100644 --- a/crates/nu-command/src/database/values/dsl/expression.rs +++ b/crates/nu-command/src/database/values/dsl/expression.rs @@ -124,7 +124,8 @@ impl CustomValue for ExprDb { | Operator::ShiftLeft | Operator::ShiftRight | Operator::StartsWith - | Operator::EndsWith => Err(ShellError::UnsupportedOperator(operator, op)), + | Operator::EndsWith + | Operator::Append => Err(ShellError::UnsupportedOperator(operator, op)), }?; let expr = Expr::BinaryOp { diff --git a/crates/nu-command/tests/commands/math/mod.rs b/crates/nu-command/tests/commands/math/mod.rs index 3f18f5798a..c7c8e88b29 100644 --- a/crates/nu-command/tests/commands/math/mod.rs +++ b/crates/nu-command/tests/commands/math/mod.rs @@ -473,3 +473,39 @@ fn compound_where_paren() { assert_eq!(actual.out, r#"[{"a": 2,"b": 1},{"a": 2,"b": 2}]"#); } + +#[test] +fn adding_lists() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + [1 3] ++ [5 6] | to nuon + "# + )); + + assert_eq!(actual.out, "[1, 3, 5, 6]"); +} + +#[test] +fn adding_list_and_value() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + [1 3] ++ 5 | to nuon + "# + )); + + assert_eq!(actual.out, "[1, 3, 5]"); +} + +#[test] +fn adding_value_and_list() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1 ++ [3 5] | to nuon + "# + )); + + assert_eq!(actual.out, "[1, 3, 5]"); +} diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 7ff14655bd..d51f26b28a 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -385,6 +385,10 @@ pub fn eval_expression( let rhs = eval_expression(engine_state, stack, rhs)?; lhs.add(op_span, &rhs, expr.span) } + Operator::Append => { + let rhs = eval_expression(engine_state, stack, rhs)?; + lhs.append(op_span, &rhs, expr.span) + } Operator::Minus => { let rhs = eval_expression(engine_state, stack, rhs)?; lhs.sub(op_span, &rhs, expr.span) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 662b20a86d..07e63d1ec2 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -4273,6 +4273,7 @@ pub fn parse_operator( b"=~" => Operator::RegexMatch, b"!~" => Operator::NotRegexMatch, b"+" => Operator::Plus, + b"++" => Operator::Append, b"-" => Operator::Minus, b"*" => Operator::Multiply, b"/" => Operator::Divide, diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index eaba67220f..a229ed2315 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -68,6 +68,35 @@ pub fn math_result_type( ) } }, + Operator::Append => match (&lhs.ty, &rhs.ty) { + (Type::List(a), Type::List(b)) => { + if a == b { + (Type::List(a.clone()), None) + } else { + (Type::List(Box::new(Type::Any)), None) + } + } + (Type::List(a), b) | (b, Type::List(a)) => { + if a == &Box::new(b.clone()) { + (Type::List(a.clone()), None) + } else { + (Type::List(Box::new(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::Minus => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, None), (Type::Float, Type::Int) => (Type::Float, None), diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index f1803ac2f9..7f414488ad 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -47,7 +47,8 @@ impl Expression { | Operator::Equal | Operator::NotEqual | Operator::In - | Operator::NotIn => 80, + | Operator::NotIn + | Operator::Append => 80, Operator::BitAnd => 75, Operator::BitXor => 70, Operator::BitOr => 60, diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index e58ebe41f6..2c29c1dbb5 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -14,6 +14,7 @@ pub enum Operator { RegexMatch, NotRegexMatch, Plus, + Append, Minus, Multiply, Divide, @@ -43,6 +44,7 @@ impl Display for Operator { Operator::RegexMatch => write!(f, "=~"), Operator::NotRegexMatch => write!(f, "!~"), Operator::Plus => write!(f, "+"), + Operator::Append => write!(f, "++"), Operator::Minus => write!(f, "-"), Operator::Multiply => write!(f, "*"), Operator::Divide => write!(f, "/"), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index b32344889a..4651d0fa2e 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -1681,6 +1681,34 @@ impl Value { } } + pub fn append(&self, op: Span, rhs: &Value, span: Span) -> Result { + match (self, rhs) { + (Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => { + let mut lhs = lhs.clone(); + let mut rhs = rhs.clone(); + lhs.append(&mut rhs); + Ok(Value::List { vals: lhs, span }) + } + (Value::List { vals: lhs, .. }, val) => { + let mut lhs = lhs.clone(); + lhs.push(val.clone()); + Ok(Value::List { vals: lhs, span }) + } + (val, Value::List { vals: rhs, .. }) => { + let mut rhs = rhs.clone(); + rhs.insert(0, val.clone()); + Ok(Value::List { vals: rhs, span }) + } + _ => 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 sub(&self, op: Span, rhs: &Value, span: Span) -> Result { match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => {