mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Let with pipeline (#9589)
# Description This changes the default behaviour of `let` to be able to take a pipeline as its initial value. For example: ``` > let x = "hello world" | str length ``` This is a change from the existing behaviour, where the right hand side is assumed to be an expression. Pipelines are more general, and can be more powerful. My google foo is failing me, but this also fixes this issue: ``` let x = foo ``` Currently, this reads `foo` as a bareword that gets converted to a string rather than running the `foo` command. In practice, this is really annoying and is a really hard to spot bug in a script. # User-Facing Changes BREAKING CHANGE BREAKING CHANGE `let` gains the power to be assigned via a pipeline. However, this changes the behaviour of `let x = foo` from assigning the string "foo" to `$x` to being "run the command `foo` and give the result to `$x`" # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass - `cargo run -- crates/nu-std/tests/run.nu` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
This commit is contained in:
parent
b70cce47e2
commit
5d9e2455f7
7 changed files with 522 additions and 291 deletions
|
@ -143,7 +143,7 @@ fn external_completer_trailing_space() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn external_completer_no_trailing_space() {
|
fn external_completer_no_trailing_space() {
|
||||||
let block = "let external_completer = {|spans| $spans}";
|
let block = "{|spans| $spans}";
|
||||||
let input = "gh alias".to_string();
|
let input = "gh alias".to_string();
|
||||||
|
|
||||||
let suggestions = run_external_completion(block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
|
@ -154,7 +154,7 @@ fn external_completer_no_trailing_space() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn external_completer_pass_flags() {
|
fn external_completer_pass_flags() {
|
||||||
let block = "let external_completer = {|spans| $spans}";
|
let block = "{|spans| $spans}";
|
||||||
let input = "gh api --".to_string();
|
let input = "gh api --".to_string();
|
||||||
|
|
||||||
let suggestions = run_external_completion(block, &input);
|
let suggestions = run_external_completion(block, &input);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use nu_engine::eval_expression_with_input;
|
use nu_engine::eval_block;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||||
|
@ -54,29 +54,24 @@ impl Command for Let {
|
||||||
.as_var()
|
.as_var()
|
||||||
.expect("internal error: missing variable");
|
.expect("internal error: missing variable");
|
||||||
|
|
||||||
let keyword_expr = call
|
let block_id = call
|
||||||
.positional_nth(1)
|
.positional_nth(1)
|
||||||
.expect("checked through parser")
|
.expect("checked through parser")
|
||||||
.as_keyword()
|
.as_block()
|
||||||
.expect("internal error: missing keyword");
|
.expect("internal error: missing right hand side");
|
||||||
|
|
||||||
let (rhs, external_failed) = eval_expression_with_input(
|
let block = engine_state.get_block(block_id);
|
||||||
|
let pipeline_data = eval_block(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
keyword_expr,
|
block,
|
||||||
input,
|
input,
|
||||||
call.redirect_stdout,
|
call.redirect_stdout,
|
||||||
call.redirect_stderr,
|
call.redirect_stderr,
|
||||||
)?;
|
)?;
|
||||||
if external_failed {
|
stack.add_var(var_id, pipeline_data.into_value(call.head));
|
||||||
// rhs must be a PipelineData::ExternalStream and it's failed
|
|
||||||
// return the failed stream (with a non-zero exit code) so the engine knows to stop running
|
|
||||||
Ok(rhs)
|
|
||||||
} else {
|
|
||||||
stack.add_var(var_id, rhs.into_value(call.head));
|
|
||||||
Ok(PipelineData::empty())
|
Ok(PipelineData::empty())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![
|
vec![
|
||||||
|
|
|
@ -26,8 +26,35 @@ fn let_doesnt_mutate() {
|
||||||
assert!(actual.err.contains("immutable"));
|
assert!(actual.err.contains("immutable"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn let_takes_pipeline() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: ".", pipeline(
|
||||||
|
r#"
|
||||||
|
let x = "hello world" | str length; print $x
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "11");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn let_pipeline_allows_in() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: ".", pipeline(
|
||||||
|
r#"
|
||||||
|
def foo [] { let x = $in | str length; print ($x + 10) }; "hello world" | foo
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "21");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ignore]
|
||||||
#[test]
|
#[test]
|
||||||
fn let_with_external_failed() {
|
fn let_with_external_failed() {
|
||||||
|
// FIXME: this test hasn't run successfully for a long time. We should
|
||||||
|
// bring it back to life at some point.
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".",
|
cwd: ".",
|
||||||
pipeline(r#"let x = nu --testbin outcome_err "aa"; echo fail"#)
|
pipeline(r#"let x = nu --testbin outcome_err "aa"; echo fail"#)
|
||||||
|
|
|
@ -50,17 +50,11 @@ pub enum LiteElement {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct LitePipeline {
|
pub struct LitePipeline {
|
||||||
pub commands: Vec<LiteElement>,
|
pub commands: Vec<LiteElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LitePipeline {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LitePipeline {
|
impl LitePipeline {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { commands: vec![] }
|
Self { commands: vec![] }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{parser_path::ParserPath, type_check::type_compatible};
|
use crate::{parse_block, parser_path::ParserPath, type_check::type_compatible};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_path::canonicalize_with;
|
use nu_path::canonicalize_with;
|
||||||
|
@ -2795,54 +2795,51 @@ pub fn parse_overlay_hide(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||||
pipeline
|
pipeline
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_let_or_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline {
|
pub fn parse_let(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline {
|
||||||
trace!("parsing: let");
|
trace!("parsing: let");
|
||||||
let name = working_set.get_span_contents(spans[0]);
|
|
||||||
|
|
||||||
if name == b"let" || name == b"const" {
|
|
||||||
let is_const = &name == b"const";
|
|
||||||
|
|
||||||
// JT: Disabling check_name because it doesn't work with optional types in the declaration
|
// JT: Disabling check_name because it doesn't work with optional types in the declaration
|
||||||
// if let Some(span) = check_name(working_set, spans) {
|
// if let Some(span) = check_name(working_set, spans) {
|
||||||
// return Pipeline::from_vec(vec![garbage(*span)]);
|
// return Pipeline::from_vec(vec![garbage(*span)]);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if let Some(decl_id) =
|
if let Some(decl_id) = working_set.find_decl(b"let", &Type::Nothing) {
|
||||||
working_set.find_decl(if is_const { b"const" } else { b"let" }, &Type::Nothing)
|
|
||||||
{
|
|
||||||
let cmd = working_set.get_decl(decl_id);
|
|
||||||
let call_signature = cmd.signature().call_signature();
|
|
||||||
|
|
||||||
if spans.len() >= 4 {
|
if spans.len() >= 4 {
|
||||||
// This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order
|
// This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order
|
||||||
// so that the var-id created by the variable isn't visible in the expression that init it
|
// so that the var-id created by the variable isn't visible in the expression that init it
|
||||||
for span in spans.iter().enumerate() {
|
for span in spans.iter().enumerate() {
|
||||||
let item = working_set.get_span_contents(*span.1);
|
let item = working_set.get_span_contents(*span.1);
|
||||||
if item == b"=" && spans.len() > (span.0 + 1) {
|
if item == b"=" && spans.len() > (span.0 + 1) {
|
||||||
let mut idx = span.0;
|
let (tokens, parse_error) = lex(
|
||||||
let rvalue = parse_multispan_value(
|
working_set.get_span_contents(nu_protocol::span(&spans[(span.0 + 1)..])),
|
||||||
working_set,
|
spans[(span.0 + 1)].start,
|
||||||
spans,
|
&[],
|
||||||
&mut idx,
|
&[],
|
||||||
&SyntaxShape::Keyword(
|
true,
|
||||||
b"=".to_vec(),
|
|
||||||
Box::new(SyntaxShape::MathExpression),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if idx < (spans.len() - 1) {
|
if let Some(parse_error) = parse_error {
|
||||||
working_set
|
working_set.parse_errors.push(parse_error)
|
||||||
.error(ParseError::ExtraPositional(call_signature, spans[idx + 1]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let rvalue_span = nu_protocol::span(&spans[(span.0 + 1)..]);
|
||||||
|
let rvalue_block = parse_block(working_set, &tokens, rvalue_span, false, true);
|
||||||
|
|
||||||
|
let output_type = rvalue_block.output_type();
|
||||||
|
|
||||||
|
let block_id = working_set.add_block(rvalue_block);
|
||||||
|
|
||||||
|
let rvalue = Expression {
|
||||||
|
expr: Expr::Block(block_id),
|
||||||
|
span: rvalue_span,
|
||||||
|
ty: output_type,
|
||||||
|
custom_completion: None,
|
||||||
|
};
|
||||||
|
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
|
|
||||||
let (lvalue, explicit_type) = parse_var_with_opt_type(
|
let (lvalue, explicit_type) =
|
||||||
working_set,
|
parse_var_with_opt_type(working_set, &spans[1..(span.0)], &mut idx, false);
|
||||||
&spans[1..(span.0)],
|
|
||||||
&mut idx,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let var_name =
|
let var_name =
|
||||||
String::from_utf8_lossy(working_set.get_span_contents(lvalue.span))
|
String::from_utf8_lossy(working_set.get_span_contents(lvalue.span))
|
||||||
|
@ -2870,24 +2867,12 @@ pub fn parse_let_or_const(working_set: &mut StateWorkingSet, spans: &[Span]) ->
|
||||||
if explicit_type.is_none() {
|
if explicit_type.is_none() {
|
||||||
working_set.set_variable_type(var_id, rhs_type);
|
working_set.set_variable_type(var_id, rhs_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_const {
|
|
||||||
match eval_constant(working_set, &rvalue) {
|
|
||||||
Ok(val) => {
|
|
||||||
working_set.add_constant(var_id, val);
|
|
||||||
}
|
|
||||||
Err(err) => working_set.error(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let call = Box::new(Call {
|
let call = Box::new(Call {
|
||||||
decl_id,
|
decl_id,
|
||||||
head: spans[0],
|
head: spans[0],
|
||||||
arguments: vec![
|
arguments: vec![Argument::Positional(lvalue), Argument::Positional(rvalue)],
|
||||||
Argument::Positional(lvalue),
|
|
||||||
Argument::Positional(rvalue),
|
|
||||||
],
|
|
||||||
redirect_stdout: true,
|
redirect_stdout: true,
|
||||||
redirect_stderr: false,
|
redirect_stderr: false,
|
||||||
parser_info: HashMap::new(),
|
parser_info: HashMap::new(),
|
||||||
|
@ -2917,6 +2902,127 @@ pub fn parse_let_or_const(working_set: &mut StateWorkingSet, spans: &[Span]) ->
|
||||||
span(spans),
|
span(spans),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
working_set.error(ParseError::UnknownState(
|
||||||
|
"internal error: let or const statement unparsable".into(),
|
||||||
|
span(spans),
|
||||||
|
));
|
||||||
|
|
||||||
|
garbage_pipeline(spans)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline {
|
||||||
|
trace!("parsing: const");
|
||||||
|
|
||||||
|
// JT: Disabling check_name because it doesn't work with optional types in the declaration
|
||||||
|
// if let Some(span) = check_name(working_set, spans) {
|
||||||
|
// return Pipeline::from_vec(vec![garbage(*span)]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if let Some(decl_id) = working_set.find_decl(b"const", &Type::Nothing) {
|
||||||
|
let cmd = working_set.get_decl(decl_id);
|
||||||
|
let call_signature = cmd.signature().call_signature();
|
||||||
|
|
||||||
|
if spans.len() >= 4 {
|
||||||
|
// This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order
|
||||||
|
// so that the var-id created by the variable isn't visible in the expression that init it
|
||||||
|
for span in spans.iter().enumerate() {
|
||||||
|
let item = working_set.get_span_contents(*span.1);
|
||||||
|
if item == b"=" && spans.len() > (span.0 + 1) {
|
||||||
|
let mut idx = span.0;
|
||||||
|
// let rvalue = parse_multispan_value(
|
||||||
|
// working_set,
|
||||||
|
// spans,
|
||||||
|
// &mut idx,
|
||||||
|
// &SyntaxShape::Keyword(
|
||||||
|
// b"=".to_vec(),
|
||||||
|
// Box::new(SyntaxShape::MathExpression),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
|
||||||
|
let rvalue = parse_multispan_value(
|
||||||
|
working_set,
|
||||||
|
spans,
|
||||||
|
&mut idx,
|
||||||
|
&SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
|
||||||
|
);
|
||||||
|
if idx < (spans.len() - 1) {
|
||||||
|
working_set
|
||||||
|
.error(ParseError::ExtraPositional(call_signature, spans[idx + 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut idx = 0;
|
||||||
|
|
||||||
|
let (lvalue, explicit_type) =
|
||||||
|
parse_var_with_opt_type(working_set, &spans[1..(span.0)], &mut idx, false);
|
||||||
|
|
||||||
|
let var_name =
|
||||||
|
String::from_utf8_lossy(working_set.get_span_contents(lvalue.span))
|
||||||
|
.trim_start_matches('$')
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if ["in", "nu", "env", "nothing"].contains(&var_name.as_str()) {
|
||||||
|
working_set.error(ParseError::NameIsBuiltinVar(var_name, lvalue.span))
|
||||||
|
}
|
||||||
|
|
||||||
|
let var_id = lvalue.as_var();
|
||||||
|
let rhs_type = rvalue.ty.clone();
|
||||||
|
|
||||||
|
if let Some(explicit_type) = &explicit_type {
|
||||||
|
if !type_compatible(explicit_type, &rhs_type) {
|
||||||
|
working_set.error(ParseError::TypeMismatch(
|
||||||
|
explicit_type.clone(),
|
||||||
|
rhs_type.clone(),
|
||||||
|
nu_protocol::span(&spans[(span.0 + 1)..]),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(var_id) = var_id {
|
||||||
|
if explicit_type.is_none() {
|
||||||
|
working_set.set_variable_type(var_id, rhs_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
match eval_constant(working_set, &rvalue) {
|
||||||
|
Ok(val) => {
|
||||||
|
working_set.add_constant(var_id, val);
|
||||||
|
}
|
||||||
|
Err(err) => working_set.error(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let call = Box::new(Call {
|
||||||
|
decl_id,
|
||||||
|
head: spans[0],
|
||||||
|
arguments: vec![Argument::Positional(lvalue), Argument::Positional(rvalue)],
|
||||||
|
redirect_stdout: true,
|
||||||
|
redirect_stderr: false,
|
||||||
|
parser_info: HashMap::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: nu_protocol::span(spans),
|
||||||
|
ty: Type::Any,
|
||||||
|
custom_completion: None,
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ParsedInternalCall { call, output } =
|
||||||
|
parse_internal_call(working_set, spans[0], &spans[1..], decl_id);
|
||||||
|
|
||||||
|
return Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: nu_protocol::span(spans),
|
||||||
|
ty: output,
|
||||||
|
custom_completion: None,
|
||||||
|
}]);
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::UnknownState(
|
||||||
|
"internal error: let or const statements not found in core language".into(),
|
||||||
|
span(spans),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
working_set.error(ParseError::UnknownState(
|
working_set.error(ParseError::UnknownState(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
eval::{eval_constant, value_as_string},
|
eval::{eval_constant, value_as_string},
|
||||||
lex::{lex, lex_signature},
|
lex::{lex, lex_signature},
|
||||||
lite_parser::{lite_parse, LiteCommand, LiteElement},
|
lite_parser::{lite_parse, LiteCommand, LiteElement, LitePipeline},
|
||||||
parse_mut,
|
parse_mut,
|
||||||
parse_patterns::{parse_match_pattern, parse_pattern},
|
parse_patterns::{parse_match_pattern, parse_pattern},
|
||||||
type_check::{math_result_type, type_compatible},
|
type_check::{math_result_type, type_compatible},
|
||||||
|
@ -21,10 +21,10 @@ use nu_protocol::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::parse_keywords::{
|
use crate::parse_keywords::{
|
||||||
find_dirs_var, is_unaliasable_parser_keyword, parse_alias, parse_def, parse_def_predecl,
|
find_dirs_var, is_unaliasable_parser_keyword, parse_alias, parse_const, parse_def,
|
||||||
parse_export_in_block, parse_extern, parse_for, parse_hide, parse_keyword, parse_let_or_const,
|
parse_def_predecl, parse_export_in_block, parse_extern, parse_for, parse_hide, parse_keyword,
|
||||||
parse_module, parse_overlay_hide, parse_overlay_new, parse_overlay_use, parse_source,
|
parse_let, parse_module, parse_overlay_hide, parse_overlay_new, parse_overlay_use,
|
||||||
parse_use, parse_where, parse_where_expr, LIB_DIRS_VAR,
|
parse_source, parse_use, parse_where, parse_where_expr, LIB_DIRS_VAR,
|
||||||
};
|
};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -5139,7 +5139,8 @@ pub fn parse_builtin_commands(
|
||||||
match name {
|
match name {
|
||||||
b"def" | b"def-env" => parse_def(working_set, lite_command, None),
|
b"def" | b"def-env" => parse_def(working_set, lite_command, None),
|
||||||
b"extern" => parse_extern(working_set, lite_command, None),
|
b"extern" => parse_extern(working_set, lite_command, None),
|
||||||
b"let" | b"const" => parse_let_or_const(working_set, &lite_command.parts),
|
b"let" => parse_let(working_set, &lite_command.parts),
|
||||||
|
b"const" => parse_const(working_set, &lite_command.parts),
|
||||||
b"mut" => parse_mut(working_set, &lite_command.parts),
|
b"mut" => parse_mut(working_set, &lite_command.parts),
|
||||||
b"for" => {
|
b"for" => {
|
||||||
let expr = parse_for(working_set, &lite_command.parts);
|
let expr = parse_for(working_set, &lite_command.parts);
|
||||||
|
@ -5239,46 +5240,109 @@ pub fn parse_record(working_set: &mut StateWorkingSet, span: Span) -> Expression
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_block(
|
pub fn parse_pipeline(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
tokens: &[Token],
|
pipeline: &LitePipeline,
|
||||||
span: Span,
|
|
||||||
scoped: bool,
|
|
||||||
is_subexpression: bool,
|
is_subexpression: bool,
|
||||||
) -> Block {
|
pipeline_index: usize,
|
||||||
let (lite_block, err) = lite_parse(tokens);
|
) -> Pipeline {
|
||||||
if let Some(err) = err {
|
|
||||||
working_set.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
trace!("parsing block: {:?}", lite_block);
|
|
||||||
|
|
||||||
if scoped {
|
|
||||||
working_set.enter_scope();
|
|
||||||
}
|
|
||||||
working_set.type_scope.enter_scope();
|
|
||||||
|
|
||||||
// Pre-declare any definition so that definitions
|
|
||||||
// that share the same block can see each other
|
|
||||||
for pipeline in &lite_block.block {
|
|
||||||
if pipeline.commands.len() == 1 {
|
|
||||||
match &pipeline.commands[0] {
|
|
||||||
LiteElement::Command(_, command)
|
|
||||||
| LiteElement::Redirection(_, _, command)
|
|
||||||
| LiteElement::SeparateRedirection {
|
|
||||||
out: (_, command), ..
|
|
||||||
}
|
|
||||||
| LiteElement::SameTargetRedirection {
|
|
||||||
cmd: (_, command), ..
|
|
||||||
} => parse_def_predecl(working_set, &command.parts),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut block = Block::new_with_capacity(lite_block.block.len());
|
|
||||||
|
|
||||||
for (idx, pipeline) in lite_block.block.iter().enumerate() {
|
|
||||||
if pipeline.commands.len() > 1 {
|
if pipeline.commands.len() > 1 {
|
||||||
|
// Special case: allow `let` to consume the whole pipeline, eg) `let abc = "foo" | str length`
|
||||||
|
match &pipeline.commands[0] {
|
||||||
|
LiteElement::Command(_, command) if !command.parts.is_empty() => {
|
||||||
|
if working_set.get_span_contents(command.parts[0]) == b"let" {
|
||||||
|
let mut new_command = LiteCommand {
|
||||||
|
comments: vec![],
|
||||||
|
parts: command.parts.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for command in &pipeline.commands[1..] {
|
||||||
|
match command {
|
||||||
|
LiteElement::Command(Some(pipe_span), command) => {
|
||||||
|
new_command.parts.push(*pipe_span);
|
||||||
|
|
||||||
|
new_command.comments.extend_from_slice(&command.comments);
|
||||||
|
new_command.parts.extend_from_slice(&command.parts);
|
||||||
|
}
|
||||||
|
_ => panic!("unsupported"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the 'let' is complete enough, use it, if not, fall through for now
|
||||||
|
if new_command.parts.len() > 3 {
|
||||||
|
let rhs_span = nu_protocol::span(&new_command.parts[3..]);
|
||||||
|
|
||||||
|
new_command.parts.truncate(3);
|
||||||
|
new_command.parts.push(rhs_span);
|
||||||
|
|
||||||
|
let mut pipeline =
|
||||||
|
parse_builtin_commands(working_set, &new_command, is_subexpression);
|
||||||
|
|
||||||
|
if pipeline_index == 0 {
|
||||||
|
if let Some(let_decl_id) = working_set.find_decl(b"let", &Type::Nothing)
|
||||||
|
{
|
||||||
|
for element in pipeline.elements.iter_mut() {
|
||||||
|
if let PipelineElement::Expression(
|
||||||
|
_,
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) = element
|
||||||
|
{
|
||||||
|
if call.decl_id == let_decl_id {
|
||||||
|
// Do an expansion
|
||||||
|
if let Some(Expression {
|
||||||
|
expr: Expr::Block(block_id),
|
||||||
|
..
|
||||||
|
}) = call.positional_iter_mut().nth(1)
|
||||||
|
{
|
||||||
|
let block = working_set.get_block(*block_id);
|
||||||
|
|
||||||
|
let element =
|
||||||
|
block.pipelines[0].elements[0].clone();
|
||||||
|
|
||||||
|
if let PipelineElement::Expression(prepend, expr) =
|
||||||
|
element
|
||||||
|
{
|
||||||
|
if expr.has_in_variable(working_set) {
|
||||||
|
let new_expr = PipelineElement::Expression(
|
||||||
|
prepend,
|
||||||
|
wrap_expr_with_collect(
|
||||||
|
working_set,
|
||||||
|
&expr,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let block =
|
||||||
|
working_set.get_block_mut(*block_id);
|
||||||
|
block.pipelines[0].elements[0] = new_expr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else if element.has_in_variable(working_set)
|
||||||
|
&& !is_subexpression
|
||||||
|
{
|
||||||
|
*element =
|
||||||
|
wrap_element_with_collect(working_set, element);
|
||||||
|
}
|
||||||
|
} else if element.has_in_variable(working_set)
|
||||||
|
&& !is_subexpression
|
||||||
|
{
|
||||||
|
*element = wrap_element_with_collect(working_set, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipeline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
let mut output = pipeline
|
let mut output = pipeline
|
||||||
.commands
|
.commands
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -5347,7 +5411,7 @@ pub fn parse_block(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
block.pipelines.push(Pipeline { elements: output })
|
Pipeline { elements: output }
|
||||||
} else {
|
} else {
|
||||||
match &pipeline.commands[0] {
|
match &pipeline.commands[0] {
|
||||||
LiteElement::Command(_, command)
|
LiteElement::Command(_, command)
|
||||||
|
@ -5355,10 +5419,9 @@ pub fn parse_block(
|
||||||
| LiteElement::SeparateRedirection {
|
| LiteElement::SeparateRedirection {
|
||||||
out: (_, command), ..
|
out: (_, command), ..
|
||||||
} => {
|
} => {
|
||||||
let mut pipeline =
|
let mut pipeline = parse_builtin_commands(working_set, command, is_subexpression);
|
||||||
parse_builtin_commands(working_set, command, is_subexpression);
|
|
||||||
|
|
||||||
if idx == 0 {
|
if pipeline_index == 0 {
|
||||||
if let Some(let_decl_id) = working_set.find_decl(b"let", &Type::Nothing) {
|
if let Some(let_decl_id) = working_set.find_decl(b"let", &Type::Nothing) {
|
||||||
for element in pipeline.elements.iter_mut() {
|
for element in pipeline.elements.iter_mut() {
|
||||||
if let PipelineElement::Expression(
|
if let PipelineElement::Expression(
|
||||||
|
@ -5372,31 +5435,39 @@ pub fn parse_block(
|
||||||
if call.decl_id == let_decl_id {
|
if call.decl_id == let_decl_id {
|
||||||
// Do an expansion
|
// Do an expansion
|
||||||
if let Some(Expression {
|
if let Some(Expression {
|
||||||
expr: Expr::Keyword(_, _, expr),
|
expr: Expr::Block(block_id),
|
||||||
..
|
..
|
||||||
}) = call.positional_iter_mut().nth(1)
|
}) = call.positional_iter_mut().nth(1)
|
||||||
|
{
|
||||||
|
let block = working_set.get_block(*block_id);
|
||||||
|
|
||||||
|
let element = block.pipelines[0].elements[0].clone();
|
||||||
|
|
||||||
|
if let PipelineElement::Expression(prepend, expr) = element
|
||||||
{
|
{
|
||||||
if expr.has_in_variable(working_set) {
|
if expr.has_in_variable(working_set) {
|
||||||
*expr = Box::new(wrap_expr_with_collect(
|
let new_expr = PipelineElement::Expression(
|
||||||
working_set,
|
prepend,
|
||||||
expr,
|
wrap_expr_with_collect(working_set, &expr),
|
||||||
));
|
);
|
||||||
|
|
||||||
|
let block = working_set.get_block_mut(*block_id);
|
||||||
|
block.pipelines[0].elements[0] = new_expr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if element.has_in_variable(working_set)
|
|
||||||
&& !is_subexpression
|
|
||||||
{
|
|
||||||
*element = wrap_element_with_collect(working_set, element);
|
|
||||||
}
|
|
||||||
} else if element.has_in_variable(working_set) && !is_subexpression
|
} else if element.has_in_variable(working_set) && !is_subexpression
|
||||||
{
|
{
|
||||||
*element = wrap_element_with_collect(working_set, element);
|
*element = wrap_element_with_collect(working_set, element);
|
||||||
}
|
}
|
||||||
|
} else if element.has_in_variable(working_set) && !is_subexpression {
|
||||||
|
*element = wrap_element_with_collect(working_set, element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
block.pipelines.push(pipeline)
|
}
|
||||||
|
pipeline
|
||||||
}
|
}
|
||||||
LiteElement::SameTargetRedirection {
|
LiteElement::SameTargetRedirection {
|
||||||
cmd: (span, command),
|
cmd: (span, command),
|
||||||
|
@ -5410,16 +5481,59 @@ pub fn parse_block(
|
||||||
|
|
||||||
working_set.type_scope.add_type(redirect_expr.ty.clone());
|
working_set.type_scope.add_type(redirect_expr.ty.clone());
|
||||||
|
|
||||||
block.pipelines.push(Pipeline {
|
Pipeline {
|
||||||
elements: vec![PipelineElement::SameTargetRedirection {
|
elements: vec![PipelineElement::SameTargetRedirection {
|
||||||
cmd: (*span, expr),
|
cmd: (*span, expr),
|
||||||
redirection: (*redirect_span, redirect_expr),
|
redirection: (*redirect_span, redirect_expr),
|
||||||
}],
|
}],
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_block(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
tokens: &[Token],
|
||||||
|
span: Span,
|
||||||
|
scoped: bool,
|
||||||
|
is_subexpression: bool,
|
||||||
|
) -> Block {
|
||||||
|
let (lite_block, err) = lite_parse(tokens);
|
||||||
|
if let Some(err) = err {
|
||||||
|
working_set.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("parsing block: {:?}", lite_block);
|
||||||
|
|
||||||
|
if scoped {
|
||||||
|
working_set.enter_scope();
|
||||||
|
}
|
||||||
|
working_set.type_scope.enter_scope();
|
||||||
|
|
||||||
|
// Pre-declare any definition so that definitions
|
||||||
|
// that share the same block can see each other
|
||||||
|
for pipeline in &lite_block.block {
|
||||||
|
if pipeline.commands.len() == 1 {
|
||||||
|
match &pipeline.commands[0] {
|
||||||
|
LiteElement::Command(_, command)
|
||||||
|
| LiteElement::Redirection(_, _, command)
|
||||||
|
| LiteElement::SeparateRedirection {
|
||||||
|
out: (_, command), ..
|
||||||
|
}
|
||||||
|
| LiteElement::SameTargetRedirection {
|
||||||
|
cmd: (_, command), ..
|
||||||
|
} => parse_def_predecl(working_set, &command.parts),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut block = Block::new_with_capacity(lite_block.block.len());
|
||||||
|
|
||||||
|
for (idx, lite_pipeline) in lite_block.block.iter().enumerate() {
|
||||||
|
let pipeline = parse_pipeline(working_set, lite_pipeline, is_subexpression, idx);
|
||||||
|
block.pipelines.push(pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
if scoped {
|
if scoped {
|
||||||
working_set.exit_scope();
|
working_set.exit_scope();
|
||||||
|
|
|
@ -153,11 +153,6 @@ fn long_flag() -> TestResult {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn let_not_statement() -> TestResult {
|
|
||||||
fail_test(r#"let x = "hello" | str length"#, "used in pipeline")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn for_in_missing_var_name() -> TestResult {
|
fn for_in_missing_var_name() -> TestResult {
|
||||||
fail_test("for in", "missing")
|
fail_test("for in", "missing")
|
||||||
|
|
Loading…
Reference in a new issue