use crate::ast::{
AndOrBinary, AndOrList, Assignment, BinOpType, CaseClause, CaseItem, Command, CommandList,
CompoundCommand, ElseClause, ElsePart, ForClause, FunctionDefinition, IfClause, IoRedirect,
IoRedirectOp, LoopClause, LoopType, Pipeline, Program, Range, SimpleCommand, Word,
};
use crate::parser::word::{here_document_line, is_name_char, peek_name, word};
use crate::parser::{HereDocPending, Parser, Symbol, is_word_quoted};
fn empty_word() -> Word {
Word::string("", false, false, None, Range::default())
}
fn parse_trailing_redirects(parser: &mut Parser) -> Vec<IoRedirect> {
let mut io_redirects = Vec::new();
while let Some(redir) = io_redirect(parser) {
io_redirects.push(redir);
}
io_redirects
}
fn io_redirect_op_str(op: IoRedirectOp) -> &'static str {
match op {
IoRedirectOp::Less => "<",
IoRedirectOp::Great => ">",
IoRedirectOp::Clobber => ">|",
IoRedirectOp::DGreat => ">>",
IoRedirectOp::LessAnd => "<&",
IoRedirectOp::GreatAnd => ">&",
IoRedirectOp::LessGreat => "<>",
IoRedirectOp::DLess => "<<",
IoRedirectOp::DLessDash => "<<-",
}
}
fn read_redirect_operand(parser: &mut Parser, op: IoRedirectOp) -> Option<Word> {
match word(parser, None) {
Some(name) => Some(name),
None => {
parser.set_error(format!(
"expected word after redirection operator `{}`",
io_redirect_op_str(op)
));
None
}
}
}
struct HereDocDelimiterInfo {
text: String,
quoted: bool,
}
fn here_doc_delimiter_info(word: &Word) -> HereDocDelimiterInfo {
match word {
Word::String(string) => HereDocDelimiterInfo {
text: string.value().to_string(),
quoted: string.single_quoted()
|| string
.source()
.is_some_and(|source| source != string.value()),
},
Word::Parameter(_) | Word::Command(_) | Word::Arithmetic(_) => HereDocDelimiterInfo {
text: crate::ast::canonical_here_doc_literal_line(word),
quoted: false,
},
Word::List(list) => {
let mut text = String::new();
let mut quoted = list.double_quoted();
for child in list.children() {
let info = here_doc_delimiter_info(child);
text.push_str(&info.text);
quoted |= info.quoted;
}
HereDocDelimiterInfo { text, quoted }
}
}
}
fn io_redirect_op(parser: &mut Parser) -> Option<IoRedirectOp> {
let sym = parser.peek_symbol();
let op = match sym {
Symbol::DLess => IoRedirectOp::DLess,
Symbol::DLessDash => IoRedirectOp::DLessDash,
Symbol::DGreat => IoRedirectOp::DGreat,
Symbol::LessAnd => IoRedirectOp::LessAnd,
Symbol::GreatAnd => IoRedirectOp::GreatAnd,
Symbol::LessGreat => IoRedirectOp::LessGreat,
Symbol::Clobber => IoRedirectOp::Clobber,
Symbol::Token => {
parser.skip_blanks();
match parser.peek_char() {
Some('<') => {
parser.read_char();
return Some(IoRedirectOp::Less);
}
Some('>') => {
parser.read_char();
return Some(IoRedirectOp::Great);
}
_ => return None,
}
}
_ => return None,
};
parser.consume_symbol(sym);
Some(op)
}
fn io_number(parser: &mut Parser) -> Option<u32> {
parser.skip_blanks();
parser.fill_buf();
if parser.buf_pos >= parser.buf.len() {
return None;
}
let remaining = &parser.buf[parser.buf_pos..];
let mut len = 0;
while len < remaining.len() && remaining[len].is_ascii_digit() {
len += 1;
}
if len > 0 && len < remaining.len() && (remaining[len] == b'<' || remaining[len] == b'>') {
let fd = std::str::from_utf8(&remaining[..len]).ok()?.parse().ok()?;
for _ in 0..len {
parser.read_char();
}
return Some(fd);
}
None
}
fn io_redirect(parser: &mut Parser) -> Option<IoRedirect> {
let saved_pos = parser.buf_pos;
let saved_parser_pos = parser.pos;
let number = io_number(parser);
if let Some(op) = io_redirect_op(parser) {
parser.skip_blanks();
if matches!(op, IoRedirectOp::DLess | IoRedirectOp::DLessDash) {
let name = read_redirect_operand(parser, op)?;
let delimiter_info = here_doc_delimiter_info(&name);
let expand = !delimiter_info.quoted;
let delimiter = delimiter_info.text;
let strip_tabs = op == IoRedirectOp::DLessDash;
parser.here_doc_pending.push(HereDocPending {
delimiter,
strip_tabs,
expand,
});
return Some(IoRedirect::with_range(
number,
op,
name,
Vec::new(),
expand,
Range {
begin: saved_parser_pos,
end: parser.pos,
},
));
}
let name = read_redirect_operand(parser, op)?;
return Some(IoRedirect::with_range(
number,
op,
name,
Vec::new(),
false,
Range {
begin: saved_parser_pos,
end: parser.pos,
},
));
}
parser.buf_pos = saved_pos;
parser.pos = saved_parser_pos;
None
}
fn assignment_word(parser: &mut Parser) -> Option<Assignment> {
parser.skip_blanks();
let begin = parser.pos;
let name = peek_name(parser, false)?;
parser.fill_buf();
let name_end = parser.buf_pos + name.len();
if name_end >= parser.buf.len() || parser.buf[name_end] != b'=' {
return None;
}
for _ in 0..name.len() + 1 {
parser.read_char();
}
let value = word(parser, None).unwrap_or_else(empty_word);
Some(Assignment::new(
name,
value,
Range {
begin,
end: parser.pos,
},
))
}
fn parse_command_prefix(parser: &mut Parser) -> (Vec<Assignment>, Vec<IoRedirect>) {
let mut assignments = Vec::new();
let mut io_redirects = Vec::new();
loop {
parser.skip_blanks();
if let Some(redir) = io_redirect(parser) {
io_redirects.push(redir);
continue;
}
if let Some(assign) = assignment_word(parser) {
assignments.push(assign);
continue;
}
break;
}
(assignments, io_redirects)
}
fn parse_command_name(
parser: &mut Parser,
assignments: &[Assignment],
io_redirects: &[IoRedirect],
) -> Option<Word> {
parser.skip_blanks();
if parser.peek_char().is_none() || matches!(parser.peek_symbol(), Symbol::Newline | Symbol::Eof)
{
return None;
}
let saved_buf_pos = parser.buf_pos;
let saved_pos = parser.pos;
let w = word(parser, None);
if let Some(ref w) = w
&& assignments.is_empty()
&& io_redirects.is_empty()
&& !is_word_quoted(w)
&& w.as_str().as_deref().is_some_and(Parser::is_keyword)
{
parser.buf_pos = saved_buf_pos;
parser.pos = saved_pos;
return None;
}
w
}
fn rewrite_command_name_alias(
parser: &mut Parser,
name: &Word,
name_start_buf_pos: usize,
name_start_pos: crate::ast::Position,
) -> bool {
if !parser.alias_expansion_enabled {
return false;
}
if parser.suppress_alias_once {
parser.suppress_alias_once = false;
return false;
}
if parser.disable_aliases {
return false;
}
if is_word_quoted(name) {
return false;
}
let Some(alias_name) = name.as_str() else {
return false;
};
let Some(alias_fn) = parser.alias.as_mut() else {
return false;
};
let Some(expanded) = alias_fn.resolve(alias_name.as_ref()) else {
return false;
};
if expanded.is_empty() {
return false;
}
let name_end_buf_pos = parser.buf_pos;
let mut rewritten = Vec::with_capacity(
name_start_buf_pos + expanded.len() + parser.buf.len().saturating_sub(name_end_buf_pos),
);
let mut rewritten_alias_inserted = Vec::with_capacity(
name_start_buf_pos + expanded.len() + parser.buf.len().saturating_sub(name_end_buf_pos),
);
rewritten.extend_from_slice(&parser.buf[..name_start_buf_pos]);
rewritten_alias_inserted.extend_from_slice(&parser.buf_alias_inserted[..name_start_buf_pos]);
rewritten.extend_from_slice(expanded.as_bytes());
rewritten_alias_inserted.extend(std::iter::repeat_n(true, expanded.len()));
rewritten.extend_from_slice(&parser.buf[name_end_buf_pos..]);
rewritten_alias_inserted.extend_from_slice(&parser.buf_alias_inserted[name_end_buf_pos..]);
parser.buf = rewritten;
parser.buf_alias_inserted = rewritten_alias_inserted;
parser.buf_pos = name_start_buf_pos;
parser.pos = name_start_pos;
parser.suppress_alias_once = true;
true
}
fn simple_command_suffix_stops(parser: &mut Parser) -> bool {
let sym = parser.peek_symbol();
if matches!(
sym,
Symbol::Newline | Symbol::Eof | Symbol::AndIf | Symbol::OrIf | Symbol::DSemi
) {
return true;
}
parser.skip_blanks();
if parser.current_rparen_is_syntax() {
return true;
}
matches!(parser.peek_char(), Some(';') | Some('&') | Some('|') | None)
}
fn simple_command(parser: &mut Parser) -> Option<Command> {
let begin = parser.pos;
let (assignments, mut io_redirects) = parse_command_prefix(parser);
let name_start_buf_pos = parser.buf_pos;
let name_start_pos = parser.pos;
let name = parse_command_name(parser, &assignments, &io_redirects);
if let Some(name_word) = name.as_ref()
&& rewrite_command_name_alias(parser, name_word, name_start_buf_pos, name_start_pos)
{
return command(parser);
}
if name.is_none() && assignments.is_empty() && io_redirects.is_empty() {
return None;
}
let mut arguments = Vec::new();
if name.is_some() {
loop {
parser.skip_blanks();
if let Some(redir) = io_redirect(parser) {
io_redirects.push(redir);
continue;
}
if simple_command_suffix_stops(parser) {
break;
}
if let Some(w) = word(parser, None) {
arguments.push(w);
} else {
break;
}
}
}
Some(Command::Simple(SimpleCommand::with_range(
name,
arguments,
io_redirects,
assignments,
Range {
begin,
end: parser.pos,
},
)))
}
fn brace_group(parser: &mut Parser) -> Option<Command> {
parser.skip_blanks();
let begin = parser.pos;
if !expect_keyword(parser, "{") {
return None;
}
let body = compound_list(parser);
if !expect_keyword(parser, "}") {
parser.set_error("expected '}'".to_string());
}
let io_redirects = parse_trailing_redirects(parser);
Some(Command::BraceGroup(CompoundCommand::with_range(
body,
io_redirects,
Range {
begin,
end: parser.pos,
},
)))
}
fn subshell(parser: &mut Parser) -> Option<Command> {
parser.skip_blanks();
let begin = parser.pos;
if parser.peek_char() != Some('(') {
return None;
}
parser.read_char();
let body = compound_list(parser);
parser.skip_blanks();
if parser.peek_char() != Some(')') {
parser.set_error("expected ')'".to_string());
} else {
parser.read_char();
}
let io_redirects = parse_trailing_redirects(parser);
Some(Command::Subshell(CompoundCommand::with_range(
body,
io_redirects,
Range {
begin,
end: parser.pos,
},
)))
}
fn if_clause(parser: &mut Parser) -> Option<Command> {
let begin = parser.pos;
if !expect_keyword(parser, "if") {
return None;
}
let condition = compound_list(parser);
if !expect_keyword(parser, "then") {
parser.set_error("expected 'then'".to_string());
return None;
}
let body = compound_list(parser);
let else_part = else_part(parser);
if !expect_keyword(parser, "fi") {
parser.set_error("expected 'fi'".to_string());
}
Some(Command::If(IfClause::with_range(
condition,
body,
else_part,
Range {
begin,
end: parser.pos,
},
)))
}
fn else_part(parser: &mut Parser) -> Option<Box<ElsePart>> {
parser.skip_blanks();
parser.linebreak();
if try_keyword(parser, "elif") {
let begin = parser.pos;
let condition = compound_list(parser);
if !expect_keyword(parser, "then") {
parser.set_error("expected 'then' after elif".to_string());
}
let body = compound_list(parser);
let nested_else = else_part(parser);
Some(Box::new(ElsePart::Elif(IfClause::with_range(
condition,
body,
nested_else,
Range {
begin,
end: parser.pos,
},
))))
} else if try_keyword(parser, "else") {
let begin = parser.pos;
let body = compound_list(parser);
Some(Box::new(ElsePart::Else(ElseClause::with_range(
body,
Range {
begin,
end: parser.pos,
},
))))
} else {
None
}
}
fn for_clause(parser: &mut Parser) -> Option<Command> {
let begin = parser.pos;
if !expect_keyword(parser, "for") {
return None;
}
parser.skip_blanks();
let name = read_word_as_name(parser)?;
parser.skip_blanks();
let mut has_in = false;
let mut word_list = Vec::new();
if try_keyword(parser, "in") {
has_in = true;
loop {
parser.skip_blanks();
if let Some(w) = word(parser, None) {
word_list.push(w);
} else {
break;
}
}
sequential_sep(parser);
} else {
sequential_sep(parser);
}
let body = expect_do_group(parser);
Some(Command::For(ForClause::with_range(
name,
has_in,
word_list,
body,
Range {
begin,
end: parser.pos,
},
)))
}
fn loop_clause(parser: &mut Parser) -> Option<Command> {
let begin = parser.pos;
let loop_type = if try_keyword(parser, "while") {
LoopType::While
} else if try_keyword(parser, "until") {
LoopType::Until
} else {
return None;
};
let condition = compound_list(parser);
let body = expect_do_group(parser);
Some(Command::Loop(LoopClause::with_range(
loop_type,
condition,
body,
Range {
begin,
end: parser.pos,
},
)))
}
fn case_clause(parser: &mut Parser) -> Option<Command> {
let begin = parser.pos;
if !expect_keyword(parser, "case") {
return None;
}
parser.skip_blanks();
let case_word = word(parser, None)?;
parser.linebreak();
if !expect_keyword(parser, "in") {
parser.set_error("expected 'in' in case clause".to_string());
}
parser.linebreak();
let mut items = Vec::new();
while !try_keyword(parser, "esac") {
if parser.peek_symbol() == Symbol::Eof {
parser.set_error("expected 'esac'".to_string());
break;
}
if let Some(item) = case_item(parser) {
items.push(item);
} else {
break;
}
parser.linebreak();
}
Some(Command::Case(CaseClause::with_range(
case_word,
items,
Range {
begin,
end: parser.pos,
},
)))
}
fn case_item(parser: &mut Parser) -> Option<CaseItem> {
let begin = parser.pos;
parser.linebreak();
parser.skip_blanks();
let had_paren = parser.peek_char() == Some('(');
if had_paren {
parser.read_char();
parser.skip_blanks();
}
let mut patterns = Vec::new();
loop {
parser.skip_blanks();
if let Some(w) = word(parser, None) {
if !is_word_quoted(&w) && w.as_str().as_deref() == Some("esac") && patterns.is_empty() {
return None;
}
patterns.push(w);
} else {
break;
}
parser.skip_blanks();
if parser.peek_char() == Some('|') {
parser.read_char();
} else {
break;
}
}
if patterns.is_empty() {
return None;
}
parser.skip_blanks();
if parser.peek_char() == Some(')') {
parser.read_char();
} else {
parser.set_error("expected ')' in case item".to_string());
}
parser.linebreak();
let body = if !peek_keyword(parser, "esac") && !peek_dsemi(parser) {
compound_list(parser)
} else {
Vec::new()
};
parser.skip_blanks();
if parser.peek_symbol() == Symbol::DSemi {
parser.consume_symbol(Symbol::DSemi);
}
Some(CaseItem::with_range(
patterns,
body,
Range {
begin,
end: parser.pos,
},
))
}
fn is_function_definition(parser: &mut Parser) -> Option<String> {
parser.fill_buf();
let remaining = &parser.buf[parser.buf_pos..];
if remaining.is_empty() {
return None;
}
let first = remaining[0] as char;
if !crate::parser::word::is_name_start(first) {
return None;
}
let mut end = 1;
while end < remaining.len() && is_name_char(remaining[end] as char) {
end += 1;
}
let name = String::from_utf8_lossy(&remaining[..end]).into_owned();
let mut i = end;
while i < remaining.len() && (remaining[i] == b' ' || remaining[i] == b'\t') {
i += 1;
}
if i >= remaining.len() || remaining[i] != b'(' {
return None;
}
i += 1;
while i < remaining.len() && (remaining[i] == b' ' || remaining[i] == b'\t') {
i += 1;
}
if i >= remaining.len() || remaining[i] != b')' {
return None;
}
Some(name)
}
fn function_definition(parser: &mut Parser) -> Option<Command> {
parser.skip_blanks();
let begin = parser.pos;
let name = is_function_definition(parser)?;
for _ in 0..name.len() {
parser.read_char();
}
parser.skip_blanks();
parser.read_char(); parser.skip_blanks();
parser.read_char();
parser.linebreak();
let Some(mut body) = compound_command(parser) else {
parser.set_error("expected compound command in function definition".to_string());
return None;
};
let mut io_redirects = match &mut body {
Command::BraceGroup(group) | Command::Subshell(group) => {
std::mem::take(&mut group.io_redirects)
}
_ => Vec::new(),
};
io_redirects.extend(parse_trailing_redirects(parser));
Some(Command::FunctionDef(FunctionDefinition::with_range(
name,
body,
io_redirects,
Range {
begin,
end: parser.pos,
},
)))
}
fn compound_command(parser: &mut Parser) -> Option<Command> {
parser.skip_blanks();
if peek_keyword(parser, "{") {
return brace_group(parser);
}
if parser.peek_char() == Some('(') {
return subshell(parser);
}
if peek_keyword(parser, "if") {
return if_clause(parser);
}
if peek_keyword(parser, "for") {
return for_clause(parser);
}
if peek_keyword(parser, "while") || peek_keyword(parser, "until") {
return loop_clause(parser);
}
if peek_keyword(parser, "case") {
return case_clause(parser);
}
None
}
fn command(parser: &mut Parser) -> Option<Command> {
parser.skip_blanks();
if let Some(cmd) = compound_command(parser) {
return Some(cmd);
}
if is_function_definition(parser).is_some() {
if !parser.function_definitions_enabled {
parser.set_error("function definitions are disabled".to_string());
return None;
}
return function_definition(parser);
}
simple_command(parser)
}
fn pipeline(parser: &mut Parser) -> Option<Pipeline> {
parser.skip_blanks();
let begin = parser.pos;
let bang = try_keyword(parser, "!");
let first = command(parser)?;
let mut commands = vec![first];
loop {
parser.skip_blanks();
if parser.peek_char() == Some('|') {
parser.fill_buf();
if parser.buf_pos + 1 < parser.buf.len() && parser.buf[parser.buf_pos + 1] == b'|' {
break;
}
parser.read_char(); parser.linebreak();
if let Some(cmd) = command(parser) {
commands.push(cmd);
} else {
parser.set_error("expected command after '|'".to_string());
break;
}
} else {
break;
}
}
Some(Pipeline {
commands,
bang,
range: Range {
begin,
end: parser.pos,
},
})
}
fn and_or(parser: &mut Parser) -> Option<AndOrList> {
let first = pipeline(parser)?;
let mut result = AndOrList::Pipeline(first);
loop {
parser.skip_blanks();
let sym = parser.peek_symbol();
let op = match sym {
Symbol::AndIf => BinOpType::And,
Symbol::OrIf => BinOpType::Or,
_ => break,
};
parser.consume_symbol(sym);
parser.linebreak();
let right = pipeline(parser);
let right = match right {
Some(p) => AndOrList::Pipeline(p),
None => {
parser.set_error("expected pipeline after && or ||".to_string());
break;
}
};
result = AndOrList::BinOp(AndOrBinary::new(op, result, right));
}
Some(result)
}
fn list(parser: &mut Parser) -> Vec<CommandList> {
let mut items = Vec::new();
loop {
let Some((item, sep)) = parse_command_list_item(parser, false) else {
if !items.is_empty() {
set_unexpected_token_error(parser);
}
break;
};
if item.ampersand {
maybe_consume_single_semicolon(parser);
}
items.push(item);
if !sep.has_separator {
break;
}
}
items
}
fn complete_command(parser: &mut Parser) -> Option<Vec<CommandList>> {
parser.skip_blanks();
let mut items = list(parser);
if items.is_empty() {
return None;
}
resolve_here_documents(parser);
let mut bodies = std::mem::take(&mut parser.here_doc_bodies);
fill_here_doc_bodies(&mut items, &mut bodies);
parser.here_doc_bodies = bodies;
Some(items)
}
fn compound_list(parser: &mut Parser) -> Vec<CommandList> {
parser.linebreak();
term(parser)
}
fn term(parser: &mut Parser) -> Vec<CommandList> {
let mut items = Vec::new();
loop {
let Some((item, _sep)) = parse_command_list_item(parser, true) else {
break;
};
items.push(item);
if parser.newline() {
if !parser.here_doc_pending.is_empty() {
resolve_here_documents(parser);
let mut bodies = std::mem::take(&mut parser.here_doc_bodies);
fill_here_doc_bodies(&mut items, &mut bodies);
parser.here_doc_bodies = bodies;
}
parser.linebreak();
}
if peek_keyword(parser, "}")
|| peek_keyword(parser, "fi")
|| peek_keyword(parser, "done")
|| peek_keyword(parser, "esac")
|| peek_keyword(parser, "then")
|| peek_keyword(parser, "else")
|| peek_keyword(parser, "elif")
|| peek_keyword(parser, "do")
{
break;
}
if parser.peek_symbol() == Symbol::Eof {
break;
}
}
items
}
fn parse_command_list_item(
parser: &mut Parser,
allow_ampersand_semicolon: bool,
) -> Option<(CommandList, ParsedSeparator)> {
parser.skip_blanks();
let begin = parser.pos;
let ao = and_or(parser)?;
let sep = parse_list_separator(parser, allow_ampersand_semicolon);
let item = CommandList::new(
ao,
sep.ampersand,
Range {
begin,
end: parser.pos,
},
);
Some((item, sep))
}
#[derive(Clone, Copy, Default)]
struct ParsedSeparator {
ampersand: bool,
has_separator: bool,
}
fn parse_list_separator(parser: &mut Parser, allow_ampersand_semicolon: bool) -> ParsedSeparator {
parser.skip_blanks();
let mut sep = ParsedSeparator::default();
if parser.peek_char() == Some('&') {
parser.fill_buf();
if parser.buf_pos + 1 < parser.buf.len() && parser.buf[parser.buf_pos + 1] == b'&' {
return sep;
}
parser.read_char();
sep.ampersand = true;
sep.has_separator = true;
if allow_ampersand_semicolon {
parser.skip_blanks();
maybe_consume_single_semicolon(parser);
}
return sep;
}
if maybe_consume_single_semicolon(parser) {
sep.has_separator = true;
}
sep
}
fn maybe_consume_single_semicolon(parser: &mut Parser) -> bool {
if parser.peek_char() != Some(';') {
return false;
}
parser.fill_buf();
if parser.buf_pos + 1 < parser.buf.len() && parser.buf[parser.buf_pos + 1] == b';' {
return false;
}
parser.read_char();
true
}
fn resolve_here_documents(parser: &mut Parser) {
let pending = std::mem::take(&mut parser.here_doc_pending);
for info in pending {
let mut body_lines: Vec<Word> = Vec::new();
let mut reached_eof = false;
if parser.peek_char() == Some('\n') {
parser.read_char();
}
loop {
let mut line = String::new();
let mut eof_before_line = false;
loop {
match parser.read_char() {
Some('\n') => break,
Some(ch) => line.push(ch),
None => {
eof_before_line = true;
break;
}
}
}
let check_line = if info.strip_tabs {
line.trim_start_matches('\t').to_string()
} else {
line.clone()
};
if check_line == info.delimiter {
break;
}
if eof_before_line {
reached_eof = true;
}
if info.expand {
let line_word = here_document_line(&check_line);
body_lines.push(line_word);
} else {
body_lines.push(Word::string(
check_line,
false,
false,
None,
Range::default(),
));
}
if eof_before_line {
break;
}
if parser.peek_char().is_none() {
reached_eof = true;
break;
}
}
if reached_eof {
parser.set_error(format!(
"syntax error: here-document at EOF, expected delimiter '{}'",
info.delimiter
));
}
parser.here_doc_bodies.push(body_lines);
}
}
fn fill_here_doc_bodies(lists: &mut [CommandList], bodies: &mut Vec<Vec<Word>>) {
for cl in lists {
fill_here_doc_bodies_in_and_or(&mut cl.and_or_list, bodies);
}
}
fn fill_here_doc_bodies_in_and_or(aol: &mut AndOrList, bodies: &mut Vec<Vec<Word>>) {
match aol {
AndOrList::Pipeline(p) => {
for cmd in &mut p.commands {
fill_here_doc_bodies_in_command(cmd, bodies);
}
}
AndOrList::BinOp(bin_op) => {
fill_here_doc_bodies_in_and_or(&mut bin_op.left, bodies);
fill_here_doc_bodies_in_and_or(&mut bin_op.right, bodies);
}
}
}
fn fill_here_doc_bodies_in_command(cmd: &mut Command, bodies: &mut Vec<Vec<Word>>) {
match cmd {
Command::Simple(sc) => {
fill_here_doc_bodies_in_redirects(&mut sc.io_redirects, bodies);
}
Command::BraceGroup(group) => {
fill_here_doc_bodies(&mut group.body, bodies);
fill_here_doc_bodies_in_redirects(&mut group.io_redirects, bodies);
}
Command::Subshell(group) => {
fill_here_doc_bodies(&mut group.body, bodies);
fill_here_doc_bodies_in_redirects(&mut group.io_redirects, bodies);
}
Command::If(ic) => fill_here_doc_bodies_in_if(ic, bodies),
Command::For(fc) => {
fill_here_doc_bodies(&mut fc.body, bodies);
}
Command::Loop(lc) => {
fill_here_doc_bodies(&mut lc.condition, bodies);
fill_here_doc_bodies(&mut lc.body, bodies);
}
Command::Case(cc) => {
for item in &mut cc.items {
fill_here_doc_bodies(&mut item.body, bodies);
}
}
Command::FunctionDef(fd) => {
fill_here_doc_bodies_in_command(&mut fd.body, bodies);
fill_here_doc_bodies_in_redirects(&mut fd.io_redirects, bodies);
}
}
}
fn fill_here_doc_bodies_in_if(ic: &mut IfClause, bodies: &mut Vec<Vec<Word>>) {
fill_here_doc_bodies(&mut ic.condition, bodies);
fill_here_doc_bodies(&mut ic.body, bodies);
if let Some(else_part) = &mut ic.else_part {
match else_part.as_mut() {
ElsePart::Elif(nested) => fill_here_doc_bodies_in_if(nested, bodies),
ElsePart::Else(body) => fill_here_doc_bodies(&mut body.body, bodies),
}
}
}
fn fill_here_doc_bodies_in_redirects(redirects: &mut [IoRedirect], bodies: &mut Vec<Vec<Word>>) {
for redir in redirects {
if matches!(redir.op, IoRedirectOp::DLess | IoRedirectOp::DLessDash)
&& redir.here_document.is_empty()
&& !bodies.is_empty()
{
redir.here_document = bodies.remove(0);
}
}
}
pub fn parse_program(parser: &mut Parser) -> Result<Program, crate::parser::ParseError> {
parser.linebreak();
let mut body = Vec::new();
loop {
parser.linebreak();
parser.skip_blanks();
if parser.peek_symbol() == Symbol::Eof {
break;
}
if let Some(items) = complete_command(parser) {
body.extend(items);
} else {
set_unexpected_token_error(parser);
break;
}
parser.linebreak();
}
if let Some(err) = parser.error.take() {
return Err(err);
}
Ok(Program::new(body))
}
pub fn parse_line(parser: &mut Parser) -> Result<Option<Program>, crate::parser::ParseError> {
parser.skip_blanks();
if parser.peek_symbol() == Symbol::Eof {
return Ok(None);
}
if parser.newline() {
return Ok(Some(Program::new(Vec::new())));
}
let items = complete_command(parser);
if items.is_none() {
set_unexpected_token_error(parser);
}
parser.newline();
if let Some(err) = parser.error.take() {
return Err(err);
}
Ok(items.map(Program::new))
}
pub fn parse_subshell_body(parser: &mut Parser) -> Program {
let body = compound_list(parser);
Program::new(body)
}
pub(crate) fn consume_command_substitution_closer(parser: &mut Parser) {
if !parser.current_rparen_is_syntax() {
parser.set_error("expected ')' in command substitution".to_string());
} else {
parser.read_char();
}
}
fn expect_do_group(parser: &mut Parser) -> Vec<CommandList> {
parser.linebreak();
if !expect_keyword(parser, "do") {
parser.set_error("expected 'do'".to_string());
}
let body = compound_list(parser);
if !expect_keyword(parser, "done") {
parser.set_error("expected 'done'".to_string());
}
body
}
fn sequential_sep(parser: &mut Parser) {
parser.skip_blanks();
if parser.peek_char() == Some(';') {
parser.fill_buf();
if parser.buf_pos + 1 >= parser.buf.len() || parser.buf[parser.buf_pos + 1] != b';' {
parser.read_char();
}
parser.linebreak();
} else {
parser.newline_list();
}
}
fn read_word_as_name(parser: &mut Parser) -> Option<String> {
parser.skip_blanks();
let w = word(parser, None)?;
if is_word_quoted(&w) {
return None;
}
w.as_str().map(|s| s.into_owned())
}
fn peek_keyword(parser: &mut Parser, kw: &str) -> bool {
parser.skip_blanks();
parser.fill_buf();
let remaining = &parser.buf[parser.buf_pos..];
let bytes = kw.as_bytes();
if remaining.len() < bytes.len() {
return false;
}
if &remaining[..bytes.len()] != bytes {
return false;
}
keyword_has_word_boundary(remaining.get(bytes.len()).copied().map(char::from))
}
fn keyword_has_word_boundary(next: Option<char>) -> bool {
match next {
None => true,
Some('\n' | ' ' | '\t' | '(' | ')') => true,
Some(ch) => Parser::is_operator_start(ch),
}
}
fn unexpected_command_token(parser: &mut Parser) -> Option<&'static str> {
if parser.current_rparen_is_syntax() {
return Some(")");
}
["}", "fi", "done", "esac", "then", "else", "elif", "do"]
.into_iter()
.find(|&token| peek_keyword(parser, token))
}
pub(crate) fn set_unexpected_token_error(parser: &mut Parser) {
if let Some(token) = unexpected_command_token(parser) {
parser.set_error(format!("syntax error near unexpected token `{token}'"));
}
}
fn try_keyword(parser: &mut Parser, kw: &str) -> bool {
if peek_keyword(parser, kw) {
for _ in 0..kw.len() {
parser.read_char();
}
true
} else {
false
}
}
fn expect_keyword(parser: &mut Parser, kw: &str) -> bool {
parser.skip_blanks();
parser.linebreak();
parser.skip_blanks();
if try_keyword(parser, kw) {
true
} else {
parser.set_error(format!("expected '{kw}'"));
false
}
}
fn peek_dsemi(parser: &mut Parser) -> bool {
parser.peek_symbol() == Symbol::DSemi
}
#[cfg(test)]
mod tests;