use crate::hir::common::{
HirBinaryOpKind, HirDecisionTarget, HirExpr, HirLogicalExpr, HirUnaryExpr, HirUnaryOpKind,
};
pub(super) fn logical_and(lhs: HirExpr, rhs: HirExpr) -> HirExpr {
if lhs == rhs {
lhs
} else {
HirExpr::LogicalAnd(Box::new(HirLogicalExpr { lhs, rhs }))
}
}
pub(super) fn logical_or(lhs: HirExpr, rhs: HirExpr) -> HirExpr {
if lhs == rhs {
lhs
} else {
HirExpr::LogicalOr(Box::new(HirLogicalExpr { lhs, rhs }))
}
}
pub(super) fn negate_expr(expr: HirExpr) -> HirExpr {
match expr {
HirExpr::Unary(unary) if unary.op == HirUnaryOpKind::Not => unary.expr,
expr => HirExpr::Unary(Box::new(HirUnaryExpr {
op: HirUnaryOpKind::Not,
expr,
})),
}
}
pub(super) fn expr_truthiness(expr: &HirExpr) -> Option<bool> {
match expr {
HirExpr::Nil => Some(false),
HirExpr::Boolean(value) => Some(*value),
HirExpr::Integer(_)
| HirExpr::Number(_)
| HirExpr::String(_)
| HirExpr::Int64(_)
| HirExpr::UInt64(_)
| HirExpr::Complex { .. }
| HirExpr::Closure(_)
| HirExpr::TableConstructor(_) => Some(true),
HirExpr::ParamRef(_)
| HirExpr::LocalRef(_)
| HirExpr::UpvalueRef(_)
| HirExpr::TempRef(_)
| HirExpr::GlobalRef(_)
| HirExpr::TableAccess(_)
| HirExpr::Unary(_)
| HirExpr::Binary(_)
| HirExpr::LogicalAnd(_)
| HirExpr::LogicalOr(_)
| HirExpr::Decision(_)
| HirExpr::Call(_)
| HirExpr::VarArg
| HirExpr::Unresolved(_) => None,
}
}
pub(super) fn expr_is_boolean_valued(expr: &HirExpr) -> bool {
match expr {
HirExpr::Boolean(_) => true,
HirExpr::Unary(unary) if unary.op == HirUnaryOpKind::Not => true,
HirExpr::Binary(binary) => matches!(
binary.op,
HirBinaryOpKind::Eq | HirBinaryOpKind::Lt | HirBinaryOpKind::Le
),
HirExpr::Decision(decision) => decision.nodes.iter().all(|node| {
decision_target_is_boolean(&node.truthy) && decision_target_is_boolean(&node.falsy)
}),
_ => false,
}
}
fn decision_target_is_boolean(target: &HirDecisionTarget) -> bool {
match target {
HirDecisionTarget::Node(_) | HirDecisionTarget::CurrentValue => false,
HirDecisionTarget::Expr(expr) => expr_is_boolean_valued(expr),
}
}
pub(super) fn simplify_lua_logical_shape(expr: &HirExpr) -> Option<HirExpr> {
match expr {
HirExpr::LogicalAnd(logical) => simplify_logical_and(&logical.lhs, &logical.rhs),
HirExpr::LogicalOr(logical) => simplify_logical_or(&logical.lhs, &logical.rhs),
_ => None,
}
}
pub(super) fn simplify_condition_truthiness_shape(expr: &HirExpr) -> Option<HirExpr> {
match expr {
HirExpr::LogicalAnd(logical) => simplify_condition_logical_and(&logical.lhs, &logical.rhs),
HirExpr::LogicalOr(logical) => simplify_condition_logical_or(&logical.lhs, &logical.rhs),
_ => None,
}
}
fn simplify_logical_and(lhs: &HirExpr, rhs: &HirExpr) -> Option<HirExpr> {
if lhs == rhs {
return Some(lhs.clone());
}
if let Some(replacement) = fold_associative_duplicate_and(lhs, rhs) {
return Some(replacement);
}
match rhs {
HirExpr::LogicalOr(inner) if lhs == &inner.lhs => Some(lhs.clone()),
_ => match lhs {
HirExpr::LogicalOr(inner) if rhs == &inner.lhs || rhs == &inner.rhs => {
Some(rhs.clone())
}
_ => None,
},
}
}
fn simplify_logical_or(lhs: &HirExpr, rhs: &HirExpr) -> Option<HirExpr> {
if lhs == rhs {
return Some(lhs.clone());
}
if let Some(replacement) = fold_associative_duplicate_or(lhs, rhs) {
return Some(replacement);
}
if let Some(replacement) = factor_shared_and_guards(lhs, rhs) {
return Some(replacement);
}
if let Some(replacement) = pull_shared_or_tail(lhs, rhs) {
return Some(replacement);
}
match rhs {
HirExpr::LogicalAnd(inner) if lhs == &inner.lhs => Some(lhs.clone()),
_ => match lhs {
HirExpr::LogicalAnd(inner) if rhs == &inner.lhs || rhs == &inner.rhs => {
Some(rhs.clone())
}
_ => None,
},
}
}
fn fold_associative_duplicate_and(lhs: &HirExpr, rhs: &HirExpr) -> Option<HirExpr> {
match lhs {
HirExpr::LogicalAnd(inner) if rhs == &inner.lhs || rhs == &inner.rhs => Some(lhs.clone()),
_ => match rhs {
HirExpr::LogicalAnd(inner) if lhs == &inner.lhs || lhs == &inner.rhs => {
Some(rhs.clone())
}
_ => None,
},
}
}
fn fold_associative_duplicate_or(lhs: &HirExpr, rhs: &HirExpr) -> Option<HirExpr> {
match lhs {
HirExpr::LogicalOr(inner) if rhs == &inner.lhs || rhs == &inner.rhs => Some(lhs.clone()),
_ => match rhs {
HirExpr::LogicalOr(inner) if lhs == &inner.lhs || lhs == &inner.rhs => {
Some(rhs.clone())
}
_ => None,
},
}
}
fn factor_shared_and_guards(lhs: &HirExpr, rhs: &HirExpr) -> Option<HirExpr> {
factor_shared_and_guards_one_side(lhs, rhs)
.or_else(|| factor_shared_and_guards_one_side(rhs, lhs))
}
fn factor_shared_and_guards_one_side(lhs: &HirExpr, rhs: &HirExpr) -> Option<HirExpr> {
let HirExpr::LogicalAnd(lhs_and) = lhs else {
return None;
};
let HirExpr::LogicalAnd(rhs_and) = rhs else {
return None;
};
if lhs_and.lhs == rhs_and.lhs {
return Some(logical_and(
lhs_and.lhs.clone(),
logical_or(lhs_and.rhs.clone(), rhs_and.rhs.clone()),
));
}
if lhs_and.rhs == rhs_and.rhs {
return Some(logical_and(
logical_or(lhs_and.lhs.clone(), rhs_and.lhs.clone()),
lhs_and.rhs.clone(),
));
}
None
}
fn pull_shared_or_tail(lhs: &HirExpr, rhs: &HirExpr) -> Option<HirExpr> {
pull_shared_or_tail_one_side(lhs, rhs).or_else(|| pull_shared_or_tail_one_side(rhs, lhs))
}
fn pull_shared_or_tail_one_side(lhs: &HirExpr, rhs: &HirExpr) -> Option<HirExpr> {
let HirExpr::LogicalAnd(lhs_and) = lhs else {
return None;
};
let HirExpr::LogicalOr(inner_or) = &lhs_and.rhs else {
return None;
};
if rhs != &inner_or.rhs {
return None;
}
Some(logical_or(
logical_and(lhs_and.lhs.clone(), inner_or.lhs.clone()),
rhs.clone(),
))
}
fn simplify_condition_logical_and(lhs: &HirExpr, rhs: &HirExpr) -> Option<HirExpr> {
if matches!(rhs, HirExpr::Boolean(true)) {
return Some(lhs.clone());
}
if matches!(rhs, HirExpr::Boolean(false)) {
return Some(HirExpr::Boolean(false));
}
if matches!(lhs, HirExpr::Boolean(true)) {
return Some(rhs.clone());
}
if matches!(lhs, HirExpr::Boolean(false)) {
return Some(HirExpr::Boolean(false));
}
None
}
fn simplify_condition_logical_or(lhs: &HirExpr, rhs: &HirExpr) -> Option<HirExpr> {
if matches!(rhs, HirExpr::Boolean(false)) {
return Some(lhs.clone());
}
if matches!(rhs, HirExpr::Boolean(true)) {
return Some(HirExpr::Boolean(true));
}
if matches!(lhs, HirExpr::Boolean(false)) {
return Some(rhs.clone());
}
if matches!(lhs, HirExpr::Boolean(true)) {
return Some(HirExpr::Boolean(true));
}
None
}