From 2cb815b7b4cdc52b3934da459917d13ce89938a2 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 1 Apr 2022 13:35:46 -0500 Subject: [PATCH] Add starts with operator (#5061) * add starts_with operator * added a test --- .../values/nu_dataframe/between_values.rs | 13 ++++++++++++ crates/nu-engine/src/eval.rs | 4 ++++ crates/nu-parser/src/parser.rs | 1 + crates/nu-parser/src/type_check.rs | 18 ++++++++++++++++ crates/nu-protocol/src/ast/expression.rs | 1 + crates/nu-protocol/src/ast/operator.rs | 2 ++ crates/nu-protocol/src/value/mod.rs | 21 +++++++++++++++++++ src/tests/test_parser.rs | 8 +++++++ 8 files changed, 68 insertions(+) diff --git a/crates/nu-command/src/dataframe/values/nu_dataframe/between_values.rs b/crates/nu-command/src/dataframe/values/nu_dataframe/between_values.rs index 70449d833d..da52985edd 100644 --- a/crates/nu-command/src/dataframe/values/nu_dataframe/between_values.rs +++ b/crates/nu-command/src/dataframe/values/nu_dataframe/between_values.rs @@ -376,6 +376,19 @@ pub(super) fn compute_series_single_value( rhs_span: right.span()?, }), }, + Operator::StartsWith => match &right { + Value::String { val, .. } => { + let starts_with_pattern = format!("^{}", val); + contains_series_pat(&lhs, &starts_with_pattern, lhs_span) + } + _ => Err(ShellError::OperatorMismatch { + op_span: operator.span, + lhs_ty: left.get_type(), + lhs_span: left.span()?, + rhs_ty: right.get_type(), + rhs_span: right.span()?, + }), + }, _ => Err(ShellError::OperatorMismatch { op_span: operator.span, lhs_ty: left.get_type(), diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index f9dccdfd02..a325065059 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -411,6 +411,10 @@ pub fn eval_expression( let rhs = eval_expression(engine_state, stack, rhs)?; lhs.pow(op_span, &rhs) } + Operator::StartsWith => { + let rhs = eval_expression(engine_state, stack, rhs)?; + lhs.starts_with(op_span, &rhs) + } } } Expr::Subexpression(block_id) => { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index de39e2564c..7b3b335589 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -3940,6 +3940,7 @@ pub fn parse_operator( b">" => Operator::GreaterThan, b">=" => Operator::GreaterThanOrEqual, b"=~" => Operator::Contains, + b"=^" => Operator::StartsWith, b"!~" => Operator::NotContains, b"+" => Operator::Plus, b"-" => Operator::Minus, diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 822a4ec201..22184e239c 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -319,6 +319,24 @@ pub fn math_result_type( ) } }, + Operator::StartsWith => 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), diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index ee28b120c3..0df6835a4a 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -34,6 +34,7 @@ impl Expression { Operator::Plus | Operator::Minus => 90, Operator::NotContains | Operator::Contains + | Operator::StartsWith | Operator::LessThan | Operator::LessThanOrEqual | Operator::GreaterThan diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index 3c8e916dca..c01ccd39eb 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -23,6 +23,7 @@ pub enum Operator { And, Or, Pow, + StartsWith, } impl Display for Operator { @@ -46,6 +47,7 @@ impl Display for Operator { Operator::Pow => write!(f, "**"), Operator::LessThanOrEqual => write!(f, "<="), Operator::GreaterThanOrEqual => write!(f, ">="), + Operator::StartsWith => write!(f, "=^"), } } } diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 1f81937290..af03af0ee6 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -2031,6 +2031,27 @@ impl Value { } } + pub fn starts_with(&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.starts_with(rhs), + span, + }), + (Value::CustomValue { val: lhs, span }, rhs) => { + lhs.operation(*span, Operator::StartsWith, 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 not_contains(&self, op: Span, rhs: &Value) -> Result { let span = span(&[self.span()?, rhs.span()?]); diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index 4b6eaab57a..c457236f32 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -315,6 +315,14 @@ fn capture_row_condition() -> TestResult { ) } +#[test] +fn starts_with_operator_succeeds() -> TestResult { + run_test( + r#"[Moe Larry Curly] | where $it =^ L | str collect"#, + "Larry", + ) +} + #[test] fn proper_missing_param() -> TestResult { fail_test(r#"def foo [x y z w] { }; foo a b c"#, "missing w")