mirror of
https://github.com/nushell/nushell.git
synced 2025-06-30 14:40:06 +02:00
range operator accepts bot..=top as well as bot..top (#8382)
# Description A compromise fix for #8162. Nushell range operator now accepts `..=` to mean the range includes the top value, so you can use your Rust habits. But the unadorned `..` range operator also includes the value, so you can also use your Nushell habits. _(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)_ ```nushell 〉1..5 ╭───┬───╮ │ 0 │ 1 │ │ 1 │ 2 │ │ 2 │ 3 │ │ 3 │ 4 │ │ 4 │ 5 │ ╰───┴───╯ -------------------------------------------- /home/bobhy/src/rust/nushell -------------------------------------------- 〉1..=5 ╭───┬───╮ │ 0 │ 1 │ │ 1 │ 2 │ │ 2 │ 3 │ │ 3 │ 4 │ │ 4 │ 5 │ ╰───┴───╯ -------------------------------------------- /home/bobhy/src/rust/nushell -------------------------------------------- 〉1..<5 ╭───┬───╮ │ 0 │ 1 │ │ 1 │ 2 │ │ 2 │ 3 │ │ 3 │ 4 │ ╰───┴───╯ ``` # User-Facing Changes Existing scripts with range operator will continue to operate as heretofore. _(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_ # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - [x] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - [x] `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - [x] `cargo test --workspace` to check that all tests pass # After Submitting Will update the book to include new syntax.
This commit is contained in:
@ -6,6 +6,7 @@ use nu_protocol::{
|
||||
engine::{Command, EngineState, Stack, StateWorkingSet},
|
||||
ParseError, PipelineData, ShellError, Signature, SyntaxShape,
|
||||
};
|
||||
use rstest::rstest;
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(Clone)]
|
||||
@ -974,334 +975,267 @@ mod range {
|
||||
use super::*;
|
||||
use nu_protocol::ast::{RangeInclusion, RangeOperator};
|
||||
|
||||
#[test]
|
||||
fn parse_inclusive_range() {
|
||||
#[rstest]
|
||||
#[case(b"0..10", RangeInclusion::Inclusive, "inclusive")]
|
||||
#[case(b"0..=10", RangeInclusion::Inclusive, "=inclusive")]
|
||||
#[case(b"0..<10", RangeInclusion::RightExclusive, "exclusive")]
|
||||
#[case(b"10..0", RangeInclusion::Inclusive, "reverse inclusive")]
|
||||
#[case(b"10..=0", RangeInclusion::Inclusive, "reverse =inclusive")]
|
||||
#[case(
|
||||
b"(3 - 3)..<(8 + 2)",
|
||||
RangeInclusion::RightExclusive,
|
||||
"subexpression exclusive"
|
||||
)]
|
||||
#[case(
|
||||
b"(3 - 3)..(8 + 2)",
|
||||
RangeInclusion::Inclusive,
|
||||
"subexpression inclusive"
|
||||
)]
|
||||
#[case(
|
||||
b"(3 - 3)..=(8 + 2)",
|
||||
RangeInclusion::Inclusive,
|
||||
"subexpression =inclusive"
|
||||
)]
|
||||
#[case(b"-10..-3", RangeInclusion::Inclusive, "negative inclusive")]
|
||||
#[case(b"-10..=-3", RangeInclusion::Inclusive, "negative =inclusive")]
|
||||
#[case(b"-10..<-3", RangeInclusion::RightExclusive, "negative exclusive")]
|
||||
|
||||
fn parse_bounded_range(
|
||||
#[case] phrase: &[u8],
|
||||
#[case] inclusion: RangeInclusion,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
let block = parse(&mut working_set, None, b"0..10", true, &[]);
|
||||
let block = parse(&mut working_set, None, phrase, true, &[]);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
assert_eq!(block.len(), 1);
|
||||
assert_eq!(block.len(), 1, "{tag}: block length");
|
||||
|
||||
let expressions = &block[0];
|
||||
assert_eq!(expressions.len(), 1);
|
||||
assert!(matches!(
|
||||
expressions[0],
|
||||
PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr: Expr::Range(
|
||||
assert_eq!(expressions.len(), 1, "{tag}: expression length");
|
||||
if let PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr:
|
||||
Expr::Range(
|
||||
Some(_),
|
||||
None,
|
||||
Some(_),
|
||||
RangeOperator {
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
inclusion: the_inclusion,
|
||||
..
|
||||
}
|
||||
},
|
||||
),
|
||||
..
|
||||
}
|
||||
)
|
||||
))
|
||||
..
|
||||
},
|
||||
) = expressions[0]
|
||||
{
|
||||
assert_eq!(
|
||||
the_inclusion, inclusion,
|
||||
"{tag}: wrong RangeInclusion {the_inclusion:?}"
|
||||
);
|
||||
} else {
|
||||
panic!("{tag}: expression mismatch.")
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_exclusive_range() {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
let block = parse(&mut working_set, None, b"0..<10", true, &[]);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
assert_eq!(block.len(), 1);
|
||||
|
||||
let expressions = &block[0];
|
||||
assert_eq!(expressions.len(), 1);
|
||||
assert!(matches!(
|
||||
expressions[0],
|
||||
PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr: Expr::Range(
|
||||
Some(_),
|
||||
None,
|
||||
Some(_),
|
||||
RangeOperator {
|
||||
inclusion: RangeInclusion::RightExclusive,
|
||||
..
|
||||
}
|
||||
),
|
||||
..
|
||||
}
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_reverse_range() {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
let block = parse(&mut working_set, None, b"10..0", true, &[]);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
assert_eq!(block.len(), 1);
|
||||
|
||||
let expressions = &block[0];
|
||||
assert_eq!(expressions.len(), 1);
|
||||
assert!(matches!(
|
||||
expressions[0],
|
||||
PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr: Expr::Range(
|
||||
Some(_),
|
||||
None,
|
||||
Some(_),
|
||||
RangeOperator {
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
..
|
||||
}
|
||||
),
|
||||
..
|
||||
}
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_subexpression_range() {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
let block = parse(&mut working_set, None, b"(3 - 3)..<(8 + 2)", true, &[]);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
assert_eq!(block.len(), 1);
|
||||
|
||||
let expressions = &block[0];
|
||||
assert_eq!(expressions.len(), 1);
|
||||
assert!(matches!(
|
||||
expressions[0],
|
||||
PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr: Expr::Range(
|
||||
Some(_),
|
||||
None,
|
||||
Some(_),
|
||||
RangeOperator {
|
||||
inclusion: RangeInclusion::RightExclusive,
|
||||
..
|
||||
}
|
||||
),
|
||||
..
|
||||
}
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_variable_range() {
|
||||
#[rstest]
|
||||
#[case(
|
||||
b"let a = 2; $a..10",
|
||||
RangeInclusion::Inclusive,
|
||||
"variable start inclusive"
|
||||
)]
|
||||
#[case(
|
||||
b"let a = 2; $a..=10",
|
||||
RangeInclusion::Inclusive,
|
||||
"variable start =inclusive"
|
||||
)]
|
||||
#[case(
|
||||
b"let a = 2; $a..<($a + 10)",
|
||||
RangeInclusion::RightExclusive,
|
||||
"subexpression variable exclusive"
|
||||
)]
|
||||
fn parse_variable_range(
|
||||
#[case] phrase: &[u8],
|
||||
#[case] inclusion: RangeInclusion,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
working_set.add_decl(Box::new(Let));
|
||||
|
||||
let block = parse(&mut working_set, None, b"let a = 2; $a..10", true, &[]);
|
||||
let block = parse(&mut working_set, None, phrase, true, &[]);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
assert_eq!(block.len(), 2);
|
||||
assert_eq!(block.len(), 2, "{tag} block len 2");
|
||||
|
||||
let expressions = &block[1];
|
||||
assert_eq!(expressions.len(), 1);
|
||||
assert!(matches!(
|
||||
expressions[0],
|
||||
PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr: Expr::Range(
|
||||
assert_eq!(expressions.len(), 1, "{tag}: expression length 1");
|
||||
if let PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr:
|
||||
Expr::Range(
|
||||
Some(_),
|
||||
None,
|
||||
Some(_),
|
||||
RangeOperator {
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
inclusion: the_inclusion,
|
||||
..
|
||||
}
|
||||
},
|
||||
),
|
||||
..
|
||||
}
|
||||
)
|
||||
))
|
||||
..
|
||||
},
|
||||
) = expressions[0]
|
||||
{
|
||||
assert_eq!(
|
||||
the_inclusion, inclusion,
|
||||
"{tag}: wrong RangeInclusion {the_inclusion:?}"
|
||||
);
|
||||
} else {
|
||||
panic!("{tag}: expression mismatch.")
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_subexpression_variable_range() {
|
||||
#[rstest]
|
||||
#[case(b"0..", RangeInclusion::Inclusive, "right unbounded")]
|
||||
#[case(b"0..=", RangeInclusion::Inclusive, "right unbounded =inclusive")]
|
||||
#[case(b"0..<", RangeInclusion::RightExclusive, "right unbounded")]
|
||||
|
||||
fn parse_right_unbounded_range(
|
||||
#[case] phrase: &[u8],
|
||||
#[case] inclusion: RangeInclusion,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
working_set.add_decl(Box::new(Let));
|
||||
|
||||
let block = parse(
|
||||
&mut working_set,
|
||||
None,
|
||||
b"let a = 2; $a..<($a + 10)",
|
||||
true,
|
||||
&[],
|
||||
);
|
||||
let block = parse(&mut working_set, None, phrase, true, &[]);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
assert_eq!(block.len(), 2);
|
||||
|
||||
let expressions = &block[1];
|
||||
assert_eq!(expressions.len(), 1);
|
||||
assert!(matches!(
|
||||
expressions[0],
|
||||
PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr: Expr::Range(
|
||||
Some(_),
|
||||
None,
|
||||
Some(_),
|
||||
RangeOperator {
|
||||
inclusion: RangeInclusion::RightExclusive,
|
||||
..
|
||||
}
|
||||
),
|
||||
..
|
||||
}
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_right_unbounded_range() {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
let block = parse(&mut working_set, None, b"0..", true, &[]);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
assert_eq!(block.len(), 1);
|
||||
assert_eq!(block.len(), 1, "{tag}: block len 1");
|
||||
|
||||
let expressions = &block[0];
|
||||
assert_eq!(expressions.len(), 1);
|
||||
assert!(matches!(
|
||||
expressions[0],
|
||||
PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr: Expr::Range(
|
||||
assert_eq!(expressions.len(), 1, "{tag}: expression length 1");
|
||||
if let PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr:
|
||||
Expr::Range(
|
||||
Some(_),
|
||||
None,
|
||||
None,
|
||||
RangeOperator {
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
inclusion: the_inclusion,
|
||||
..
|
||||
}
|
||||
},
|
||||
),
|
||||
..
|
||||
}
|
||||
)
|
||||
))
|
||||
..
|
||||
},
|
||||
) = expressions[0]
|
||||
{
|
||||
assert_eq!(
|
||||
the_inclusion, inclusion,
|
||||
"{tag}: wrong RangeInclusion {the_inclusion:?}"
|
||||
);
|
||||
} else {
|
||||
panic!("{tag}: expression mismatch.")
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_left_unbounded_range() {
|
||||
#[rstest]
|
||||
#[case(b"..10", RangeInclusion::Inclusive, "left unbounded inclusive")]
|
||||
#[case(b"..=10", RangeInclusion::Inclusive, "left unbounded =inclusive")]
|
||||
#[case(b"..<10", RangeInclusion::RightExclusive, "left unbounded exclusive")]
|
||||
|
||||
fn parse_left_unbounded_range(
|
||||
#[case] phrase: &[u8],
|
||||
#[case] inclusion: RangeInclusion,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
let block = parse(&mut working_set, None, b"..10", true, &[]);
|
||||
let block = parse(&mut working_set, None, phrase, true, &[]);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
assert_eq!(block.len(), 1);
|
||||
assert_eq!(block.len(), 1, "{tag}: block len 1");
|
||||
|
||||
let expressions = &block[0];
|
||||
assert_eq!(expressions.len(), 1);
|
||||
assert!(matches!(
|
||||
expressions[0],
|
||||
PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr: Expr::Range(
|
||||
assert_eq!(expressions.len(), 1, "{tag}: expression length 1");
|
||||
if let PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr:
|
||||
Expr::Range(
|
||||
None,
|
||||
None,
|
||||
Some(_),
|
||||
RangeOperator {
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
inclusion: the_inclusion,
|
||||
..
|
||||
}
|
||||
},
|
||||
),
|
||||
..
|
||||
}
|
||||
)
|
||||
))
|
||||
..
|
||||
},
|
||||
) = expressions[0]
|
||||
{
|
||||
assert_eq!(
|
||||
the_inclusion, inclusion,
|
||||
"{tag}: wrong RangeInclusion {the_inclusion:?}"
|
||||
);
|
||||
} else {
|
||||
panic!("{tag}: expression mismatch.")
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_negative_range() {
|
||||
#[rstest]
|
||||
#[case(b"2.0..4.0..10.0", RangeInclusion::Inclusive, "float inclusive")]
|
||||
#[case(b"2.0..4.0..=10.0", RangeInclusion::Inclusive, "float =inclusive")]
|
||||
#[case(b"2.0..4.0..<10.0", RangeInclusion::RightExclusive, "float exclusive")]
|
||||
|
||||
fn parse_float_range(
|
||||
#[case] phrase: &[u8],
|
||||
#[case] inclusion: RangeInclusion,
|
||||
#[case] tag: &str,
|
||||
) {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
let block = parse(&mut working_set, None, b"-10..-3", true, &[]);
|
||||
let block = parse(&mut working_set, None, phrase, true, &[]);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
assert_eq!(block.len(), 1);
|
||||
assert_eq!(block.len(), 1, "{tag}: block length 1");
|
||||
|
||||
let expressions = &block[0];
|
||||
assert_eq!(expressions.len(), 1);
|
||||
assert!(matches!(
|
||||
expressions[0],
|
||||
PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr: Expr::Range(
|
||||
Some(_),
|
||||
None,
|
||||
Some(_),
|
||||
RangeOperator {
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
..
|
||||
}
|
||||
),
|
||||
..
|
||||
}
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_float_range() {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
let block = parse(&mut working_set, None, b"2.0..4.0..10.0", true, &[]);
|
||||
|
||||
assert!(working_set.parse_errors.is_empty());
|
||||
assert_eq!(block.len(), 1);
|
||||
|
||||
let expressions = &block[0];
|
||||
assert_eq!(expressions.len(), 1);
|
||||
assert!(matches!(
|
||||
expressions[0],
|
||||
PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr: Expr::Range(
|
||||
assert_eq!(expressions.len(), 1, "{tag}: expression length 1");
|
||||
if let PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr:
|
||||
Expr::Range(
|
||||
Some(_),
|
||||
Some(_),
|
||||
Some(_),
|
||||
RangeOperator {
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
inclusion: the_inclusion,
|
||||
..
|
||||
}
|
||||
},
|
||||
),
|
||||
..
|
||||
}
|
||||
)
|
||||
))
|
||||
..
|
||||
},
|
||||
) = expressions[0]
|
||||
{
|
||||
assert_eq!(
|
||||
the_inclusion, inclusion,
|
||||
"{tag}: wrong RangeInclusion {the_inclusion:?}"
|
||||
);
|
||||
} else {
|
||||
panic!("{tag}: expression mismatch.")
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1309,7 +1243,7 @@ mod range {
|
||||
let engine_state = EngineState::new();
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
parse(&mut working_set, None, b"(0)..\"a\"", true, &[]);
|
||||
let _ = parse(&mut working_set, None, b"(0)..\"a\"", true, &[]);
|
||||
|
||||
assert!(!working_set.parse_errors.is_empty());
|
||||
}
|
||||
|
Reference in New Issue
Block a user