mirror of
https://github.com/nushell/nushell.git
synced 2025-08-16 18:12:46 +02: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:
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
eval::{eval_constant, value_as_string},
|
||||
lex::{lex, lex_signature},
|
||||
lite_parser::{lite_parse, LiteCommand, LiteElement},
|
||||
lite_parser::{lite_parse, LiteCommand, LiteElement, LitePipeline},
|
||||
parse_mut,
|
||||
parse_patterns::{parse_match_pattern, parse_pattern},
|
||||
type_check::{math_result_type, type_compatible},
|
||||
@ -21,10 +21,10 @@ use nu_protocol::{
|
||||
};
|
||||
|
||||
use crate::parse_keywords::{
|
||||
find_dirs_var, is_unaliasable_parser_keyword, parse_alias, parse_def, parse_def_predecl,
|
||||
parse_export_in_block, parse_extern, parse_for, parse_hide, parse_keyword, parse_let_or_const,
|
||||
parse_module, parse_overlay_hide, parse_overlay_new, parse_overlay_use, parse_source,
|
||||
parse_use, parse_where, parse_where_expr, LIB_DIRS_VAR,
|
||||
find_dirs_var, is_unaliasable_parser_keyword, parse_alias, parse_const, parse_def,
|
||||
parse_def_predecl, parse_export_in_block, parse_extern, parse_for, parse_hide, parse_keyword,
|
||||
parse_let, parse_module, parse_overlay_hide, parse_overlay_new, parse_overlay_use,
|
||||
parse_source, parse_use, parse_where, parse_where_expr, LIB_DIRS_VAR,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
@ -5139,7 +5139,8 @@ pub fn parse_builtin_commands(
|
||||
match name {
|
||||
b"def" | b"def-env" => parse_def(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"for" => {
|
||||
let expr = parse_for(working_set, &lite_command.parts);
|
||||
@ -5239,6 +5240,258 @@ pub fn parse_record(working_set: &mut StateWorkingSet, span: Span) -> Expression
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_pipeline(
|
||||
working_set: &mut StateWorkingSet,
|
||||
pipeline: &LitePipeline,
|
||||
is_subexpression: bool,
|
||||
pipeline_index: usize,
|
||||
) -> Pipeline {
|
||||
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
|
||||
.commands
|
||||
.iter()
|
||||
.map(|command| match command {
|
||||
LiteElement::Command(span, command) => {
|
||||
trace!("parsing: pipeline element: command");
|
||||
let expr = parse_expression(working_set, &command.parts, is_subexpression);
|
||||
working_set.type_scope.add_type(expr.ty.clone());
|
||||
|
||||
PipelineElement::Expression(*span, expr)
|
||||
}
|
||||
LiteElement::Redirection(span, redirection, command) => {
|
||||
trace!("parsing: pipeline element: redirection");
|
||||
let expr = parse_string(working_set, command.parts[0]);
|
||||
|
||||
working_set.type_scope.add_type(expr.ty.clone());
|
||||
|
||||
PipelineElement::Redirection(*span, redirection.clone(), expr)
|
||||
}
|
||||
LiteElement::SeparateRedirection {
|
||||
out: (out_span, out_command),
|
||||
err: (err_span, err_command),
|
||||
} => {
|
||||
trace!("parsing: pipeline element: separate redirection");
|
||||
let out_expr = parse_string(working_set, out_command.parts[0]);
|
||||
|
||||
working_set.type_scope.add_type(out_expr.ty.clone());
|
||||
|
||||
let err_expr = parse_string(working_set, err_command.parts[0]);
|
||||
|
||||
working_set.type_scope.add_type(err_expr.ty.clone());
|
||||
|
||||
PipelineElement::SeparateRedirection {
|
||||
out: (*out_span, out_expr),
|
||||
err: (*err_span, err_expr),
|
||||
}
|
||||
}
|
||||
LiteElement::SameTargetRedirection {
|
||||
cmd: (cmd_span, command),
|
||||
redirection: (redirect_span, redirect_command),
|
||||
} => {
|
||||
trace!("parsing: pipeline element: same target redirection");
|
||||
let expr = parse_expression(working_set, &command.parts, is_subexpression);
|
||||
working_set.type_scope.add_type(expr.ty.clone());
|
||||
let redirect_expr = parse_string(working_set, redirect_command.parts[0]);
|
||||
working_set.type_scope.add_type(redirect_expr.ty.clone());
|
||||
PipelineElement::SameTargetRedirection {
|
||||
cmd: (*cmd_span, expr),
|
||||
redirection: (*redirect_span, redirect_expr),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<PipelineElement>>();
|
||||
|
||||
if is_subexpression {
|
||||
for element in output.iter_mut().skip(1) {
|
||||
if element.has_in_variable(working_set) {
|
||||
*element = wrap_element_with_collect(working_set, element);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for element in output.iter_mut() {
|
||||
if element.has_in_variable(working_set) {
|
||||
*element = wrap_element_with_collect(working_set, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Pipeline { elements: output }
|
||||
} else {
|
||||
match &pipeline.commands[0] {
|
||||
LiteElement::Command(_, command)
|
||||
| LiteElement::Redirection(_, _, command)
|
||||
| LiteElement::SeparateRedirection {
|
||||
out: (_, command), ..
|
||||
} => {
|
||||
let mut pipeline = parse_builtin_commands(working_set, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pipeline
|
||||
}
|
||||
LiteElement::SameTargetRedirection {
|
||||
cmd: (span, command),
|
||||
redirection: (redirect_span, redirect_cmd),
|
||||
} => {
|
||||
trace!("parsing: pipeline element: same target redirection");
|
||||
let expr = parse_expression(working_set, &command.parts, is_subexpression);
|
||||
working_set.type_scope.add_type(expr.ty.clone());
|
||||
|
||||
let redirect_expr = parse_string(working_set, redirect_cmd.parts[0]);
|
||||
|
||||
working_set.type_scope.add_type(redirect_expr.ty.clone());
|
||||
|
||||
Pipeline {
|
||||
elements: vec![PipelineElement::SameTargetRedirection {
|
||||
cmd: (*span, expr),
|
||||
redirection: (*redirect_span, redirect_expr),
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_block(
|
||||
working_set: &mut StateWorkingSet,
|
||||
tokens: &[Token],
|
||||
@ -5277,148 +5530,9 @@ pub fn parse_block(
|
||||
|
||||
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 {
|
||||
let mut output = pipeline
|
||||
.commands
|
||||
.iter()
|
||||
.map(|command| match command {
|
||||
LiteElement::Command(span, command) => {
|
||||
trace!("parsing: pipeline element: command");
|
||||
let expr = parse_expression(working_set, &command.parts, is_subexpression);
|
||||
working_set.type_scope.add_type(expr.ty.clone());
|
||||
|
||||
PipelineElement::Expression(*span, expr)
|
||||
}
|
||||
LiteElement::Redirection(span, redirection, command) => {
|
||||
trace!("parsing: pipeline element: redirection");
|
||||
let expr = parse_string(working_set, command.parts[0]);
|
||||
|
||||
working_set.type_scope.add_type(expr.ty.clone());
|
||||
|
||||
PipelineElement::Redirection(*span, redirection.clone(), expr)
|
||||
}
|
||||
LiteElement::SeparateRedirection {
|
||||
out: (out_span, out_command),
|
||||
err: (err_span, err_command),
|
||||
} => {
|
||||
trace!("parsing: pipeline element: separate redirection");
|
||||
let out_expr = parse_string(working_set, out_command.parts[0]);
|
||||
|
||||
working_set.type_scope.add_type(out_expr.ty.clone());
|
||||
|
||||
let err_expr = parse_string(working_set, err_command.parts[0]);
|
||||
|
||||
working_set.type_scope.add_type(err_expr.ty.clone());
|
||||
|
||||
PipelineElement::SeparateRedirection {
|
||||
out: (*out_span, out_expr),
|
||||
err: (*err_span, err_expr),
|
||||
}
|
||||
}
|
||||
LiteElement::SameTargetRedirection {
|
||||
cmd: (cmd_span, command),
|
||||
redirection: (redirect_span, redirect_command),
|
||||
} => {
|
||||
trace!("parsing: pipeline element: same target redirection");
|
||||
let expr = parse_expression(working_set, &command.parts, is_subexpression);
|
||||
working_set.type_scope.add_type(expr.ty.clone());
|
||||
let redirect_expr = parse_string(working_set, redirect_command.parts[0]);
|
||||
working_set.type_scope.add_type(redirect_expr.ty.clone());
|
||||
PipelineElement::SameTargetRedirection {
|
||||
cmd: (*cmd_span, expr),
|
||||
redirection: (*redirect_span, redirect_expr),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<PipelineElement>>();
|
||||
|
||||
if is_subexpression {
|
||||
for element in output.iter_mut().skip(1) {
|
||||
if element.has_in_variable(working_set) {
|
||||
*element = wrap_element_with_collect(working_set, element);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for element in output.iter_mut() {
|
||||
if element.has_in_variable(working_set) {
|
||||
*element = wrap_element_with_collect(working_set, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
block.pipelines.push(Pipeline { elements: output })
|
||||
} else {
|
||||
match &pipeline.commands[0] {
|
||||
LiteElement::Command(_, command)
|
||||
| LiteElement::Redirection(_, _, command)
|
||||
| LiteElement::SeparateRedirection {
|
||||
out: (_, command), ..
|
||||
} => {
|
||||
let mut pipeline =
|
||||
parse_builtin_commands(working_set, command, is_subexpression);
|
||||
|
||||
if idx == 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::Keyword(_, _, expr),
|
||||
..
|
||||
}) = call.positional_iter_mut().nth(1)
|
||||
{
|
||||
if expr.has_in_variable(working_set) {
|
||||
*expr = Box::new(wrap_expr_with_collect(
|
||||
working_set,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
block.pipelines.push(pipeline)
|
||||
}
|
||||
LiteElement::SameTargetRedirection {
|
||||
cmd: (span, command),
|
||||
redirection: (redirect_span, redirect_cmd),
|
||||
} => {
|
||||
trace!("parsing: pipeline element: same target redirection");
|
||||
let expr = parse_expression(working_set, &command.parts, is_subexpression);
|
||||
working_set.type_scope.add_type(expr.ty.clone());
|
||||
|
||||
let redirect_expr = parse_string(working_set, redirect_cmd.parts[0]);
|
||||
|
||||
working_set.type_scope.add_type(redirect_expr.ty.clone());
|
||||
|
||||
block.pipelines.push(Pipeline {
|
||||
elements: vec![PipelineElement::SameTargetRedirection {
|
||||
cmd: (*span, expr),
|
||||
redirection: (*redirect_span, redirect_expr),
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
|
Reference in New Issue
Block a user