use crate::math::MathEval;
use crate::parser::*;
pub fn optimize(commands: &mut Vec<ShellCommand>) {
for cmd in commands.iter_mut() {
optimize_command(cmd);
}
}
fn optimize_command(cmd: &mut ShellCommand) {
match cmd {
ShellCommand::Simple(simple) => {
for word in simple.words.iter_mut() {
optimize_word(word);
}
for (_, val, _) in simple.assignments.iter_mut() {
optimize_word(val);
}
}
ShellCommand::Pipeline(cmds, _) => {
for c in cmds.iter_mut() {
optimize_command(c);
}
}
ShellCommand::List(items) => {
for (c, _) in items.iter_mut() {
optimize_command(c);
}
}
ShellCommand::Compound(compound) => optimize_compound(compound),
ShellCommand::FunctionDef(_, body) => {
optimize_command(body);
}
}
}
fn optimize_compound(compound: &mut CompoundCommand) {
match compound {
CompoundCommand::BraceGroup(cmds) | CompoundCommand::Subshell(cmds) => {
for c in cmds.iter_mut() { optimize_command(c); }
}
CompoundCommand::If { conditions, else_part } => {
for (cond, body) in conditions.iter_mut() {
for c in cond.iter_mut() { optimize_command(c); }
for c in body.iter_mut() { optimize_command(c); }
}
if let Some(els) = else_part {
for c in els.iter_mut() { optimize_command(c); }
}
}
CompoundCommand::For { words, body, .. } => {
if let Some(ws) = words {
for w in ws.iter_mut() { optimize_word(w); }
}
for c in body.iter_mut() { optimize_command(c); }
}
CompoundCommand::ForArith { body, .. } => {
for c in body.iter_mut() { optimize_command(c); }
}
CompoundCommand::While { condition, body } | CompoundCommand::Until { condition, body } => {
for c in condition.iter_mut() { optimize_command(c); }
for c in body.iter_mut() { optimize_command(c); }
}
CompoundCommand::Case { word, cases } => {
optimize_word(word);
for (pats, cmds, _) in cases.iter_mut() {
for p in pats.iter_mut() { optimize_word(p); }
for c in cmds.iter_mut() { optimize_command(c); }
}
}
CompoundCommand::Select { words, body, .. } => {
if let Some(ws) = words {
for w in ws.iter_mut() { optimize_word(w); }
}
for c in body.iter_mut() { optimize_command(c); }
}
CompoundCommand::Try { try_body, always_body } => {
for c in try_body.iter_mut() { optimize_command(c); }
for c in always_body.iter_mut() { optimize_command(c); }
}
CompoundCommand::Repeat { body, .. } => {
for c in body.iter_mut() { optimize_command(c); }
}
CompoundCommand::Coproc { body, .. } => {
optimize_command(body);
}
CompoundCommand::WithRedirects(cmd, _) => {
optimize_command(cmd);
}
_ => {}
}
}
fn optimize_word(word: &mut ShellWord) {
match word {
ShellWord::ArithSub(expr) => {
if is_constant_expr(expr) {
if let Some(val) = eval_constant(expr) {
tracing::trace!(expr = %expr, result = %val, "ast_opt: constant folded");
*word = ShellWord::Literal(val);
}
}
}
ShellWord::DoubleQuoted(parts) => {
for p in parts.iter_mut() { optimize_word(p); }
merge_adjacent_literals(parts);
}
ShellWord::Concat(parts) => {
for p in parts.iter_mut() { optimize_word(p); }
merge_adjacent_literals(parts);
if parts.len() == 1 {
*word = parts.remove(0);
}
}
ShellWord::ArrayLiteral(elements) => {
for e in elements.iter_mut() { optimize_word(e); }
}
ShellWord::CommandSub(cmd) => {
optimize_command(cmd);
}
ShellWord::ProcessSubIn(cmd) | ShellWord::ProcessSubOut(cmd) => {
optimize_command(cmd);
}
_ => {}
}
}
fn is_constant_expr(expr: &str) -> bool {
!expr.contains('$') &&
expr.chars().all(|c| {
c.is_ascii_digit() || c.is_ascii_whitespace() ||
matches!(c, '+' | '-' | '*' | '/' | '%' | '(' | ')' |
'&' | '|' | '^' | '~' | '<' | '>' | '!' |
'=' | '?' | ':' | '.' | 'x' | 'X' |
'a' | 'b' | 'c' | 'd' | 'e' | 'f' |
'A' | 'B' | 'C' | 'D' | 'E' | 'F')
})
}
fn eval_constant(expr: &str) -> Option<String> {
let mut evaluator = MathEval::new(expr);
match evaluator.evaluate() {
Ok(result) => {
let s = match result {
crate::math::MathNum::Integer(i) => i.to_string(),
crate::math::MathNum::Float(f) => {
if f.fract() == 0.0 && f.abs() < i64::MAX as f64 {
(f as i64).to_string()
} else {
f.to_string()
}
}
_ => return None,
};
Some(s)
}
Err(_) => None,
}
}
fn merge_adjacent_literals(parts: &mut Vec<ShellWord>) {
let mut i = 0;
while i + 1 < parts.len() {
if let (ShellWord::Literal(a), ShellWord::Literal(b)) = (&parts[i], &parts[i + 1]) {
let merged = format!("{}{}", a, b);
parts[i] = ShellWord::Literal(merged);
parts.remove(i + 1);
} else {
i += 1;
}
}
}