From 63e3552eef761a8bdec9a7cd8efb86459657a722 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 12 Oct 2021 09:35:12 +1300 Subject: [PATCH] Add the remaining missing operators --- TODO.md | 2 +- crates/nu-engine/src/eval.rs | 7 +- crates/nu-parser/src/type_check.rs | 74 ++++++++++--- crates/nu-protocol/src/value/mod.rs | 158 ++++++++++++++++++++++++++++ src/tests.rs | 35 ++++++ 5 files changed, 257 insertions(+), 19 deletions(-) diff --git a/TODO.md b/TODO.md index fd65445985..79ad759288 100644 --- a/TODO.md +++ b/TODO.md @@ -28,11 +28,11 @@ - [x] Error shortcircuit (stopping on first error). Revised: errors emit first, but can be seen by commands. - [x] Value serialization - [x] Handling rows with missing columns during a cell path +- [x] finish operator type-checking - [ ] Input/output types - [ ] Support for `$in` - [ ] ctrl-c support - [ ] operator overflow -- [ ] finish operator type-checking - [ ] Overlays (replacement for `autoenv`) ## Maybe: diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 5edf80cd15..6a642f7d50 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -192,7 +192,12 @@ pub fn eval_expression( Operator::NotEqual => lhs.ne(op_span, &rhs), Operator::In => lhs.r#in(op_span, &rhs), Operator::NotIn => lhs.not_in(op_span, &rhs), - x => Err(ShellError::UnsupportedOperator(x, op_span)), + Operator::Contains => lhs.contains(op_span, &rhs), + Operator::NotContains => lhs.not_contains(op_span, &rhs), + Operator::Modulo => lhs.modulo(op_span, &rhs), + Operator::And => lhs.and(op_span, &rhs), + Operator::Or => lhs.or(op_span, &rhs), + Operator::Pow => lhs.pow(op_span, &rhs), } } Expr::Subexpression(block_id) => { diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index ca087d0a87..ad91fd861e 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -86,7 +86,7 @@ pub fn math_result_type( ) } }, - Operator::Multiply => match (&lhs.ty, &rhs.ty) { + Operator::Multiply | Operator::Pow => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, None), (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), @@ -108,7 +108,7 @@ pub fn math_result_type( ) } }, - Operator::Divide => match (&lhs.ty, &rhs.ty) { + Operator::Divide | Operator::Modulo => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, None), (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), @@ -130,6 +130,25 @@ pub fn math_result_type( ) } }, + Operator::And | Operator::Or => match (&lhs.ty, &rhs.ty) { + (Type::Bool, Type::Bool) => (Type::Int, None), + + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, Operator::LessThan => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Bool, None), (Type::Float, Type::Int) => (Type::Bool, None), @@ -273,6 +292,42 @@ pub fn math_result_type( ) } }, + Operator::Contains => match (&lhs.ty, &rhs.ty) { + (Type::String, Type::String) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::NotContains => match (&lhs.ty, &rhs.ty) { + (Type::String, Type::String) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, Operator::In => match (&lhs.ty, &rhs.ty) { (t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None), (Type::Int | Type::Float, Type::Range) => (Type::Bool, None), @@ -317,21 +372,6 @@ pub fn math_result_type( ) } }, - - _ => { - *op = Expression::garbage(op.span); - - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } }, _ => { *op = Expression::garbage(op.span); diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index e83bbee158..8eb7dd566f 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -872,6 +872,164 @@ impl Value { }), } } + + pub fn contains(&self, op: Span, rhs: &Value) -> Result { + let span = span(&[self.span()?, rhs.span()?]); + + match (self, rhs) { + (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::Bool { + val: lhs.contains(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 not_contains(&self, op: Span, rhs: &Value) -> Result { + let span = span(&[self.span()?, rhs.span()?]); + + match (self, rhs) { + (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::Bool { + val: !lhs.contains(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 modulo(&self, op: Span, rhs: &Value) -> Result { + let span = span(&[self.span()?, rhs.span()?]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Int { + val: lhs % rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + if *rhs != 0.0 { + Ok(Value::Float { + val: *lhs as f64 % *rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Float { + val: *lhs % *rhs as f64, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + if *rhs != 0.0 { + Ok(Value::Float { + val: lhs % rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + + _ => 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 and(&self, op: Span, rhs: &Value) -> Result { + let span = span(&[self.span()?, rhs.span()?]); + + match (self, rhs) { + (Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => Ok(Value::Bool { + val: *lhs && *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 or(&self, op: Span, rhs: &Value) -> Result { + let span = span(&[self.span()?, rhs.span()?]); + + match (self, rhs) { + (Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => Ok(Value::Bool { + val: *lhs || *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 pow(&self, op: Span, rhs: &Value) -> Result { + let span = span(&[self.span()?, rhs.span()?]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int { + val: lhs.pow(*rhs as u32), + span, + }), + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { + val: (*lhs as f64).powf(*rhs), + span, + }), + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Float { + val: lhs.powf(*rhs as f64), + span, + }), + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { + val: lhs.powf(*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()?, + }), + } + } } /// Format a duration in nanoseconds into a string diff --git a/src/tests.rs b/src/tests.rs index 5da55fa5ae..1d441cc3ee 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -77,6 +77,41 @@ fn broken_math() -> TestResult { fail_test("3 + ", "incomplete") } +#[test] +fn modulo1() -> TestResult { + run_test("5 mod 2", "1") +} + +#[test] +fn modulo2() -> TestResult { + run_test("5.25 mod 2", "1.25") +} + +#[test] +fn and() -> TestResult { + run_test("$true && $false", "false") +} + +#[test] +fn or() -> TestResult { + run_test("$true || $false", "true") +} + +#[test] +fn pow() -> TestResult { + run_test("3 ** 3", "27") +} + +#[test] +fn contains() -> TestResult { + run_test("'testme' =~ 'test'", "true") +} + +#[test] +fn not_contains() -> TestResult { + run_test("'testme' !~ 'test'", "false") +} + #[test] fn if_test1() -> TestResult { run_test("if $true { 10 } else { 20 } ", "10")