use super::core::Parser;
use super::core::Rec;
use super::core::Result;
use super::error::Error;
use super::error::SyntaxError;
use super::lex::Operator::{And, Newline, Semicolon};
use super::lex::TokenId::Operator;
use crate::syntax::Item;
use crate::syntax::List;
use std::rc::Rc;
use super::lex::TokenId::EndOfInput;
use std::future::Future;
use std::pin::Pin;
impl Parser<'_, '_> {
pub async fn list(&mut self) -> Result<Rec<List>> {
let mut items = vec![];
let mut result = match self.and_or_list().await? {
Rec::AliasSubstituted => return Ok(Rec::AliasSubstituted),
Rec::Parsed(result) => result,
};
while let Some(and_or) = result {
let token = self.peek_token().await?;
let (async_flag, next) = match token.id {
Operator(Semicolon) => (None, true),
Operator(And) => (Some(token.word.location.clone()), true),
_ => (None, false),
};
let and_or = Rc::new(and_or);
items.push(Item { and_or, async_flag });
if !next {
break;
}
self.take_token_raw().await?;
result = loop {
if let Rec::Parsed(result) = self.and_or_list().await? {
break result;
}
};
}
Ok(Rec::Parsed(List(items)))
}
pub async fn newline_and_here_doc_contents(&mut self) -> Result<bool> {
if self.peek_token().await?.id != Operator(Newline) {
return Ok(false);
}
self.take_token_raw().await?;
self.here_doc_contents().await?;
Ok(true)
}
pub async fn command_line(&mut self) -> Result<Option<List>> {
let list = loop {
if let Rec::Parsed(list) = self.list().await? {
break list;
}
};
if !self.newline_and_here_doc_contents().await? {
let next = self.peek_token().await?;
if next.id != EndOfInput {
return Err(Error {
cause: SyntaxError::InvalidCommandToken.into(),
location: next.word.location.clone(),
});
}
if list.0.is_empty() {
return Ok(None);
}
}
self.ensure_no_unread_here_doc()?;
Ok(Some(list))
}
pub async fn maybe_compound_list(&mut self) -> Result<List> {
let mut items = vec![];
loop {
let list = loop {
if let Rec::Parsed(list) = self.list().await? {
break list;
}
};
items.extend(list.0);
if !self.newline_and_here_doc_contents().await? {
break;
}
}
let next = self.peek_token().await?;
if next.id.is_clause_delimiter() {
Ok(List(items))
} else {
let cause = SyntaxError::InvalidCommandToken.into();
let location = next.word.location.clone();
Err(Error { cause, location })
}
}
pub fn maybe_compound_list_boxed(
&mut self,
) -> Pin<Box<dyn Future<Output = Result<List>> + '_>> {
Box::pin(self.maybe_compound_list())
}
}
#[allow(clippy::bool_assert_comparison)]
#[cfg(test)]
mod tests {
use super::super::error::ErrorCause;
use super::super::lex::Lexer;
use super::*;
use crate::source::Source;
use crate::syntax::AndOrList;
use crate::syntax::Command;
use crate::syntax::Pipeline;
use crate::syntax::RedirBody;
use assert_matches::assert_matches;
use futures_executor::block_on;
#[test]
fn parser_list_eof() {
let mut lexer = Lexer::from_memory("", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let list = block_on(parser.list()).unwrap().unwrap();
assert_eq!(list.0, vec![]);
}
#[test]
fn parser_list_one_item_without_last_semicolon() {
let mut lexer = Lexer::from_memory("foo", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let list = block_on(parser.list()).unwrap().unwrap();
assert_eq!(list.0.len(), 1);
assert_eq!(list.0[0].async_flag, None);
assert_eq!(list.0[0].and_or.to_string(), "foo");
}
#[test]
fn parser_list_one_item_with_last_semicolon() {
let mut lexer = Lexer::from_memory("foo;", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let list = block_on(parser.list()).unwrap().unwrap();
assert_eq!(list.0.len(), 1);
assert_eq!(list.0[0].async_flag, None);
assert_eq!(list.0[0].and_or.to_string(), "foo");
}
#[test]
fn parser_list_many_items() {
let mut lexer = Lexer::from_memory("foo & bar ; baz&", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let list = block_on(parser.list()).unwrap().unwrap();
assert_eq!(list.0.len(), 3);
let location = list.0[0].async_flag.as_ref().unwrap();
assert_eq!(*location.code.value.borrow(), "foo & bar ; baz&");
assert_eq!(location.code.start_line_number.get(), 1);
assert_eq!(location.code.source, Source::Unknown);
assert_eq!(location.range, 4..5);
assert_eq!(list.0[0].and_or.to_string(), "foo");
assert_eq!(list.0[1].async_flag, None);
assert_eq!(list.0[1].and_or.to_string(), "bar");
let location = list.0[2].async_flag.as_ref().unwrap();
assert_eq!(*location.code.value.borrow(), "foo & bar ; baz&");
assert_eq!(location.code.start_line_number.get(), 1);
assert_eq!(location.code.source, Source::Unknown);
assert_eq!(location.range, 15..16);
assert_eq!(list.0[2].and_or.to_string(), "baz");
}
#[test]
fn parser_command_line_eof() {
let mut lexer = Lexer::from_memory("", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = block_on(parser.command_line()).unwrap();
assert!(result.is_none());
}
#[test]
fn parser_command_line_command_and_newline() {
let mut lexer = Lexer::from_memory("<<END\nfoo\nEND\n", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let List(items) = block_on(parser.command_line()).unwrap().unwrap();
assert_eq!(items.len(), 1);
let item = items.first().unwrap();
assert_eq!(item.async_flag, None);
let AndOrList { first, rest } = &*item.and_or;
assert!(rest.is_empty(), "expected empty rest: {:?}", rest);
let Pipeline { commands, negation } = first;
assert_eq!(*negation, false);
assert_eq!(commands.len(), 1);
let cmd = assert_matches!(*commands[0], Command::Simple(ref c) => c);
assert_eq!(cmd.words, []);
assert_eq!(cmd.redirs.len(), 1);
assert_eq!(cmd.redirs[0].fd, None);
assert_matches!(cmd.redirs[0].body, RedirBody::HereDoc(ref here_doc) => {
assert_eq!(here_doc.delimiter.to_string(), "END");
assert_eq!(here_doc.remove_tabs, false);
assert_eq!(here_doc.content.borrow().to_string(), "foo\n");
});
}
#[test]
fn parser_command_line_command_without_newline() {
let mut lexer = Lexer::from_memory("foo", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let cmd = block_on(parser.command_line()).unwrap().unwrap();
assert_eq!(cmd.to_string(), "foo");
}
#[test]
fn parser_command_line_newline_only() {
let mut lexer = Lexer::from_memory("\n", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let list = block_on(parser.command_line()).unwrap().unwrap();
assert_eq!(list.0, []);
}
#[test]
fn parser_command_line_here_doc_without_newline() {
let mut lexer = Lexer::from_memory("<<END", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let e = block_on(parser.command_line()).unwrap_err();
assert_eq!(
e.cause,
ErrorCause::Syntax(SyntaxError::MissingHereDocContent)
);
assert_eq!(*e.location.code.value.borrow(), "<<END");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 2..5);
}
#[test]
fn parser_command_line_wrong_delimiter() {
let mut lexer = Lexer::from_memory("foo)", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let e = block_on(parser.command_line()).unwrap_err();
assert_eq!(
e.cause,
ErrorCause::Syntax(SyntaxError::InvalidCommandToken)
);
assert_eq!(*e.location.code.value.borrow(), "foo)");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 3..4);
}
#[test]
fn parser_maybe_compound_list_empty() {
let mut lexer = Lexer::from_memory("", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let list = block_on(parser.maybe_compound_list()).unwrap();
assert_eq!(list.0, []);
}
#[test]
fn parser_maybe_compound_list_some_commands() {
let mut lexer = Lexer::from_memory("echo; ls& cat", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let list = block_on(parser.maybe_compound_list()).unwrap();
assert_eq!(list.to_string(), "echo; ls& cat");
}
#[test]
fn parser_maybe_compound_list_some_commands_with_newline() {
let mut lexer = Lexer::from_memory("echo& ls\n\ncat\n\n", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let list = block_on(parser.maybe_compound_list()).unwrap();
assert_eq!(list.to_string(), "echo& ls; cat");
assert_eq!(lexer.index(), 15);
}
#[test]
fn parser_maybe_compound_list_empty_with_delimiter() {
let mut lexer = Lexer::from_memory("}", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let list = block_on(parser.maybe_compound_list()).unwrap();
assert_eq!(list.0, []);
}
#[test]
fn parser_maybe_compound_list_empty_with_invalid_delimiter() {
let mut lexer = Lexer::from_memory(";", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let e = block_on(parser.maybe_compound_list()).unwrap_err();
assert_eq!(
e.cause,
ErrorCause::Syntax(SyntaxError::InvalidCommandToken)
);
assert_eq!(*e.location.code.value.borrow(), ";");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 0..1);
}
#[test]
fn parser_maybe_compound_list_some_commands_with_invalid_delimiter() {
let mut lexer = Lexer::from_memory("echo; ls\n &", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let e = block_on(parser.maybe_compound_list()).unwrap_err();
assert_eq!(
e.cause,
ErrorCause::Syntax(SyntaxError::InvalidCommandToken)
);
assert_eq!(*e.location.code.value.borrow(), "echo; ls\n &");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 10..11);
}
}