forked from extern/nushell
Merge branch 'main' of https://github.com/nushell/engine-q into externals
This commit is contained in:
commit
1a9247b77f
2
TODO.md
2
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)
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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((
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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");
|
||||
|
@ -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(
|
||||
|
@ -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,22 +33,44 @@ 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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(&state, block, Value::nothing()) {
|
||||
Ok(v) => v,
|
||||
@ -55,16 +83,34 @@ 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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(&state, block, Value::nothing()) {
|
||||
Ok(v) => v,
|
||||
@ -76,16 +122,34 @@ 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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(&state, block, Value::nothing()) {
|
||||
Ok(v) => v,
|
||||
|
@ -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(
|
||||
|
51
crates/nu-command/src/git.rs
Normal file
51
crates/nu-command/src/git.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66
crates/nu-command/src/git_checkout.rs
Normal file
66
crates/nu-command/src/git_checkout.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)),
|
||||
|
@ -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;
|
||||
|
69
crates/nu-command/src/list_git_branches.rs
Normal file
69
crates/nu-command/src/list_git_branches.rs
Normal 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 })
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -30,4 +30,5 @@ pub enum ParseError {
|
||||
RestNeedsName(Span),
|
||||
ExtraColumns(usize, Span),
|
||||
MissingColumns(usize, Span),
|
||||
AssignmentMismatch(String, String, Span),
|
||||
}
|
||||
|
@ -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(_) => {
|
||||
|
@ -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,17 +2669,17 @@ 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 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_block(
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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(", ")
|
||||
)
|
||||
|
@ -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),
|
||||
_ => {
|
||||
(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),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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 +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
|
||||
|
34
src/tests.rs
34
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]")
|
||||
@ -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",
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user