Implement exclusive and inclusive ranges with ..< and .. (#2541)

* Implement exclusive and inclusive ranges with .. and ..=

This commit adds right-exclusive ranges.

The original a..b inclusive syntax was changed to reflect the Rust notation.
New a..=b syntax was introduced to have the old behavior.

Currently, both a.. and b..= is valid, and it is unclear whether it's valid
to impose restrictions.

The original issue suggests .. for inclusive and ..< for exclusive ranges,
this can be implemented by making simple changes to this commit.

* Fix collect tests by changing ranges to ..=

* Fix clippy lints in exclusive range matching

* Implement exclusive ranges using `..<`
This commit is contained in:
Radek Vít 2020-09-13 23:53:08 +02:00 committed by GitHub
parent c355585112
commit 599bb9797d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 108 additions and 19 deletions

View File

@ -5,7 +5,7 @@ use crate::prelude::*;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use log::trace; use log::trace;
use nu_errors::{ArgumentError, ShellError}; use nu_errors::{ArgumentError, ShellError};
use nu_protocol::hir::{self, Expression, ExternalRedirection, SpannedExpression}; use nu_protocol::hir::{self, Expression, ExternalRedirection, RangeOperator, SpannedExpression};
use nu_protocol::{ use nu_protocol::{
ColumnPath, Primitive, RangeInclusion, UnspannedPathMember, UntaggedValue, Value, ColumnPath, Primitive, RangeInclusion, UnspannedPathMember, UntaggedValue, Value,
}; };
@ -82,7 +82,10 @@ pub(crate) async fn evaluate_baseline_expr(
); );
let right = ( let right = (
right.as_primitive()?.spanned(right_span), right.as_primitive()?.spanned(right_span),
RangeInclusion::Inclusive, match &range.operator.item {
RangeOperator::Inclusive => RangeInclusion::Inclusive,
RangeOperator::RightExclusive => RangeInclusion::Exclusive,
},
); );
Ok(UntaggedValue::range(left, right).into_value(tag)) Ok(UntaggedValue::range(left, right).into_value(tag))

View File

@ -26,6 +26,9 @@ impl Palette for DefaultPalette {
} }
FlatShape::Type => single_style_span(Color::Blue.bold(), shape.span), FlatShape::Type => single_style_span(Color::Blue.bold(), shape.span),
FlatShape::Operator => single_style_span(Color::Yellow.normal(), shape.span), FlatShape::Operator => single_style_span(Color::Yellow.normal(), shape.span),
FlatShape::DotDotLeftAngleBracket => {
single_style_span(Color::Yellow.bold(), shape.span)
}
FlatShape::DotDot => single_style_span(Color::Yellow.bold(), shape.span), FlatShape::DotDot => single_style_span(Color::Yellow.bold(), shape.span),
FlatShape::Dot => single_style_span(Style::new().fg(Color::White), shape.span), FlatShape::Dot => single_style_span(Style::new().fg(Color::White), shape.span),
FlatShape::InternalCommand => single_style_span(Color::Cyan.bold(), shape.span), FlatShape::InternalCommand => single_style_span(Color::Cyan.bold(), shape.span),
@ -91,6 +94,9 @@ impl Palette for ThemedPalette {
FlatShape::Identifier => single_style_span(self.theme.identifier.normal(), shape.span), FlatShape::Identifier => single_style_span(self.theme.identifier.normal(), shape.span),
FlatShape::Type => single_style_span(self.theme.r#type.bold(), shape.span), FlatShape::Type => single_style_span(self.theme.r#type.bold(), shape.span),
FlatShape::Operator => single_style_span(self.theme.operator.normal(), shape.span), FlatShape::Operator => single_style_span(self.theme.operator.normal(), shape.span),
FlatShape::DotDotLeftAngleBracket => {
single_style_span(self.theme.dot_dot.bold(), shape.span)
}
FlatShape::DotDot => single_style_span(self.theme.dot_dot.bold(), shape.span), FlatShape::DotDot => single_style_span(self.theme.dot_dot.bold(), shape.span),
FlatShape::Dot => single_style_span(Style::new().fg(*self.theme.dot), shape.span), FlatShape::Dot => single_style_span(Style::new().fg(*self.theme.dot), shape.span),
FlatShape::InternalCommand => { FlatShape::InternalCommand => {

View File

@ -143,7 +143,11 @@ impl PrettyDebug for FormatInlineShape {
let op = match (left_inclusion, right_inclusion) { let op = match (left_inclusion, right_inclusion) {
(RangeInclusion::Inclusive, RangeInclusion::Inclusive) => "..", (RangeInclusion::Inclusive, RangeInclusion::Inclusive) => "..",
_ => unimplemented!("No syntax for ranges that aren't inclusive on the left and inclusive on the right") (RangeInclusion::Inclusive, RangeInclusion::Exclusive) => "..<",
_ => unimplemented!(
"No syntax for ranges that aren't inclusive on the left and exclusive \
or inclusive on the right"
),
}; };
left.clone().format().pretty() + b::operator(op) + right.clone().format().pretty() left.clone().format().pretty() + b::operator(op) + right.clone().format().pretty()

View File

@ -5,7 +5,7 @@ use nu_errors::{ArgumentError, ParseError};
use nu_protocol::hir::{ use nu_protocol::hir::{
self, Binary, Block, ClassifiedBlock, ClassifiedCommand, ClassifiedPipeline, Commands, self, Binary, Block, ClassifiedBlock, ClassifiedCommand, ClassifiedPipeline, Commands,
Expression, ExternalRedirection, Flag, FlagKind, InternalCommand, Member, NamedArguments, Expression, ExternalRedirection, Flag, FlagKind, InternalCommand, Member, NamedArguments,
Operator, SpannedExpression, Unit, Operator, RangeOperator, SpannedExpression, Unit,
}; };
use nu_protocol::{NamedType, PositionalType, Signature, SyntaxShape, UnspannedPathMember}; use nu_protocol::{NamedType, PositionalType, Signature, SyntaxShape, UnspannedPathMember};
use nu_source::{Span, Spanned, SpannedItem}; use nu_source::{Span, Spanned, SpannedItem};
@ -242,8 +242,18 @@ fn parse_range(
) -> (SpannedExpression, Option<ParseError>) { ) -> (SpannedExpression, Option<ParseError>) {
let lite_arg_span_start = lite_arg.span.start(); let lite_arg_span_start = lite_arg.span.start();
let lite_arg_len = lite_arg.item.len(); let lite_arg_len = lite_arg.item.len();
let dotdot_pos = lite_arg.item.find(".."); let (dotdot_pos, operator_str, operator) = if let Some(pos) = lite_arg.item.find("..<") {
let numbers: Vec<_> = lite_arg.item.split("..").collect(); (pos, "..<", RangeOperator::RightExclusive)
} else if let Some(pos) = lite_arg.item.find("..") {
(pos, "..", RangeOperator::Inclusive)
} else {
return (
garbage(lite_arg.span),
Some(ParseError::mismatch("range", lite_arg.clone())),
);
};
let numbers: Vec<_> = lite_arg.item.split(operator_str).collect();
if numbers.len() != 2 { if numbers.len() != 2 {
return ( return (
@ -252,19 +262,19 @@ fn parse_range(
); );
} }
let dotdot_pos = dotdot_pos.expect("Internal error: range .. can't be found but should be"); let right_number_offset = operator_str.len();
let lhs = numbers[0].to_string().spanned(Span::new( let lhs = numbers[0].to_string().spanned(Span::new(
lite_arg_span_start, lite_arg_span_start,
lite_arg_span_start + dotdot_pos, lite_arg_span_start + dotdot_pos,
)); ));
let rhs = numbers[1].to_string().spanned(Span::new( let rhs = numbers[1].to_string().spanned(Span::new(
lite_arg_span_start + dotdot_pos + 2, lite_arg_span_start + dotdot_pos + right_number_offset,
lite_arg_span_start + lite_arg_len, lite_arg_span_start + lite_arg_len,
)); ));
let left_hand_open = dotdot_pos == 0; let left_hand_open = dotdot_pos == 0;
let right_hand_open = dotdot_pos == lite_arg_len - 2; let right_hand_open = dotdot_pos == lite_arg_len - right_number_offset;
let left = if left_hand_open { let left = if left_hand_open {
None None
@ -292,10 +302,10 @@ fn parse_range(
SpannedExpression::new( SpannedExpression::new(
Expression::range( Expression::range(
left, left,
Span::new( operator.spanned(Span::new(
lite_arg_span_start + dotdot_pos, lite_arg_span_start + dotdot_pos,
lite_arg_span_start + dotdot_pos + 2, lite_arg_span_start + dotdot_pos + right_number_offset,
), )),
right, right,
), ),
lite_arg.span, lite_arg.span,

View File

@ -68,7 +68,13 @@ pub fn expression_to_flat_shape(e: &SpannedExpression) -> Vec<Spanned<FlatShape>
if let Some(left) = &range.left { if let Some(left) = &range.left {
output.append(&mut expression_to_flat_shape(left)); output.append(&mut expression_to_flat_shape(left));
} }
output.push(FlatShape::DotDot.spanned(range.dotdot)); output.push(
match &range.operator.item {
RangeOperator::Inclusive => FlatShape::DotDot,
RangeOperator::RightExclusive => FlatShape::DotDotLeftAngleBracket,
}
.spanned(&range.operator.span),
);
if let Some(right) = &range.right { if let Some(right) = &range.right {
output.append(&mut expression_to_flat_shape(right)); output.append(&mut expression_to_flat_shape(right));
} }

View File

@ -906,7 +906,7 @@ impl ShellTypeName for Synthetic {
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)] #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)]
pub struct Range { pub struct Range {
pub left: Option<SpannedExpression>, pub left: Option<SpannedExpression>,
pub dotdot: Span, pub operator: Spanned<RangeOperator>,
pub right: Option<SpannedExpression>, pub right: Option<SpannedExpression>,
} }
@ -919,7 +919,7 @@ impl PrettyDebugWithSource for Range {
} else { } else {
DebugDocBuilder::blank() DebugDocBuilder::blank()
}) + b::space() }) + b::space()
+ b::keyword(self.dotdot.slice(source)) + b::keyword(self.operator.span().slice(source))
+ b::space() + b::space()
+ (if let Some(right) = &self.right { + (if let Some(right) = &self.right {
right.pretty_debug(source) right.pretty_debug(source)
@ -932,6 +932,12 @@ impl PrettyDebugWithSource for Range {
} }
} }
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)]
pub enum RangeOperator {
Inclusive,
RightExclusive,
}
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)] #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)]
pub enum Literal { pub enum Literal {
Number(Number), Number(Number),
@ -1107,12 +1113,12 @@ impl Expression {
pub fn range( pub fn range(
left: Option<SpannedExpression>, left: Option<SpannedExpression>,
dotdot: Span, operator: Spanned<RangeOperator>,
right: Option<SpannedExpression>, right: Option<SpannedExpression>,
) -> Expression { ) -> Expression {
Expression::Range(Box::new(Range { Expression::Range(Box::new(Range {
left, left,
dotdot, operator,
right, right,
})) }))
} }
@ -1291,6 +1297,7 @@ pub enum FlatShape {
Operator, Operator,
Dot, Dot,
DotDot, DotDot,
DotDotLeftAngleBracket,
InternalCommand, InternalCommand,
ExternalCommand, ExternalCommand,
ExternalWord, ExternalWord,

View File

@ -1,6 +1,6 @@
use crate::type_name::ShellTypeName; use crate::type_name::ShellTypeName;
use crate::value::column_path::ColumnPath; use crate::value::column_path::ColumnPath;
use crate::value::range::Range; use crate::value::range::{Range, RangeInclusion};
use crate::value::{serde_bigdecimal, serde_bigint}; use crate::value::{serde_bigdecimal, serde_bigint};
use bigdecimal::BigDecimal; use bigdecimal::BigDecimal;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@ -251,8 +251,13 @@ pub fn format_primitive(primitive: &Primitive, field_name: Option<&String>) -> S
Primitive::Int(i) => i.to_string(), Primitive::Int(i) => i.to_string(),
Primitive::Decimal(decimal) => format!("{:.4}", decimal), Primitive::Decimal(decimal) => format!("{:.4}", decimal),
Primitive::Range(range) => format!( Primitive::Range(range) => format!(
"{}..{}", "{}..{}{}",
format_primitive(&range.from.0.item, None), format_primitive(&range.from.0.item, None),
if range.to.1 == RangeInclusion::Exclusive {
"<"
} else {
""
},
format_primitive(&range.to.0.item, None) format_primitive(&range.to.0.item, None)
), ),
Primitive::Pattern(s) => s.to_string(), Primitive::Pattern(s) => s.to_string(),

View File

@ -418,6 +418,18 @@ fn echoing_ranges() {
assert_eq!(actual.out, "6"); assert_eq!(actual.out, "6");
} }
#[test]
fn echoing_exclusive_ranges() {
let actual = nu!(
cwd: ".",
r#"
echo 1..<4 | math sum
"#
);
assert_eq!(actual.out, "6");
}
#[test] #[test]
fn table_literals1() { fn table_literals1() {
let actual = nu!( let actual = nu!(
@ -490,6 +502,18 @@ fn range_with_open_left() {
assert_eq!(actual.out, "465"); assert_eq!(actual.out, "465");
} }
#[test]
fn exclusive_range_with_open_left() {
let actual = nu!(
cwd: ".",
r#"
echo ..<31 | math sum
"#
);
assert_eq!(actual.out, "465");
}
#[test] #[test]
fn range_with_open_right() { fn range_with_open_right() {
let actual = nu!( let actual = nu!(
@ -502,6 +526,18 @@ fn range_with_open_right() {
assert_eq!(actual.out, "95"); assert_eq!(actual.out, "95");
} }
#[test]
fn exclusive_range_with_open_right() {
let actual = nu!(
cwd: ".",
r#"
echo 5..< | first 10 | math sum
"#
);
assert_eq!(actual.out, "95");
}
#[test] #[test]
fn range_with_mixed_types() { fn range_with_mixed_types() {
let actual = nu!( let actual = nu!(
@ -514,6 +550,18 @@ fn range_with_mixed_types() {
assert_eq!(actual.out, "55"); assert_eq!(actual.out, "55");
} }
#[test]
fn exclusive_range_with_mixed_types() {
let actual = nu!(
cwd: ".",
r#"
echo 1..<10.5 | math sum
"#
);
assert_eq!(actual.out, "55");
}
#[test] #[test]
fn it_expansion_of_tables() { fn it_expansion_of_tables() {
let actual = nu!( let actual = nu!(