Add or-patterns, fix var binding scope (#8633)

# Description

Adds `|` patterns to `match`, allowing you to try multiple patterns for
the same case.

Example:

```
match {b: 1} { {a: $b} | {b: $b} => { print $b } }
```

Variables that don't bind are set to `$nothing` so that they can be
later checked.

This PR also:
fixes #8631 

Creates a set of integration tests for pattern matching also

# User-Facing Changes

Adds `|` to `match`. Fixes variable binding scope. 
# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
JT
2023-03-27 11:31:57 +13:00
committed by GitHub
parent 332f1192a6
commit 7ec5f2f2eb
8 changed files with 355 additions and 9 deletions

View File

@ -559,6 +559,11 @@ pub fn flatten_pattern(match_pattern: &MatchPattern) -> Vec<(Span, FlatShape)> {
Pattern::Variable(_) => {
output.push((match_pattern.span, FlatShape::Variable));
}
Pattern::Or(patterns) => {
for pattern in patterns {
output.extend(flatten_pattern(pattern));
}
}
}
output
}

View File

@ -79,7 +79,7 @@ pub fn parse_variable_pattern(
let bytes = working_set.get_span_contents(span);
if is_variable(bytes) {
if let Some(var_id) = working_set.find_variable(bytes) {
if let Some(var_id) = working_set.find_variable_in_current_frame(bytes) {
(
MatchPattern {
pattern: Pattern::Variable(var_id),

View File

@ -4526,7 +4526,7 @@ pub fn parse_match_block_expression(
let source = working_set.get_span_contents(inner_span);
let (output, err) = lex(source, start, &[b' ', b'\r', b'\n', b','], &[], false);
let (output, err) = lex(source, start, &[b' ', b'\r', b'\n', b',', b'|'], &[], false);
error = error.or(err);
let mut position = 0;
@ -4539,7 +4539,7 @@ pub fn parse_match_block_expression(
working_set.enter_scope();
// First parse the pattern
let (pattern, err) = parse_pattern(working_set, output[position].span);
let (mut pattern, err) = parse_pattern(working_set, output[position].span);
error = error.or(err);
position += 1;
@ -4555,19 +4555,75 @@ pub fn parse_match_block_expression(
break;
}
// Then the =>
let thick_arrow = working_set.get_span_contents(output[position].span);
if thick_arrow != b"=>" {
// Multiple patterns connected by '|'
let mut connector = working_set.get_span_contents(output[position].span);
if connector == b"|" && position < output.len() {
let mut or_pattern = vec![pattern];
while connector == b"|" && position < output.len() {
connector = b"";
position += 1;
if position >= output.len() {
error = error.or(Some(ParseError::Mismatch(
"pattern".into(),
"end of input".into(),
Span::new(output[position - 1].span.end, output[position - 1].span.end),
)));
working_set.exit_scope();
break;
}
let (pattern, err) = parse_pattern(working_set, output[position].span);
error = error.or(err);
or_pattern.push(pattern);
position += 1;
if position >= output.len() {
error = error.or(Some(ParseError::Mismatch(
"=>".into(),
"end of input".into(),
Span::new(output[position - 1].span.end, output[position - 1].span.end),
)));
working_set.exit_scope();
break;
} else {
connector = working_set.get_span_contents(output[position].span);
}
}
let start = or_pattern
.first()
.expect("internal error: unexpected state of or-pattern")
.span
.start;
let end = or_pattern
.last()
.expect("internal error: unexpected state of or-pattern")
.span
.end;
pattern = MatchPattern {
pattern: Pattern::Or(or_pattern),
span: Span::new(start, end),
}
}
// Then the `=>` arrow
if connector != b"=>" {
error = error.or(Some(ParseError::Mismatch(
"=>".into(),
"end of input".into(),
Span::new(output[position - 1].span.end, output[position - 1].span.end),
)));
} else {
position += 1;
}
// Finally, the value/expression/block that we will run to produce the result
position += 1;
if position >= output.len() {
error = error.or(Some(ParseError::Mismatch(
"match result".into(),
@ -6094,6 +6150,11 @@ pub fn discover_captures_in_pattern(pattern: &MatchPattern, seen: &mut Vec<VarId
discover_captures_in_pattern(&item.1, seen)
}
}
Pattern::Or(patterns) => {
for pattern in patterns {
discover_captures_in_pattern(pattern, seen)
}
}
Pattern::Value(_) | Pattern::IgnoreValue | Pattern::Garbage => {}
}
}