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

@ -10,12 +10,48 @@ pub struct MatchPattern {
pub span: Span,
}
impl MatchPattern {
pub fn variables(&self) -> Vec<VarId> {
self.pattern.variables()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Pattern {
Record(Vec<(String, MatchPattern)>),
List(Vec<MatchPattern>),
Value(Expression),
Variable(VarId),
Or(Vec<MatchPattern>),
IgnoreValue, // the _ pattern
Garbage,
}
impl Pattern {
pub fn variables(&self) -> Vec<VarId> {
let mut output = vec![];
match self {
Pattern::Record(items) => {
for item in items {
output.append(&mut item.1.variables());
}
}
Pattern::List(items) => {
for item in items {
output.append(&mut item.variables());
}
}
Pattern::Value(_) => {}
Pattern::Variable(var_id) => output.push(*var_id),
Pattern::Or(patterns) => {
for pattern in patterns {
output.append(&mut pattern.variables());
}
}
Pattern::IgnoreValue => {}
Pattern::Garbage => {}
}
output
}
}

View File

@ -1803,6 +1803,24 @@ impl<'a> StateWorkingSet<'a> {
None
}
pub fn find_variable_in_current_frame(&self, name: &[u8]) -> Option<VarId> {
let mut removed_overlays = vec![];
for scope_frame in self.delta.scope.iter().rev().take(1) {
for overlay_frame in scope_frame
.active_overlays(&mut removed_overlays)
.iter()
.rev()
{
if let Some(var_id) = overlay_frame.vars.get(name) {
return Some(*var_id);
}
}
}
None
}
pub fn add_variable(
&mut self,
mut name: Vec<u8>,

View File

@ -1,6 +1,6 @@
use crate::{
ast::{Expr, MatchPattern, Pattern, RangeInclusion},
Unit, Value, VarId,
Span, Unit, Value, VarId,
};
pub trait Matcher {
@ -217,6 +217,54 @@ impl Matcher for Pattern {
_ => false,
}
}
Pattern::Or(patterns) => {
let mut result = false;
for pattern in patterns {
let mut local_matches = vec![];
if !result {
if pattern.match_value(value, &mut local_matches) {
// TODO: do we need to replace previous variables that defaulted to nothing?
matches.append(&mut local_matches);
result = true;
} else {
// Create variables that don't match and assign them to null
let vars = pattern.variables();
for var in &vars {
let mut found = false;
for match_ in matches.iter() {
if match_.0 == *var {
found = true;
}
}
if !found {
// FIXME: don't use Span::unknown()
matches.push((*var, Value::nothing(Span::unknown())))
}
}
}
} else {
// We already have a match, so ignore the remaining match variables
// And assign them to null
let vars = pattern.variables();
for var in &vars {
let mut found = false;
for match_ in matches.iter() {
if match_.0 == *var {
found = true;
}
}
if !found {
// FIXME: don't use Span::unknown()
matches.push((*var, Value::nothing(Span::unknown())))
}
}
}
}
result
}
}
}
}