use crate::ast::{BinOp, VerifyBlock, VerifyLaw};
use crate::codegen::CodegenContext;
use crate::ir::WrapperDriver;
use super::super::super::expr::aver_name_to_lean;
use super::super::AutoProof;
#[allow(clippy::too_many_arguments)]
pub(in super::super) fn emit_wrapper_over_recursion_law(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
wrapper_fn: &str,
inner_fn: &str,
other_fn: &str,
combine_op: BinOp,
driver: &WrapperDriver,
combine_fn: Option<&str>,
) -> Option<AutoProof> {
let wrapper_l = aver_name_to_lean(wrapper_fn);
let inner_l = aver_name_to_lean(inner_fn);
let other_l = aver_name_to_lean(other_fn);
match driver {
WrapperDriver::List => emit_list_fold(&wrapper_l, &inner_l, &other_l, combine_op),
WrapperDriver::PeanoNat { value_first, .. } => emit_peano_nat_fold(
vb,
law,
ctx,
&wrapper_l,
&inner_l,
&other_l,
combine_op,
combine_fn?,
*value_first,
),
}
}
#[allow(clippy::too_many_arguments)]
pub(in super::super) fn emit_tailrec_fixed_base_fold_law(
law: &VerifyLaw,
ctx: &CodegenContext,
theorem_base: &str,
spec_fn: &str,
loop_fn: &str,
combine_fn: &str,
combine_op: BinOp,
) -> Option<AutoProof> {
if !matches!(combine_op, BinOp::Add | BinOp::Mul) {
return None;
}
let spec_l = aver_name_to_lean(spec_fn);
let loop_l = aver_name_to_lean(loop_fn);
let combine_l = aver_name_to_lean(combine_fn);
let neutral = if combine_op == BinOp::Mul { "1" } else { "0" };
let base = theorem_base.to_string();
let acc_thm = format!("{base}__acc");
let mut extra = std::collections::BTreeSet::new();
if let Some(cfd) = ctx.fn_def_by_name(combine_fn, ctx.active_module_scope().as_deref()) {
crate::codegen::proof_recognize::collect_called_fns_in_body(&cfd.body, &mut extra);
}
let (bridge_support, simp_extra, _bridged) =
super::super::induction::lean_nat_lift_support(law, ctx, &base, &extra);
let bridges: Vec<String> = simp_extra
.into_iter()
.filter(|n| n.starts_with(&base))
.collect();
if bridges.is_empty() {
return None;
}
let bridge_set = bridges.join(", ");
let (decomp_close, main_close) = match combine_op {
BinOp::Mul => (
format!(
"simp [{bridge_set}, Nat.mul_assoc, Nat.mul_comm, Nat.mul_left_comm, Nat.mul_one]"
),
format!(
"simp [{bridge_set}, Nat.mul_assoc, Nat.mul_comm, Nat.mul_left_comm, Nat.mul_one]"
),
),
_ => (
format!("simp only [{bridge_set}]; omega"),
format!("simp only [{bridge_set}]; omega"),
),
};
let mut lines: Vec<String> = bridge_support;
lines.push(format!(
"theorem {acc_thm} : ∀ (x y z : Nat), {loop_l} x y z = {combine_l} ({loop_l} x y {neutral}) z := by"
));
lines.push(" intro x y z".to_string());
lines.push(" induction y generalizing z with".to_string());
lines.push(format!(" | zero => simp [{loop_l}, {bridge_set}]"));
lines.push(format!(
" | succ m ih => simp only [{loop_l}]; rw [ih ({combine_l} x z), ih ({combine_l} x {neutral})]; {decomp_close}"
));
lines.push(format!(
"theorem {base} : ∀ (x y : Nat), {spec_l} x y = {loop_l} x y {neutral} := by"
));
lines.push(" intro x y".to_string());
lines.push(" induction y with".to_string());
lines.push(format!(" | zero => simp [{spec_l}, {loop_l}]"));
lines.push(" | succ m ih =>".to_string());
lines.push(format!(" simp only [{spec_l}, {loop_l}]"));
lines.push(format!(" rw [{acc_thm} x m ({combine_l} x {neutral})]"));
lines.push(" rw [ih]".to_string());
lines.push(format!(" {main_close}"));
Some(AutoProof {
support_lines: lines,
body: crate::codegen::lean::tactic_ir::Tactic::raw(Vec::new()),
replaces_theorem: true,
})
}
fn emit_list_fold(
wrapper_l: &str,
inner_l: &str,
other_l: &str,
combine_op: BinOp,
) -> Option<AutoProof> {
let op = match combine_op {
BinOp::Add => "+",
BinOp::Mul => "*",
BinOp::Sub => "-",
_ => return None,
};
let neutral = match combine_op {
BinOp::Mul => "1",
_ => "0",
};
let acc_thm = format!("{inner_l}_acc");
let (acc_close, main_close) = match combine_op {
BinOp::Mul => (
"simp [Int.one_mul, Int.mul_one, Int.mul_assoc, Int.mul_comm, Int.mul_left_comm]",
"simp [ih, Int.one_mul, Int.mul_one, Int.mul_assoc, Int.mul_comm, Int.mul_left_comm]",
),
_ => ("omega", "omega"),
};
let support_lines = vec![
format!(
"theorem {acc_thm} (xs : List Int) (a : Int) : {inner_l} xs a = a {op} {inner_l} xs {neutral} := by"
),
" induction xs generalizing a with".to_string(),
format!(" | nil => simp [{inner_l}]"),
format!(
" | cons h t ih => simp only [{inner_l}]; rw [ih (a {op} h), ih ({neutral} {op} h)]; {acc_close}"
),
];
let proof_lines = vec![
" intro xs".to_string(),
" induction xs with".to_string(),
format!(" | nil => simp [{wrapper_l}, {inner_l}, {other_l}]"),
" | cons h t ih =>".to_string(),
format!(" simp only [{wrapper_l}, {inner_l}, {other_l}]"),
format!(" rw [{acc_thm} t ({neutral} {op} h)]"),
format!(" simp only [{wrapper_l}] at ih"),
format!(" {main_close}"),
];
Some(AutoProof {
support_lines,
body: crate::codegen::lean::tactic_ir::Tactic::raw(proof_lines),
replaces_theorem: false,
})
}
#[allow(clippy::too_many_arguments)]
fn emit_peano_nat_fold(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
wrapper_l: &str,
inner_l: &str,
other_l: &str,
combine_op: BinOp,
combine_fn: &str,
value_first: bool,
) -> Option<AutoProof> {
if !matches!(combine_op, BinOp::Add | BinOp::Mul) {
return None;
}
let neutral = if combine_op == BinOp::Mul { "1" } else { "0" };
let combine_l = aver_name_to_lean(combine_fn);
let acc_thm = format!("{inner_l}_acc");
let law_uid = format!(
"{}_{}",
aver_name_to_lean(&vb.fn_name),
aver_name_to_lean(&law.name)
);
let mut extra = std::collections::BTreeSet::new();
if let Some(cfd) = ctx.fn_def_by_name(combine_fn, ctx.active_module_scope().as_deref()) {
crate::codegen::proof_recognize::collect_called_fns_in_body(&cfd.body, &mut extra);
}
let (bridge_support, simp_extra, _bridged) =
super::super::induction::lean_nat_lift_support(law, ctx, &law_uid, &extra);
if simp_extra.is_empty() {
return None;
}
let simp_set = simp_extra.join(", ");
let (decomp_close, main_close) = match combine_op {
BinOp::Mul => (
format!("simp [{simp_set}]"),
format!("simp [{simp_set}, Nat.mul_comm]"),
),
_ => (
format!("simp only [{simp_set}]; omega"),
format!("simp only [{simp_set}]; omega"),
),
};
let combine_apply = |value: &str, other: &str| -> String {
if value_first {
format!("{combine_l} {value} {other}")
} else {
format!("{combine_l} {other} {value}")
}
};
let succ_acc = combine_apply("(m + 1)", "acc");
let succ_one = combine_apply("(m + 1)", neutral);
let mut support_lines = bridge_support;
support_lines.push(format!(
"theorem {acc_thm} : ∀ (n acc : Nat), {inner_l} n acc = {combine_l} ({inner_l} n {neutral}) acc := by"
));
support_lines.push(" intro n acc".to_string());
support_lines.push(" induction n generalizing acc with".to_string());
support_lines.push(format!(" | zero => simp [{inner_l}, {simp_set}]"));
support_lines.push(format!(
" | succ m ih => simp only [{inner_l}]; rw [ih ({succ_acc}), ih ({succ_one})]; {decomp_close}"
));
let main_step = combine_apply("(m + 1)", neutral);
let proof_lines = vec![
" intro n".to_string(),
format!(" simp only [{wrapper_l}, Nat.zero_add]"),
" induction n with".to_string(),
format!(" | zero => simp [{inner_l}, {other_l}]"),
" | succ m ih =>".to_string(),
format!(" simp only [{inner_l}, {other_l}]"),
format!(" rw [{acc_thm} m ({main_step})]"),
" rw [ih]".to_string(),
format!(" {main_close}"),
];
Some(AutoProof {
support_lines,
body: crate::codegen::lean::tactic_ir::Tactic::raw(proof_lines),
replaces_theorem: false,
})
}