use super::core::Parser;
use super::core::Result;
use super::error::Error;
use super::error::SyntaxError;
use super::lex::Operator::{GreaterOpenParen, LessLess, LessLessDash, LessOpenParen};
use super::lex::TokenId::{EndOfInput, IoLocation, IoNumber, Operator, Token};
use crate::source::Location;
use crate::syntax::Fd;
use crate::syntax::HereDoc;
use crate::syntax::Redir;
use crate::syntax::RedirBody;
use crate::syntax::RedirOp;
use crate::syntax::Word;
use std::cell::OnceCell;
use std::rc::Rc;
impl Parser<'_, '_> {
async fn redirection_operand(&mut self) -> Result<std::result::Result<Word, Location>> {
let operand = self.take_token_auto(&[]).await?;
match operand.id {
Token(_) => (),
Operator(_) | EndOfInput => return Ok(Err(operand.word.location)),
IoNumber | IoLocation => (), }
Ok(Ok(operand.word))
}
async fn normal_redirection_body(&mut self, operator: RedirOp) -> Result<RedirBody> {
self.take_token_raw().await?;
let operand = self
.redirection_operand()
.await?
.map_err(|location| Error {
cause: SyntaxError::MissingRedirOperand.into(),
location,
})?;
Ok(RedirBody::Normal { operator, operand })
}
async fn here_doc_redirection_body(&mut self, remove_tabs: bool) -> Result<RedirBody> {
self.take_token_raw().await?;
let delimiter = self
.redirection_operand()
.await?
.map_err(|location| Error {
cause: SyntaxError::MissingHereDocDelimiter.into(),
location,
})?;
let here_doc = Rc::new(HereDoc {
delimiter,
remove_tabs,
content: OnceCell::new(),
});
self.memorize_unread_here_doc(Rc::clone(&here_doc));
Ok(RedirBody::HereDoc(here_doc))
}
async fn redirection_body(&mut self) -> Result<Option<RedirBody>> {
let operator = match self.peek_token().await?.id {
Operator(operator) => operator,
_ => return Ok(None),
};
if let Ok(operator) = RedirOp::try_from(operator) {
return Ok(Some(self.normal_redirection_body(operator).await?));
}
match operator {
LessLess => Ok(Some(self.here_doc_redirection_body(false).await?)),
LessLessDash => Ok(Some(self.here_doc_redirection_body(true).await?)),
LessOpenParen | GreaterOpenParen => {
let cause = SyntaxError::UnsupportedProcessRedirection.into();
let location = self.peek_token().await?.word.location.clone();
Err(Error { cause, location })
}
_ => Ok(None),
}
}
pub async fn redirection(&mut self) -> Result<Option<Redir>> {
let fd = match self.peek_token().await?.id {
IoNumber => {
let token = self.take_token_raw().await?;
if let Ok(fd) = token.word.to_string().parse() {
Some(Fd(fd))
} else {
return Err(Error {
cause: SyntaxError::FdOutOfRange.into(),
location: token.word.location,
});
}
}
IoLocation => {
let token = self.take_token_raw().await?;
return Err(Error {
cause: SyntaxError::InvalidIoLocation.into(),
location: token.word.location,
});
}
_ => None,
};
Ok(self
.redirection_body()
.await?
.map(|body| Redir { fd, body }))
}
pub async fn redirections(&mut self) -> Result<Vec<Redir>> {
let mut redirs = vec![];
while let Some(redir) = self.redirection().await? {
redirs.push(redir);
}
Ok(redirs)
}
}
#[allow(clippy::bool_assert_comparison)]
#[cfg(test)]
mod tests {
use super::super::error::ErrorCause;
use super::super::lex::Lexer;
use super::super::lex::Operator::Newline;
use super::*;
use crate::source::Source;
use assert_matches::assert_matches;
use futures_util::FutureExt;
#[test]
fn parser_redirection_less() {
let mut lexer = Lexer::with_code("</dev/null\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, None);
assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
assert_eq!(operator, RedirOp::FileIn);
assert_eq!(operand.to_string(), "/dev/null")
});
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, Operator(Newline));
}
#[test]
fn parser_redirection_less_greater() {
let mut lexer = Lexer::with_code("<> /dev/null\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, None);
assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
assert_eq!(operator, RedirOp::FileInOut);
assert_eq!(operand.to_string(), "/dev/null")
});
}
#[test]
fn parser_redirection_greater() {
let mut lexer = Lexer::with_code(">/dev/null\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, None);
assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
assert_eq!(operator, RedirOp::FileOut);
assert_eq!(operand.to_string(), "/dev/null")
});
}
#[test]
fn parser_redirection_greater_greater() {
let mut lexer = Lexer::with_code(" >> /dev/null\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, None);
assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
assert_eq!(operator, RedirOp::FileAppend);
assert_eq!(operand.to_string(), "/dev/null")
});
}
#[test]
fn parser_redirection_greater_bar() {
let mut lexer = Lexer::with_code(">| /dev/null\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, None);
assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
assert_eq!(operator, RedirOp::FileClobber);
assert_eq!(operand.to_string(), "/dev/null")
});
}
#[test]
fn parser_redirection_less_and() {
let mut lexer = Lexer::with_code("<& -\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, None);
assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
assert_eq!(operator, RedirOp::FdIn);
assert_eq!(operand.to_string(), "-")
});
}
#[test]
fn parser_redirection_greater_and() {
let mut lexer = Lexer::with_code(">& 3\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, None);
assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
assert_eq!(operator, RedirOp::FdOut);
assert_eq!(operand.to_string(), "3")
});
}
#[test]
fn parser_redirection_greater_greater_bar() {
let mut lexer = Lexer::with_code(">>| 3\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, None);
assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
assert_eq!(operator, RedirOp::Pipe);
assert_eq!(operand.to_string(), "3")
});
}
#[test]
fn parser_redirection_less_paren() {
let mut lexer = Lexer::with_code("<(foo)\n");
let mut parser = Parser::new(&mut lexer);
let e = parser.redirection().now_or_never().unwrap().unwrap_err();
assert_eq!(
e.cause,
ErrorCause::Syntax(SyntaxError::UnsupportedProcessRedirection)
);
assert_eq!(*e.location.code.value.borrow(), "<(foo)\n");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(*e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 0..2);
}
#[test]
fn parser_redirection_greater_paren() {
let mut lexer = Lexer::with_code(">(foo)\n");
let mut parser = Parser::new(&mut lexer);
let e = parser.redirection().now_or_never().unwrap().unwrap_err();
assert_eq!(
e.cause,
ErrorCause::Syntax(SyntaxError::UnsupportedProcessRedirection)
);
assert_eq!(*e.location.code.value.borrow(), ">(foo)\n");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(*e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 0..2);
}
#[test]
fn parser_redirection_less_less_less() {
let mut lexer = Lexer::with_code("<<< foo\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, None);
assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
assert_eq!(operator, RedirOp::String);
assert_eq!(operand.to_string(), "foo")
});
}
#[test]
fn parser_redirection_less_less() {
let mut lexer = Lexer::with_code("<<end \nend\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, None);
let here_doc = assert_matches!(redir.body, RedirBody::HereDoc(here_doc) => here_doc);
parser
.newline_and_here_doc_contents()
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(here_doc.delimiter.to_string(), "end");
assert_eq!(here_doc.remove_tabs, false);
assert_eq!(here_doc.content.get().unwrap().to_string(), "");
}
#[test]
fn parser_redirection_less_less_dash() {
let mut lexer = Lexer::with_code("<<-end \nend\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, None);
let here_doc = assert_matches!(redir.body, RedirBody::HereDoc(here_doc) => here_doc);
parser
.newline_and_here_doc_contents()
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(here_doc.delimiter.to_string(), "end");
assert_eq!(here_doc.remove_tabs, true);
assert_eq!(here_doc.content.get().unwrap().to_string(), "");
}
#[test]
fn parser_redirection_with_io_number() {
let mut lexer = Lexer::with_code("12< /dev/null\n");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
let redir = result.unwrap().unwrap();
assert_eq!(redir.fd, Some(Fd(12)));
assert_matches!(redir.body, RedirBody::Normal { operator, operand } => {
assert_eq!(operator, RedirOp::FileIn);
assert_eq!(operand.to_string(), "/dev/null")
});
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, Operator(Newline));
}
#[test]
fn parser_redirection_fd_out_of_range() {
let mut lexer = Lexer::with_code("9999999999999999999999999999999999999999< x");
let mut parser = Parser::new(&mut lexer);
let e = parser.redirection().now_or_never().unwrap().unwrap_err();
assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::FdOutOfRange));
assert_eq!(
*e.location.code.value.borrow(),
"9999999999999999999999999999999999999999< x"
);
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(*e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 0..40);
}
#[test]
fn parser_redirection_io_location() {
let mut lexer = Lexer::with_code("{n}< /dev/null\n");
let mut parser = Parser::new(&mut lexer);
let e = parser.redirection().now_or_never().unwrap().unwrap_err();
assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidIoLocation));
assert_eq!(*e.location.code.value.borrow(), "{n}< /dev/null\n");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(*e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 0..3);
}
#[test]
fn parser_redirection_not_operator() {
let mut lexer = Lexer::with_code("x");
let mut parser = Parser::new(&mut lexer);
let result = parser.redirection().now_or_never().unwrap();
assert_eq!(result, Ok(None));
}
#[test]
fn parser_redirection_non_word_operand() {
let mut lexer = Lexer::with_code(" < >");
let mut parser = Parser::new(&mut lexer);
let e = parser.redirection().now_or_never().unwrap().unwrap_err();
assert_eq!(
e.cause,
ErrorCause::Syntax(SyntaxError::MissingRedirOperand)
);
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, 3..4);
}
#[test]
fn parser_redirection_eof_operand() {
let mut lexer = Lexer::with_code(" < ");
let mut parser = Parser::new(&mut lexer);
let e = parser.redirection().now_or_never().unwrap().unwrap_err();
assert_eq!(
e.cause,
ErrorCause::Syntax(SyntaxError::MissingRedirOperand)
);
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, 4..4);
}
#[test]
fn parser_redirection_not_heredoc_delimiter() {
let mut lexer = Lexer::with_code("<< <<");
let mut parser = Parser::new(&mut lexer);
let e = parser.redirection().now_or_never().unwrap().unwrap_err();
assert_eq!(
e.cause,
ErrorCause::Syntax(SyntaxError::MissingHereDocDelimiter)
);
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, 3..5);
}
#[test]
fn parser_redirection_eof_heredoc_delimiter() {
let mut lexer = Lexer::with_code("<<");
let mut parser = Parser::new(&mut lexer);
let e = parser.redirection().now_or_never().unwrap().unwrap_err();
assert_eq!(
e.cause,
ErrorCause::Syntax(SyntaxError::MissingHereDocDelimiter)
);
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, 2..2);
}
}