use std::fmt;
use crate::ast::{ListItem, ListOperator, Node, NodeKind};
use super::{
write_escaped_word, write_optional, write_redirects, write_spaced3, write_tagged_list,
};
pub(super) fn write_pipeline(f: &mut fmt::Formatter<'_>, commands: &[Node]) -> fmt::Result {
if commands.len() == 1 {
return write!(f, "{}", commands[0]);
}
let mut groups: Vec<Vec<&Node>> = Vec::new();
for cmd in commands {
if matches!(cmd.kind, NodeKind::Redirect { .. }) {
if let Some(last) = groups.last_mut() {
last.push(cmd);
} else {
groups.push(vec![cmd]);
}
} else {
groups.push(vec![cmd]);
}
}
write_pipeline_groups(f, &groups, 0)
}
fn write_pipeline_groups(
f: &mut fmt::Formatter<'_>,
groups: &[Vec<&Node>],
idx: usize,
) -> fmt::Result {
if idx >= groups.len() {
return Ok(());
}
if idx == groups.len() - 1 {
for (j, node) in groups[idx].iter().enumerate() {
if j > 0 {
write!(f, " ")?;
}
write!(f, "{node}")?;
}
return Ok(());
}
write!(f, "(pipe ")?;
for (j, node) in groups[idx].iter().enumerate() {
if j > 0 {
write!(f, " ")?;
}
write!(f, "{node}")?;
}
write!(f, " ")?;
write_pipeline_groups(f, groups, idx + 1)?;
write!(f, ")")
}
pub(super) fn write_list(f: &mut fmt::Formatter<'_>, items: &[ListItem]) -> fmt::Result {
if items.len() == 1 && items[0].operator.is_none() {
return write!(f, "{}", items[0].command);
}
let cmds: Vec<&Node> = items.iter().map(|i| &i.command).collect();
let ops: Vec<ListOperator> = items.iter().filter_map(|i| i.operator).collect();
write_list_left_assoc(f, &cmds, &ops)
}
fn write_list_left_assoc(
f: &mut fmt::Formatter<'_>,
items: &[&Node],
ops: &[ListOperator],
) -> fmt::Result {
if items.len() == 1 && ops.len() == 1 {
let sexp_op = list_op_name(ops[0]);
return write!(f, "({sexp_op} {})", items[0]);
}
if items.len() <= 1 && ops.is_empty() {
if let Some(item) = items.first() {
return write!(f, "{item}");
}
return Ok(());
}
if items.len() == ops.len() {
let sexp_op = list_op_name(ops[ops.len() - 1]);
write!(f, "({sexp_op} ")?;
write_list_left_assoc(f, items, &ops[..ops.len() - 1])?;
return write!(f, ")");
}
for i in (1..ops.len()).rev() {
write!(f, "({} ", list_op_name(ops[i]))?;
}
write!(f, "({} {} {})", list_op_name(ops[0]), items[0], items[1])?;
for i in 1..ops.len() {
write!(f, " {})", items[i + 1])?;
}
Ok(())
}
const fn list_op_name(op: ListOperator) -> &'static str {
match op {
ListOperator::And => "and",
ListOperator::Or => "or",
ListOperator::Semi => "semi",
ListOperator::Background => "background",
}
}
pub(super) fn write_in_list(f: &mut fmt::Formatter<'_>, words: Option<&[Node]>) -> fmt::Result {
if let Some(ws) = words {
write!(f, " (in")?;
for w in ws {
write!(f, " {w}")?;
}
write!(f, ")")?;
}
Ok(())
}
pub(super) fn fmt_command_like(f: &mut fmt::Formatter<'_>, kind: &NodeKind) -> fmt::Result {
match kind {
NodeKind::Command {
assignments,
words,
redirects,
} => write_spaced3(f, "(command", assignments, words, redirects),
NodeKind::Pipeline { commands, .. } => write_pipeline(f, commands),
NodeKind::List { items } => write_list(f, items),
NodeKind::Empty => write!(f, "(command)"),
NodeKind::Comment { text } => write!(f, "(comment \"{text}\")"),
NodeKind::Negation { pipeline } => write!(f, "(negation {pipeline})"),
NodeKind::Time { pipeline, posix } => {
let tag = if *posix { "(time -p " } else { "(time " };
write!(f, "{tag}{pipeline})")
}
NodeKind::Array { elements } => write_tagged_list(f, "array", elements),
_ => unreachable!("fmt_command_like called with non-command-like variant"),
}
}
pub(super) fn fmt_compound(f: &mut fmt::Formatter<'_>, kind: &NodeKind) -> fmt::Result {
match kind {
NodeKind::If { .. } | NodeKind::While { .. } | NodeKind::Until { .. } => {
fmt_cond_body(f, kind)
}
NodeKind::For { .. }
| NodeKind::ForArith { .. }
| NodeKind::Select { .. }
| NodeKind::Case { .. } => fmt_loop(f, kind),
NodeKind::Function { .. }
| NodeKind::Subshell { .. }
| NodeKind::BraceGroup { .. }
| NodeKind::Coproc { .. } => fmt_group(f, kind),
_ => unreachable!("fmt_compound called with non-compound variant"),
}
}
fn fmt_cond_body(f: &mut fmt::Formatter<'_>, kind: &NodeKind) -> fmt::Result {
match kind {
NodeKind::If {
condition,
then_body,
else_body,
redirects,
} => {
write!(f, "(if {condition} {then_body}")?;
write_optional(f, else_body.as_deref())?;
write!(f, ")")?;
write_redirects(f, redirects)
}
NodeKind::While {
condition,
body,
redirects,
} => {
write!(f, "(while {condition} {body})")?;
write_redirects(f, redirects)
}
NodeKind::Until {
condition,
body,
redirects,
} => {
write!(f, "(until {condition} {body})")?;
write_redirects(f, redirects)
}
_ => unreachable!("fmt_cond_body called with unexpected variant"),
}
}
fn fmt_loop(f: &mut fmt::Formatter<'_>, kind: &NodeKind) -> fmt::Result {
match kind {
NodeKind::For {
var,
words,
body,
redirects,
} => {
write!(f, "(for (word \"{var}\")")?;
write_in_list(f, words.as_deref())?;
write!(f, " {body})")?;
write_redirects(f, redirects)
}
NodeKind::ForArith {
init,
cond,
incr,
body,
redirects,
} => {
write!(f, "(arith-for (init (word \"")?;
write_escaped_word(f, init)?;
write!(f, "\")) (test (word \"")?;
write_escaped_word(f, cond)?;
write!(f, "\")) (step (word \"")?;
write_escaped_word(f, incr)?;
write!(f, "\")) {body})")?;
write_redirects(f, redirects)
}
NodeKind::Select {
var,
words,
body,
redirects,
} => {
write!(f, "(select (word \"{var}\")")?;
write_in_list(f, words.as_deref())?;
write!(f, " {body})")?;
write_redirects(f, redirects)
}
NodeKind::Case {
word,
patterns,
redirects,
} => {
write!(f, "(case {word}")?;
for p in patterns {
write!(f, " {p}")?;
}
write!(f, ")")?;
write_redirects(f, redirects)
}
_ => unreachable!("fmt_loop called with unexpected variant"),
}
}
fn fmt_group(f: &mut fmt::Formatter<'_>, kind: &NodeKind) -> fmt::Result {
match kind {
NodeKind::Function { name, body } => write!(f, "(function \"{name}\" {body})"),
NodeKind::Subshell { body, redirects } => {
write!(f, "(subshell {body})")?;
write_redirects(f, redirects)
}
NodeKind::BraceGroup { body, redirects } => {
write!(f, "(brace-group {body})")?;
write_redirects(f, redirects)
}
NodeKind::Coproc { name, command } => {
let n = name.as_deref().unwrap_or("COPROC");
write!(f, "(coproc \"{n}\" {command})")
}
_ => unreachable!("fmt_group called with unexpected variant"),
}
}
pub(super) fn fmt_conditional(f: &mut fmt::Formatter<'_>, kind: &NodeKind) -> fmt::Result {
match kind {
NodeKind::ConditionalExpr { body, redirects } => {
write!(f, "(cond {body})")?;
write_redirects(f, redirects)
}
NodeKind::UnaryTest { op, operand } => write!(f, "(cond-unary \"{op}\" {operand})"),
NodeKind::BinaryTest { op, left, right } => {
write!(f, "(cond-binary \"{op}\" {left} {right})")
}
NodeKind::CondAnd { left, right } => write!(f, "(cond-and {left} {right})"),
NodeKind::CondOr { left, right } => write!(f, "(cond-or {left} {right})"),
NodeKind::CondNot { operand } => write!(f, "{operand}"),
NodeKind::CondParen { inner } => write!(f, "(cond-expr {inner})"),
_ => unreachable!("fmt_conditional called with non-conditional variant"),
}
}