diff --git a/crates/nu-cli/tests/completions.rs b/crates/nu-cli/tests/completions.rs index 1acc797aff..135978c704 100644 --- a/crates/nu-cli/tests/completions.rs +++ b/crates/nu-cli/tests/completions.rs @@ -143,7 +143,7 @@ fn external_completer_trailing_space() { #[test] fn external_completer_no_trailing_space() { - let block = "let external_completer = {|spans| $spans}"; + let block = "{|spans| $spans}"; let input = "gh alias".to_string(); let suggestions = run_external_completion(block, &input); @@ -154,7 +154,7 @@ fn external_completer_no_trailing_space() { #[test] fn external_completer_pass_flags() { - let block = "let external_completer = {|spans| $spans}"; + let block = "{|spans| $spans}"; let input = "gh api --".to_string(); let suggestions = run_external_completion(block, &input); diff --git a/crates/nu-cmd-lang/src/core_commands/let_.rs b/crates/nu-cmd-lang/src/core_commands/let_.rs index 579396b73b..cd6ae2338e 100644 --- a/crates/nu-cmd-lang/src/core_commands/let_.rs +++ b/crates/nu-cmd-lang/src/core_commands/let_.rs @@ -1,4 +1,4 @@ -use nu_engine::eval_expression_with_input; +use nu_engine::eval_block; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type}; @@ -54,28 +54,23 @@ impl Command for Let { .as_var() .expect("internal error: missing variable"); - let keyword_expr = call + let block_id = call .positional_nth(1) .expect("checked through parser") - .as_keyword() - .expect("internal error: missing keyword"); + .as_block() + .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, stack, - keyword_expr, + block, input, call.redirect_stdout, call.redirect_stderr, )?; - if external_failed { - // 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()) - } + stack.add_var(var_id, pipeline_data.into_value(call.head)); + Ok(PipelineData::empty()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/tests/commands/let_.rs b/crates/nu-command/tests/commands/let_.rs index 749b96a4d4..9671dd171a 100644 --- a/crates/nu-command/tests/commands/let_.rs +++ b/crates/nu-command/tests/commands/let_.rs @@ -26,8 +26,35 @@ fn let_doesnt_mutate() { 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] 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!( cwd: ".", pipeline(r#"let x = nu --testbin outcome_err "aa"; echo fail"#) diff --git a/crates/nu-parser/src/lite_parser.rs b/crates/nu-parser/src/lite_parser.rs index 53b495b646..881aaa19d8 100644 --- a/crates/nu-parser/src/lite_parser.rs +++ b/crates/nu-parser/src/lite_parser.rs @@ -50,17 +50,11 @@ pub enum LiteElement { }, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct LitePipeline { pub commands: Vec, } -impl Default for LitePipeline { - fn default() -> Self { - Self::new() - } -} - impl LitePipeline { pub fn new() -> Self { Self { commands: vec![] } diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 19a1e64495..e8e87e025d 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -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 log::trace; use nu_path::canonicalize_with; @@ -2795,128 +2795,234 @@ pub fn parse_overlay_hide(working_set: &mut StateWorkingSet, call: Box) -> 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"); - 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 + // if let Some(span) = check_name(working_set, spans) { + // return Pipeline::from_vec(vec![garbage(*span)]); + // } - // 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"let", &Type::Nothing) { + 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 (tokens, parse_error) = lex( + working_set.get_span_contents(nu_protocol::span(&spans[(span.0 + 1)..])), + spans[(span.0 + 1)].start, + &[], + &[], + true, + ); - if let Some(decl_id) = - 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 { - // 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), - ), - ); - - 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); - } - - 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 { - 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, - }]); + if let Some(parse_error) = parse_error { + working_set.parse_errors.push(parse_error) } + + 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 (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); + } + } + + 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), - )) } + 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( + "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( diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 92c33219d1..bc44b80ca2 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -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::>(); + + 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::>(); - - 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 { diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index 97e5f1c2e5..3db0957e8d 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -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] fn for_in_missing_var_name() -> TestResult { fail_test("for in", "missing")