diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index 3290a774f4..ac9145f260 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -6,6 +6,7 @@ pub enum TokenContents { Comment, Pipe, PipePipe, + AssignmentOperator, ErrGreaterPipe, OutErrGreaterPipe, Semicolon, @@ -297,6 +298,10 @@ pub fn lex_item( let mut err = None; let output = match &input[(span.start - span_offset)..(span.end - span_offset)] { + b"=" | b"+=" | b"++=" | b"-=" | b"*=" | b"/=" => Token { + contents: TokenContents::AssignmentOperator, + span, + }, b"out>" | b"o>" => Token { contents: TokenContents::OutGreaterThan, span, diff --git a/crates/nu-parser/src/lite_parser.rs b/crates/nu-parser/src/lite_parser.rs index f04b8befdc..ba4ad73295 100644 --- a/crates/nu-parser/src/lite_parser.rs +++ b/crates/nu-parser/src/lite_parser.rs @@ -167,10 +167,43 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { let mut last_token = TokenContents::Eol; let mut file_redirection = None; let mut curr_comment: Option> = None; + let mut is_assignment = false; let mut error = None; for (idx, token) in tokens.iter().enumerate() { - if let Some((source, append, span)) = file_redirection.take() { + if is_assignment { + match &token.contents { + // Consume until semicolon or terminating EOL. Assignments absorb pipelines and + // redirections. + TokenContents::Eol => { + // Handle `[Command] [Pipe] ([Comment] | [Eol])+ [Command]` + // + // `[Eol]` branch checks if previous token is `[Pipe]` to construct pipeline + // and so `[Comment] | [Eol]` should be ignore to make it work + let actual_token = last_non_comment_token(tokens, idx); + if actual_token != Some(TokenContents::Pipe) { + is_assignment = false; + pipeline.push(&mut command); + block.push(&mut pipeline); + } + + if last_token == TokenContents::Eol { + // Clear out the comment as we're entering a new comment + curr_comment = None; + } + } + TokenContents::Semicolon => { + is_assignment = false; + pipeline.push(&mut command); + block.push(&mut pipeline); + } + TokenContents::Comment => { + command.comments.push(token.span); + curr_comment = None; + } + _ => command.push(token.span), + } + } else if let Some((source, append, span)) = file_redirection.take() { match &token.contents { TokenContents::PipePipe => { error = error.or(Some(ParseError::ShellOrOr(token.span))); @@ -189,6 +222,11 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { command.push(token.span) } } + TokenContents::AssignmentOperator => { + error = error.or(Some(ParseError::Expected("redirection target", token.span))); + command.push(span); + command.push(token.span); + } TokenContents::OutGreaterThan | TokenContents::OutGreaterGreaterThan | TokenContents::ErrGreaterThan @@ -251,6 +289,15 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { } command.push(token.span); } + TokenContents::AssignmentOperator => { + // When in assignment mode, we'll just consume pipes or redirections as part of + // the command. + is_assignment = true; + if let Some(curr_comment) = curr_comment.take() { + command.comments = curr_comment; + } + command.push(token.span); + } TokenContents::OutGreaterThan => { error = error.or(command.check_accepts_redirection(token.span)); file_redirection = Some((RedirectionSource::Stdout, false, token.span)); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index fc2131aad7..eeb95b1b97 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1430,7 +1430,8 @@ fn parse_binary_with_base( | TokenContents::ErrGreaterThan | TokenContents::ErrGreaterGreaterThan | TokenContents::OutErrGreaterThan - | TokenContents::OutErrGreaterGreaterThan => { + | TokenContents::OutErrGreaterGreaterThan + | TokenContents::AssignmentOperator => { working_set.error(ParseError::Expected("binary", span)); return garbage(working_set, span); }