use crate::ast::{Node, NodeKind};
use crate::token::{Token, TokenType};
pub(super) const fn is_redirect_op_kind(kind: TokenType) -> bool {
matches!(
kind,
TokenType::Less
| TokenType::Greater
| TokenType::DoubleGreater
| TokenType::LessAnd
| TokenType::GreaterAnd
| TokenType::LessGreater
| TokenType::GreaterPipe
| TokenType::AndGreater
| TokenType::AndDoubleGreater
| TokenType::DoubleLess
| TokenType::DoubleLessDash
| TokenType::TripleLess
)
}
pub fn word_node_from_token(tok: Token) -> Node {
let parts = super::word_parts::decompose_word_with_spans(&tok.value, &tok.spans);
Node::empty(NodeKind::Word {
parts,
value: tok.value,
spans: tok.spans,
})
}
pub fn word_node(value: &str) -> Node {
Node::empty(NodeKind::Word {
parts: super::word_parts::decompose_word_literal(value),
value: value.to_string(),
spans: Vec::new(),
})
}
pub(super) fn cond_term_from_token(tok: Token) -> Node {
Node::empty(NodeKind::CondTerm {
value: tok.value,
spans: tok.spans,
})
}
pub(super) fn is_fd_number(s: &str) -> bool {
!s.is_empty() && s.len() <= 2 && s.chars().all(|c| c.is_ascii_digit())
}
pub(super) fn is_varfd(s: &str) -> bool {
s.starts_with('{')
&& s.ends_with('}')
&& s.len() >= 3
&& s.as_bytes()
.get(1)
.is_some_and(|&c| c.is_ascii_alphabetic() || c == b'_')
&& s[1..s.len() - 1]
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
pub(super) fn is_cond_binary_op(s: &str) -> bool {
matches!(
s,
"==" | "!="
| "=~"
| "<"
| ">"
| "-eq"
| "-ne"
| "-lt"
| "-le"
| "-gt"
| "-ge"
| "-nt"
| "-ot"
| "-ef"
| "="
)
}
#[allow(clippy::needless_pass_by_value)]
pub(super) fn add_stderr_redirect(node: Option<&mut Node>) -> bool {
if let Some(Node {
kind: NodeKind::Command { redirects, .. },
..
}) = node
{
redirects.push(make_stderr_redirect());
true
} else {
false
}
}
pub(super) fn make_stderr_redirect() -> Node {
Node::empty(NodeKind::Redirect {
op: ">&".to_string(),
target: Box::new(Node::empty(NodeKind::Word {
value: "1".to_string(),
parts: vec![Node::empty(NodeKind::WordLiteral {
value: "1".to_string(),
})],
spans: Vec::new(),
})),
fd: 2,
varfd: None,
})
}
pub(super) fn fill_heredoc_contents(node: &mut Node, lexer: &mut crate::lexer::Lexer) {
match &mut node.kind {
NodeKind::HereDoc { content, .. } if content.is_empty() => {
if let Some(c) = lexer.take_heredoc_content() {
*content = c;
}
}
NodeKind::Command {
assignments,
words,
redirects,
} => fill_command(assignments, words, redirects, lexer),
NodeKind::Pipeline { commands, .. } => fill_each(commands, lexer),
NodeKind::List { items } => {
for item in items {
fill_heredoc_contents(&mut item.command, lexer);
}
}
NodeKind::If {
condition,
then_body,
else_body,
redirects,
} => fill_if(
condition,
then_body,
else_body.as_deref_mut(),
redirects,
lexer,
),
NodeKind::While {
condition,
body,
redirects,
}
| NodeKind::Until {
condition,
body,
redirects,
} => fill_cond_body_redirects(condition, body, redirects, lexer),
NodeKind::Subshell { body, redirects }
| NodeKind::BraceGroup { body, redirects }
| NodeKind::For {
body, redirects, ..
}
| NodeKind::Select {
body, redirects, ..
} => fill_body_and_redirects(body, redirects, lexer),
NodeKind::Case {
patterns,
redirects,
..
} => fill_case(patterns, redirects, lexer),
NodeKind::Negation { pipeline } | NodeKind::Time { pipeline, .. } => {
fill_heredoc_contents(pipeline, lexer);
}
NodeKind::Function { body, .. } | NodeKind::Coproc { command: body, .. } => {
fill_heredoc_contents(body, lexer);
}
_ => {}
}
}
fn fill_each(nodes: &mut [Node], lexer: &mut crate::lexer::Lexer) {
for n in nodes {
fill_heredoc_contents(n, lexer);
}
}
fn fill_command(
assignments: &mut [Node],
words: &mut [Node],
redirects: &mut [Node],
lexer: &mut crate::lexer::Lexer,
) {
fill_each(assignments, lexer);
fill_each(words, lexer);
fill_each(redirects, lexer);
}
fn fill_if(
condition: &mut Node,
then_body: &mut Node,
else_body: Option<&mut Node>,
redirects: &mut [Node],
lexer: &mut crate::lexer::Lexer,
) {
fill_heredoc_contents(condition, lexer);
fill_heredoc_contents(then_body, lexer);
if let Some(eb) = else_body {
fill_heredoc_contents(eb, lexer);
}
fill_each(redirects, lexer);
}
fn fill_body_and_redirects(
body: &mut Node,
redirects: &mut [Node],
lexer: &mut crate::lexer::Lexer,
) {
fill_heredoc_contents(body, lexer);
fill_each(redirects, lexer);
}
fn fill_cond_body_redirects(
condition: &mut Node,
body: &mut Node,
redirects: &mut [Node],
lexer: &mut crate::lexer::Lexer,
) {
fill_heredoc_contents(condition, lexer);
fill_heredoc_contents(body, lexer);
fill_each(redirects, lexer);
}
fn fill_case(
patterns: &mut [crate::ast::CasePattern],
redirects: &mut [Node],
lexer: &mut crate::lexer::Lexer,
) {
for p in patterns {
if let Some(body) = &mut p.body {
fill_heredoc_contents(body, lexer);
}
}
fill_each(redirects, lexer);
}