Add rest and ignore-rest patterns (#8681)

# Description

Adds two more patterns when working with lists:

```
[1, ..$remainder]
```
and
```
[1, ..]
```
The first one collects the remaining items and assigns them into the
variable. The second one ignores any remaining values.

# User-Facing Changes

Adds more capability to list pattern matching.

# 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
- `cargo run -- crates/nu-utils/standard_library/tests.nu` to run the
tests for the standard library

> **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-31 11:08:53 +13:00
committed by GitHub
parent 09276db2a5
commit 3db0aed9f7
6 changed files with 125 additions and 32 deletions

View File

@ -18,6 +18,8 @@ impl Matcher for Pattern {
match self {
Pattern::Garbage => false,
Pattern::IgnoreValue => true,
Pattern::IgnoreRest => false, // `..` and `..$foo` only match in specific contexts
Pattern::Rest(_) => false, // so we return false here and handle them elsewhere
Pattern::Record(field_patterns) => match value {
Value::Record { cols, vals, .. } => {
'top: for field_pattern in field_patterns {
@ -46,17 +48,47 @@ impl Matcher for Pattern {
Pattern::List(items) => match &value {
Value::List { vals, .. } => {
if items.len() > vals.len() {
// We need more items in our pattern than are available in the Value
return false;
// The only we we allow this is to have a rest pattern in the n+1 position
if items.len() == (vals.len() + 1) {
match &items[vals.len()].pattern {
Pattern::IgnoreRest => {}
Pattern::Rest(var_id) => {
matches.push((*var_id, Value::nothing(items[vals.len()].span)))
}
_ => {
// There is a pattern which can't skip missing values, so we fail
return false;
}
}
} else {
// There are patterns that can't be matches, so we fail
return false;
}
}
for (val_idx, val) in vals.iter().enumerate() {
// We require that the pattern and the value have the same number of items, or the pattern does not match
// The only exception is if the pattern includes a `..` pattern
if let Some(pattern) = items.get(val_idx) {
if !pattern.match_value(val, matches) {
return false;
match &pattern.pattern {
Pattern::IgnoreRest => {
break;
}
Pattern::Rest(var_id) => {
let rest_vals = vals[val_idx..].to_vec();
matches.push((
*var_id,
Value::List {
vals: rest_vals,
span: pattern.span,
},
));
break;
}
_ => {
if !pattern.match_value(val, matches) {
return false;
}
}
}
} else {
return false;