impl super::posix::PosixEmitter {
pub(crate) fn emit_while_statement(
&self,
output: &mut String,
condition: &ShellValue,
body: &ShellIR,
indent: usize,
) -> Result<()> {
self.record_decision("while_construct", "while_test", "While");
let indent_str = get_indent(indent + 1);
let condition_test = match condition {
ShellValue::Bool(true) => {
"true".to_string()
}
ShellValue::Comparison { .. } => {
self.emit_shell_value(condition)?
}
ShellValue::LogicalAnd { left, right } => {
let left_cond = self.emit_while_condition(left)?;
let right_cond = self.emit_while_condition(right)?;
format!("{left_cond} && {right_cond}")
}
ShellValue::LogicalOr { left, right } => {
let left_cond = self.emit_while_condition(left)?;
let right_cond = self.emit_while_condition(right)?;
format!("{left_cond} || {right_cond}")
}
ShellValue::LogicalNot { operand } => {
let inner = self.emit_while_condition(operand)?;
format!("! {inner}")
}
_ => {
let cond_str = self.emit_shell_value(condition)?;
format!("[ {cond_str} ]")
}
};
writeln!(output, "{indent_str}while {condition_test}; do")?;
self.emit_ir(output, body, indent + 1)?;
writeln!(output, "{indent_str}done")?;
Ok(())
}
pub(crate) fn emit_while_condition(&self, value: &ShellValue) -> Result<String> {
match value {
ShellValue::Bool(true) => Ok("true".to_string()),
ShellValue::Bool(false) => Ok("false".to_string()),
ShellValue::Comparison { .. } => self.emit_shell_value(value),
ShellValue::LogicalAnd { left, right } => {
let l = self.emit_while_condition(left)?;
let r = self.emit_while_condition(right)?;
Ok(format!("{l} && {r}"))
}
ShellValue::LogicalOr { left, right } => {
let l = self.emit_while_condition(left)?;
let r = self.emit_while_condition(right)?;
Ok(format!("{l} || {r}"))
}
ShellValue::LogicalNot { operand } => {
let inner = self.emit_while_condition(operand)?;
Ok(format!("! {inner}"))
}
_ => {
let cond_str = self.emit_shell_value(value)?;
Ok(format!("[ {cond_str} ]"))
}
}
}
pub(crate) fn emit_case_statement(
&self,
output: &mut String,
scrutinee: &ShellValue,
arms: &[crate::ir::shell_ir::CaseArm],
indent: usize,
) -> Result<()> {
use crate::ir::shell_ir::CasePattern;
self.record_decision("case_dispatch", "case_arms", "Case");
let indent_str = get_indent(indent + 1);
let scrutinee_str = self.emit_shell_value(scrutinee)?;
writeln!(output, "{indent_str}case {scrutinee_str} in")?;
for arm in arms {
let pattern_str = match &arm.pattern {
CasePattern::Literal(lit) => lit.clone(),
CasePattern::Wildcard => "*".to_string(),
};
writeln!(output, "{} {})", indent_str, pattern_str)?;
if let Some(guard) = &arm.guard {
let guard_str = self.emit_shell_value(guard)?;
writeln!(output, "{} if {guard_str}; then", indent_str)?;
self.emit_ir(output, &arm.body, indent + 2)?;
writeln!(output, "{} fi", indent_str)?;
} else {
self.emit_ir(output, &arm.body, indent + 1)?;
}
writeln!(output, "{} ;;", indent_str)?;
}
writeln!(output, "{indent_str}esac")?;
Ok(())
}
pub(crate) fn emit_function(
&self,
output: &mut String,
name: &str,
params: &[String],
body: &ShellIR,
indent: usize,
) -> Result<()> {
let is_empty_body = matches!(body, ShellIR::Noop)
|| matches!(body, ShellIR::Sequence(items) if items.is_empty());
if is_empty_body && self.is_known_command(name) {
return Ok(());
}
let indent_str = get_indent(indent);
let body_indent_str = get_indent(indent + 1);
writeln!(output, "{indent_str}{name}() {{")?;
for (i, param) in params.iter().enumerate() {
let pos = i + 1;
let param_name = escape_variable_name(param);
writeln!(output, "{body_indent_str}{param_name}=\"${pos}\"")?;
}
if !params.is_empty() {
writeln!(output)?;
}
self.emit_ir(output, body, indent + 1)?;
writeln!(output, "{indent_str}}}")?;
writeln!(output)?;
Ok(())
}
pub(crate) fn is_known_command(&self, name: &str) -> bool {
const BUILTINS: &[&str] = &[
"echo", "cd", "pwd", "test", "export", "readonly", "shift", "set", "unset", "read",
"printf", "return", "exit", "trap", "true", "false", ":", ".", "source", "eval",
"exec", "wait",
];
const EXTERNAL_COMMANDS: &[&str] = &[
"cat", "grep", "sed", "awk", "cut", "sort", "uniq", "wc", "ls", "cp", "mv", "rm",
"mkdir", "rmdir", "touch", "chmod", "chown", "find", "xargs", "tar", "gzip", "curl",
"wget", "git", "make", "docker", "ssh", "scp", "rsync",
];
BUILTINS.contains(&name) || EXTERNAL_COMMANDS.contains(&name)
}
}