use super::AutoProof;
use super::aver_name_to_lean;
use super::shared::{
PositivityFact, clause_gives_nonneg, clause_is_lt, divisor_positivity, expr_dotted_name,
flatten_and, floor_call, is_euclidean_floor_fn, render,
};
use crate::ast::{BinOp, Expr, Spanned, VerifyBlock, VerifyLaw};
use crate::codegen::CodegenContext;
fn floordiv_eq_lemma(base: &str, floor: &str) -> String {
format!(
r#"theorem {base}__floordiv_eq (a d : Int) (hd : 0 < d) : {floor} a d = a / d := by
have hne : ¬((d == 0) = true) := by simp only [beq_iff_eq]; omega
simp only [{floor}]
rw [if_neg hne]
simp only [Except.withDefault]"#
)
}
fn intro_names(law: &VerifyLaw) -> String {
law.givens
.iter()
.map(|g| aver_name_to_lean(&g.name))
.collect::<Vec<_>>()
.join(" ")
}
fn atom_arg(render: &str) -> String {
if render.contains(char::is_whitespace) {
format!("({render})")
} else {
render.to_string()
}
}
fn pos_have(atom: &str, fact: &PositivityFact) -> (String, String) {
let ty = if fact.needs_int_ascription() {
format!("({atom} : Int)")
} else {
atom.to_string()
};
(ty, fact.lean_term())
}
fn split_shared(l_r: &str, r_r: &str, target: &str) -> Option<(String, bool)> {
if l_r == target {
Some((r_r.to_string(), true))
} else if r_r == target {
Some((l_r.to_string(), false))
} else {
None
}
}
struct CancelShape {
floor_lean: String,
a: String,
d: String,
c: String,
d_fact: PositivityFact,
c_fact: PositivityFact,
dividend: String,
divisor: String,
c_left_in_dividend: bool,
c_left_in_divisor: bool,
}
fn recognize_cancel(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Option<CancelShape> {
let Expr::FnCall(callee, args) = &law.rhs.node else {
return None;
};
let floor_src = expr_dotted_name(callee)?;
if args.len() != 2 {
return None;
}
let a_render = render(&args[0], ctx);
let d_render = render(&args[1], ctx);
let (prod_a, prod_d) = floor_call(&law.lhs, &floor_src)?;
let Expr::BinOp(BinOp::Mul, a_l, c_a) = &prod_a.node else {
return None;
};
let Expr::BinOp(BinOp::Mul, d_l, c_d) = &prod_d.node else {
return None;
};
let (c_from_a, a_left) = split_shared(&render(a_l, ctx), &render(c_a, ctx), &a_render)?;
let (c_from_d, d_left) = split_shared(&render(d_l, ctx), &render(c_d, ctx), &d_render)?;
if c_from_a != c_from_d {
return None;
}
let c_render = c_from_a;
let (d_expr, c_expr): (&Spanned<Expr>, &Spanned<Expr>) =
if d_left { (d_l, c_d) } else { (c_d, d_l) };
if !is_euclidean_floor_fn(&floor_src, ctx) {
return None;
}
let when = law.when.as_ref()?;
let mut clauses = Vec::new();
flatten_and(when, &mut clauses);
let d_fact = divisor_positivity(d_expr, &clauses, ctx, vb.line)?;
let c_fact = divisor_positivity(c_expr, &clauses, ctx, vb.line)?;
Some(CancelShape {
floor_lean: aver_name_to_lean(&floor_src),
a: a_render,
d: d_render,
c: c_render,
d_fact,
c_fact,
dividend: render(prod_a, ctx),
divisor: render(prod_d, ctx),
c_left_in_dividend: !a_left,
c_left_in_divisor: !d_left,
})
}
pub(in crate::codegen::lean) fn recognize_cancel_common_factor(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> bool {
recognize_cancel(vb, law, ctx).is_some()
}
pub(super) fn emit_cancel_common_factor_law(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
theorem_base: &str,
quant_params: &str,
) -> Option<AutoProof> {
let CancelShape {
floor_lean: floor,
a,
d,
c,
d_fact,
c_fact,
dividend,
divisor,
c_left_in_dividend,
c_left_in_divisor,
} = recognize_cancel(vb, law, ctx)?;
let when = render(law.when.as_ref()?, ctx);
let lhs = render(&law.lhs, ctx);
let rhs = render(&law.rhs, ctx);
let (d_ty, d_pos) = pos_have(&d, &d_fact);
let (c_ty, c_pos) = pos_have(&c, &c_fact);
let hdc = if c_left_in_divisor {
"Int.mul_pos hc hd" } else {
"Int.mul_pos hd hc" };
let divisor_ty = if d_fact.needs_int_ascription() && c_fact.needs_int_ascription() {
format!("({divisor} : Int)")
} else {
divisor.clone()
};
let (aw, dw, cw) = (atom_arg(&a), atom_arg(&d), atom_arg(&c));
let mut normalize = String::new();
if c_left_in_dividend {
normalize.push_str(&format!("\n rw [Int.mul_comm {cw} {aw}]"));
}
if c_left_in_divisor {
normalize.push_str(&format!("\n rw [Int.mul_comm {cw} {dw}]"));
}
let text = format!(
r#"{peel}
theorem {base} : ∀ {quant}, {when} = true -> {lhs} = {rhs} := by
intro {intro} h_when
simp only [Bool.and_eq_true, decide_eq_true_eq, ge_iff_le, gt_iff_lt] at h_when
have hd : 0 < {d_ty} := {d_pos}
have hc : 0 < {c_ty} := {c_pos}
have hdc : 0 < {divisor_ty} := {hdc}
rw [{base}__floordiv_eq ({dividend}) ({divisor}) hdc, {base}__floordiv_eq {aw} {dw} hd]{normalize}
exact Int.mul_ediv_mul_of_pos_left {aw} {dw} hc"#,
peel = floordiv_eq_lemma(theorem_base, &floor),
base = theorem_base,
quant = quant_params,
intro = intro_names(law),
);
Some(AutoProof {
support_lines: text.lines().map(str::to_string).collect(),
body: crate::codegen::lean::tactic_ir::Tactic::raw(Vec::new()),
replaces_theorem: true,
})
}
struct AbsorbShape {
floor_lean: String,
d: String,
q: String,
r: String,
d_fact: PositivityFact,
dividend: String,
d_left_in_product: bool,
r_first: bool,
}
fn split_sum<'a>(
add_l: &'a Spanned<Expr>,
add_r: &'a Spanned<Expr>,
d_render: &str,
rhs_render: &str,
ctx: &CodegenContext,
) -> Option<(bool, &'a Spanned<Expr>, bool)> {
for (prod, rem, r_first) in [(add_l, add_r, false), (add_r, add_l, true)] {
let Expr::BinOp(BinOp::Mul, x, y) = &prod.node else {
continue;
};
let (xr, yr) = (render(x, ctx), render(y, ctx));
if xr == d_render && yr == rhs_render {
return Some((true, rem, r_first));
}
if yr == d_render && xr == rhs_render {
return Some((false, rem, r_first));
}
}
None
}
fn recognize_absorb(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Option<AbsorbShape> {
let floor_src = {
let Expr::FnCall(callee, _) = &law.lhs.node else {
return None;
};
expr_dotted_name(callee)?
};
let (dividend, d_l) = floor_call(&law.lhs, &floor_src)?;
let Expr::BinOp(BinOp::Add, add_l, add_r) = ÷nd.node else {
return None;
};
let d_render = render(d_l, ctx);
let q_render = render(&law.rhs, ctx);
let (d_left_in_product, rem, r_first) = split_sum(add_l, add_r, &d_render, &q_render, ctx)?;
let r_render = render(rem, ctx);
if !is_euclidean_floor_fn(&floor_src, ctx) {
return None;
}
let when = law.when.as_ref()?;
let mut clauses = Vec::new();
flatten_and(when, &mut clauses);
let d_fact = divisor_positivity(d_l, &clauses, ctx, vb.line)?;
let nonneg_r = clauses
.iter()
.any(|cl| clause_gives_nonneg(cl, &r_render, ctx));
let r_lt_d = clauses
.iter()
.any(|cl| clause_is_lt(cl, &r_render, &d_render, ctx));
if !nonneg_r || !r_lt_d {
return None;
}
Some(AbsorbShape {
floor_lean: aver_name_to_lean(&floor_src),
d: d_render,
q: q_render,
r: r_render,
d_fact,
dividend: render(dividend, ctx),
d_left_in_product,
r_first,
})
}
pub(in crate::codegen::lean) fn recognize_absorb_remainder(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> bool {
recognize_absorb(vb, law, ctx).is_some()
}
pub(super) fn emit_absorb_remainder_law(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
theorem_base: &str,
quant_params: &str,
) -> Option<AutoProof> {
let AbsorbShape {
floor_lean: floor,
d,
q,
r,
d_fact,
dividend,
d_left_in_product,
r_first,
} = recognize_absorb(vb, law, ctx)?;
let when = render(law.when.as_ref()?, ctx);
let lhs = render(&law.lhs, ctx);
let rhs = render(&law.rhs, ctx);
let (d_ty, d_pos) = pos_have(&d, &d_fact);
let (dw, qw, ra) = (atom_arg(&d), atom_arg(&q), atom_arg(&r));
let mut normalize = String::new();
if !d_left_in_product {
normalize.push_str(&format!("\n rw [Int.mul_comm {qw} {dw}]"));
}
if !r_first {
normalize.push_str(&format!(
"\n rw [show {d} * {q} + {r} = {r} + {d} * {q} from by omega]"
));
}
let text = format!(
r#"{peel}
theorem {base} : ∀ {quant}, {when} = true -> {lhs} = {rhs} := by
intro {intro} h_when
simp only [Bool.and_eq_true, decide_eq_true_eq, ge_iff_le, gt_iff_lt] at h_when
have hd : 0 < {d_ty} := {d_pos}
have h0 : 0 <= {r} := by omega
have hr : {r} < {d} := by omega
rw [{base}__floordiv_eq ({dividend}) {dw} hd]{normalize}
rw [Int.add_mul_ediv_left {ra} {qw} (by omega : {d_ty} ≠ 0)]
rw [Int.ediv_eq_zero_of_lt h0 hr]
omega"#,
peel = floordiv_eq_lemma(theorem_base, &floor),
base = theorem_base,
quant = quant_params,
intro = intro_names(law),
);
Some(AutoProof {
support_lines: text.lines().map(str::to_string).collect(),
body: crate::codegen::lean::tactic_ir::Tactic::raw(Vec::new()),
replaces_theorem: true,
})
}