use super::shell_ir;
use super::{ShellIR, ShellValue};
pub(crate) fn adjust_range_end(end_val: ShellValue, inclusive: bool) -> ShellValue {
if inclusive {
return end_val;
}
match &end_val {
ShellValue::String(s) => {
if let Ok(n) = s.parse::<i32>() {
ShellValue::String((n - 1).to_string())
} else {
ShellValue::Arithmetic {
op: shell_ir::ArithmeticOp::Sub,
left: Box::new(end_val),
right: Box::new(ShellValue::String("1".to_string())),
}
}
}
_ => ShellValue::Arithmetic {
op: shell_ir::ArithmeticOp::Sub,
left: Box::new(end_val),
right: Box::new(ShellValue::String("1".to_string())),
},
}
}
fn eval_arithmetic_op(op: &shell_ir::ArithmeticOp, left: i64, right: i64) -> Option<i64> {
match op {
shell_ir::ArithmeticOp::Add => Some(left + right),
shell_ir::ArithmeticOp::Sub => Some(left - right),
shell_ir::ArithmeticOp::Mul => Some(left * right),
shell_ir::ArithmeticOp::Div if right != 0 => Some(left / right),
shell_ir::ArithmeticOp::Mod if right != 0 => Some(left % right),
shell_ir::ArithmeticOp::BitAnd => Some(left & right),
shell_ir::ArithmeticOp::BitOr => Some(left | right),
shell_ir::ArithmeticOp::BitXor => Some(left ^ right),
shell_ir::ArithmeticOp::Shl => Some(left << right),
shell_ir::ArithmeticOp::Shr => Some(left >> right),
_ => None,
}
}
fn try_fold_constant_arithmetic(
op: &shell_ir::ArithmeticOp,
left: &ShellValue,
right: &ShellValue,
) -> Option<String> {
if let (ShellValue::String(l), ShellValue::String(r)) = (left, right) {
if let (Ok(ln), Ok(rn)) = (l.parse::<i64>(), r.parse::<i64>()) {
return eval_arithmetic_op(op, ln, rn).map(|v| v.to_string());
}
}
None
}
pub(crate) fn constant_fold(ir: ShellIR) -> ShellIR {
let mut transform_fn = |node| match node {
ShellIR::Let {
name,
value: ShellValue::Concat(parts),
effects,
} => {
if parts.iter().all(|p| matches!(p, ShellValue::String(_))) {
let folded = parts
.iter()
.filter_map(|p| match p {
ShellValue::String(s) => Some(s.as_str()),
_ => None,
})
.collect::<String>();
ShellIR::Let {
name,
value: ShellValue::String(folded),
effects,
}
} else {
ShellIR::Let {
name,
value: ShellValue::Concat(parts),
effects,
}
}
}
ShellIR::Let {
name,
value: ShellValue::Arithmetic { op, left, right },
effects,
} => {
let folded_left = fold_arithmetic_value(*left);
let folded_right = fold_arithmetic_value(*right);
let value = match try_fold_constant_arithmetic(&op, &folded_left, &folded_right) {
Some(result) => ShellValue::String(result),
None => ShellValue::Arithmetic {
op,
left: Box::new(folded_left),
right: Box::new(folded_right),
},
};
ShellIR::Let {
name,
value,
effects,
}
}
_ => node,
};
transform_ir(ir, &mut transform_fn)
}
fn fold_arithmetic_value(value: ShellValue) -> ShellValue {
match value {
ShellValue::Arithmetic { op, left, right } => {
let folded_left = fold_arithmetic_value(*left);
let folded_right = fold_arithmetic_value(*right);
match try_fold_constant_arithmetic(&op, &folded_left, &folded_right) {
Some(result) => ShellValue::String(result),
None => ShellValue::Arithmetic {
op,
left: Box::new(folded_left),
right: Box::new(folded_right),
},
}
}
other => other,
}
}
pub(crate) fn eliminate_dead_code(ir: ShellIR) -> ShellIR {
ir }
pub(crate) fn is_string_value(value: &ShellValue) -> bool {
match value {
ShellValue::String(s) => {
s.parse::<i64>().is_err() && s.parse::<f64>().is_err()
}
ShellValue::Bool(_) => false, ShellValue::Variable(_) => false, ShellValue::EnvVar { .. } => false, ShellValue::Concat(_) => true, ShellValue::CommandSubst(_) => false, ShellValue::Comparison { .. } => false,
ShellValue::Arithmetic { .. } => false,
ShellValue::LogicalAnd { .. }
| ShellValue::LogicalOr { .. }
| ShellValue::LogicalNot { .. } => false,
ShellValue::Arg { .. } | ShellValue::ArgCount => false,
ShellValue::ArgWithDefault { .. } => false,
ShellValue::ExitCode => false,
ShellValue::DynamicArrayAccess { .. } => false,
ShellValue::Glob(_) => false,
}
}
fn transform_ir<F>(ir: ShellIR, transform: &mut F) -> ShellIR
where
F: FnMut(ShellIR) -> ShellIR,
{
let transformed = match ir {
ShellIR::Sequence(stmts) => {
let new_stmts = stmts
.into_iter()
.map(|stmt| transform_ir(stmt, transform))
.collect();
ShellIR::Sequence(new_stmts)
}
ShellIR::If {
test,
then_branch,
else_branch,
} => {
let new_then = Box::new(transform_ir(*then_branch, transform));
let new_else = else_branch.map(|eb| Box::new(transform_ir(*eb, transform)));
ShellIR::If {
test,
then_branch: new_then,
else_branch: new_else,
}
}
ShellIR::Function { name, params, body } => {
let new_body = Box::new(transform_ir(*body, transform));
ShellIR::Function {
name,
params,
body: new_body,
}
}
other => other,
};
transform(transformed)
}