Merge branch 'main' of https://github.com/nushell/engine-q into externals

This commit is contained in:
Fernando Herrera 2021-09-14 07:19:31 +01:00
commit 1a9247b77f
29 changed files with 864 additions and 172 deletions

View File

@ -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)

View File

@ -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);

View File

@ -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");

View File

@ -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((

View File

@ -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(

View File

@ -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(

View File

@ -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<RefCell<EngineState>> {
@ -48,6 +48,11 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
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");

View File

@ -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(

View File

@ -15,7 +15,13 @@ 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(Some(vec![SyntaxShape::Any])),
"the block to run",
)
.switch("numbered", "iterate with an index", Some('n'))
}
fn run(
@ -27,20 +33,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 +83,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 +122,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);
}
}
}

View File

@ -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(

View File

@ -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<nu_protocol::Value, nu_protocol::ShellError> {
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())
}
}
}
}

View File

@ -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<nu_protocol::Value, nu_protocol::ShellError> {
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())
}
}
}
}

View File

@ -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)),

View File

@ -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;

View File

@ -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<nu_protocol::Value, nu_protocol::ShellError> {
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 })
}
}
}

View File

@ -114,13 +114,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(),
}
};
@ -128,31 +134,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,
})
}
@ -190,7 +178,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);

View File

@ -30,4 +30,5 @@ pub enum ParseError {
RestNeedsName(Span),
ExtraColumns(usize, Span),
MissingColumns(usize, Span),
AssignmentMismatch(String, String, Span),
}

View File

@ -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![];
@ -85,15 +90,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(_) => {

View File

@ -63,16 +63,35 @@ fn check_call(command: Span, sig: &Signature, call: &Call) -> Option<ParseError>
}
}
fn check_name(working_set: &mut StateWorkingSet, spans: &[Span]) -> Option<ParseError> {
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
@ -94,6 +113,7 @@ pub fn parse_external_call(
expr: Expr::ExternalCall(name, args),
span: span(spans),
ty: Type::Unknown,
custom_completion: None,
},
None,
)
@ -341,6 +361,7 @@ fn parse_multispan_value(
),
span: arg_span,
ty: Type::Unknown,
custom_completion: None,
},
error,
);
@ -355,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,
)
@ -534,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,
@ -577,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,
@ -610,7 +636,7 @@ pub fn parse_call(
return (
garbage(Span::new(0, 0)),
Some(ParseError::UnknownState(
"internal error: incomplete statement".into(),
"Incomplete statement".into(),
span(spans),
)),
);
@ -625,10 +651,19 @@ pub fn parse_call(
expr: Expr::Call(call),
span: span(spans),
ty: Type::Unknown, // FIXME
custom_completion: None,
},
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)
}
}
@ -641,6 +676,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option<ParseError>) {
expr: Expr::Int(v),
span,
ty: Type::Int,
custom_completion: None,
},
None,
)
@ -661,6 +697,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option<ParseError>) {
expr: Expr::Int(v),
span,
ty: Type::Int,
custom_completion: None,
},
None,
)
@ -681,6 +718,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option<ParseError>) {
expr: Expr::Int(v),
span,
ty: Type::Int,
custom_completion: None,
},
None,
)
@ -700,6 +738,7 @@ pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option<ParseError>) {
expr: Expr::Int(x),
span,
ty: Type::Int,
custom_completion: None,
},
None,
)
@ -718,6 +757,7 @@ pub fn parse_float(token: &[u8], span: Span) -> (Expression, Option<ParseError>)
expr: Expr::Float(x),
span,
ty: Type::Float,
custom_completion: None,
},
None,
)
@ -746,8 +786,8 @@ 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 ".."
// Range follows the following syntax: [<from>][<next_operator><next>]<range_operator>[<to>]
// where <next_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)
@ -762,42 +802,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),
@ -813,21 +839,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]);
@ -867,11 +886,35 @@ 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,
custom_completion: None,
},
None,
)
@ -942,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;
@ -984,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,
});
}
}
@ -1015,6 +1060,7 @@ pub fn parse_string_interpolation(
})),
span,
ty: Type::String,
custom_completion: None,
},
error,
)
@ -1038,6 +1084,7 @@ pub fn parse_variable_expr(
expr: Expr::Bool(true),
span,
ty: Type::Bool,
custom_completion: None,
},
None,
);
@ -1047,6 +1094,7 @@ pub fn parse_variable_expr(
expr: Expr::Bool(false),
span,
ty: Type::Bool,
custom_completion: None,
},
None,
);
@ -1061,21 +1109,12 @@ pub fn parse_variable_expr(
expr: Expr::Var(id),
span,
ty: working_set.get_variable(id).clone(),
custom_completion: None,
},
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,
)
(garbage(span), Some(ParseError::VariableNotFound(span)))
}
} else {
(garbage(span), err)
@ -1140,6 +1179,7 @@ pub fn parse_full_column_path(
expr: Expr::Subexpression(block_id),
span,
ty: Type::Unknown, // FIXME
custom_completion: None,
},
true,
)
@ -1156,6 +1196,7 @@ pub fn parse_full_column_path(
expr: Expr::Var(var_id),
span: Span::unknown(),
ty: Type::Unknown,
custom_completion: None,
},
false,
)
@ -1222,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,
)
@ -1249,6 +1291,7 @@ pub fn parse_string(
expr: Expr::String(token),
span,
ty: Type::String,
custom_completion: None,
},
None,
)
@ -1275,7 +1318,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,
@ -1318,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,
)
@ -1328,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])),
)
@ -1340,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,
)
@ -1376,6 +1422,7 @@ pub fn parse_row_condition(
ty: Type::Bool,
span,
expr: Expr::RowCondition(var_id, Box::new(expression)),
custom_completion: None,
},
err,
)
@ -1416,6 +1463,7 @@ pub fn parse_signature(
expr: Expr::Signature(sig),
span,
ty: Type::Unknown,
custom_completion: None,
},
error,
)
@ -1815,6 +1863,7 @@ pub fn parse_list_expression(
} else {
Type::Unknown
})),
custom_completion: None,
},
error,
)
@ -1863,6 +1912,7 @@ pub fn parse_table_expression(
expr: Expr::List(vec![]),
span,
ty: Type::List(Box::new(Type::Unknown)),
custom_completion: None,
},
None,
),
@ -1928,6 +1978,7 @@ pub fn parse_table_expression(
expr: Expr::Table(table_headers, rows),
span,
ty: Type::Table,
custom_completion: None,
},
error,
)
@ -1937,6 +1988,7 @@ pub fn parse_table_expression(
pub fn parse_block_expression(
working_set: &mut StateWorkingSet,
shape: &SyntaxShape,
span: Span,
) -> (Expression, Option<ParseError>) {
let bytes = working_set.get_span_contents(span);
@ -1977,7 +2029,7 @@ pub fn parse_block_expression(
working_set.enter_scope();
// Check to see if we have parameters
let (signature, amt_to_skip): (Option<Box<Signature>>, usize) = match output.first() {
let (mut signature, amt_to_skip): (Option<Box<Signature>>, usize) = match output.first() {
Some(Token {
contents: TokenContents::Pipe,
span,
@ -2023,12 +2075,31 @@ 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);
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 {
@ -2050,6 +2121,7 @@ pub fn parse_block_expression(
expr: Expr::Block(block_id),
span,
ty: Type::Block,
custom_completion: None,
},
error,
)
@ -2079,8 +2151,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),
@ -2103,15 +2175,20 @@ 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),
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),
@ -2159,7 +2236,7 @@ pub fn parse_value(
SyntaxShape::Range,
SyntaxShape::Filesize,
SyntaxShape::Duration,
SyntaxShape::Block,
SyntaxShape::Block(None),
SyntaxShape::String,
];
for shape in shapes.iter() {
@ -2215,6 +2292,7 @@ pub fn parse_operator(
expr: Expr::Operator(operator),
span,
ty: Type::Unknown,
custom_completion: None,
},
None,
)
@ -2294,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,
});
}
}
@ -2328,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,
});
}
@ -2411,7 +2491,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();
@ -2456,6 +2537,7 @@ pub fn parse_def(
expr: Expr::Call(call),
span: span(spans),
ty: Type::Unknown,
custom_completion: None,
}])),
error,
)
@ -2469,7 +2551,7 @@ pub fn parse_def(
(
garbage_statement(spans),
Some(ParseError::UnknownState(
"definition unparseable. Expected structure: def <name> [] {}".into(),
"Expected structure: def <name> [] {}".into(),
span(spans),
)),
)
@ -2483,9 +2565,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),
);
}
@ -2519,6 +2601,7 @@ pub fn parse_alias(
expr: Expr::Call(call),
span: call_span,
ty: Type::Unknown,
custom_completion: None,
}])),
None,
);
@ -2541,9 +2624,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),
);
}
@ -2567,6 +2650,7 @@ pub fn parse_let(
expr: Expr::Call(call),
span: call_span,
ty: Type::Unknown,
custom_completion: None,
}])),
err,
);
@ -2585,16 +2669,16 @@ pub fn parse_statement(
working_set: &mut StateWorkingSet,
spans: &[Span],
) -> (Statement, Option<ParseError>) {
// 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)
}
}
}

View File

@ -20,6 +20,7 @@ pub fn math_result_type(
op: &mut Expression,
rhs: &mut Expression,
) -> (Type, Option<ParseError>) {
//println!("checking: {:?} {:?} {:?}", lhs, op, rhs);
match &op.expr {
Expr::Operator(operator) => match operator {
Operator::Plus => match (&lhs.ty, &rhs.ty) {

View File

@ -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<nu_protocol::Value, nu_protocol::ShellError> {
todo!()
}
}
#[test]
pub fn parse_int() {
let engine_state = EngineState::new();
@ -164,6 +197,7 @@ mod range {
Expression {
expr: Expr::Range(
Some(_),
None,
Some(_),
RangeOperator {
inclusion: RangeInclusion::Inclusive,
@ -195,6 +229,7 @@ mod range {
Expression {
expr: Expr::Range(
Some(_),
None,
Some(_),
RangeOperator {
inclusion: RangeInclusion::RightExclusive,
@ -209,6 +244,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 +293,7 @@ mod range {
Expression {
expr: Expr::Range(
Some(_),
None,
Some(_),
RangeOperator {
inclusion: RangeInclusion::RightExclusive,
@ -245,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());
@ -257,6 +327,7 @@ mod range {
Expression {
expr: Expr::Range(
Some(_),
None,
Some(_),
RangeOperator {
inclusion: RangeInclusion::Inclusive,
@ -276,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());
@ -288,6 +361,7 @@ mod range {
Expression {
expr: Expr::Range(
Some(_),
None,
Some(_),
RangeOperator {
inclusion: RangeInclusion::RightExclusive,
@ -320,6 +394,39 @@ mod range {
expr: Expr::Range(
Some(_),
None,
None,
RangeOperator {
inclusion: RangeInclusion::Inclusive,
..
}
),
..
}
))
}
_ => 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(&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,
None,
Some(_),
RangeOperator {
inclusion: RangeInclusion::Inclusive,
..
@ -349,6 +456,39 @@ mod range {
expressions[0],
Expression {
expr: Expr::Range(
Some(_),
None,
Some(_),
RangeOperator {
inclusion: RangeInclusion::Inclusive,
..
}
),
..
}
))
}
_ => panic!("No match"),
}
}
#[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 {

View File

@ -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
}
}

View File

@ -7,8 +7,9 @@ pub enum Expr {
Int(i64),
Float(f64),
Range(
Option<Box<Expression>>,
Option<Box<Expression>>,
Option<Box<Expression>>, // from
Option<Box<Expression>>, // next value after "from"
Option<Box<Expression>>, // to
RangeOperator,
),
Var(VarId),

View File

@ -6,6 +6,7 @@ pub struct Expression {
pub expr: Expr,
pub span: Span,
pub ty: Type,
pub custom_completion: Option<String>,
}
impl Expression {
@ -14,6 +15,7 @@ impl Expression {
expr: Expr::Garbage,
span,
ty: Type::Unknown,
custom_completion: None,
}
}

View File

@ -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 {

View File

@ -34,7 +34,7 @@ pub enum SyntaxShape {
GlobPattern,
/// A block is allowed, eg `{start this thing}`
Block,
Block(Option<Vec<SyntaxShape>>),
/// A table is allowed, eg `[[first, second]; [1, 2]]`
Table,
@ -69,14 +69,18 @@ pub enum SyntaxShape {
/// A general expression, eg `1 + 2` or `foo --bar`
Expression,
/// A custom shape with custom completion logic
Custom(Box<SyntaxShape>, String),
}
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::Custom(custom, _) => custom.to_type(),
SyntaxShape::Duration => Type::Duration,
SyntaxShape::Expression => Type::Unknown,
SyntaxShape::FilePath => Type::FilePath,

View File

@ -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<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())
val.into_iter()
.map(|x| x.into_string())
.collect::<Vec<String>>()
.join(", ")
)

View File

@ -1,12 +1,108 @@
use crate::{ast::RangeInclusion, *};
use std::cmp::Ordering;
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<Range, ShellError> {
// 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 +121,7 @@ pub struct RangeIterator {
span: Span,
is_end_inclusive: bool,
moves_up: bool,
one: Value,
negative_one: Value,
incr: Value,
done: bool,
}
@ -52,41 +147,66 @@ 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,
}
}
}
// Compare two floating point numbers. The decision interval for equality is dynamically scaled
// as the value being compared increases in magnitude.
fn compare_floats(val: f64, other: f64) -> Option<Ordering> {
let prec = f64::EPSILON.max(val.abs() * f64::EPSILON);
if (other - val).abs() < prec {
return Some(Ordering::Equal);
}
val.partial_cmp(&other)
}
impl Iterator for RangeIterator {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
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: 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),
_ => {
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,
}
};
if self.moves_up
&& (ordering == Ordering::Less || self.is_end_inclusive && ordering == Ordering::Equal)
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 {
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 +218,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

View File

@ -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]")
@ -308,3 +334,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",
)
}