/// Lite parsing converts a flat stream of tokens from the lexer to a syntax element structure that /// can be parsed. use crate::{ParseError, Token, TokenContents}; use nu_protocol::{ast::Redirection, Span}; #[derive(Debug)] pub struct LiteCommand { pub comments: Vec, pub parts: Vec, } impl Default for LiteCommand { fn default() -> Self { Self::new() } } impl LiteCommand { pub fn new() -> Self { Self { comments: vec![], parts: vec![], } } pub fn push(&mut self, span: Span) { self.parts.push(span); } pub fn is_empty(&self) -> bool { self.parts.is_empty() } } // Note: the Span is the span of the connector not the whole element #[derive(Debug)] pub enum LiteElement { Command(Option, LiteCommand), Redirection(Span, Redirection, LiteCommand), // SeparateRedirection variant can only be generated by two different Redirection variant SeparateRedirection { out: (Span, LiteCommand), err: (Span, LiteCommand), }, } #[derive(Debug)] pub struct LitePipeline { pub commands: Vec, } impl Default for LitePipeline { fn default() -> Self { Self::new() } } impl LitePipeline { pub fn new() -> Self { Self { commands: vec![] } } pub fn push(&mut self, element: LiteElement) { self.commands.push(element); } pub fn insert(&mut self, index: usize, element: LiteElement) { self.commands.insert(index, element); } pub fn is_empty(&self) -> bool { self.commands.is_empty() } } #[derive(Debug)] pub struct LiteBlock { pub block: Vec, } impl Default for LiteBlock { fn default() -> Self { Self::new() } } impl LiteBlock { pub fn new() -> Self { Self { block: vec![] } } pub fn push(&mut self, mut pipeline: LitePipeline) { // once we push `pipeline` to our block // the block takes ownership of `pipeline`, which means that // our `pipeline` is complete on collecting commands. self.merge_redirections(&mut pipeline); self.block.push(pipeline); } pub fn is_empty(&self) -> bool { self.block.is_empty() } fn merge_redirections(&self, pipeline: &mut LitePipeline) { // In case our command may contains both stdout and stderr redirection. // We pick them out and Combine them into one LiteElement::SeparateRedirection variant. let mut stdout_index = None; let mut stderr_index = None; for (index, cmd) in pipeline.commands.iter().enumerate() { if let LiteElement::Redirection(_span, redirection, _target_cmd) = cmd { match *redirection { Redirection::Stderr => stderr_index = Some(index), Redirection::Stdout => stdout_index = Some(index), Redirection::StdoutAndStderr => {} } } } if let (Some(out_indx), Some(err_indx)) = (stdout_index, stderr_index) { let (out_redirect, err_redirect, new_indx) = { // to avoid panic, we need to remove commands which have larger index first. if out_indx > err_indx { let out_redirect = pipeline.commands.remove(out_indx); let err_redirect = pipeline.commands.remove(err_indx); (out_redirect, err_redirect, err_indx) } else { let err_redirect = pipeline.commands.remove(err_indx); let out_redirect = pipeline.commands.remove(out_indx); (out_redirect, err_redirect, out_indx) } }; // `out_redirect` and `err_redirect` should always be `LiteElement::Redirection` if let ( LiteElement::Redirection(out_span, _, out_command), LiteElement::Redirection(err_span, _, err_command), ) = (out_redirect, err_redirect) { // using insert with specific index to keep original // pipeline commands order. pipeline.insert( new_indx, LiteElement::SeparateRedirection { out: (out_span, out_command), err: (err_span, err_command), }, ) } } } } pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { let mut block = LiteBlock::new(); let mut curr_pipeline = LitePipeline::new(); let mut curr_command = LiteCommand::new(); let mut last_token = TokenContents::Eol; let mut last_connector = TokenContents::Pipe; let mut last_connector_span: Option = None; if tokens.is_empty() { return (LiteBlock::new(), None); } let mut curr_comment: Option> = None; let mut error = None; for token in tokens.iter() { match &token.contents { TokenContents::PipePipe => { error = error.or(Some(ParseError::ShellOrOr(token.span))); curr_command.push(token.span); last_token = TokenContents::Item; } TokenContents::Item => { // If we have a comment, go ahead and attach it if let Some(curr_comment) = curr_comment.take() { curr_command.comments = curr_comment; } curr_command.push(token.span); last_token = TokenContents::Item; } TokenContents::OutGreaterThan | TokenContents::ErrGreaterThan | TokenContents::OutErrGreaterThan => { if !curr_command.is_empty() { match last_connector { TokenContents::OutGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span .expect("internal error: redirection missing span information"), Redirection::Stdout, curr_command, )); } TokenContents::ErrGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span .expect("internal error: redirection missing span information"), Redirection::Stderr, curr_command, )); } TokenContents::OutErrGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span .expect("internal error: redirection missing span information"), Redirection::StdoutAndStderr, curr_command, )); } _ => { curr_pipeline .push(LiteElement::Command(last_connector_span, curr_command)); } } curr_command = LiteCommand::new(); } last_token = token.contents; last_connector = token.contents; last_connector_span = Some(token.span); } TokenContents::Pipe => { if !curr_command.is_empty() { match last_connector { TokenContents::OutGreaterThan => { curr_pipeline.push(LiteElement::Redirection( token.span, Redirection::Stdout, curr_command, )); } TokenContents::ErrGreaterThan => { curr_pipeline.push(LiteElement::Redirection( token.span, Redirection::Stderr, curr_command, )); } TokenContents::OutErrGreaterThan => { curr_pipeline.push(LiteElement::Redirection( token.span, Redirection::StdoutAndStderr, curr_command, )); } _ => { curr_pipeline .push(LiteElement::Command(last_connector_span, curr_command)); } } curr_command = LiteCommand::new(); } last_token = TokenContents::Pipe; last_connector = TokenContents::Pipe; last_connector_span = Some(token.span); } TokenContents::Eol => { if last_token != TokenContents::Pipe && last_token != TokenContents::OutGreaterThan { if !curr_command.is_empty() { match last_connector { TokenContents::OutGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span.expect( "internal error: redirection missing span information", ), Redirection::Stdout, curr_command, )); } TokenContents::ErrGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span.expect( "internal error: redirection missing span information", ), Redirection::Stderr, curr_command, )); } TokenContents::OutErrGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span.expect( "internal error: redirection missing span information", ), Redirection::StdoutAndStderr, curr_command, )); } _ => { curr_pipeline .push(LiteElement::Command(last_connector_span, curr_command)); } } curr_command = LiteCommand::new(); } if !curr_pipeline.is_empty() { block.push(curr_pipeline); curr_pipeline = LitePipeline::new(); } } if last_token == TokenContents::Eol { // Clear out the comment as we're entering a new comment curr_comment = None; } last_token = TokenContents::Eol; } TokenContents::Semicolon => { if !curr_command.is_empty() { match last_connector { TokenContents::OutGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span .expect("internal error: redirection missing span information"), Redirection::Stdout, curr_command, )); } TokenContents::ErrGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span .expect("internal error: redirection missing span information"), Redirection::Stderr, curr_command, )); } TokenContents::OutErrGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span .expect("internal error: redirection missing span information"), Redirection::StdoutAndStderr, curr_command, )); } _ => { curr_pipeline .push(LiteElement::Command(last_connector_span, curr_command)); } } curr_command = LiteCommand::new(); } if !curr_pipeline.is_empty() { block.push(curr_pipeline); curr_pipeline = LitePipeline::new(); last_connector = TokenContents::Pipe; last_connector_span = None; } last_token = TokenContents::Semicolon; } TokenContents::Comment => { // Comment is beside something if last_token != TokenContents::Eol { curr_command.comments.push(token.span); curr_comment = None; } else { // Comment precedes something if let Some(curr_comment) = &mut curr_comment { curr_comment.push(token.span); } else { curr_comment = Some(vec![token.span]); } } last_token = TokenContents::Comment; } } } if !curr_command.is_empty() { match last_connector { TokenContents::OutGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span .expect("internal error: redirection missing span information"), Redirection::Stdout, curr_command, )); } TokenContents::ErrGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span .expect("internal error: redirection missing span information"), Redirection::Stderr, curr_command, )); } TokenContents::OutErrGreaterThan => { curr_pipeline.push(LiteElement::Redirection( last_connector_span .expect("internal error: redirection missing span information"), Redirection::StdoutAndStderr, curr_command, )); } _ => { curr_pipeline.push(LiteElement::Command(last_connector_span, curr_command)); } } } if !curr_pipeline.is_empty() { block.push(curr_pipeline); } if last_token == TokenContents::Pipe { ( block, Some(ParseError::UnexpectedEof( "pipeline missing end".into(), tokens[tokens.len() - 1].span, )), ) } else { (block, error) } }