From 47497769845e31906d8ba4e11e8b12dfc2f0b202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 11 Sep 2021 14:13:04 +0300 Subject: [PATCH 01/16] Add stepping to ranges & enable reverse ranges Follows the following syntax: .... --- crates/nu-engine/src/eval.rs | 37 +++---- crates/nu-parser/src/flatten.rs | 8 +- crates/nu-parser/src/parser.rs | 66 ++++++------ crates/nu-parser/tests/test_parser.rs | 39 +++++++ crates/nu-protocol/src/ast/expr.rs | 5 +- crates/nu-protocol/src/ast/operator.rs | 1 + crates/nu-protocol/src/value/mod.rs | 16 +-- crates/nu-protocol/src/value/range.rs | 137 ++++++++++++++++++++----- 8 files changed, 207 insertions(+), 102 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index b3da15c87a..0be42ff4f8 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -85,13 +85,19 @@ 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 + Expr::Range(from, next, to, operator) => { let from = if let Some(f) = from { eval_expression(context, f)? } else { - Value::Int { - val: 0i64, + Value::Nothing { + span: Span::unknown(), + } + }; + + let next = if let Some(s) = next { + eval_expression(context, s)? + } else { + Value::Nothing { span: Span::unknown(), } }; @@ -99,31 +105,13 @@ pub fn eval_expression( let to = if let Some(t) = to { eval_expression(context, t)? } else { - Value::Int { - val: 100i64, + Value::Nothing { 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), + val: Box::new(Range::new(expr.span, from, next, to, operator)?), span: expr.span, }) } @@ -159,7 +147,6 @@ pub fn eval_expression( x => Err(ShellError::UnsupportedOperator(x, op_span)), } } - Expr::Subexpression(block_id) => { let engine_state = context.engine_state.borrow(); let block = engine_state.get_block(*block_id); diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 17bc3178c9..f32de5eca9 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -85,15 +85,19 @@ pub fn flatten_expression( } output } - Expr::Range(from, to, op) => { + Expr::Range(from, next, to, op) => { let mut output = vec![]; if let Some(f) = from { output.extend(flatten_expression(working_set, f)); } + if let Some(s) = next { + output.extend(vec![(op.next_op_span, FlatShape::Operator)]); + output.extend(flatten_expression(working_set, s)); + } + output.extend(vec![(op.span, FlatShape::Operator)]); if let Some(t) = to { output.extend(flatten_expression(working_set, t)); } - output.extend(vec![(op.span, FlatShape::Operator)]); output } Expr::Bool(_) => { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index cb68e00ec2..6199e87ee1 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -709,8 +709,8 @@ pub fn parse_range( working_set: &mut StateWorkingSet, span: Span, ) -> (Expression, Option) { - // Range follows the following syntax: [][][] - // where is ".." + // Range follows the following syntax: [][][] + // where is ".." // and is ".." or "..<" // and one of the or bounds must be present (just '..' is not allowed since it // looks like parent directory) @@ -725,42 +725,28 @@ pub fn parse_range( // 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) = + let (next_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 ('..')" + "one range operator ('..' or '..<') and optionally one next 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("..<") { + let (inclusion, 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, - ) + (RangeInclusion::RightExclusive, "..<", op_span) } else { return ( garbage(span), @@ -776,21 +762,14 @@ pub fn parse_range( span.start + range_op_pos, span.start + range_op_pos + op_str.len(), ); - ( - RangeOperator { - inclusion: RangeInclusion::Inclusive, - span: op_span, - }, - "..", - op_span, - ) + (RangeInclusion::Inclusive, "..", op_span) }; - // Now, based on the operator positions, figure out where the bounds & step are located and + // Now, based on the operator positions, figure out where the bounds & next are located and // parse them - // TODO: Actually parse the step number + // TODO: Actually parse the next number let from = if token.starts_with("..") { - // token starts with either step operator, or range operator -- we don't care which one + // token starts with either next operator, or range operator -- we don't care which one None } else { let from_span = Span::new(span.start, span.start + dotdot_pos[0]); @@ -830,9 +809,32 @@ pub fn parse_range( ); } + let (next, next_op_span) = if let Some(pos) = next_op_pos { + let next_op_span = Span::new(span.start + pos, span.start + pos + "..".len()); + let next_span = Span::new(next_op_span.end, range_op_span.start); + + match parse_value(working_set, next_span, &SyntaxShape::Number) { + (expression, None) => (Some(Box::new(expression)), next_op_span), + _ => { + return ( + garbage(span), + Some(ParseError::Expected("number".into(), span)), + ) + } + } + } else { + (None, Span::unknown()) + }; + + let range_op = RangeOperator { + inclusion, + span: range_op_span, + next_op_span, + }; + ( Expression { - expr: Expr::Range(from, to, range_op), + expr: Expr::Range(from, next, to, range_op), span, ty: Type::Range, }, diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index ef52f1fbcf..2015f356c0 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -164,6 +164,7 @@ mod range { Expression { expr: Expr::Range( Some(_), + None, Some(_), RangeOperator { inclusion: RangeInclusion::Inclusive, @@ -195,6 +196,7 @@ mod range { Expression { expr: Expr::Range( Some(_), + None, Some(_), RangeOperator { inclusion: RangeInclusion::RightExclusive, @@ -209,6 +211,38 @@ mod range { } } + #[test] + fn parse_reverse_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse(&mut working_set, None, b"10..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, + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } + #[test] fn parse_subexpression_range() { let engine_state = EngineState::new(); @@ -226,6 +260,7 @@ mod range { Expression { expr: Expr::Range( Some(_), + None, Some(_), RangeOperator { inclusion: RangeInclusion::RightExclusive, @@ -257,6 +292,7 @@ mod range { Expression { expr: Expr::Range( Some(_), + None, Some(_), RangeOperator { inclusion: RangeInclusion::Inclusive, @@ -288,6 +324,7 @@ mod range { Expression { expr: Expr::Range( Some(_), + None, Some(_), RangeOperator { inclusion: RangeInclusion::RightExclusive, @@ -320,6 +357,7 @@ mod range { expr: Expr::Range( Some(_), None, + None, RangeOperator { inclusion: RangeInclusion::Inclusive, .. @@ -350,6 +388,7 @@ mod range { Expression { expr: Expr::Range( Some(_), + None, Some(_), RangeOperator { inclusion: RangeInclusion::Inclusive, diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index f26673f2a1..62d7a25c8f 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -7,8 +7,9 @@ pub enum Expr { Int(i64), Float(f64), Range( - Option>, - Option>, + Option>, // from + Option>, // next value after "from" + Option>, // to RangeOperator, ), Var(VarId), diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index c7c82eba41..edd4f56fc2 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -59,6 +59,7 @@ pub enum RangeInclusion { pub struct RangeOperator { pub inclusion: RangeInclusion, pub span: Span, + pub next_op_span: Span, } impl Display for RangeOperator { diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 094ca2d590..5d81e03f91 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -8,7 +8,7 @@ pub use stream::*; use std::fmt::Debug; -use crate::ast::{PathMember, RangeInclusion}; +use crate::ast::PathMember; use crate::{span, BlockId, Span, Type}; use crate::ShellError; @@ -131,20 +131,10 @@ impl Value { Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), Value::Range { val, .. } => { - let vals: Vec = 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()) + val.into_iter() + .map(|x| x.into_string()) .collect::>() .join(", ") ) diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index 54d836de16..ff138e4af6 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -1,12 +1,106 @@ -use crate::{ast::RangeInclusion, *}; +use crate::{ + ast::{RangeInclusion, RangeOperator}, + *, +}; #[derive(Debug, Clone, PartialEq)] pub struct Range { pub from: Value, + pub incr: Value, pub to: Value, pub inclusion: RangeInclusion, } +impl Range { + pub fn new( + expr_span: Span, + from: Value, + next: Value, + to: Value, + operator: &RangeOperator, + ) -> Result { + // Select from & to values if they're not specified + // TODO: Replace the placeholder values with proper min/max based on data type + let from = if let Value::Nothing { .. } = from { + Value::Int { + val: 0i64, + span: Span::unknown(), + } + } else { + from + }; + + let to = if let Value::Nothing { .. } = to { + if let Ok(Value::Bool { val: true, .. }) = next.lt(expr_span, &from) { + Value::Int { + val: -100i64, + span: Span::unknown(), + } + } else { + Value::Int { + val: 100i64, + span: Span::unknown(), + } + } + } else { + to + }; + + // Check if the range counts up or down + let moves_up = matches!(from.lte(expr_span, &to), Ok(Value::Bool { val: true, .. })); + + // Convert the next value into the inctement + let incr = if let Value::Nothing { .. } = next { + if moves_up { + Value::Int { + val: 1i64, + span: Span::unknown(), + } + } else { + Value::Int { + val: -1i64, + span: Span::unknown(), + } + } + } else { + next.sub(operator.next_op_span, &from)? + }; + + let zero = Value::Int { + val: 0i64, + span: Span::unknown(), + }; + + // Increment must be non-zero, otherwise we iterate forever + if matches!(incr.eq(expr_span, &zero), Ok(Value::Bool { val: true, .. })) { + return Err(ShellError::CannotCreateRange(expr_span)); + } + + // If to > from, then incr > 0, otherwise we iterate forever + if let (Value::Bool { val: true, .. }, Value::Bool { val: false, .. }) = ( + to.gt(operator.span, &from)?, + incr.gt(operator.next_op_span, &zero)?, + ) { + return Err(ShellError::CannotCreateRange(expr_span)); + } + + // If to < from, then incr < 0, otherwise we iterate forever + if let (Value::Bool { val: true, .. }, Value::Bool { val: false, .. }) = ( + to.lt(operator.span, &from)?, + incr.lt(operator.next_op_span, &zero)?, + ) { + return Err(ShellError::CannotCreateRange(expr_span)); + } + + Ok(Range { + from, + incr, + to, + inclusion: operator.inclusion, + }) + } +} + impl IntoIterator for Range { type Item = Value; @@ -25,8 +119,7 @@ pub struct RangeIterator { span: Span, is_end_inclusive: bool, moves_up: bool, - one: Value, - negative_one: Value, + incr: Value, done: bool, } @@ -52,8 +145,7 @@ impl RangeIterator { span, is_end_inclusive: matches!(range.inclusion, RangeInclusion::Inclusive), done: false, - one: Value::Int { val: 1, span }, - negative_one: Value::Int { val: -1, span }, + incr: range.incr, } } } @@ -70,10 +162,10 @@ impl Iterator for RangeIterator { Ordering::Less } else { match (&self.curr, &self.end) { - (Value::Int { val: x, .. }, Value::Int { val: y, .. }) => x.cmp(y), - // (Value::Float { val: x, .. }, Value::Float { val: y, .. }) => x.cmp(y), - // (Value::Float { val: x, .. }, Value::Int { val: y, .. }) => x.cmp(y), - // (Value::Int { val: x, .. }, Value::Float { val: y, .. }) => x.cmp(y), + (Value::Int { val: curr, .. }, Value::Int { val: end, .. }) => curr.cmp(end), + // (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => curr.cmp(end), + // (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => curr.cmp(end), + // (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => curr.cmp(end), _ => { self.done = true; return Some(Value::Error { @@ -83,10 +175,15 @@ impl Iterator for RangeIterator { } }; - if self.moves_up - && (ordering == Ordering::Less || self.is_end_inclusive && ordering == Ordering::Equal) + let desired_ordering = if self.moves_up { + Ordering::Less + } else { + Ordering::Greater + }; + + if (ordering == desired_ordering) || (self.is_end_inclusive && ordering == Ordering::Equal) { - let next_value = self.curr.add(self.span, &self.one); + let next_value = self.curr.add(self.span, &self.incr); let mut next = match next_value { Ok(result) => result, @@ -98,22 +195,6 @@ impl Iterator for RangeIterator { }; std::mem::swap(&mut self.curr, &mut next); - Some(next) - } else if !self.moves_up - && (ordering == Ordering::Greater - || self.is_end_inclusive && ordering == Ordering::Equal) - { - let next_value = self.curr.add(self.span, &self.negative_one); - - let mut next = match next_value { - Ok(result) => result, - Err(error) => { - self.done = true; - return Some(Value::Error { error }); - } - }; - std::mem::swap(&mut self.curr, &mut next); - Some(next) } else { None From 55aa70c88a197d8a0ca70d0350e6308e128ecbd4 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 12 Sep 2021 09:26:35 +1200 Subject: [PATCH 02/16] WIP --- TODO.md | 2 + crates/nu-command/src/each.rs | 74 +++++++++++++++++++++++++++--- crates/nu-parser/src/parser.rs | 1 + crates/nu-parser/src/type_check.rs | 1 + crates/nu-protocol/src/ast/call.rs | 10 ++++ 5 files changed, 81 insertions(+), 7 deletions(-) diff --git a/TODO.md b/TODO.md index 92049f2ac8..4ea8afd785 100644 --- a/TODO.md +++ b/TODO.md @@ -19,6 +19,8 @@ - [x] Iteration (`each`) over tables - [x] Row conditions - [x] Simple completions +- [ ] Detecting `$it` currently only looks at top scope but should find any free `$it` in the expression (including subexprs) +- [ ] Signature needs to make parameters visible in scope before block is parsed - [ ] Value serialization - [ ] Handling rows with missing columns during a cell path - [ ] Error shortcircuit (stopping on first error) diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index cd2894e9b2..109b082f1f 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -15,7 +15,9 @@ impl Command for Each { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("each").required("block", SyntaxShape::Block, "the block to run") + Signature::build("each") + .required("block", SyntaxShape::Block, "the block to run") + .switch("numbered", "iterate with an index", Some('n')) } fn run( @@ -27,20 +29,42 @@ impl Command for Each { let block_id = call.positional[0] .as_block() .expect("internal error: expected block"); + + let numbered = call.has_flag("numbered"); let context = context.clone(); + let span = call.head; match input { Value::Range { val, .. } => Ok(Value::Stream { stream: val .into_iter() - .map(move |x| { + .enumerate() + .map(move |(idx, x)| { let engine_state = context.engine_state.borrow(); let block = engine_state.get_block(block_id); let state = context.enter_scope(); + if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { - state.add_var(*var_id, x); + if numbered { + state.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + state.add_var(*var_id, x); + } } } @@ -55,14 +79,32 @@ impl Command for Each { Value::List { vals: val, .. } => Ok(Value::Stream { stream: val .into_iter() - .map(move |x| { + .enumerate() + .map(move |(idx, x)| { let engine_state = context.engine_state.borrow(); let block = engine_state.get_block(block_id); let state = context.enter_scope(); if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { - state.add_var(*var_id, x); + if numbered { + state.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + state.add_var(*var_id, x); + } } } @@ -76,14 +118,32 @@ impl Command for Each { }), Value::Stream { stream, .. } => Ok(Value::Stream { stream: stream - .map(move |x| { + .enumerate() + .map(move |(idx, x)| { let engine_state = context.engine_state.borrow(); let block = engine_state.get_block(block_id); let state = context.enter_scope(); if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { - state.add_var(*var_id, x); + if numbered { + state.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + state.add_var(*var_id, x); + } } } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index cb68e00ec2..98601e55b6 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1992,6 +1992,7 @@ pub fn parse_block_expression( if let Some(signature) = signature { output.signature = signature; } else if let Some(last) = working_set.delta.scope.last() { + // FIXME: this only supports the top $it. Instead, we should look for a free $it in the expression. if let Some(var_id) = last.get_var(b"$it") { let mut signature = Signature::new(""); signature.required_positional.push(PositionalArg { diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 67a082c67f..58167b47f8 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -20,6 +20,7 @@ pub fn math_result_type( op: &mut Expression, rhs: &mut Expression, ) -> (Type, Option) { + //println!("checking: {:?} {:?} {:?}", lhs, op, rhs); match &op.expr { Expr::Operator(operator) => match operator { Operator::Plus => match (&lhs.ty, &rhs.ty) { diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index efe5d7b482..7ee6d6c162 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -25,4 +25,14 @@ impl Call { named: vec![], } } + + pub fn has_flag(&self, flag_name: &str) -> bool { + for name in &self.named { + if flag_name == name.0 { + return true; + } + } + + false + } } From 2f04c172fe74981be82149823d96574f7ff4383f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Sep 2021 14:12:53 +0300 Subject: [PATCH 03/16] Add floating point support for ranges --- crates/nu-protocol/src/value/range.rs | 41 +++++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index ff138e4af6..2832d09b2b 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use crate::{ ast::{RangeInclusion, RangeOperator}, *, @@ -150,31 +152,46 @@ impl RangeIterator { } } +// Compare two floating point numbers. The decision interval for equality is dynamically scaled +// based on the value being compared. +fn compare_floats(val: f64, other: f64) -> Option { + let prec = val * f64::EPSILON; + + if (other - val).abs() < prec.abs() { + return Some(Ordering::Equal); + } + + val.partial_cmp(&other) +} + impl Iterator for RangeIterator { type Item = Value; fn next(&mut self) -> Option { - use std::cmp::Ordering; if self.done { return None; } let ordering = if matches!(self.end, Value::Nothing { .. }) { - Ordering::Less + Some(Ordering::Less) } else { match (&self.curr, &self.end) { - (Value::Int { val: curr, .. }, Value::Int { val: end, .. }) => curr.cmp(end), - // (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => curr.cmp(end), - // (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => curr.cmp(end), - // (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => curr.cmp(end), - _ => { - self.done = true; - return Some(Value::Error { - error: ShellError::CannotCreateRange(self.span), - }); - } + (Value::Int { val: curr, .. }, Value::Int { val: end, .. }) => Some(curr.cmp(end)), + (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => compare_floats(*curr, *end), + (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => compare_floats(*curr, *end as f64), + (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => compare_floats(*curr as f64, *end), + _ => None, } }; + let ordering = if let Some(ord) = ordering { + ord + } else { + self.done = true; + return Some(Value::Error { + error: ShellError::CannotCreateRange(self.span), + }); + }; + let desired_ordering = if self.moves_up { Ordering::Less } else { From 013b12a86451556903ea1866010864d234afdbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Sep 2021 14:55:11 +0300 Subject: [PATCH 04/16] Do not allow precision interval to rach < epsilon --- crates/nu-protocol/src/value/range.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index 2832d09b2b..44a97d7b59 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -153,11 +153,11 @@ impl RangeIterator { } // Compare two floating point numbers. The decision interval for equality is dynamically scaled -// based on the value being compared. +// as the value being compared increases in magnitude. fn compare_floats(val: f64, other: f64) -> Option { - let prec = val * f64::EPSILON; + let prec = f64::EPSILON.max(val.abs() * f64::EPSILON); - if (other - val).abs() < prec.abs() { + if (other - val).abs() < prec { return Some(Ordering::Equal); } From 9936946eb57306ccfae5bdc70b61f8b20ee38f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Sep 2021 14:58:32 +0300 Subject: [PATCH 05/16] Fmt --- crates/nu-protocol/src/value/range.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index 44a97d7b59..80eb568827 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -176,9 +176,15 @@ impl Iterator for RangeIterator { } else { match (&self.curr, &self.end) { (Value::Int { val: curr, .. }, Value::Int { val: end, .. }) => Some(curr.cmp(end)), - (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => compare_floats(*curr, *end), - (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => compare_floats(*curr, *end as f64), - (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => compare_floats(*curr as f64, *end), + (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => { + compare_floats(*curr, *end) + } + (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => { + compare_floats(*curr, *end as f64) + } + (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => { + compare_floats(*curr as f64, *end) + } _ => None, } }; From ce0b5bf4ab43cb60f360873d1a0fece0b06d2750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Sep 2021 15:36:54 +0300 Subject: [PATCH 06/16] Add test for float ranges --- crates/nu-parser/tests/test_parser.rs | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 2015f356c0..e0359378c2 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -403,6 +403,38 @@ mod range { } } + #[test] + fn parse_float_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse(&mut working_set, None, b"2.0..4.0..10.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(_), + Some(_), + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } + #[test] fn bad_parse_does_crash() { let engine_state = EngineState::new(); From 78054a53525901c2a36b1bb0f9ae4ba6716572bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Sep 2021 15:29:27 +0300 Subject: [PATCH 07/16] Allow parsing left-unbounded range (..10) It is implemented as a preliminary check when parsing a call and relies on a fact that a token that successfully parses as a range is unlikely to be a valid path or command name. --- crates/nu-parser/src/parser.rs | 9 ++++++++ crates/nu-parser/tests/test_parser.rs | 31 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 6199e87ee1..839f43db88 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -469,6 +469,15 @@ pub fn parse_call( spans: &[Span], expand_aliases: bool, ) -> (Expression, Option) { + // We might be parsing left-unbounded range ("..10") + let bytes = working_set.get_span_contents(spans[0]); + if let (Some(b'.'), Some(b'.')) = (bytes.get(0), bytes.get(1)) { + let (range_expr, range_err) = parse_range(working_set, spans[0]); + if range_err.is_none() { + return (range_expr, range_err); + } + } + // assume spans.len() > 0? let mut pos = 0; let mut shorthand = vec![]; diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 2015f356c0..11c8189f78 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -371,6 +371,37 @@ mod range { } } + #[test] + fn parse_left_unbounded_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse(&mut working_set, None, 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_negative_range() { let engine_state = EngineState::new(); From 8577d3ff413e6cecf8f84e03f89080c99995632f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Sep 2021 15:48:19 +0300 Subject: [PATCH 08/16] Check for left-unbounded range before external cmd --- crates/nu-parser/src/parser.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 839f43db88..27acdeca6b 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -469,15 +469,6 @@ pub fn parse_call( spans: &[Span], expand_aliases: bool, ) -> (Expression, Option) { - // We might be parsing left-unbounded range ("..10") - let bytes = working_set.get_span_contents(spans[0]); - if let (Some(b'.'), Some(b'.')) = (bytes.get(0), bytes.get(1)) { - let (range_expr, range_err) = parse_range(working_set, spans[0]); - if range_err.is_none() { - return (range_expr, range_err); - } - } - // assume spans.len() > 0? let mut pos = 0; let mut shorthand = vec![]; @@ -601,6 +592,14 @@ pub fn parse_call( err, ) } else { + // We might be parsing left-unbounded range ("..10") + let bytes = working_set.get_span_contents(spans[0]); + if let (Some(b'.'), Some(b'.')) = (bytes.get(0), bytes.get(1)) { + let (range_expr, range_err) = parse_range(working_set, spans[0]); + if range_err.is_none() { + return (range_expr, range_err); + } + } parse_external_call(working_set, spans) } } From e6a2e27e3360be80cd4e86f6bccc1fdade1db922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Sep 2021 15:57:49 +0300 Subject: [PATCH 09/16] Fix failing compilation after rebase --- crates/nu-parser/tests/test_parser.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 11c8189f78..ca5f3d13ca 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -387,6 +387,7 @@ mod range { expressions[0], Expression { expr: Expr::Range( + None, None, Some(_), RangeOperator { From 66c58217af28d652c8fba519830afa1b92abcb27 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 12 Sep 2021 16:36:16 +0100 Subject: [PATCH 10/16] change message --- crates/nu-parser/src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 5c03e08ddb..f4fd1348fd 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -610,7 +610,7 @@ pub fn parse_call( return ( garbage(Span::new(0, 0)), Some(ParseError::UnknownState( - "internal error: incomplete statement".into(), + "Incomplete statement".into(), span(spans), )), ); From eb67eab1228c46c9f3049de3f89a7f65e59616b9 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 13 Sep 2021 19:31:11 +1200 Subject: [PATCH 11/16] WIP --- crates/nu-parser/src/parser.rs | 24 ++++++++++++++---------- crates/nu-protocol/src/syntax_shape.rs | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index dde250716b..7a7c09db98 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1030,17 +1030,20 @@ pub fn parse_variable_expr( None, ) } else { - let name = working_set.get_span_contents(span).to_vec(); + // let name = working_set.get_span_contents(span).to_vec(); // this seems okay to set it to unknown here, but we should double-check - let id = working_set.add_variable(name, Type::Unknown); - ( - Expression { - expr: Expr::Var(id), - span, - ty: Type::Unknown, - }, - None, - ) + //let id = working_set.add_variable(name, Type::Unknown); + // ( + // Expression { + // expr: Expr::Var(id), + // span, + // ty: Type::Unknown, + // }, + // None, + // ) + // } else { + (garbage(span), err) + // } } } else { (garbage(span), err) @@ -1995,6 +1998,7 @@ pub fn parse_block_expression( output.signature = signature; } else if let Some(last) = working_set.delta.scope.last() { // FIXME: this only supports the top $it. Instead, we should look for a free $it in the expression. + if let Some(var_id) = last.get_var(b"$it") { let mut signature = Signature::new(""); signature.required_positional.push(PositionalArg { diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index 3e3337cd0f..bee2450a8a 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -34,7 +34,7 @@ pub enum SyntaxShape { GlobPattern, /// A block is allowed, eg `{start this thing}` - Block, + Block(Vec<(Vec, SyntaxShape)>), /// A table is allowed, eg `[[first, second]; [1, 2]]` Table, From 32c1f0c8d40e1d0aad78c5d5963a30dfc0d595f6 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 13 Sep 2021 19:54:13 +1200 Subject: [PATCH 12/16] better it detection and block params in shapes --- crates/nu-command/src/benchmark.rs | 6 +++- crates/nu-command/src/def.rs | 6 +++- crates/nu-command/src/do_.rs | 6 +++- crates/nu-command/src/each.rs | 6 +++- crates/nu-command/src/for_.rs | 6 +++- crates/nu-command/src/if_.rs | 2 +- crates/nu-parser/src/parser.rs | 50 ++++++++++++++------------ crates/nu-protocol/src/syntax_shape.rs | 4 +-- src/tests.rs | 8 +++++ 9 files changed, 64 insertions(+), 30 deletions(-) diff --git a/crates/nu-command/src/benchmark.rs b/crates/nu-command/src/benchmark.rs index 5bd5a6afcc..1590e8af84 100644 --- a/crates/nu-command/src/benchmark.rs +++ b/crates/nu-command/src/benchmark.rs @@ -17,7 +17,11 @@ impl Command for Benchmark { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("benchmark").required("block", SyntaxShape::Block, "the block to run") + Signature::build("benchmark").required( + "block", + SyntaxShape::Block(Some(vec![])), + "the block to run", + ) } fn run( diff --git a/crates/nu-command/src/def.rs b/crates/nu-command/src/def.rs index 25004d800d..0f6ef1b56b 100644 --- a/crates/nu-command/src/def.rs +++ b/crates/nu-command/src/def.rs @@ -17,7 +17,11 @@ impl Command for Def { Signature::build("def") .required("def_name", SyntaxShape::String, "definition name") .required("params", SyntaxShape::Signature, "parameters") - .required("block", SyntaxShape::Block, "body of the definition") + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "body of the definition", + ) } fn run( diff --git a/crates/nu-command/src/do_.rs b/crates/nu-command/src/do_.rs index 3630e84ff6..20bd0eb7d2 100644 --- a/crates/nu-command/src/do_.rs +++ b/crates/nu-command/src/do_.rs @@ -15,7 +15,11 @@ impl Command for Do { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("do").required("block", SyntaxShape::Block, "the block to run") + Signature::build("do").required( + "block", + SyntaxShape::Block(Some(vec![])), + "the block to run", + ) } fn run( diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index 109b082f1f..e48cfb719a 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -16,7 +16,11 @@ impl Command for Each { fn signature(&self) -> nu_protocol::Signature { Signature::build("each") - .required("block", SyntaxShape::Block, "the block to run") + .required( + "block", + SyntaxShape::Block(Some(vec![SyntaxShape::Any])), + "the block to run", + ) .switch("numbered", "iterate with an index", Some('n')) } diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs index 35d806dd56..158cd6b6c8 100644 --- a/crates/nu-command/src/for_.rs +++ b/crates/nu-command/src/for_.rs @@ -29,7 +29,11 @@ impl Command for For { ), "range of the loop", ) - .required("block", SyntaxShape::Block, "the block to run") + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "the block to run", + ) } fn run( diff --git a/crates/nu-command/src/if_.rs b/crates/nu-command/src/if_.rs index 2b8df48671..a9b1489a66 100644 --- a/crates/nu-command/src/if_.rs +++ b/crates/nu-command/src/if_.rs @@ -17,7 +17,7 @@ impl Command for If { fn signature(&self) -> nu_protocol::Signature { Signature::build("if") .required("cond", SyntaxShape::Expression, "condition") - .required("then_block", SyntaxShape::Block, "then block") + .required("then_block", SyntaxShape::Block(Some(vec![])), "then block") .optional( "else", SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)), diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 7a7c09db98..d1dada0fb9 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1030,20 +1030,7 @@ pub fn parse_variable_expr( None, ) } else { - // let name = working_set.get_span_contents(span).to_vec(); - // this seems okay to set it to unknown here, but we should double-check - //let id = working_set.add_variable(name, Type::Unknown); - // ( - // Expression { - // expr: Expr::Var(id), - // span, - // ty: Type::Unknown, - // }, - // None, - // ) - // } else { - (garbage(span), err) - // } + (garbage(span), Some(ParseError::VariableNotFound(span))) } } else { (garbage(span), err) @@ -1243,7 +1230,7 @@ pub fn parse_shape_name( b"int" => SyntaxShape::Int, b"path" => SyntaxShape::FilePath, b"glob" => SyntaxShape::GlobPattern, - b"block" => SyntaxShape::Block, + b"block" => SyntaxShape::Block(None), //FIXME b"cond" => SyntaxShape::RowCondition, b"operator" => SyntaxShape::Operator, b"math" => SyntaxShape::MathExpression, @@ -1905,6 +1892,7 @@ pub fn parse_table_expression( pub fn parse_block_expression( working_set: &mut StateWorkingSet, + shape: &SyntaxShape, span: Span, ) -> (Expression, Option) { let bytes = working_set.get_span_contents(span); @@ -1945,7 +1933,7 @@ pub fn parse_block_expression( working_set.enter_scope(); // Check to see if we have parameters - let (signature, amt_to_skip): (Option>, usize) = match output.first() { + let (mut signature, amt_to_skip): (Option>, usize) = match output.first() { Some(Token { contents: TokenContents::Pipe, span, @@ -1991,6 +1979,23 @@ pub fn parse_block_expression( let (output, err) = lite_parse(&output[amt_to_skip..]); error = error.or(err); + if let SyntaxShape::Block(Some(v)) = shape { + if signature.is_none() && v.len() == 1 { + // We'll assume there's an `$it` present + let var_id = working_set.add_variable(b"$it".to_vec(), Type::Unknown); + + let mut new_sigature = Signature::new(""); + new_sigature.required_positional.push(PositionalArg { + var_id: Some(var_id), + name: "$it".into(), + desc: String::new(), + shape: SyntaxShape::Any, + }); + + signature = Some(Box::new(new_sigature)); + } + } + let (mut output, err) = parse_block(working_set, &output, false); error = error.or(err); @@ -2049,8 +2054,8 @@ pub fn parse_value( return parse_full_column_path(working_set, None, span); } } else if bytes.starts_with(b"{") { - if matches!(shape, SyntaxShape::Block) || matches!(shape, SyntaxShape::Any) { - return parse_block_expression(working_set, span); + if matches!(shape, SyntaxShape::Block(_)) || matches!(shape, SyntaxShape::Any) { + return parse_block_expression(working_set, shape, span); } else { return ( Expression::garbage(span), @@ -2079,9 +2084,9 @@ pub fn parse_value( SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => { parse_string(working_set, span) } - SyntaxShape::Block => { + SyntaxShape::Block(_) => { if bytes.starts_with(b"{") { - parse_block_expression(working_set, span) + parse_block_expression(working_set, shape, span) } else { ( Expression::garbage(span), @@ -2129,7 +2134,7 @@ pub fn parse_value( SyntaxShape::Range, SyntaxShape::Filesize, SyntaxShape::Duration, - SyntaxShape::Block, + SyntaxShape::Block(None), SyntaxShape::String, ]; for shape in shapes.iter() { @@ -2381,7 +2386,8 @@ pub fn parse_def( let (sig, err) = parse_signature(working_set, spans[2]); error = error.or(err); - let (block, err) = parse_block_expression(working_set, spans[3]); + let (block, err) = + parse_block_expression(working_set, &SyntaxShape::Block(Some(vec![])), spans[3]); error = error.or(err); working_set.exit_scope(); diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index bee2450a8a..b5b81fe9ab 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -34,7 +34,7 @@ pub enum SyntaxShape { GlobPattern, /// A block is allowed, eg `{start this thing}` - Block(Vec<(Vec, SyntaxShape)>), + Block(Option>), /// A table is allowed, eg `[[first, second]; [1, 2]]` Table, @@ -75,7 +75,7 @@ impl SyntaxShape { pub fn to_type(&self) -> Type { match self { SyntaxShape::Any => Type::Unknown, - SyntaxShape::Block => Type::Block, + SyntaxShape::Block(_) => Type::Block, SyntaxShape::CellPath => Type::Unknown, SyntaxShape::Duration => Type::Duration, SyntaxShape::Expression => Type::Unknown, diff --git a/src/tests.rs b/src/tests.rs index df619f2b91..8684e817b7 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -308,3 +308,11 @@ fn row_condition2() -> TestResult { "1", ) } + +#[test] +fn better_block_types() -> TestResult { + run_test( + r#"([1, 2, 3] | each -n { $"($it.index) is ($it.item)" }).1"#, + "1 is 2", + ) +} From d7a3c7522bb4e94b4f61e4f9bb45c2211c72d15e Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 13 Sep 2021 20:19:05 +1200 Subject: [PATCH 13/16] Fix test --- crates/nu-parser/tests/test_parser.rs | 39 ++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index cdc6f5a8de..26393f37e0 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -2,10 +2,43 @@ use nu_parser::ParseError; use nu_parser::*; use nu_protocol::{ ast::{Expr, Expression, Pipeline, Statement}, - engine::{EngineState, StateWorkingSet}, + engine::{Command, EngineState, StateWorkingSet}, Signature, SyntaxShape, }; +#[cfg(test)] +pub struct Let; + +#[cfg(test)] +impl Command for Let { + fn name(&self) -> &str { + "let" + } + + fn usage(&self) -> &str { + "Create a variable and give it a value." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("let") + .required("var_name", SyntaxShape::VarWithOptType, "variable name") + .required( + "initial_value", + SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), + "equals sign followed by value", + ) + } + + fn run( + &self, + _context: &nu_protocol::engine::EvaluationContext, + _call: &nu_protocol::ast::Call, + _input: nu_protocol::Value, + ) -> Result { + todo!() + } +} + #[test] pub fn parse_int() { let engine_state = EngineState::new(); @@ -280,6 +313,8 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); + working_set.add_decl(Box::new(Let)); + let (block, err) = parse(&mut working_set, None, b"let a = 2; $a..10", true); assert!(err.is_none()); @@ -312,6 +347,8 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); + working_set.add_decl(Box::new(Let)); + let (block, err) = parse(&mut working_set, None, b"let a = 2; $a..<($a + 10)", true); assert!(err.is_none()); From b6fdf611f6b90e8cd5921e4ffbe7e635beb9483b Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Mon, 13 Sep 2021 09:32:03 -0700 Subject: [PATCH 14/16] more block param and build string tests --- src/tests.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/tests.rs b/src/tests.rs index 8684e817b7..7a04df6320 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -214,6 +214,16 @@ fn block_param2() -> TestResult { run_test("[3] | each { |y| $y + 10 }", "[13]") } +#[test] +fn block_param3_list_iteration() -> TestResult { + run_test("[1,2,3] | each { $it + 10 }", "[11, 12, 13]") +} + +#[test] +fn block_param4_list_iteration() -> TestResult { + run_test("[1,2,3] | each { |y| $y + 10 }", "[11, 12, 13]") +} + #[test] fn range_iteration1() -> TestResult { run_test("1..4 | each { |y| $y + 10 }", "[11, 12, 13, 14]") @@ -255,6 +265,22 @@ fn build_string3() -> TestResult { ) } +#[test] +fn build_string4() -> TestResult { + run_test( + "['sam','rick','pete'] | each { build-string $it ' is studying'}", + "[sam is studying, rick is studying, pete is studying]", + ) +} + +#[test] +fn build_string5() -> TestResult { + run_test( + "['sam','rick','pete'] | each { |x| build-string $x ' is studying'}", + "[sam is studying, rick is studying, pete is studying]", + ) +} + #[test] fn cell_path_subexpr1() -> TestResult { run_test("([[lang, gems]; [nu, 100]]).lang", "[nu]") From 7aa1d8ac2af76696f4b0d08a5ad0fde02adfc8d3 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Mon, 13 Sep 2021 20:59:11 +0100 Subject: [PATCH 15/16] error check on def and alias --- crates/nu-cli/src/errors.rs | 8 ++++ crates/nu-parser/src/errors.rs | 1 + crates/nu-parser/src/parser.rs | 67 ++++++++++++++++++++++------------ 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index b5470ead13..a7ee992168 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -261,6 +261,14 @@ pub fn report_parsing_error( .with_labels(vec![Label::primary(diag_file_id, diag_range) .with_message("needs a parameter name")]) } + ParseError::AssignmentMismatch(msg, label, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message(msg) + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message(label) + ]) + } }; // println!("DIAG"); diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index f4c486e0fd..2965d5f149 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -30,4 +30,5 @@ pub enum ParseError { RestNeedsName(Span), ExtraColumns(usize, Span), MissingColumns(usize, Span), + AssignmentMismatch(String, String, Span), } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 1c44c6bbad..1d24bea002 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -63,16 +63,35 @@ fn check_call(command: Span, sig: &Signature, call: &Call) -> Option } } -fn check_name(working_set: &mut StateWorkingSet, spans: &[Span]) -> Option { - if spans[1..].len() < 2 { - Some(ParseError::UnknownState( - "missing definition name".into(), - span(spans), - )) +fn check_name<'a>( + working_set: &mut StateWorkingSet, + spans: &'a [Span], +) -> Option<(&'a Span, ParseError)> { + if spans.len() == 1 { + None + } else if spans.len() < 4 { + if working_set.get_span_contents(spans[1]) == b"=" { + let name = String::from_utf8_lossy(working_set.get_span_contents(spans[0])); + Some(( + &spans[1], + ParseError::AssignmentMismatch( + format!("{} missing name", name), + "missing name".into(), + spans[1], + ), + )) + } else { + None + } } else if working_set.get_span_contents(spans[2]) != b"=" { - Some(ParseError::UnknownState( - "missing equal sign in definition".into(), - span(spans), + let name = String::from_utf8_lossy(working_set.get_span_contents(spans[0])); + Some(( + &spans[2], + ParseError::AssignmentMismatch( + format!("{} missing sign", name), + "missing equal sign".into(), + spans[2], + ), )) } else { None @@ -2490,7 +2509,7 @@ pub fn parse_def( ( garbage_statement(spans), Some(ParseError::UnknownState( - "definition unparseable. Expected structure: def [] {}".into(), + "Expected structure: def [] {}".into(), span(spans), )), ) @@ -2504,9 +2523,9 @@ pub fn parse_alias( let name = working_set.get_span_contents(spans[0]); if name == b"alias" { - if let Some(err) = check_name(working_set, spans) { + if let Some((span, err)) = check_name(working_set, spans) { return ( - Statement::Pipeline(Pipeline::from_vec(vec![garbage(span(spans))])), + Statement::Pipeline(Pipeline::from_vec(vec![garbage(*span)])), Some(err), ); } @@ -2562,9 +2581,9 @@ pub fn parse_let( let name = working_set.get_span_contents(spans[0]); if name == b"let" { - if let Some(err) = check_name(working_set, spans) { + if let Some((span, err)) = check_name(working_set, spans) { return ( - Statement::Pipeline(Pipeline::from_vec(vec![garbage(span(spans))])), + Statement::Pipeline(Pipeline::from_vec(vec![garbage(*span)])), Some(err), ); } @@ -2606,16 +2625,16 @@ pub fn parse_statement( working_set: &mut StateWorkingSet, spans: &[Span], ) -> (Statement, Option) { - // FIXME: improve errors by checking keyword first - if let (decl, None) = parse_def(working_set, spans) { - (decl, None) - } else if let (stmt, None) = parse_let(working_set, spans) { - (stmt, None) - } else if let (stmt, None) = parse_alias(working_set, spans) { - (stmt, None) - } else { - let (expr, err) = parse_expression(working_set, spans); - (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err) + let name = working_set.get_span_contents(spans[0]); + + match name { + b"def" => parse_def(working_set, spans), + b"let" => parse_let(working_set, spans), + b"alias" => parse_alias(working_set, spans), + _ => { + let (expr, err) = parse_expression(working_set, spans); + (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err) + } } } From b4f918b889afcbca836ad11b9df7e9353324690b Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 14 Sep 2021 16:59:46 +1200 Subject: [PATCH 16/16] Very early proof-of-concept git branch completion --- crates/nu-cli/src/completions.rs | 40 ++++++++++++- crates/nu-cli/src/syntax_highlight.rs | 1 + crates/nu-command/src/default_context.rs | 9 ++- crates/nu-command/src/git.rs | 51 ++++++++++++++++ crates/nu-command/src/git_checkout.rs | 66 +++++++++++++++++++++ crates/nu-command/src/lib.rs | 6 ++ crates/nu-command/src/list_git_branches.rs | 69 ++++++++++++++++++++++ crates/nu-parser/src/flatten.rs | 5 ++ crates/nu-parser/src/parser.rs | 44 ++++++++++++++ crates/nu-protocol/src/ast/expression.rs | 2 + crates/nu-protocol/src/syntax_shape.rs | 4 ++ 11 files changed, 293 insertions(+), 4 deletions(-) create mode 100644 crates/nu-command/src/git.rs create mode 100644 crates/nu-command/src/git_checkout.rs create mode 100644 crates/nu-command/src/list_git_branches.rs diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 45b9b17bd0..2140ca0da9 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -1,7 +1,11 @@ use std::{cell::RefCell, rc::Rc}; +use nu_engine::eval_block; use nu_parser::{flatten_block, parse}; -use nu_protocol::engine::{EngineState, StateWorkingSet}; +use nu_protocol::{ + engine::{EngineState, EvaluationContext, Stack, StateWorkingSet}, + Value, +}; use reedline::Completer; pub struct NuCompleter { @@ -26,7 +30,39 @@ impl Completer for NuCompleter { for flat in flattened { if pos >= flat.0.start && pos <= flat.0.end { - match flat.1 { + match &flat.1 { + nu_parser::FlatShape::Custom(custom_completion) => { + let prefix = working_set.get_span_contents(flat.0).to_vec(); + + let (block, ..) = + parse(&mut working_set, None, custom_completion.as_bytes(), false); + let context = EvaluationContext { + engine_state: self.engine_state.clone(), + stack: Stack::default(), + }; + let result = eval_block(&context, &block, Value::nothing()); + + let v: Vec<_> = match result { + Ok(Value::List { vals, .. }) => vals + .into_iter() + .map(move |x| { + let s = x.as_string().expect("FIXME"); + + ( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + s, + ) + }) + .filter(|x| x.1.as_bytes().starts_with(&prefix)) + .collect(), + _ => vec![], + }; + + return v; + } nu_parser::FlatShape::External | nu_parser::FlatShape::InternalCall => { let prefix = working_set.get_span_contents(flat.0); let results = working_set.find_commands_by_prefix(prefix); diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 54804b76f5..87ec535ed0 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -39,6 +39,7 @@ impl Highlighter for NuHighlighter { [(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)] .to_string(); match shape.1 { + FlatShape::Custom(..) => output.push((Style::new().bold(), next_token)), FlatShape::External => output.push((Style::new().bold(), next_token)), FlatShape::ExternalArg => output.push((Style::new().bold(), next_token)), FlatShape::Garbage => output.push(( diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index f9a61a77b9..9291a9742b 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -6,8 +6,8 @@ use nu_protocol::{ }; use crate::{ - where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv, Ls, - Table, + where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, Git, GitCheckout, If, Length, + Let, LetEnv, ListGitBranches, Ls, Table, }; pub fn create_default_context() -> Rc> { @@ -48,6 +48,11 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Table)); + // This is a WIP proof of concept + working_set.add_decl(Box::new(ListGitBranches)); + working_set.add_decl(Box::new(Git)); + working_set.add_decl(Box::new(GitCheckout)); + let sig = Signature::build("exit"); working_set.add_decl(sig.predeclare()); let sig = Signature::build("vars"); diff --git a/crates/nu-command/src/git.rs b/crates/nu-command/src/git.rs new file mode 100644 index 0000000000..5fe8521f39 --- /dev/null +++ b/crates/nu-command/src/git.rs @@ -0,0 +1,51 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, Value}; + +pub struct Git; + +impl Command for Git { + fn name(&self) -> &str { + "git" + } + + fn usage(&self) -> &str { + "Run a block" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("git") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + use std::process::Command as ProcessCommand; + use std::process::Stdio; + + let proc = ProcessCommand::new("git").stdout(Stdio::piped()).spawn(); + + match proc { + Ok(child) => { + match child.wait_with_output() { + Ok(val) => { + let result = val.stdout; + + Ok(Value::string(&String::from_utf8_lossy(&result), call.head)) + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } +} diff --git a/crates/nu-command/src/git_checkout.rs b/crates/nu-command/src/git_checkout.rs new file mode 100644 index 0000000000..143fff96b3 --- /dev/null +++ b/crates/nu-command/src/git_checkout.rs @@ -0,0 +1,66 @@ +use nu_engine::eval_expression; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct GitCheckout; + +impl Command for GitCheckout { + fn name(&self) -> &str { + "git checkout" + } + + fn usage(&self) -> &str { + "Checkout a git revision" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("git checkout").required( + "branch", + SyntaxShape::Custom(Box::new(SyntaxShape::String), "list-git-branches".into()), + "the branch to checkout", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + use std::process::Command as ProcessCommand; + use std::process::Stdio; + + let block = &call.positional[0]; + + let out = eval_expression(context, block)?; + + let out = out.as_string()?; + + let proc = ProcessCommand::new("git") + .arg("checkout") + .arg(out) + .stdout(Stdio::piped()) + .spawn(); + + match proc { + Ok(child) => { + match child.wait_with_output() { + Ok(val) => { + let result = val.stdout; + + Ok(Value::string(&String::from_utf8_lossy(&result), call.head)) + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } +} diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 2b7a3cac0a..a508e0cb47 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -6,10 +6,13 @@ mod default_context; mod do_; mod each; mod for_; +mod git; +mod git_checkout; mod if_; mod length; mod let_; mod let_env; +mod list_git_branches; mod ls; mod table; mod where_; @@ -22,9 +25,12 @@ pub use default_context::create_default_context; pub use do_::Do; pub use each::Each; pub use for_::For; +pub use git::Git; +pub use git_checkout::GitCheckout; pub use if_::If; pub use length::Length; pub use let_::Let; pub use let_env::LetEnv; +pub use list_git_branches::ListGitBranches; pub use ls::Ls; pub use table::Table; diff --git a/crates/nu-command/src/list_git_branches.rs b/crates/nu-command/src/list_git_branches.rs new file mode 100644 index 0000000000..3a0a148925 --- /dev/null +++ b/crates/nu-command/src/list_git_branches.rs @@ -0,0 +1,69 @@ +// Note: this is a temporary command that later will be converted into a pipeline + +use std::process::Command as ProcessCommand; +use std::process::Stdio; + +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, Value}; + +pub struct ListGitBranches; + +//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one. +impl Command for ListGitBranches { + fn name(&self) -> &str { + "list-git-branches" + } + + fn usage(&self) -> &str { + "List the git branches of the current directory." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("list-git-branches") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let list_branches = ProcessCommand::new("git") + .arg("branch") + .stdout(Stdio::piped()) + .spawn(); + + if let Ok(child) = list_branches { + if let Ok(output) = child.wait_with_output() { + let val = output.stdout; + + let s = String::from_utf8_lossy(&val).to_string(); + + let lines: Vec<_> = s + .lines() + .filter_map(|x| { + if x.starts_with("* ") { + None + } else { + Some(x.trim()) + } + }) + .map(|x| Value::String { + val: x.into(), + span: call.head, + }) + .collect(); + + Ok(Value::List { + vals: lines, + span: call.head, + }) + } else { + Ok(Value::Nothing { span: call.head }) + } + } else { + Ok(Value::Nothing { span: call.head }) + } + } +} diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index f32de5eca9..ba3fde5db3 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -16,6 +16,7 @@ pub enum FlatShape { Signature, String, Variable, + Custom(String), } pub fn flatten_block(working_set: &StateWorkingSet, block: &Block) -> Vec<(Span, FlatShape)> { @@ -40,6 +41,10 @@ pub fn flatten_expression( working_set: &StateWorkingSet, expr: &Expression, ) -> Vec<(Span, FlatShape)> { + if let Some(custom_completion) = &expr.custom_completion { + return vec![(expr.span, FlatShape::Custom(custom_completion.clone()))]; + } + match &expr.expr { Expr::BinaryOp(lhs, op, rhs) => { let mut output = vec![]; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 1d24bea002..7e66e760ab 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -113,6 +113,7 @@ pub fn parse_external_call( expr: Expr::ExternalCall(name, args), span: span(spans), ty: Type::Unknown, + custom_completion: None, }, None, ) @@ -360,6 +361,7 @@ fn parse_multispan_value( ), span: arg_span, ty: Type::Unknown, + custom_completion: None, }, error, ); @@ -374,6 +376,7 @@ fn parse_multispan_value( expr: Expr::Keyword(keyword.clone(), keyword_span, Box::new(expr)), span: arg_span, ty, + custom_completion: None, }, error, ) @@ -553,12 +556,14 @@ pub fn parse_call( expr: Expr::Call(mut call), span, ty, + custom_completion: None, } => { call.head = orig_span; Expression { expr: Expr::Call(call), span, ty, + custom_completion: None, } } x => x, @@ -596,12 +601,14 @@ pub fn parse_call( expr: Expr::Call(mut call), span, ty, + custom_completion: None, } => { call.head = orig_span; Expression { expr: Expr::Call(call), span, ty, + custom_completion: None, } } x => x, @@ -644,6 +651,7 @@ pub fn parse_call( expr: Expr::Call(call), span: span(spans), ty: Type::Unknown, // FIXME + custom_completion: None, }, err, ) @@ -668,6 +676,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option) { expr: Expr::Int(v), span, ty: Type::Int, + custom_completion: None, }, None, ) @@ -688,6 +697,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option) { expr: Expr::Int(v), span, ty: Type::Int, + custom_completion: None, }, None, ) @@ -708,6 +718,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option) { expr: Expr::Int(v), span, ty: Type::Int, + custom_completion: None, }, None, ) @@ -727,6 +738,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option) { expr: Expr::Int(x), span, ty: Type::Int, + custom_completion: None, }, None, ) @@ -745,6 +757,7 @@ pub fn parse_float(token: &[u8], span: Span) -> (Expression, Option) expr: Expr::Float(x), span, ty: Type::Float, + custom_completion: None, }, None, ) @@ -901,6 +914,7 @@ pub fn parse_range( expr: Expr::Range(from, next, to, range_op), span, ty: Type::Range, + custom_completion: None, }, None, ) @@ -971,6 +985,7 @@ pub fn parse_string_interpolation( expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()), span, ty: Type::String, + custom_completion: None, }); } token_start = b; @@ -1013,6 +1028,7 @@ pub fn parse_string_interpolation( expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()), span, ty: Type::String, + custom_completion: None, }); } } @@ -1044,6 +1060,7 @@ pub fn parse_string_interpolation( })), span, ty: Type::String, + custom_completion: None, }, error, ) @@ -1067,6 +1084,7 @@ pub fn parse_variable_expr( expr: Expr::Bool(true), span, ty: Type::Bool, + custom_completion: None, }, None, ); @@ -1076,6 +1094,7 @@ pub fn parse_variable_expr( expr: Expr::Bool(false), span, ty: Type::Bool, + custom_completion: None, }, None, ); @@ -1090,6 +1109,7 @@ pub fn parse_variable_expr( expr: Expr::Var(id), span, ty: working_set.get_variable(id).clone(), + custom_completion: None, }, None, ) @@ -1159,6 +1179,7 @@ pub fn parse_full_column_path( expr: Expr::Subexpression(block_id), span, ty: Type::Unknown, // FIXME + custom_completion: None, }, true, ) @@ -1175,6 +1196,7 @@ pub fn parse_full_column_path( expr: Expr::Var(var_id), span: Span::unknown(), ty: Type::Unknown, + custom_completion: None, }, false, ) @@ -1241,6 +1263,7 @@ pub fn parse_full_column_path( expr: Expr::FullCellPath(Box::new(FullCellPath { head, tail })), ty: Type::Unknown, span: full_column_span, + custom_completion: None, }, error, ) @@ -1268,6 +1291,7 @@ pub fn parse_string( expr: Expr::String(token), span, ty: Type::String, + custom_completion: None, }, None, ) @@ -1337,6 +1361,7 @@ pub fn parse_var_with_opt_type( expr: Expr::Var(id), span: span(&spans[*spans_idx - 1..*spans_idx + 1]), ty, + custom_completion: None, }, None, ) @@ -1347,6 +1372,7 @@ pub fn parse_var_with_opt_type( expr: Expr::Var(id), span: spans[*spans_idx], ty: Type::Unknown, + custom_completion: None, }, Some(ParseError::MissingType(spans[*spans_idx])), ) @@ -1359,6 +1385,7 @@ pub fn parse_var_with_opt_type( expr: Expr::Var(id), span: span(&spans[*spans_idx..*spans_idx + 1]), ty: Type::Unknown, + custom_completion: None, }, None, ) @@ -1395,6 +1422,7 @@ pub fn parse_row_condition( ty: Type::Bool, span, expr: Expr::RowCondition(var_id, Box::new(expression)), + custom_completion: None, }, err, ) @@ -1435,6 +1463,7 @@ pub fn parse_signature( expr: Expr::Signature(sig), span, ty: Type::Unknown, + custom_completion: None, }, error, ) @@ -1834,6 +1863,7 @@ pub fn parse_list_expression( } else { Type::Unknown })), + custom_completion: None, }, error, ) @@ -1882,6 +1912,7 @@ pub fn parse_table_expression( expr: Expr::List(vec![]), span, ty: Type::List(Box::new(Type::Unknown)), + custom_completion: None, }, None, ), @@ -1947,6 +1978,7 @@ pub fn parse_table_expression( expr: Expr::Table(table_headers, rows), span, ty: Type::Table, + custom_completion: None, }, error, ) @@ -2089,6 +2121,7 @@ pub fn parse_block_expression( expr: Expr::Block(block_id), span, ty: Type::Block, + custom_completion: None, }, error, ) @@ -2142,6 +2175,11 @@ pub fn parse_value( } match shape { + SyntaxShape::Custom(shape, custom_completion) => { + let (mut expression, err) = parse_value(working_set, span, shape); + expression.custom_completion = Some(custom_completion.clone()); + (expression, err) + } SyntaxShape::Number => parse_number(bytes, span), SyntaxShape::Int => parse_int(bytes, span), SyntaxShape::Range => parse_range(working_set, span), @@ -2254,6 +2292,7 @@ pub fn parse_operator( expr: Expr::Operator(operator), span, ty: Type::Unknown, + custom_completion: None, }, None, ) @@ -2333,6 +2372,7 @@ pub fn parse_math_expression( expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), span: op_span, ty: result_ty, + custom_completion: None, }); } } @@ -2367,6 +2407,7 @@ pub fn parse_math_expression( expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), span: binary_op_span, ty: result_ty, + custom_completion: None, }); } @@ -2496,6 +2537,7 @@ pub fn parse_def( expr: Expr::Call(call), span: span(spans), ty: Type::Unknown, + custom_completion: None, }])), error, ) @@ -2559,6 +2601,7 @@ pub fn parse_alias( expr: Expr::Call(call), span: call_span, ty: Type::Unknown, + custom_completion: None, }])), None, ); @@ -2607,6 +2650,7 @@ pub fn parse_let( expr: Expr::Call(call), span: call_span, ty: Type::Unknown, + custom_completion: None, }])), err, ); diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 228bdf50ec..da79d9cc9d 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -6,6 +6,7 @@ pub struct Expression { pub expr: Expr, pub span: Span, pub ty: Type, + pub custom_completion: Option, } impl Expression { @@ -14,6 +15,7 @@ impl Expression { expr: Expr::Garbage, span, ty: Type::Unknown, + custom_completion: None, } } diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index b5b81fe9ab..b0c1b96599 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -69,6 +69,9 @@ pub enum SyntaxShape { /// A general expression, eg `1 + 2` or `foo --bar` Expression, + + /// A custom shape with custom completion logic + Custom(Box, String), } impl SyntaxShape { @@ -77,6 +80,7 @@ impl SyntaxShape { SyntaxShape::Any => Type::Unknown, SyntaxShape::Block(_) => Type::Block, SyntaxShape::CellPath => Type::Unknown, + SyntaxShape::Custom(custom, _) => custom.to_type(), SyntaxShape::Duration => Type::Duration, SyntaxShape::Expression => Type::Unknown, SyntaxShape::FilePath => Type::FilePath,