use super::core::Parser;
use super::core::Rec;
use super::core::Result;
use super::error::Error;
use super::error::SyntaxError;
use super::lex::Keyword::{Do, For, In};
use super::lex::Operator::{Newline, Semicolon};
use super::lex::TokenId::{EndOfInput, IoNumber, Operator, Token};
use crate::source::Location;
use crate::syntax::CompoundCommand;
use crate::syntax::List;
use crate::syntax::Word;
impl Parser<'_, '_> {
async fn for_loop_name(&mut self) -> Result<Word> {
let name = self.take_token_auto(&[]).await?;
match name.id {
EndOfInput | Operator(Newline) | Operator(Semicolon) => {
let cause = SyntaxError::MissingForName.into();
let location = name.word.location;
return Err(Error { cause, location });
}
Operator(_) => {
let cause = SyntaxError::InvalidForName.into();
let location = name.word.location;
return Err(Error { cause, location });
}
Token(_) | IoNumber => (),
}
Ok(name.word)
}
async fn for_loop_values(
&mut self,
opening_location: Location,
) -> Result<(Option<Vec<Word>>, Location)> {
let mut first_line = true;
loop {
match self.peek_token().await?.id {
Operator(Semicolon) if first_line => {
self.take_token_raw().await?;
return Ok((None, opening_location));
}
Token(Some(Do)) => {
return Ok((None, opening_location));
}
Operator(Newline) => {
assert!(self.newline_and_here_doc_contents().await?);
first_line = false;
}
Token(Some(In)) => {
self.take_token_raw().await?;
break;
}
_ => match self.take_token_manual(false).await? {
Rec::AliasSubstituted => (),
Rec::Parsed(token) => {
let cause = SyntaxError::MissingForBody { opening_location }.into();
let location = token.word.location;
return Err(Error { cause, location });
}
},
}
}
let mut values = Vec::new();
loop {
let next = self.take_token_auto(&[]).await?;
match next.id {
Token(_) | IoNumber => {
values.push(next.word);
}
Operator(Semicolon) | Operator(Newline) => {
return Ok((Some(values), opening_location));
}
_ => {
let cause = SyntaxError::InvalidForValue.into();
let location = next.word.location;
return Err(Error { cause, location });
}
}
}
}
async fn for_loop_body(&mut self, opening_location: Location) -> Result<List> {
loop {
while self.newline_and_here_doc_contents().await? {}
if let Some(body) = self.do_clause().await? {
return Ok(body);
}
match self.take_token_manual(false).await? {
Rec::AliasSubstituted => (),
Rec::Parsed(token) => {
let cause = SyntaxError::MissingForBody { opening_location }.into();
let location = token.word.location;
return Err(Error { cause, location });
}
}
}
}
pub async fn for_loop(&mut self) -> Result<CompoundCommand> {
let open = self.take_token_raw().await?;
assert_eq!(open.id, Token(Some(For)));
let opening_location = open.word.location;
let name = self.for_loop_name().await?;
let (values, opening_location) = self.for_loop_values(opening_location).await?;
let body = self.for_loop_body(opening_location).await?;
Ok(CompoundCommand::For { name, values, body })
}
}
#[cfg(test)]
mod tests {
use super::super::error::ErrorCause;
use super::super::lex::Lexer;
use super::*;
use crate::alias::{AliasSet, HashEntry};
use crate::source::Source;
use assert_matches::assert_matches;
use futures_util::FutureExt;
#[test]
fn parser_for_loop_short() {
let mut lexer = Lexer::from_memory("for A do :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let compound_command = result.unwrap().unwrap();
assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
assert_eq!(name.to_string(), "A");
assert_eq!(values, None);
assert_eq!(body.to_string(), ":");
});
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, EndOfInput);
}
#[test]
fn parser_for_loop_with_semicolon_before_do() {
let mut lexer = Lexer::from_memory("for B ; do :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let compound_command = result.unwrap().unwrap();
assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
assert_eq!(name.to_string(), "B");
assert_eq!(values, None);
assert_eq!(body.to_string(), ":");
});
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, EndOfInput);
}
#[test]
fn parser_for_loop_with_semicolon_and_newlines_before_do() {
let mut lexer = Lexer::from_memory("for B ; \n\t\n do :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let compound_command = result.unwrap().unwrap();
assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
assert_eq!(name.to_string(), "B");
assert_eq!(values, None);
assert_eq!(body.to_string(), ":");
});
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, EndOfInput);
}
#[test]
fn parser_for_loop_with_newlines_before_do() {
let mut lexer = Lexer::from_memory("for B \n \\\n \n do :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let compound_command = result.unwrap().unwrap();
assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
assert_eq!(name.to_string(), "B");
assert_eq!(values, None);
assert_eq!(body.to_string(), ":");
});
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, EndOfInput);
}
#[test]
fn parser_for_loop_with_zero_values_delimited_by_semicolon() {
let mut lexer = Lexer::from_memory("for foo in; do :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let compound_command = result.unwrap().unwrap();
assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
assert_eq!(name.to_string(), "foo");
assert_eq!(values, Some(vec![]));
assert_eq!(body.to_string(), ":");
});
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, EndOfInput);
}
#[test]
fn parser_for_loop_with_one_value_delimited_by_semicolon_and_newlines() {
let mut lexer = Lexer::from_memory("for foo in bar; \n \n do :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let compound_command = result.unwrap().unwrap();
assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
assert_eq!(name.to_string(), "foo");
let values = values
.unwrap()
.iter()
.map(ToString::to_string)
.collect::<Vec<String>>();
assert_eq!(values, vec!["bar"]);
assert_eq!(body.to_string(), ":");
});
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, EndOfInput);
}
#[test]
fn parser_for_loop_with_many_values_delimited_by_one_newline() {
let mut lexer = Lexer::from_memory("for in in in a b c\ndo :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let compound_command = result.unwrap().unwrap();
assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
assert_eq!(name.to_string(), "in");
let values = values
.unwrap()
.iter()
.map(ToString::to_string)
.collect::<Vec<String>>();
assert_eq!(values, vec!["in", "a", "b", "c"]);
assert_eq!(body.to_string(), ":");
});
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, EndOfInput);
}
#[test]
fn parser_for_loop_with_zero_values_delimited_by_many_newlines() {
let mut lexer = Lexer::from_memory("for foo in \n \n \n do :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let compound_command = result.unwrap().unwrap();
assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
assert_eq!(name.to_string(), "foo");
assert_eq!(values, Some(vec![]));
assert_eq!(body.to_string(), ":");
});
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, EndOfInput);
}
#[test]
fn parser_for_loop_newlines_before_in() {
let mut lexer = Lexer::from_memory("for foo\n \n\nin\ndo :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let compound_command = result.unwrap().unwrap();
assert_matches!(compound_command, CompoundCommand::For { name, values, body } => {
assert_eq!(name.to_string(), "foo");
assert_eq!(values, Some(vec![]));
assert_eq!(body.to_string(), ":");
});
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, EndOfInput);
}
#[test]
fn parser_for_loop_aliasing_on_semicolon() {
let mut lexer = Lexer::from_memory(" FOR_A if :; done", Source::Unknown);
let mut aliases = AliasSet::new();
let origin = Location::dummy("");
aliases.insert(HashEntry::new(
"if".to_string(),
" ;\n\ndo".to_string(),
false,
origin.clone(),
));
aliases.insert(HashEntry::new(
"FOR_A".to_string(),
"for A ".to_string(),
false,
origin,
));
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.take_token_manual(true).now_or_never().unwrap();
assert_matches!(result, Ok(Rec::AliasSubstituted));
let result = parser.compound_command().now_or_never().unwrap();
let compound_command = result.unwrap().unwrap();
assert_eq!(compound_command.to_string(), "for A do :; done");
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, EndOfInput);
}
#[test]
fn parser_for_loop_aliasing_on_do() {
let mut lexer = Lexer::from_memory(" FOR_A if :; done", Source::Unknown);
let mut aliases = AliasSet::new();
let origin = Location::dummy("");
aliases.insert(HashEntry::new(
"if".to_string(),
"\ndo".to_string(),
false,
origin.clone(),
));
aliases.insert(HashEntry::new(
"FOR_A".to_string(),
"for A ".to_string(),
false,
origin,
));
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.take_token_manual(true).now_or_never().unwrap();
assert_matches!(result, Ok(Rec::AliasSubstituted));
let result = parser.compound_command().now_or_never().unwrap();
let compound_command = result.unwrap().unwrap();
assert_eq!(compound_command.to_string(), "for A do :; done");
let next = parser.peek_token().now_or_never().unwrap().unwrap();
assert_eq!(next.id, EndOfInput);
}
#[test]
fn parser_for_loop_missing_name_eof() {
let mut lexer = Lexer::from_memory(" for ", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let e = result.unwrap_err();
assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingForName));
assert_eq!(*e.location.code.value.borrow(), " for ");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 5..5);
}
#[test]
fn parser_for_loop_missing_name_newline() {
let mut lexer = Lexer::from_memory(" for\ndo :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let e = result.unwrap_err();
assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingForName));
assert_eq!(*e.location.code.value.borrow(), " for\n");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 4..5);
}
#[test]
fn parser_for_loop_missing_name_semicolon() {
let mut lexer = Lexer::from_memory("for; do :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let e = result.unwrap_err();
assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::MissingForName));
assert_eq!(*e.location.code.value.borrow(), "for; do :; done");
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_for_loop_invalid_name() {
let mut lexer = Lexer::from_memory("FOR if do :; done", Source::Unknown);
let mut aliases = AliasSet::new();
let origin = Location::dummy("");
aliases.insert(HashEntry::new(
"FOR".to_string(),
"for ".to_string(),
false,
origin.clone(),
));
aliases.insert(HashEntry::new(
"if".to_string(),
"&".to_string(),
false,
origin,
));
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.take_token_manual(true).now_or_never().unwrap();
assert_matches!(result, Ok(Rec::AliasSubstituted));
let result = parser.compound_command().now_or_never().unwrap();
let e = result.unwrap_err();
assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidForName));
assert_eq!(*e.location.code.value.borrow(), "&");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(e.location.range, 0..1);
assert_matches!(&e.location.code.source, Source::Alias { original, alias } => {
assert_eq!(*original.code.value.borrow(), "FOR if do :; done");
assert_eq!(original.code.start_line_number.get(), 1);
assert_eq!(original.code.source, Source::Unknown);
assert_eq!(original.range, 4..6);
assert_eq!(alias.name, "if");
});
}
#[test]
fn parser_for_loop_semicolon_after_newline() {
let mut lexer = Lexer::from_memory("for X\n; do :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let e = result.unwrap_err();
assert_matches!(&e.cause,
ErrorCause::Syntax(SyntaxError::MissingForBody { opening_location }) => {
assert_eq!(*opening_location.code.value.borrow(), "for X\n; do :; done");
assert_eq!(opening_location.code.start_line_number.get(), 1);
assert_eq!(opening_location.code.source, Source::Unknown);
assert_eq!(opening_location.range, 0..3);
});
assert_eq!(*e.location.code.value.borrow(), "for X\n; do :; done");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 6..7);
}
#[test]
fn parser_for_loop_invalid_values_delimiter() {
let mut lexer = Lexer::from_memory("for_A_in_a_b if c; do :; done", Source::Unknown);
let mut aliases = AliasSet::new();
let origin = Location::dummy("");
aliases.insert(HashEntry::new(
"for_A_in_a_b".to_string(),
"for A in a b ".to_string(),
false,
origin.clone(),
));
aliases.insert(HashEntry::new(
"if".to_string(),
"&".to_string(),
false,
origin,
));
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.take_token_manual(true).now_or_never().unwrap();
assert_matches!(result, Ok(Rec::AliasSubstituted));
let result = parser.compound_command().now_or_never().unwrap();
let e = result.unwrap_err();
assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::InvalidForValue));
assert_eq!(*e.location.code.value.borrow(), "&");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(e.location.range, 0..1);
assert_matches!(&e.location.code.source, Source::Alias { original, alias } => {
assert_eq!(*original.code.value.borrow(), "for_A_in_a_b if c; do :; done");
assert_eq!(original.code.start_line_number.get(), 1);
assert_eq!(original.code.source, Source::Unknown);
assert_eq!(original.range, 13..15);
assert_eq!(alias.name, "if");
});
}
#[test]
fn parser_for_loop_invalid_token_after_semicolon() {
let mut lexer = Lexer::from_memory(" for X; ! do :; done", Source::Unknown);
let aliases = Default::default();
let mut parser = Parser::new(&mut lexer, &aliases);
let result = parser.compound_command().now_or_never().unwrap();
let e = result.unwrap_err();
assert_matches!(&e.cause,
ErrorCause::Syntax(SyntaxError::MissingForBody { opening_location }) => {
assert_eq!(*opening_location.code.value.borrow(), " for X; ! do :; done");
assert_eq!(opening_location.code.start_line_number.get(), 1);
assert_eq!(opening_location.code.source, Source::Unknown);
assert_eq!(opening_location.range, 1..4);
});
assert_eq!(*e.location.code.value.borrow(), " for X; ! do :; done");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 8..9);
}
}