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

@ -4270,8 +4270,9 @@ pub fn parse_match_block_expression(working_set: &mut StateWorkingSet, span: Spa
break;
}
// Multiple patterns connected by '|'
let mut connector = working_set.get_span_contents(output[position].span);
// Multiple patterns connected by '|'
if connector == b"|" && position < output.len() {
let mut or_pattern = vec![pattern];
@ -4322,10 +4323,56 @@ pub fn parse_match_block_expression(working_set: &mut StateWorkingSet, span: Spa
pattern = MatchPattern {
pattern: Pattern::Or(or_pattern),
guard: None,
span: Span::new(start, end),
}
}
// A match guard
} else if connector == b"if" {
let if_end = {
let end = output[position].span.end;
Span::new(end, end)
};
position += 1;
let mk_err = || ParseError::LabeledErrorWithHelp {
error: "Match guard without an expression".into(),
label: "expected an expression".into(),
help: "The `if` keyword must be followed with an expression".into(),
span: if_end,
};
if output.get(position).is_none() {
working_set.error(mk_err());
return garbage(span);
};
let (tokens, found) = if let Some((pos, _)) = output[position..]
.iter()
.find_position(|t| working_set.get_span_contents(t.span) == b"=>")
{
if position + pos == position {
working_set.error(mk_err());
return garbage(span);
}
(&output[position..position + pos], true)
} else {
(&output[position..], false)
};
let mut start = 0;
let guard = parse_multispan_value(
working_set,
&tokens.iter().map(|tok| tok.span).collect_vec(),
&mut start,
&SyntaxShape::MathExpression,
);
pattern.guard = Some(guard);
position += if found { start + 1 } else { start };
connector = working_set.get_span_contents(output[position].span);
}
// Then the `=>` arrow
if connector != b"=>" {
working_set.error(ParseError::Mismatch(