use super::*;
impl<'a> Parser<'a> {
pub(super) fn parse_function_body_command(
&mut self,
allow_bare_compound: bool,
) -> Result<Stmt> {
if let Some(compound) = self.try_parse_compact_function_brace_body()? {
let redirects = self.parse_trailing_redirects();
return Ok(Self::lower_non_sequence_command_to_stmt(Command::Compound(
Box::new(compound),
redirects,
)));
}
let compound = match self.current_keyword() {
Some(Keyword::If) if allow_bare_compound => self.parse_if()?,
Some(Keyword::For) if allow_bare_compound => self.parse_for()?,
Some(Keyword::Repeat) if allow_bare_compound && self.zsh_short_repeat_enabled() => {
self.parse_repeat()?
}
Some(Keyword::Foreach) if allow_bare_compound && self.zsh_short_loops_enabled() => {
self.parse_foreach()?
}
Some(Keyword::While) if allow_bare_compound => self.parse_while()?,
Some(Keyword::Until) if allow_bare_compound => self.parse_until()?,
Some(Keyword::Case) if allow_bare_compound => self.parse_case()?,
Some(Keyword::Select) if allow_bare_compound => self.parse_select()?,
_ => match self.current_token_kind {
Some(TokenKind::LeftBrace) => self.parse_brace_group(BraceBodyContext::Function)?,
Some(TokenKind::LeftParen) => self.parse_subshell()?,
Some(TokenKind::DoubleLeftBracket) if allow_bare_compound => {
self.parse_conditional()?
}
Some(TokenKind::DoubleLeftParen) if allow_bare_compound => {
if self.looks_like_command_style_double_paren() {
self.split_current_double_left_paren();
self.parse_subshell()?
} else {
let checkpoint = self.checkpoint();
if let Ok(compound) = self.parse_arithmetic_command() {
compound
} else {
self.restore(checkpoint);
self.split_current_double_left_paren();
self.parse_subshell()?
}
}
}
_ => {
return Err(Error::parse(
"expected compound command for function body".to_string(),
));
}
},
};
let redirects = self.parse_trailing_redirects();
Ok(Self::lower_non_sequence_command_to_stmt(Command::Compound(
Box::new(compound),
redirects,
)))
}
pub(super) fn parse_function_header_entry(&mut self) -> Result<FunctionHeaderEntry> {
let word = self
.take_current_function_header_word_and_advance()
.ok_or_else(|| self.error("expected function name"))?;
Ok(self.function_header_entry_from_word(word))
}
pub(super) fn parse_function_keyword_header_entry(&mut self) -> Result<FunctionHeaderEntry> {
let word = self
.take_current_function_header_word_and_advance()
.or_else(|| self.take_current_function_keyword_name_and_advance())
.ok_or_else(|| self.error("expected function name"))?;
Ok(self.function_header_entry_from_word(word))
}
pub(super) fn take_current_function_header_word_and_advance(&mut self) -> Option<Word> {
let span = self.current_span;
if let Some(token) = self.current_token.clone()
&& let Some(word) = self.simple_word_from_token(&token, span)
{
self.advance_past_word(&word);
return Some(word);
}
let token = self.current_token.take()?;
let word = self.decode_word_from_token(&token, span);
self.current_token = Some(token);
if let Some(word) = word.as_ref() {
self.advance_past_word(word);
}
word
}
pub(super) fn take_current_function_keyword_name_and_advance(&mut self) -> Option<Word> {
let text = match self.current_token_kind? {
TokenKind::DoubleLeftBracket => "[[",
TokenKind::DoubleRightBracket => "]]",
TokenKind::LeftBrace => "{",
TokenKind::RightBrace => "}",
_ => return None,
};
let word = Word::literal_with_span(text, self.current_span);
self.advance();
Some(word)
}
pub(super) fn function_header_entry_from_word(&self, word: Word) -> FunctionHeaderEntry {
let static_name = self.literal_word_text(&word).map(Name::from);
FunctionHeaderEntry { word, static_name }
}
pub(super) fn parse_function_parens_span(&mut self) -> Result<Span> {
if !self.at(TokenKind::LeftParen) {
return Err(self.error("expected '(' in function definition"));
}
let left_paren_span = self.current_span;
self.advance();
if !self.at(TokenKind::RightParen) {
return Err(Error::parse(
"expected ')' in function definition".to_string(),
));
}
let right_paren_span = self.current_span;
self.advance();
Ok(left_paren_span.merge(right_paren_span))
}
pub(super) fn parse_zsh_function_body_stmt(&mut self) -> Result<Stmt> {
self.skip_newlines()?;
if let Some(compound) = self.try_parse_compact_function_brace_body()? {
let redirects = self.parse_trailing_redirects();
return Ok(Self::lower_non_sequence_command_to_stmt(Command::Compound(
Box::new(compound),
redirects,
)));
}
if self.at(TokenKind::LeftBrace) {
let compound = self.parse_brace_group(BraceBodyContext::Function)?;
let redirects = self.parse_trailing_redirects();
return Ok(Self::lower_non_sequence_command_to_stmt(Command::Compound(
Box::new(compound),
redirects,
)));
}
self.parse_single_stmt_command()
}
pub(super) fn parse_single_stmt_command(&mut self) -> Result<Stmt> {
let mut stmt = self
.parse_pipeline()?
.ok_or_else(|| self.error("expected command"))?;
let Some(kind) = self.current_token_kind else {
return Ok(stmt);
};
let operator = match kind {
TokenKind::And => Some((Some(BinaryOp::And), None, false)),
TokenKind::Or => Some((Some(BinaryOp::Or), None, false)),
TokenKind::Semicolon => Some((None, Some(StmtTerminator::Semicolon), true)),
TokenKind::Background => Some((
None,
Some(StmtTerminator::Background(BackgroundOperator::Plain)),
true,
)),
TokenKind::BackgroundPipe => Some((
None,
Some(StmtTerminator::Background(BackgroundOperator::Pipe)),
true,
)),
TokenKind::BackgroundBang => Some((
None,
Some(StmtTerminator::Background(BackgroundOperator::Bang)),
true,
)),
_ => None,
};
let Some((binary_op, terminator, allow_empty_tail)) = operator else {
return Ok(stmt);
};
let operator_span = self.current_span;
self.advance();
if let Some(binary_op) = binary_op {
self.skip_newlines()?;
if let Some(right) = self.parse_pipeline()? {
stmt = Self::binary_stmt(stmt, binary_op, operator_span, right);
}
return Ok(stmt);
}
if allow_empty_tail
&& matches!(
self.current_token_kind,
Some(TokenKind::Semicolon | TokenKind::Newline)
)
{
self.advance();
}
stmt.terminator = terminator;
stmt.terminator_span = Some(operator_span);
Ok(stmt)
}
pub(super) fn parse_anonymous_function_args(&mut self) -> Result<SmallVec<[Word; 2]>> {
let mut args = SmallVec::<[Word; 2]>::new();
while self.current_token_kind.is_some_and(TokenKind::is_word_like) {
let word = self
.take_current_word_and_advance()
.ok_or_else(|| self.error("expected anonymous function argument"))?;
args.push(word);
}
Ok(args)
}
pub(super) fn parse_function_keyword(&mut self) -> Result<Command> {
self.ensure_function_keyword()?;
let start_span = self.current_span;
self.advance(); self.skip_newlines()?;
if self.dialect == ShellDialect::Zsh {
let mut entries = Vec::new();
while self.current_token_kind.is_some_and(TokenKind::is_word_like) {
if !entries.is_empty() && self.current_token_is_compact_zsh_brace_body() {
break;
}
entries.push(self.parse_function_header_entry()?);
if self.at(TokenKind::LeftParen) {
break;
}
}
let trailing_parens_span = if !entries.is_empty() && self.at(TokenKind::LeftParen) {
Some(self.parse_function_parens_span()?)
} else {
None
};
if entries.is_empty() {
let body = self.parse_zsh_function_body_stmt()?;
let args = self.parse_anonymous_function_args()?;
let redirects = self.parse_trailing_redirects();
let span = start_span.merge(self.current_span);
return Ok(Command::AnonymousFunction(
AnonymousFunctionCommand {
surface: AnonymousFunctionSurface::FunctionKeyword {
function_keyword_span: start_span,
},
body: Box::new(body),
args: args.into_vec(),
span,
},
redirects,
));
}
let body = self.parse_zsh_function_body_stmt()?;
let span = start_span.merge(self.current_span);
return Ok(Command::Function(FunctionDef {
header: FunctionHeader {
function_keyword_span: Some(start_span),
entries,
trailing_parens_span,
},
body: Box::new(body),
span,
}));
}
let entry = self.parse_function_keyword_header_entry()?;
let saw_newline_after_name = self.skip_newlines_with_flag()?;
let (trailing_parens_span, allow_bare_compound) = if self.at(TokenKind::LeftParen) {
let parens_span = self.parse_function_parens_span()?;
(Some(parens_span), self.skip_newlines_with_flag()?)
} else {
(None, saw_newline_after_name)
};
let body = self.parse_function_body_command(allow_bare_compound)?;
let span = start_span.merge(self.current_span);
Ok(Command::Function(FunctionDef {
header: FunctionHeader {
function_keyword_span: Some(start_span),
entries: vec![entry],
trailing_parens_span,
},
body: Box::new(body),
span,
}))
}
pub(super) fn parse_function_posix(&mut self) -> Result<Command> {
let start_span = self.current_span;
let entry = self.parse_function_header_entry()?;
let trailing_parens_span = self.parse_function_parens_span()?;
self.finish_parse_function_posix(start_span, entry, trailing_parens_span)
}
pub(super) fn finish_parse_function_posix(
&mut self,
start_span: Span,
entry: FunctionHeaderEntry,
trailing_parens_span: Span,
) -> Result<Command> {
let body = if self.dialect == ShellDialect::Zsh {
self.parse_zsh_function_body_stmt()?
} else {
self.skip_newlines()?;
self.parse_function_body_command(true)?
};
Ok(Command::Function(FunctionDef {
header: FunctionHeader {
function_keyword_span: None,
entries: vec![entry],
trailing_parens_span: Some(trailing_parens_span),
},
body: Box::new(body),
span: start_span.merge(self.current_span),
}))
}
pub(super) fn try_parse_zsh_attached_parens_function(&mut self) -> Result<Option<Command>> {
if self.dialect != ShellDialect::Zsh || !self.at_word_like() {
return Ok(None);
}
let Some(word_text) = self.current_source_like_word_text() else {
return Ok(None);
};
let Some(header_text) = word_text.as_ref().strip_suffix("()") else {
return Ok(None);
};
if header_text.is_empty() || header_text.contains('=') {
return Ok(None);
}
let checkpoint = self.checkpoint();
self.advance();
if let Err(error) = self.skip_newlines() {
self.restore(checkpoint);
return Err(error);
}
if !self.at(TokenKind::LeftBrace) {
self.restore(checkpoint);
return Ok(None);
}
self.restore(checkpoint);
let start_span = self.current_span;
let header_span =
Span::from_positions(start_span.start, start_span.start.advanced_by(header_text));
let parens_span = Span::from_positions(header_span.end, start_span.end);
let header_word =
self.parse_word_with_context(header_text, header_span, header_span.start, true);
let entry = self.function_header_entry_from_word(header_word);
self.advance();
self.finish_parse_function_posix(start_span, entry, parens_span)
.map(Some)
}
pub(super) fn parse_anonymous_paren_function(&mut self) -> Result<Command> {
let start_span = self.current_span;
let parens_span = self.parse_function_parens_span()?;
let body = self.parse_zsh_function_body_stmt()?;
let args = self.parse_anonymous_function_args()?;
let redirects = self.parse_trailing_redirects();
let span = start_span.merge(self.current_span);
Ok(Command::AnonymousFunction(
AnonymousFunctionCommand {
surface: AnonymousFunctionSurface::Parens { parens_span },
body: Box::new(body),
args: args.into_vec(),
span,
},
redirects,
))
}
}