mirror of
https://github.com/nushell/nushell
synced 2024-11-14 17:07:07 +00:00
Merge pull request #16 from kubouch/ranges-new
Implement positive integer ranges
This commit is contained in:
commit
6f17695891
10 changed files with 526 additions and 5 deletions
|
@ -57,6 +57,10 @@ impl Highlighter for NuHighlighter {
|
||||||
FlatShape::Float => {
|
FlatShape::Float => {
|
||||||
output.push((Style::new().fg(nu_ansi_term::Color::Green), next_token))
|
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 => {
|
FlatShape::Bool => {
|
||||||
output.push((Style::new().fg(nu_ansi_term::Color::LightCyan), next_token))
|
output.push((Style::new().fg(nu_ansi_term::Color::LightCyan), next_token))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement};
|
use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement};
|
||||||
use nu_protocol::engine::EvaluationContext;
|
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> {
|
pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
|
||||||
match op {
|
match op {
|
||||||
|
@ -55,6 +55,48 @@ pub fn eval_expression(
|
||||||
val: *f,
|
val: *f,
|
||||||
span: expr.span,
|
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
|
Expr::Var(var_id) => context
|
||||||
.get_var(*var_id)
|
.get_var(*var_id)
|
||||||
.map_err(move |_| ShellError::VariableNotFound(expr.span)),
|
.map_err(move |_| ShellError::VariableNotFound(expr.span)),
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub enum FlatShape {
|
||||||
Bool,
|
Bool,
|
||||||
Int,
|
Int,
|
||||||
Float,
|
Float,
|
||||||
|
Range,
|
||||||
InternalCall,
|
InternalCall,
|
||||||
External,
|
External,
|
||||||
Literal,
|
Literal,
|
||||||
|
@ -66,6 +67,17 @@ pub fn flatten_expression(
|
||||||
Expr::Float(_) => {
|
Expr::Float(_) => {
|
||||||
vec![(expr.span, FlatShape::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(_) => {
|
Expr::Bool(_) => {
|
||||||
vec![(expr.span, FlatShape::Bool)]
|
vec![(expr.span, FlatShape::Bool)]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Block, Call, Expr, Expression, Operator, Pipeline, Statement},
|
ast::{
|
||||||
|
Block, Call, Expr, Expression, Operator, Pipeline, RangeInclusion, RangeOperator, Statement,
|
||||||
|
},
|
||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId,
|
span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId,
|
||||||
};
|
};
|
||||||
|
@ -702,6 +704,141 @@ 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 = step_op_pos.map(|pos| {
|
||||||
|
Span::new(
|
||||||
|
span.start + pos,
|
||||||
|
span.start + pos + "..".len(), // Only ".." is allowed for step operator
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
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(
|
pub(crate) fn parse_dollar_expr(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
span: Span,
|
span: Span,
|
||||||
|
@ -710,6 +847,8 @@ pub(crate) fn parse_dollar_expr(
|
||||||
|
|
||||||
if contents.starts_with(b"$\"") {
|
if contents.starts_with(b"$\"") {
|
||||||
parse_string_interpolation(working_set, span)
|
parse_string_interpolation(working_set, span)
|
||||||
|
} else if let (expr, None) = parse_range(working_set, span) {
|
||||||
|
(expr, None)
|
||||||
} else {
|
} else {
|
||||||
parse_variable_expr(working_set, span)
|
parse_variable_expr(working_set, span)
|
||||||
}
|
}
|
||||||
|
@ -1684,7 +1823,11 @@ pub fn parse_value(
|
||||||
} else if bytes.starts_with(b"$") {
|
} else if bytes.starts_with(b"$") {
|
||||||
return parse_dollar_expr(working_set, span);
|
return parse_dollar_expr(working_set, span);
|
||||||
} else if bytes.starts_with(b"(") {
|
} 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"{") {
|
} else if bytes.starts_with(b"{") {
|
||||||
if matches!(shape, SyntaxShape::Block) || matches!(shape, SyntaxShape::Any) {
|
if matches!(shape, SyntaxShape::Block) || matches!(shape, SyntaxShape::Any) {
|
||||||
return parse_block_expression(working_set, span);
|
return parse_block_expression(working_set, span);
|
||||||
|
@ -1730,6 +1873,7 @@ pub fn parse_value(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SyntaxShape::Range => parse_range(working_set, span),
|
||||||
SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => {
|
SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => {
|
||||||
parse_string(working_set, span)
|
parse_string(working_set, span)
|
||||||
}
|
}
|
||||||
|
@ -1959,7 +2103,7 @@ pub fn parse_expression(
|
||||||
|
|
||||||
match bytes[0] {
|
match bytes[0] {
|
||||||
b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{'
|
b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{'
|
||||||
| b'[' | b'$' | b'"' | b'\'' => parse_math_expression(working_set, spans),
|
| b'[' | b'$' | b'"' | b'\'' | b'-' => parse_math_expression(working_set, spans),
|
||||||
_ => parse_call(working_set, spans, true),
|
_ => parse_call(working_set, spans, true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,3 +142,256 @@ pub fn parse_call_missing_req_flag() {
|
||||||
let (_, err) = parse_source(&mut working_set, b"foo", true);
|
let (_, err) = parse_source(&mut working_set, b"foo", true);
|
||||||
assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..))));
|
assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod range {
|
||||||
|
use super::*;
|
||||||
|
use nu_protocol::ast::{RangeInclusion, RangeOperator};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_inclusive_range() {
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
let (block, err) = parse_source(&mut working_set, b"0..10", true);
|
||||||
|
|
||||||
|
assert!(err.is_none());
|
||||||
|
assert!(block.len() == 1);
|
||||||
|
match &block[0] {
|
||||||
|
Statement::Pipeline(Pipeline { expressions }) => {
|
||||||
|
assert!(expressions.len() == 1);
|
||||||
|
assert!(matches!(
|
||||||
|
expressions[0],
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Range(
|
||||||
|
Some(_),
|
||||||
|
Some(_),
|
||||||
|
RangeOperator {
|
||||||
|
inclusion: RangeInclusion::Inclusive,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => panic!("No match"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_exclusive_range() {
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
let (block, err) = parse_source(&mut working_set, b"0..<10", true);
|
||||||
|
|
||||||
|
assert!(err.is_none());
|
||||||
|
assert!(block.len() == 1);
|
||||||
|
match &block[0] {
|
||||||
|
Statement::Pipeline(Pipeline { expressions }) => {
|
||||||
|
assert!(expressions.len() == 1);
|
||||||
|
assert!(matches!(
|
||||||
|
expressions[0],
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Range(
|
||||||
|
Some(_),
|
||||||
|
Some(_),
|
||||||
|
RangeOperator {
|
||||||
|
inclusion: RangeInclusion::RightExclusive,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => panic!("No match"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_subexpression_range() {
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
let (block, err) = parse_source(&mut working_set, b"(3 - 3)..<(8 + 2)", true);
|
||||||
|
|
||||||
|
assert!(err.is_none());
|
||||||
|
assert!(block.len() == 1);
|
||||||
|
match &block[0] {
|
||||||
|
Statement::Pipeline(Pipeline { expressions }) => {
|
||||||
|
assert!(expressions.len() == 1);
|
||||||
|
assert!(matches!(
|
||||||
|
expressions[0],
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Range(
|
||||||
|
Some(_),
|
||||||
|
Some(_),
|
||||||
|
RangeOperator {
|
||||||
|
inclusion: RangeInclusion::RightExclusive,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => panic!("No match"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_variable_range() {
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
let (block, err) = parse_source(&mut working_set, b"let a = 2; $a..10", true);
|
||||||
|
|
||||||
|
assert!(err.is_none());
|
||||||
|
assert!(block.len() == 2);
|
||||||
|
match &block[1] {
|
||||||
|
Statement::Pipeline(Pipeline { expressions }) => {
|
||||||
|
assert!(expressions.len() == 1);
|
||||||
|
assert!(matches!(
|
||||||
|
expressions[0],
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Range(
|
||||||
|
Some(_),
|
||||||
|
Some(_),
|
||||||
|
RangeOperator {
|
||||||
|
inclusion: RangeInclusion::Inclusive,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => panic!("No match"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_subexpression_variable_range() {
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
let (block, err) = parse_source(&mut working_set, b"let a = 2; $a..<($a + 10)", true);
|
||||||
|
|
||||||
|
assert!(err.is_none());
|
||||||
|
assert!(block.len() == 2);
|
||||||
|
match &block[1] {
|
||||||
|
Statement::Pipeline(Pipeline { expressions }) => {
|
||||||
|
assert!(expressions.len() == 1);
|
||||||
|
assert!(matches!(
|
||||||
|
expressions[0],
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Range(
|
||||||
|
Some(_),
|
||||||
|
Some(_),
|
||||||
|
RangeOperator {
|
||||||
|
inclusion: RangeInclusion::RightExclusive,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => panic!("No match"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_left_unbounded_range() {
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
let (block, err) = parse_source(&mut working_set, b"..10", true);
|
||||||
|
|
||||||
|
assert!(err.is_none());
|
||||||
|
assert!(block.len() == 1);
|
||||||
|
match &block[0] {
|
||||||
|
Statement::Pipeline(Pipeline { expressions }) => {
|
||||||
|
assert!(expressions.len() == 1);
|
||||||
|
assert!(matches!(
|
||||||
|
expressions[0],
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Range(
|
||||||
|
None,
|
||||||
|
Some(_),
|
||||||
|
RangeOperator {
|
||||||
|
inclusion: RangeInclusion::Inclusive,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => panic!("No match"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_right_unbounded_range() {
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
let (block, err) = parse_source(&mut working_set, b"0..", true);
|
||||||
|
|
||||||
|
assert!(err.is_none());
|
||||||
|
assert!(block.len() == 1);
|
||||||
|
match &block[0] {
|
||||||
|
Statement::Pipeline(Pipeline { expressions }) => {
|
||||||
|
assert!(expressions.len() == 1);
|
||||||
|
assert!(matches!(
|
||||||
|
expressions[0],
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Range(
|
||||||
|
Some(_),
|
||||||
|
None,
|
||||||
|
RangeOperator {
|
||||||
|
inclusion: RangeInclusion::Inclusive,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => panic!("No match"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_negative_range() {
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
let (block, err) = parse_source(&mut working_set, b"-10..-3", true);
|
||||||
|
|
||||||
|
assert!(err.is_none());
|
||||||
|
assert!(block.len() == 1);
|
||||||
|
match &block[0] {
|
||||||
|
Statement::Pipeline(Pipeline { expressions }) => {
|
||||||
|
assert!(expressions.len() == 1);
|
||||||
|
assert!(matches!(
|
||||||
|
expressions[0],
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Range(
|
||||||
|
Some(_),
|
||||||
|
Some(_),
|
||||||
|
RangeOperator {
|
||||||
|
inclusion: RangeInclusion::Inclusive,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => panic!("No match"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{Call, Expression, Operator};
|
use super::{Call, Expression, Operator, RangeOperator};
|
||||||
use crate::{BlockId, Signature, Span, VarId};
|
use crate::{BlockId, Signature, Span, VarId};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -6,6 +6,11 @@ pub enum Expr {
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
Int(i64),
|
Int(i64),
|
||||||
Float(f64),
|
Float(f64),
|
||||||
|
Range(
|
||||||
|
Option<Box<Expression>>,
|
||||||
|
Option<Box<Expression>>,
|
||||||
|
RangeOperator,
|
||||||
|
),
|
||||||
Var(VarId),
|
Var(VarId),
|
||||||
Call(Box<Call>),
|
Call(Box<Call>),
|
||||||
ExternalCall(Vec<u8>, Vec<Vec<u8>>),
|
ExternalCall(Vec<u8>, Vec<Vec<u8>>),
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub struct Expression {
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
pub ty: Type,
|
pub ty: Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expression {
|
impl Expression {
|
||||||
pub fn garbage(span: Span) -> Expression {
|
pub fn garbage(span: Span) -> Expression {
|
||||||
Expression {
|
Expression {
|
||||||
|
@ -15,6 +16,7 @@ impl Expression {
|
||||||
ty: Type::Unknown,
|
ty: Type::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn precedence(&self) -> usize {
|
pub fn precedence(&self) -> usize {
|
||||||
match &self.expr {
|
match &self.expr {
|
||||||
Expr::Operator(operator) => {
|
Expr::Operator(operator) => {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::Span;
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[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, "..<"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::fmt::Display;
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
Int,
|
Int,
|
||||||
Float,
|
Float,
|
||||||
|
Range,
|
||||||
Bool,
|
Bool,
|
||||||
String,
|
String,
|
||||||
Block,
|
Block,
|
||||||
|
@ -31,6 +32,7 @@ impl Display for Type {
|
||||||
Type::Filesize => write!(f, "filesize"),
|
Type::Filesize => write!(f, "filesize"),
|
||||||
Type::Float => write!(f, "float"),
|
Type::Float => write!(f, "float"),
|
||||||
Type::Int => write!(f, "int"),
|
Type::Int => write!(f, "int"),
|
||||||
|
Type::Range => write!(f, "range"),
|
||||||
Type::List(l) => write!(f, "list<{}>", l),
|
Type::List(l) => write!(f, "list<{}>", l),
|
||||||
Type::Nothing => write!(f, "nothing"),
|
Type::Nothing => write!(f, "nothing"),
|
||||||
Type::Number => write!(f, "number"),
|
Type::Number => write!(f, "number"),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::{cell::RefCell, fmt::Debug, rc::Rc};
|
use std::{cell::RefCell, fmt::Debug, rc::Rc};
|
||||||
|
|
||||||
|
use crate::ast::RangeInclusion;
|
||||||
use crate::{span, BlockId, Span, Type};
|
use crate::{span, BlockId, Span, Type};
|
||||||
|
|
||||||
use crate::ShellError;
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
Bool {
|
Bool {
|
||||||
|
@ -113,6 +121,10 @@ pub enum Value {
|
||||||
val: i64,
|
val: i64,
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
Range {
|
||||||
|
val: Box<Range>,
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
Float {
|
Float {
|
||||||
val: f64,
|
val: f64,
|
||||||
span: Span,
|
span: Span,
|
||||||
|
@ -161,6 +173,7 @@ impl Value {
|
||||||
Value::Bool { span, .. } => *span,
|
Value::Bool { span, .. } => *span,
|
||||||
Value::Int { span, .. } => *span,
|
Value::Int { span, .. } => *span,
|
||||||
Value::Float { span, .. } => *span,
|
Value::Float { span, .. } => *span,
|
||||||
|
Value::Range { span, .. } => *span,
|
||||||
Value::String { span, .. } => *span,
|
Value::String { span, .. } => *span,
|
||||||
Value::List { span, .. } => *span,
|
Value::List { span, .. } => *span,
|
||||||
Value::Table { span, .. } => *span,
|
Value::Table { span, .. } => *span,
|
||||||
|
@ -176,6 +189,7 @@ impl Value {
|
||||||
Value::Bool { span, .. } => *span = new_span,
|
Value::Bool { span, .. } => *span = new_span,
|
||||||
Value::Int { span, .. } => *span = new_span,
|
Value::Int { span, .. } => *span = new_span,
|
||||||
Value::Float { span, .. } => *span = new_span,
|
Value::Float { span, .. } => *span = new_span,
|
||||||
|
Value::Range { span, .. } => *span = new_span,
|
||||||
Value::String { span, .. } => *span = new_span,
|
Value::String { span, .. } => *span = new_span,
|
||||||
Value::RowStream { span, .. } => *span = new_span,
|
Value::RowStream { span, .. } => *span = new_span,
|
||||||
Value::ValueStream { span, .. } => *span = new_span,
|
Value::ValueStream { span, .. } => *span = new_span,
|
||||||
|
@ -193,6 +207,7 @@ impl Value {
|
||||||
Value::Bool { .. } => Type::Bool,
|
Value::Bool { .. } => Type::Bool,
|
||||||
Value::Int { .. } => Type::Int,
|
Value::Int { .. } => Type::Int,
|
||||||
Value::Float { .. } => Type::Float,
|
Value::Float { .. } => Type::Float,
|
||||||
|
Value::Range { .. } => Type::Range,
|
||||||
Value::String { .. } => Type::String,
|
Value::String { .. } => Type::String,
|
||||||
Value::List { .. } => Type::List(Box::new(Type::Unknown)), // FIXME
|
Value::List { .. } => Type::List(Box::new(Type::Unknown)), // FIXME
|
||||||
Value::Table { .. } => Type::Table, // FIXME
|
Value::Table { .. } => Type::Table, // FIXME
|
||||||
|
@ -208,6 +223,25 @@ impl Value {
|
||||||
Value::Bool { val, .. } => val.to_string(),
|
Value::Bool { val, .. } => val.to_string(),
|
||||||
Value::Int { val, .. } => val.to_string(),
|
Value::Int { val, .. } => val.to_string(),
|
||||||
Value::Float { 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(", ")
|
||||||
|
)
|
||||||
|
}
|
||||||
Value::String { val, .. } => val,
|
Value::String { val, .. } => val,
|
||||||
Value::ValueStream { stream, .. } => stream.into_string(),
|
Value::ValueStream { stream, .. } => stream.into_string(),
|
||||||
Value::List { val, .. } => val
|
Value::List { val, .. } => val
|
||||||
|
|
Loading…
Reference in a new issue