Add support for positive integer ranges

Including support for variables and subexpressions as range bounds.
This commit is contained in:
Jakub Žádník 2021-09-05 00:52:57 +03:00
parent 2794556eaa
commit 0b412cd6b3
9 changed files with 277 additions and 5 deletions

View file

@ -57,6 +57,10 @@ impl Highlighter for NuHighlighter {
FlatShape::Float => {
output.push((Style::new().fg(nu_ansi_term::Color::Green), next_token))
}
FlatShape::Range => output.push((
Style::new().fg(nu_ansi_term::Color::LightPurple),
next_token,
)),
FlatShape::Bool => {
output.push((Style::new().fg(nu_ansi_term::Color::LightCyan), next_token))
}

View file

@ -1,6 +1,6 @@
use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement};
use nu_protocol::engine::EvaluationContext;
use nu_protocol::{ShellError, Value};
use nu_protocol::{Range, ShellError, Span, Value};
pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
match op {
@ -55,6 +55,48 @@ pub fn eval_expression(
val: *f,
span: expr.span,
}),
Expr::Range(from, to, operator) => {
// TODO: Embed the min/max into Range and set max to be the true max
let from = if let Some(f) = from {
eval_expression(context, &f)?
} else {
Value::Int {
val: 0i64,
span: Span::unknown(),
}
};
let to = if let Some(t) = to {
eval_expression(context, &t)?
} else {
Value::Int {
val: 100i64,
span: Span::unknown(),
}
};
let range = match (&from, &to) {
(&Value::Int { .. }, &Value::Int { .. }) => Range {
from: from.clone(),
to: to.clone(),
inclusion: operator.inclusion,
},
(lhs, rhs) => {
return Err(ShellError::OperatorMismatch {
op_span: operator.span,
lhs_ty: lhs.get_type(),
lhs_span: lhs.span(),
rhs_ty: rhs.get_type(),
rhs_span: rhs.span(),
})
}
};
Ok(Value::Range {
val: Box::new(range),
span: expr.span,
})
}
Expr::Var(var_id) => context
.get_var(*var_id)
.map_err(move |_| ShellError::VariableNotFound(expr.span)),

View file

@ -7,6 +7,7 @@ pub enum FlatShape {
Bool,
Int,
Float,
Range,
InternalCall,
External,
Literal,
@ -65,6 +66,17 @@ pub fn flatten_expression(
}
Expr::Float(_) => {
vec![(expr.span, FlatShape::Float)]
}
Expr::Range(from, to, op) => {
let mut output = vec![];
if let Some(f) = from {
output.extend(flatten_expression(working_set, f));
}
if let Some(t) = to {
output.extend(flatten_expression(working_set, t));
}
output.extend(vec![(op.span, FlatShape::Operator)]);
output
}
Expr::Bool(_) => {
vec![(expr.span, FlatShape::Bool)]

View file

@ -5,7 +5,9 @@ use crate::{
};
use nu_protocol::{
ast::{Block, Call, Expr, Expression, Operator, Pipeline, Statement},
ast::{
Block, Call, Expr, Expression, Operator, Pipeline, RangeInclusion, RangeOperator, Statement,
},
engine::StateWorkingSet,
span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId,
};
@ -702,6 +704,143 @@ pub fn parse_number(token: &str, span: Span) -> (Expression, Option<ParseError>)
}
}
pub fn parse_range(
working_set: &mut StateWorkingSet,
span: Span,
) -> (Expression, Option<ParseError>) {
// Range follows the following syntax: [<from>][<step_operator><step>]<range_operator>[<to>]
// where <step_operator> is ".."
// and <range_operator> is ".." or "..<"
// and one of the <from> or <to> bounds must be present (just '..' is not allowed since it
// looks like parent directory)
let contents = working_set.get_span_contents(span);
let token = if let Ok(s) = String::from_utf8(contents.into()) {
s
} else {
return (garbage(span), Some(ParseError::NonUtf8(span)));
};
// First, figure out what exact operators are used and determine their positions
let dotdot_pos: Vec<_> = token.match_indices("..").map(|(pos, _)| pos).collect();
let (step_op_pos, range_op_pos) =
match dotdot_pos.len() {
1 => (None, dotdot_pos[0]),
2 => (Some(dotdot_pos[0]), dotdot_pos[1]),
_ => return (
garbage(span),
Some(ParseError::Expected(
"one range operator ('..' or '..<') and optionally one step operator ('..')"
.into(),
span,
)),
),
};
let step_op_span = if let Some(pos) = step_op_pos {
Some(Span::new(
span.start + pos,
span.start + pos + "..".len(), // Only ".." is allowed for step operator
))
} else {
None
};
let (range_op, range_op_str, range_op_span) = if let Some(pos) = token.find("..<") {
if pos == range_op_pos {
let op_str = "..<";
let op_span = Span::new(
span.start + range_op_pos,
span.start + range_op_pos + op_str.len(),
);
(
RangeOperator {
inclusion: RangeInclusion::RightExclusive,
span: op_span,
},
"..<",
op_span,
)
} else {
return (
garbage(span),
Some(ParseError::Expected(
"inclusive operator preceding second range bound".into(),
span,
)),
);
}
} else {
let op_str = "..";
let op_span = Span::new(
span.start + range_op_pos,
span.start + range_op_pos + op_str.len(),
);
(
RangeOperator {
inclusion: RangeInclusion::Inclusive,
span: op_span,
},
"..",
op_span,
)
};
// Now, based on the operator positions, figure out where the bounds & step are located and
// parse them
// TODO: Actually parse the step number
let from = if token.starts_with("..") {
// token starts with either step operator, or range operator -- we don't care which one
None
} else {
let from_span = Span::new(span.start, span.start + dotdot_pos[0]);
match parse_value(working_set, from_span, &SyntaxShape::Number) {
(expression, None) => Some(Box::new(expression)),
_ => {
return (
garbage(span),
Some(ParseError::Expected("number".into(), span)),
)
}
}
};
let to = if token.ends_with(range_op_str) {
None
} else {
let to_span = Span::new(range_op_span.end, span.end);
match parse_value(working_set, to_span, &SyntaxShape::Number) {
(expression, None) => Some(Box::new(expression)),
_ => {
return (
garbage(span),
Some(ParseError::Expected("number".into(), span)),
)
}
}
};
if let (None, None) = (&from, &to) {
return (
garbage(span),
Some(ParseError::Expected(
"at least one range bound set".into(),
span,
)),
);
}
(
Expression {
expr: Expr::Range(from, to, range_op),
span,
ty: Type::Range,
},
None,
)
}
pub(crate) fn parse_dollar_expr(
working_set: &mut StateWorkingSet,
span: Span,
@ -711,7 +850,11 @@ pub(crate) fn parse_dollar_expr(
if contents.starts_with(b"$\"") {
parse_string_interpolation(working_set, span)
} else {
parse_variable_expr(working_set, span)
if let (expr, None) = parse_range(working_set, span) {
(expr, None)
} else {
parse_variable_expr(working_set, span)
}
}
}
@ -1684,7 +1827,11 @@ pub fn parse_value(
} else if bytes.starts_with(b"$") {
return parse_dollar_expr(working_set, span);
} else if bytes.starts_with(b"(") {
return parse_full_column_path(working_set, span);
if let (expr, None) = parse_range(working_set, span) {
return (expr, None);
} else {
return parse_full_column_path(working_set, span);
}
} else if bytes.starts_with(b"{") {
if matches!(shape, SyntaxShape::Block) || matches!(shape, SyntaxShape::Any) {
return parse_block_expression(working_set, span);
@ -1730,6 +1877,7 @@ pub fn parse_value(
)
}
}
SyntaxShape::Range => parse_range(working_set, span),
SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => {
parse_string(working_set, span)
}

View file

@ -1,4 +1,4 @@
use super::{Call, Expression, Operator};
use super::{Call, Expression, Operator, RangeOperator};
use crate::{BlockId, Signature, Span, VarId};
#[derive(Debug, Clone)]
@ -6,6 +6,11 @@ pub enum Expr {
Bool(bool),
Int(i64),
Float(f64),
Range(
Option<Box<Expression>>,
Option<Box<Expression>>,
RangeOperator,
),
Var(VarId),
Call(Box<Call>),
ExternalCall(Vec<u8>, Vec<Vec<u8>>),

View file

@ -7,6 +7,7 @@ pub struct Expression {
pub span: Span,
pub ty: Type,
}
impl Expression {
pub fn garbage(span: Span) -> Expression {
Expression {
@ -15,6 +16,7 @@ impl Expression {
ty: Type::Unknown,
}
}
pub fn precedence(&self) -> usize {
match &self.expr {
Expr::Operator(operator) => {

View file

@ -1,3 +1,5 @@
use crate::Span;
use std::fmt::Display;
#[derive(Debug, Clone, PartialEq, Eq)]
@ -46,3 +48,24 @@ impl Display for Operator {
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum RangeInclusion {
Inclusive,
RightExclusive,
}
#[derive(Debug, Copy, Clone)]
pub struct RangeOperator {
pub inclusion: RangeInclusion,
pub span: Span,
}
impl Display for RangeOperator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.inclusion {
RangeInclusion::Inclusive => write!(f, ".."),
RangeInclusion::RightExclusive => write!(f, "..<"),
}
}
}

View file

@ -4,6 +4,7 @@ use std::fmt::Display;
pub enum Type {
Int,
Float,
Range,
Bool,
String,
Block,
@ -31,6 +32,7 @@ impl Display for Type {
Type::Filesize => write!(f, "filesize"),
Type::Float => write!(f, "float"),
Type::Int => write!(f, "int"),
Type::Range => write!(f, "range"),
Type::List(l) => write!(f, "list<{}>", l),
Type::Nothing => write!(f, "nothing"),
Type::Number => write!(f, "number"),

View file

@ -1,5 +1,6 @@
use std::{cell::RefCell, fmt::Debug, rc::Rc};
use crate::ast::RangeInclusion;
use crate::{span, BlockId, Span, Type};
use crate::ShellError;
@ -103,6 +104,13 @@ impl IntoRowStream for Vec<Vec<Value>> {
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Range {
pub from: Value,
pub to: Value,
pub inclusion: RangeInclusion,
}
#[derive(Debug, Clone)]
pub enum Value {
Bool {
@ -113,6 +121,10 @@ pub enum Value {
val: i64,
span: Span,
},
Range {
val: Box<Range>,
span: Span,
},
Float {
val: f64,
span: Span,
@ -161,6 +173,7 @@ impl Value {
Value::Bool { span, .. } => *span,
Value::Int { span, .. } => *span,
Value::Float { span, .. } => *span,
Value::Range { span, .. } => *span,
Value::String { span, .. } => *span,
Value::List { span, .. } => *span,
Value::Table { span, .. } => *span,
@ -176,6 +189,7 @@ impl Value {
Value::Bool { span, .. } => *span = new_span,
Value::Int { span, .. } => *span = new_span,
Value::Float { span, .. } => *span = new_span,
Value::Range { span, .. } => *span = new_span,
Value::String { span, .. } => *span = new_span,
Value::RowStream { span, .. } => *span = new_span,
Value::ValueStream { span, .. } => *span = new_span,
@ -193,6 +207,7 @@ impl Value {
Value::Bool { .. } => Type::Bool,
Value::Int { .. } => Type::Int,
Value::Float { .. } => Type::Float,
Value::Range { .. } => Type::Range,
Value::String { .. } => Type::String,
Value::List { .. } => Type::List(Box::new(Type::Unknown)), // FIXME
Value::Table { .. } => Type::Table, // FIXME
@ -208,6 +223,25 @@ impl Value {
Value::Bool { val, .. } => val.to_string(),
Value::Int { val, .. } => val.to_string(),
Value::Float { val, .. } => val.to_string(),
Value::Range { val, .. } => {
let vals: Vec<i64> = match (&val.from, &val.to) {
(Value::Int { val: from, .. }, Value::Int { val: to, .. }) => {
match val.inclusion {
RangeInclusion::Inclusive => (*from..=*to).collect(),
RangeInclusion::RightExclusive => (*from..*to).collect(),
}
}
_ => Vec::new(),
};
format!(
"range: [{}]",
vals.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join(", ".into())
)
},
Value::String { val, .. } => val,
Value::ValueStream { stream, .. } => stream.into_string(),
Value::List { val, .. } => val