diff --git a/crates/nu-command/src/filesystem/glob.rs b/crates/nu-command/src/filesystem/glob.rs index da0ea79d2..9c9e9cb9e 100644 --- a/crates/nu-command/src/filesystem/glob.rs +++ b/crates/nu-command/src/filesystem/glob.rs @@ -111,6 +111,7 @@ impl Command for Glob { } }; + #[allow(clippy::needless_collect)] let glob_results: Vec = glob .walk(path, folder_depth) .flatten() diff --git a/crates/nu-command/src/formats/from/nuon.rs b/crates/nu-command/src/formats/from/nuon.rs index 4344f096e..7337e1bad 100644 --- a/crates/nu-command/src/formats/from/nuon.rs +++ b/crates/nu-command/src/formats/from/nuon.rs @@ -195,6 +195,12 @@ fn convert_to_value( "binary operators not supported in nuon".into(), expr.span, )), + Expr::UnaryNot(..) => Err(ShellError::OutsideSpannedLabeledError( + original_text.to_string(), + "Error when loading".into(), + "unary operators not supported in nuon".into(), + expr.span, + )), Expr::Block(..) => Err(ShellError::OutsideSpannedLabeledError( original_text.to_string(), "Error when loading".into(), diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 03116068d..1a38c553c 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -319,6 +319,16 @@ pub fn eval_expression( span: expr.span, }), Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), + Expr::UnaryNot(expr) => { + let lhs = eval_expression(engine_state, stack, expr)?; + match lhs { + Value::Bool { val, .. } => Ok(Value::Bool { + val: !val, + span: expr.span, + }), + _ => Err(ShellError::TypeMismatch("bool".to_string(), expr.span)), + } + } Expr::BinaryOp(lhs, op, rhs) => { let op_span = op.span; let lhs = eval_expression(engine_state, stack, lhs)?; diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 84720d70c..93f97e43f 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -88,6 +88,17 @@ pub fn flatten_expression( output.extend(flatten_expression(working_set, rhs)); output } + Expr::UnaryNot(inner_expr) => { + let mut output = vec![( + Span { + start: expr.span.start, + end: expr.span.start + 3, + }, + FlatShape::Operator, + )]; + output.extend(flatten_expression(working_set, inner_expr)); + output + } Expr::Block(block_id) | Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { let outer_span = expr.span; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 7d59222c2..e63e57561 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -49,7 +49,7 @@ pub fn is_math_expression_like(bytes: &[u8]) -> bool { return false; } - if bytes == b"true" || bytes == b"false" || bytes == b"null" { + if bytes == b"true" || bytes == b"false" || bytes == b"null" || bytes == b"not" { return true; } @@ -872,6 +872,10 @@ pub fn parse_call( let bytes = working_set.get_span_contents(*word_span); if is_math_expression_like(bytes) + && bytes != b"true" + && bytes != b"false" + && bytes != b"null" + && bytes != b"not" && !working_set .permanent_state .external_exceptions @@ -4005,6 +4009,40 @@ pub fn parse_math_expression( let mut last_prec = 1000000; let mut error = None; + + let first_span = working_set.get_span_contents(spans[0]); + + if first_span == b"not" { + if spans.len() > 1 { + let (remainder, err) = parse_math_expression( + working_set, + &spans[1..], + lhs_row_var_id, + expand_aliases_denylist, + ); + return ( + Expression { + expr: Expr::UnaryNot(Box::new(remainder)), + span: span(spans), + ty: Type::Bool, + custom_completion: None, + }, + err, + ); + } else { + return ( + garbage(spans[0]), + Some(ParseError::Expected( + "expression".into(), + Span { + start: spans[0].end, + end: spans[0].end, + }, + )), + ); + } + } + let (mut lhs, err) = parse_value( working_set, spans[0], @@ -4716,6 +4754,10 @@ pub fn discover_captures_in_expr( output.extend(&lhs_result); output.extend(&rhs_result); } + Expr::UnaryNot(expr) => { + let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks); + output.extend(&result); + } Expr::Block(block_id) => { let block = working_set.get_block(*block_id); let results = { diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 17656065f..3f1597476 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -22,6 +22,7 @@ pub enum Expr { ExternalCall(Box, Vec), Operator(Operator), RowCondition(BlockId), + UnaryNot(Box), BinaryOp(Box, Box, Box), //lhs, op, rhs Subexpression(BlockId), Block(BlockId), diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 0df6835a4..a0c4d034f 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -113,6 +113,7 @@ impl Expression { Expr::BinaryOp(left, _, right) => { left.has_in_variable(working_set) || right.has_in_variable(working_set) } + Expr::UnaryNot(expr) => expr.has_in_variable(working_set), Expr::Block(block_id) => { let block = working_set.get_block(*block_id); @@ -264,6 +265,9 @@ impl Expression { left.replace_in_variable(working_set, new_var_id); right.replace_in_variable(working_set, new_var_id); } + Expr::UnaryNot(expr) => { + expr.replace_in_variable(working_set, new_var_id); + } Expr::Block(block_id) => { let block = working_set.get_block(*block_id); @@ -425,6 +429,9 @@ impl Expression { left.replace_span(working_set, replaced, new_span); right.replace_span(working_set, replaced, new_span); } + Expr::UnaryNot(expr) => { + expr.replace_span(working_set, replaced, new_span); + } Expr::Block(block_id) => { let mut block = working_set.get_block(*block_id).clone(); diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index e98c61e1f..3a8cc3e94 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -375,3 +375,39 @@ fn single_value_row_condition() -> TestResult { "2", ) } + +#[test] +fn unary_not_1() -> TestResult { + run_test(r#"not false"#, "true") +} + +#[test] +fn unary_not_2() -> TestResult { + run_test(r#"not (false)"#, "true") +} + +#[test] +fn unary_not_3() -> TestResult { + run_test(r#"(not false)"#, "true") +} + +#[test] +fn unary_not_4() -> TestResult { + run_test(r#"if not false { "hello" } else { "world" }"#, "hello") +} + +#[test] +fn unary_not_5() -> TestResult { + run_test( + r#"if not not not not false { "hello" } else { "world" }"#, + "world", + ) +} + +#[test] +fn unary_not_6() -> TestResult { + run_test( + r#"[[name, present]; [abc, true], [def, false]] | where not present | get name.0"#, + "def", + ) +}