add match guards (#9621)

## description

this pr adds [match
guards](https://doc.rust-lang.org/reference/expressions/match-expr.html#match-guards)
to match patterns
```nushell
match $x {
   _ if $x starts-with 'nu' => {},
   $x => {}
}
```

these work pretty much like rust's match guards, with few limitations:

1. multiple matches using the `|` are not (yet?) supported
 
```nushell
match $num {
    0 | _ if (is-odd $num) => {},
    _ => {}
}
```

2. blocks cannot be used as guards, (yet?)

```nushell
match $num {
    $x if { $x ** $x == inf } => {},
     _ => {}
}
```

## checklist
- [x] syntax
- [x] syntax highlighting[^1]
- [x] semantics
- [x] tests
- [x] clean up

[^1]: defered for another pr
This commit is contained in:
mike
2023-07-16 03:25:12 +03:00
committed by GitHub
parent 57d96c09fa
commit 5bfec20244
6 changed files with 165 additions and 26 deletions

View File

@ -1,4 +1,4 @@
use nu_engine::{eval_block, eval_expression_with_input, CallExt};
use nu_engine::{eval_block, eval_expression, eval_expression_with_input, CallExt};
use nu_protocol::ast::{Call, Expr, Expression};
use nu_protocol::engine::{Command, EngineState, Matcher, Stack};
use nu_protocol::{
@ -52,26 +52,38 @@ impl Command for Match {
stack.add_var(match_variable.0, match_variable.1);
}
if let Some(block_id) = match_.1.as_block() {
let block = engine_state.get_block(block_id);
return eval_block(
engine_state,
stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
);
let guard_matches = if let Some(guard) = &match_.0.guard {
let Value::Bool { val, .. } = eval_expression(engine_state, stack, guard)? else {
return Err(ShellError::MatchGuardNotBool { span: guard.span});
};
val
} else {
return eval_expression_with_input(
engine_state,
stack,
&match_.1,
input,
call.redirect_stdout,
call.redirect_stderr,
)
.map(|x| x.0);
true
};
if guard_matches {
return if let Some(block_id) = match_.1.as_block() {
let block = engine_state.get_block(block_id);
eval_block(
engine_state,
stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
)
} else {
eval_expression_with_input(
engine_state,
stack,
&match_.1,
input,
call.redirect_stdout,
call.redirect_stderr,
)
.map(|x| x.0)
};
}
}
}
@ -107,6 +119,16 @@ impl Command for Match {
example: "{a: {b: 3}} | match $in {{a: { $b }} => ($b + 10) }",
result: Some(Value::test_int(13)),
},
Example {
description: "Match with a guard",
example: "
match [1 2 3] {
[$x, ..$y] if $x == 1 => { 'good list' },
_ => { 'not a very good list' }
}
",
result: Some(Value::test_string("good list")),
},
]
}
}