use super::trace::{DecisionTrace, TranspilerDecision};
use crate::ir::{ShellIR, ShellValue};
use crate::models::Result;
use std::cell::RefCell;
use std::fmt::Write;
const INDENT_CACHE: [&str; 9] = [
"",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
];
pub(crate) fn get_indent(depth: usize) -> std::borrow::Cow<'static, str> {
if depth < INDENT_CACHE.len() {
std::borrow::Cow::Borrowed(INDENT_CACHE[depth])
} else {
std::borrow::Cow::Owned(" ".repeat(depth))
}
}
pub(crate) fn arithmetic_op_str(op: &crate::ir::shell_ir::ArithmeticOp) -> &'static str {
use crate::ir::shell_ir::ArithmeticOp;
match op {
ArithmeticOp::Add => "+",
ArithmeticOp::Sub => "-",
ArithmeticOp::Mul => "*",
ArithmeticOp::Div => "/",
ArithmeticOp::Mod => "%",
ArithmeticOp::BitAnd => "&",
ArithmeticOp::BitOr => "|",
ArithmeticOp::BitXor => "^",
ArithmeticOp::Shl => "<<",
ArithmeticOp::Shr => ">>",
}
}
pub(crate) fn arithmetic_precedence(op: &crate::ir::shell_ir::ArithmeticOp) -> u8 {
use crate::ir::shell_ir::ArithmeticOp;
match op {
ArithmeticOp::BitOr => 0,
ArithmeticOp::BitXor => 1,
ArithmeticOp::BitAnd => 2,
ArithmeticOp::Shl | ArithmeticOp::Shr => 3,
ArithmeticOp::Add | ArithmeticOp::Sub => 4,
ArithmeticOp::Mul | ArithmeticOp::Div | ArithmeticOp::Mod => 5,
}
}
pub(crate) fn try_fold_logical(value: &ShellValue) -> Option<bool> {
match value {
ShellValue::Bool(b) => Some(*b),
ShellValue::LogicalNot { operand } => try_fold_logical(operand).map(|b| !b),
ShellValue::LogicalAnd { left, right } => {
Some(try_fold_logical(left)? && try_fold_logical(right)?)
}
ShellValue::LogicalOr { left, right } => {
Some(try_fold_logical(left)? || try_fold_logical(right)?)
}
_ => None,
}
}
pub(crate) fn unwrap_single_if(ir: &ShellIR) -> &ShellIR {
if let ShellIR::Sequence(items) = ir {
if items.len() == 1 && matches!(&items[0], ShellIR::If { .. }) {
return &items[0];
}
}
ir
}
pub(crate) fn classify_if_structure(else_branch: Option<&ShellIR>) -> &'static str {
match else_branch {
None => "simple_if",
Some(ir) => {
let unwrapped = unwrap_single_if(ir);
if matches!(unwrapped, ShellIR::If { .. }) {
"elif_chain"
} else {
"if_else"
}
}
}
}
pub(crate) fn classify_test_expression(test: &ShellValue) -> &'static str {
match test {
ShellValue::Bool(_) => "bool_literal",
ShellValue::Variable(_) => "variable_test",
ShellValue::String(_) => "string_check",
ShellValue::Comparison { .. } => "bracket_test",
ShellValue::LogicalAnd { .. }
| ShellValue::LogicalOr { .. }
| ShellValue::LogicalNot { .. } => "logical_op",
ShellValue::CommandSubst(_) => "cmd_subst_test",
_ => "other_test",
}
}
pub(crate) fn needs_arithmetic_parens(
child_op: &crate::ir::shell_ir::ArithmeticOp,
parent_op: Option<&crate::ir::shell_ir::ArithmeticOp>,
is_right: bool,
) -> bool {
if let Some(parent) = parent_op {
let child_prec = arithmetic_precedence(child_op);
let parent_prec = arithmetic_precedence(parent);
child_prec < parent_prec || (child_prec == parent_prec && is_right)
} else {
false
}
}
pub struct PosixEmitter {
trace: RefCell<Vec<TranspilerDecision>>,
tracing: bool,
}
impl Default for PosixEmitter {
fn default() -> Self {
Self {
trace: RefCell::new(Vec::new()),
tracing: false,
}
}
}
impl PosixEmitter {
pub fn new() -> Self {
Self::default()
}
pub fn new_with_tracing() -> Self {
Self {
trace: RefCell::new(Vec::new()),
tracing: true,
}
}
#[inline]
pub(crate) fn record_decision(&self, decision_type: &str, choice: &str, ir_node: &str) {
if !self.tracing {
return;
}
self.trace.borrow_mut().push(TranspilerDecision {
decision_type: decision_type.to_string(),
choice: choice.to_string(),
ir_node: ir_node.to_string(),
});
}
pub fn take_trace(&self) -> DecisionTrace {
self.trace.borrow_mut().drain(..).collect()
}
pub fn emit(&self, ir: &ShellIR) -> Result<String> {
contract_pre_configuration!(ir);
let mut output = String::new();
let used_functions = ir.collect_used_functions();
self.write_header_without_main(&mut output, &used_functions)?;
let (helper_functions, main_body) = self.separate_functions(ir);
for func_ir in &helper_functions {
self.emit_ir(&mut output, func_ir, 0)?;
writeln!(&mut output)?;
}
writeln!(&mut output, "# Main script begins")?;
writeln!(&mut output, "main() {{")?;
if main_body.is_empty() {
writeln!(&mut output, " :")?;
} else {
for stmt_ir in &main_body {
self.emit_ir(&mut output, stmt_ir, 1)?;
}
}
self.write_footer(&mut output)?;
Ok(output)
}
pub(crate) fn separate_functions<'a>(
&self,
ir: &'a ShellIR,
) -> (Vec<&'a ShellIR>, Vec<&'a ShellIR>) {
let mut functions = Vec::new();
let mut main_body = Vec::new();
if let ShellIR::Sequence(items) = ir {
for item in items {
match item {
ShellIR::Function { .. } => functions.push(item),
_ => main_body.push(item),
}
}
} else {
main_body.push(ir);
}
(functions, main_body)
}
pub(crate) fn write_header_without_main(
&self,
output: &mut String,
used_functions: &std::collections::HashSet<&str>,
) -> Result<()> {
writeln!(output, "#!/bin/sh")?;
writeln!(output, "# Generated by Rash v{}", env!("CARGO_PKG_VERSION"))?;
writeln!(output, "# POSIX-compliant shell script")?;
writeln!(output)?;
writeln!(output, "set -euf")?;
writeln!(output, "IFS=' \t\n'")?; writeln!(output, "export LC_ALL=C")?;
writeln!(output)?;
if self.needs_runtime(used_functions) {
self.write_selective_runtime(output, used_functions)?;
}
Ok(())
}
pub(crate) fn write_footer(&self, output: &mut String) -> Result<()> {
writeln!(output, "}}")?;
writeln!(output)?;
writeln!(output, "# Cleanup on exit")?;
writeln!(output, "trap 'rm -rf \"${{TMPDIR:-/tmp}}/rash.$$\"' EXIT")?;
writeln!(output)?;
writeln!(output, "# Execute main function")?;
writeln!(output, "main \"$@\"")?;
Ok(())
}
}
#[cfg(test)]
#[path = "posix_tests.rs"]
mod tests;