use logicaffeine_kernel::{Context, KernelError, KernelResult, Term};
use crate::{DerivationTree, InferenceRule, ProofExpr, ProofTerm};
struct InductionState {
fix_name: String,
step_var: String,
ih_conclusion: ProofExpr,
}
pub struct CertificationContext<'a> {
kernel_ctx: &'a Context,
locals: Vec<String>,
induction_state: Option<InductionState>,
}
impl<'a> CertificationContext<'a> {
pub fn new(kernel_ctx: &'a Context) -> Self {
Self {
kernel_ctx,
locals: Vec::new(),
induction_state: None,
}
}
fn with_local(&self, name: &str) -> Self {
let mut new_locals = self.locals.clone();
new_locals.push(name.to_string());
Self {
kernel_ctx: self.kernel_ctx,
locals: new_locals,
induction_state: self.induction_state.clone(),
}
}
fn with_induction(&self, fix_name: &str, step_var: &str, ih: ProofExpr) -> Self {
Self {
kernel_ctx: self.kernel_ctx,
locals: self.locals.clone(),
induction_state: Some(InductionState {
fix_name: fix_name.to_string(),
step_var: step_var.to_string(),
ih_conclusion: ih,
}),
}
}
fn is_local(&self, name: &str) -> bool {
self.locals.iter().any(|n| n == name)
}
fn get_ih_term(&self, conclusion: &ProofExpr) -> Option<Term> {
if let Some(state) = &self.induction_state {
if conclusions_match(conclusion, &state.ih_conclusion) {
return Some(Term::App(
Box::new(Term::Var(state.fix_name.clone())),
Box::new(Term::Var(state.step_var.clone())),
));
}
}
None
}
}
impl Clone for InductionState {
fn clone(&self) -> Self {
Self {
fix_name: self.fix_name.clone(),
step_var: self.step_var.clone(),
ih_conclusion: self.ih_conclusion.clone(),
}
}
}
fn conclusions_match(a: &ProofExpr, b: &ProofExpr) -> bool {
a == b
}
pub fn certify(tree: &DerivationTree, ctx: &CertificationContext) -> KernelResult<Term> {
match &tree.rule {
InferenceRule::Axiom | InferenceRule::PremiseMatch => {
certify_hypothesis(&tree.conclusion, ctx)
}
InferenceRule::ModusPonens => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"ModusPonens requires exactly 2 premises".to_string(),
));
}
let impl_term = certify(&tree.premises[0], ctx)?;
let arg_term = certify(&tree.premises[1], ctx)?;
Ok(Term::App(Box::new(impl_term), Box::new(arg_term)))
}
InferenceRule::ConjunctionIntro => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"ConjunctionIntro requires exactly 2 premises".to_string(),
));
}
let (p_type, q_type) = extract_and_types(&tree.conclusion)?;
let p_term = certify(&tree.premises[0], ctx)?;
let q_term = certify(&tree.premises[1], ctx)?;
let conj = Term::Global("conj".to_string());
let applied = Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(Box::new(conj), Box::new(p_type))),
Box::new(q_type),
)),
Box::new(p_term),
)),
Box::new(q_term),
);
Ok(applied)
}
InferenceRule::UniversalInst(witness) => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"UniversalInst requires exactly 1 premise".to_string(),
));
}
let forall_proof = certify(&tree.premises[0], ctx)?;
let witness_term = if ctx.is_local(witness) {
Term::Var(witness.clone())
} else {
Term::Global(witness.clone())
};
Ok(Term::App(Box::new(forall_proof), Box::new(witness_term)))
}
InferenceRule::UniversalIntro { variable, var_type } => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"UniversalIntro requires exactly 1 premise".to_string(),
));
}
let type_term = Term::Global(var_type.clone());
let extended_ctx = ctx.with_local(variable);
let body_term = certify(&tree.premises[0], &extended_ctx)?;
Ok(Term::Lambda {
param: variable.clone(),
param_type: Box::new(type_term),
body: Box::new(body_term),
})
}
InferenceRule::StructuralInduction {
variable: var_name,
ind_type,
step_var,
} => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"StructuralInduction requires exactly 2 premises (base, step)".to_string(),
));
}
let motive_body = extract_motive_body(&tree.conclusion, var_name)?;
let fix_name = format!("rec_{}", var_name);
let base_term = certify(&tree.premises[0], ctx)?;
let ih_conclusion = compute_ih_conclusion(&tree.conclusion, var_name, step_var)?;
let step_ctx = ctx
.with_local(step_var)
.with_induction(&fix_name, step_var, ih_conclusion);
let step_body = certify(&tree.premises[1], &step_ctx)?;
let step_term = Term::Lambda {
param: step_var.clone(),
param_type: Box::new(Term::Global(ind_type.clone())),
body: Box::new(step_body),
};
let match_term = Term::Match {
discriminant: Box::new(Term::Var(var_name.clone())),
motive: Box::new(build_motive(ind_type, &motive_body, var_name)),
cases: vec![base_term, step_term],
};
let lambda_term = Term::Lambda {
param: var_name.clone(),
param_type: Box::new(Term::Global(ind_type.clone())),
body: Box::new(match_term),
};
Ok(Term::Fix {
name: fix_name,
body: Box::new(lambda_term),
})
}
InferenceRule::ExistentialIntro {
witness: witness_str,
witness_type,
} => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"ExistentialIntro requires exactly 1 premise".to_string(),
));
}
let (variable, body) = match &tree.conclusion {
ProofExpr::Exists { variable, body } => (variable.clone(), body.as_ref().clone()),
_ => {
return Err(KernelError::CertificationError(
"ExistentialIntro conclusion must be Exists".to_string(),
))
}
};
let witness_term = if ctx.is_local(witness_str) {
Term::Var(witness_str.clone())
} else {
Term::Global(witness_str.clone())
};
let proof_term = certify(&tree.premises[0], ctx)?;
let type_a = Term::Global(witness_type.clone());
let predicate = match &body {
ProofExpr::Predicate { name, args, .. }
if args.len() == 1
&& matches!(&args[0], ProofTerm::Variable(v) if v == &variable) =>
{
Term::Global(name.clone())
}
_ => {
let body_type = proof_expr_to_type(&body)?;
Term::Lambda {
param: variable.clone(),
param_type: Box::new(type_a.clone()),
body: Box::new(body_type),
}
}
};
let witness_ctor = Term::Global("witness".to_string());
let applied = Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(Box::new(witness_ctor), Box::new(type_a))),
Box::new(predicate),
)),
Box::new(witness_term),
)),
Box::new(proof_term),
);
Ok(applied)
}
InferenceRule::Rewrite { from, to } => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"Rewrite requires exactly 2 premises (equality, source)".to_string(),
));
}
let eq_proof = certify(&tree.premises[0], ctx)?;
let source_proof = certify(&tree.premises[1], ctx)?;
let from_term = proof_term_to_kernel_term(from)?;
let to_term = proof_term_to_kernel_term(to)?;
let predicate = build_equality_predicate(&tree.conclusion, to)?;
let eq_rec = Term::Global("Eq_rec".to_string());
let entity = Term::Global("Entity".to_string());
let applied = Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(Box::new(eq_rec), Box::new(entity))),
Box::new(from_term),
)),
Box::new(predicate),
)),
Box::new(source_proof),
)),
Box::new(to_term),
)),
Box::new(eq_proof),
);
Ok(applied)
}
InferenceRule::EqualitySymmetry => {
if tree.premises.len() != 1 {
return Err(KernelError::CertificationError(
"EqualitySymmetry requires exactly 1 premise".to_string(),
));
}
let premise_proof = certify(&tree.premises[0], ctx)?;
let (x, y) = match &tree.premises[0].conclusion {
ProofExpr::Identity(l, r) => {
(proof_term_to_kernel_term(l)?, proof_term_to_kernel_term(r)?)
}
_ => {
return Err(KernelError::CertificationError(
"EqualitySymmetry premise must be an Identity".to_string(),
))
}
};
let eq_sym = Term::Global("Eq_sym".to_string());
let entity = Term::Global("Entity".to_string());
Ok(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(Box::new(eq_sym), Box::new(entity))),
Box::new(x),
)),
Box::new(y),
)),
Box::new(premise_proof),
))
}
InferenceRule::EqualityTransitivity => {
if tree.premises.len() != 2 {
return Err(KernelError::CertificationError(
"EqualityTransitivity requires exactly 2 premises".to_string(),
));
}
let proof1 = certify(&tree.premises[0], ctx)?;
let proof2 = certify(&tree.premises[1], ctx)?;
let (x, y) = match &tree.premises[0].conclusion {
ProofExpr::Identity(l, r) => {
(proof_term_to_kernel_term(l)?, proof_term_to_kernel_term(r)?)
}
_ => {
return Err(KernelError::CertificationError(
"EqualityTransitivity first premise must be Identity".to_string(),
))
}
};
let z = match &tree.premises[1].conclusion {
ProofExpr::Identity(_, r) => proof_term_to_kernel_term(r)?,
_ => {
return Err(KernelError::CertificationError(
"EqualityTransitivity second premise must be Identity".to_string(),
))
}
};
let eq_trans = Term::Global("Eq_trans".to_string());
let entity = Term::Global("Entity".to_string());
Ok(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::App(Box::new(eq_trans), Box::new(entity))),
Box::new(x),
)),
Box::new(y),
)),
Box::new(z),
)),
Box::new(proof1),
)),
Box::new(proof2),
))
}
rule => Err(KernelError::CertificationError(format!(
"Certification not implemented for {:?}",
rule
))),
}
}
fn build_equality_predicate(goal: &ProofExpr, replace_term: &ProofTerm) -> KernelResult<Term> {
if let ProofExpr::Predicate { name, args, .. } = goal {
if args.len() == 1 && &args[0] == replace_term {
return Ok(Term::Global(name.clone()));
}
}
let goal_type = proof_expr_to_type(goal)?;
let param_name = "_eq_var".to_string();
let substituted = substitute_term_in_kernel(
&goal_type,
&proof_term_to_kernel_term(replace_term)?,
&Term::Var(param_name.clone()),
);
Ok(Term::Lambda {
param: param_name,
param_type: Box::new(Term::Global("Entity".to_string())),
body: Box::new(substituted),
})
}
fn substitute_term_in_kernel(term: &Term, from: &Term, to: &Term) -> Term {
if term == from {
return to.clone();
}
match term {
Term::App(f, a) => Term::App(
Box::new(substitute_term_in_kernel(f, from, to)),
Box::new(substitute_term_in_kernel(a, from, to)),
),
Term::Pi {
param,
param_type,
body_type,
} => Term::Pi {
param: param.clone(),
param_type: Box::new(substitute_term_in_kernel(param_type, from, to)),
body_type: Box::new(substitute_term_in_kernel(body_type, from, to)),
},
Term::Lambda {
param,
param_type,
body,
} => Term::Lambda {
param: param.clone(),
param_type: Box::new(substitute_term_in_kernel(param_type, from, to)),
body: Box::new(substitute_term_in_kernel(body, from, to)),
},
Term::Match {
discriminant,
motive,
cases,
} => Term::Match {
discriminant: Box::new(substitute_term_in_kernel(discriminant, from, to)),
motive: Box::new(substitute_term_in_kernel(motive, from, to)),
cases: cases
.iter()
.map(|c| substitute_term_in_kernel(c, from, to))
.collect(),
},
Term::Fix { name, body } => Term::Fix {
name: name.clone(),
body: Box::new(substitute_term_in_kernel(body, from, to)),
},
other => other.clone(),
}
}
fn certify_hypothesis(conclusion: &ProofExpr, ctx: &CertificationContext) -> KernelResult<Term> {
if let Some(ih_term) = ctx.get_ih_term(conclusion) {
return Ok(ih_term);
}
match conclusion {
ProofExpr::Atom(name) => {
if ctx.is_local(name) {
return Ok(Term::Var(name.clone()));
}
if ctx.kernel_ctx.get_global(name).is_some() {
Ok(Term::Global(name.clone()))
} else {
Err(KernelError::CertificationError(format!(
"Unknown hypothesis: {}",
name
)))
}
}
ProofExpr::Predicate { name, args, .. } => {
let mut target_type = Term::Global(name.clone());
for arg in args {
let arg_term = proof_term_to_kernel_term(arg)?;
target_type = Term::App(Box::new(target_type), Box::new(arg_term));
}
for (decl_name, decl_type) in ctx.kernel_ctx.iter_declarations() {
if types_structurally_match(&target_type, decl_type) {
return Ok(Term::Global(decl_name.to_string()));
}
}
Err(KernelError::CertificationError(format!(
"Cannot find hypothesis with type: {:?}",
conclusion
)))
}
ProofExpr::ForAll { .. } | ProofExpr::Implies(_, _) | ProofExpr::Identity(_, _) => {
let target_type = proof_expr_to_type(conclusion)?;
for (name, decl_type) in ctx.kernel_ctx.iter_declarations() {
if types_structurally_match(&target_type, decl_type) {
return Ok(Term::Global(name.to_string()));
}
}
Err(KernelError::CertificationError(format!(
"Cannot find hypothesis with type matching: {:?}",
conclusion
)))
}
_ => Err(KernelError::CertificationError(format!(
"Cannot certify hypothesis: {:?}",
conclusion
))),
}
}
fn types_structurally_match(a: &Term, b: &Term) -> bool {
types_alpha_equiv(a, b, &mut Vec::new())
}
fn types_alpha_equiv(a: &Term, b: &Term, bindings: &mut Vec<(String, String)>) -> bool {
match (a, b) {
(Term::Sort(u1), Term::Sort(u2)) => u1 == u2,
(Term::Var(v1), Term::Var(v2)) => {
for (bound_a, bound_b) in bindings.iter().rev() {
if v1 == bound_a {
return v2 == bound_b;
}
if v2 == bound_b {
return false; }
}
v1 == v2
}
(Term::Global(g1), Term::Global(g2)) => g1 == g2,
(Term::App(f1, a1), Term::App(f2, a2)) => {
types_alpha_equiv(f1, f2, bindings) && types_alpha_equiv(a1, a2, bindings)
}
(
Term::Pi {
param: p1,
param_type: pt1,
body_type: bt1,
},
Term::Pi {
param: p2,
param_type: pt2,
body_type: bt2,
},
) => {
if !types_alpha_equiv(pt1, pt2, bindings) {
return false;
}
bindings.push((p1.clone(), p2.clone()));
let result = types_alpha_equiv(bt1, bt2, bindings);
bindings.pop();
result
}
(
Term::Lambda {
param: p1,
param_type: pt1,
body: b1,
},
Term::Lambda {
param: p2,
param_type: pt2,
body: b2,
},
) => {
if !types_alpha_equiv(pt1, pt2, bindings) {
return false;
}
bindings.push((p1.clone(), p2.clone()));
let result = types_alpha_equiv(b1, b2, bindings);
bindings.pop();
result
}
_ => false,
}
}
fn extract_and_types(conclusion: &ProofExpr) -> KernelResult<(Term, Term)> {
match conclusion {
ProofExpr::And(p, q) => {
let p_term = proof_expr_to_type(p)?;
let q_term = proof_expr_to_type(q)?;
Ok((p_term, q_term))
}
_ => Err(KernelError::CertificationError(format!(
"Expected And, got {:?}",
conclusion
))),
}
}
fn proof_expr_to_type(expr: &ProofExpr) -> KernelResult<Term> {
match expr {
ProofExpr::Atom(name) => Ok(Term::Global(name.clone())),
ProofExpr::And(p, q) => Ok(Term::App(
Box::new(Term::App(
Box::new(Term::Global("And".to_string())),
Box::new(proof_expr_to_type(p)?),
)),
Box::new(proof_expr_to_type(q)?),
)),
ProofExpr::Or(p, q) => Ok(Term::App(
Box::new(Term::App(
Box::new(Term::Global("Or".to_string())),
Box::new(proof_expr_to_type(p)?),
)),
Box::new(proof_expr_to_type(q)?),
)),
ProofExpr::Implies(p, q) => Ok(Term::Pi {
param: "_".to_string(),
param_type: Box::new(proof_expr_to_type(p)?),
body_type: Box::new(proof_expr_to_type(q)?),
}),
ProofExpr::ForAll { variable, body } => {
let body_type = proof_expr_to_type(body)?;
Ok(Term::Pi {
param: variable.clone(),
param_type: Box::new(Term::Global("Entity".to_string())),
body_type: Box::new(body_type),
})
}
ProofExpr::Predicate { name, args, .. } => {
let mut result = Term::Global(name.clone());
for arg in args {
let arg_term = proof_term_to_kernel_term(arg)?;
result = Term::App(Box::new(result), Box::new(arg_term));
}
Ok(result)
}
ProofExpr::Identity(l, r) => {
let l_term = proof_term_to_kernel_term(l)?;
let r_term = proof_term_to_kernel_term(r)?;
Ok(Term::App(
Box::new(Term::App(
Box::new(Term::App(
Box::new(Term::Global("Eq".to_string())),
Box::new(Term::Global("Entity".to_string())),
)),
Box::new(l_term),
)),
Box::new(r_term),
))
}
ProofExpr::Exists { variable, body } => {
let var_type = Term::Global("Nat".to_string()); let body_type = proof_expr_to_type(body)?;
let predicate = Term::Lambda {
param: variable.clone(),
param_type: Box::new(var_type.clone()),
body: Box::new(body_type),
};
Ok(Term::App(
Box::new(Term::App(
Box::new(Term::Global("Ex".to_string())),
Box::new(var_type),
)),
Box::new(predicate),
))
}
_ => Err(KernelError::CertificationError(format!(
"Cannot convert {:?} to kernel type",
expr
))),
}
}
fn proof_term_to_kernel_term(term: &ProofTerm) -> KernelResult<Term> {
match term {
ProofTerm::Constant(name) => Ok(Term::Global(name.clone())),
ProofTerm::Variable(name) => Ok(Term::Var(name.clone())),
ProofTerm::BoundVarRef(name) => Ok(Term::Var(name.clone())),
ProofTerm::Function(name, args) => {
let mut result = Term::Global(name.clone());
for arg in args {
let arg_term = proof_term_to_kernel_term(arg)?;
result = Term::App(Box::new(result), Box::new(arg_term));
}
Ok(result)
}
ProofTerm::Group(_) => Err(KernelError::CertificationError(
"Cannot convert Group to kernel term".to_string(),
)),
}
}
fn extract_motive_body(conclusion: &ProofExpr, var_name: &str) -> KernelResult<Term> {
match conclusion {
ProofExpr::ForAll { variable, body } if variable == var_name => {
proof_expr_to_type(body)
}
_ => Err(KernelError::CertificationError(format!(
"StructuralInduction conclusion must be ForAll over {}, got {:?}",
var_name, conclusion
))),
}
}
fn compute_ih_conclusion(
conclusion: &ProofExpr,
orig_var: &str,
step_var: &str,
) -> KernelResult<ProofExpr> {
match conclusion {
ProofExpr::ForAll { body, .. } => Ok(substitute_var_in_expr(body, orig_var, step_var)),
_ => Err(KernelError::CertificationError(
"Expected ForAll for IH computation".to_string(),
)),
}
}
fn build_motive(ind_type: &str, result_type: &Term, motive_param: &str) -> Term {
Term::Lambda {
param: motive_param.to_string(),
param_type: Box::new(Term::Global(ind_type.to_string())),
body: Box::new(result_type.clone()),
}
}
fn substitute_var_in_expr(expr: &ProofExpr, from: &str, to: &str) -> ProofExpr {
match expr {
ProofExpr::Atom(s) if s == from => ProofExpr::Atom(to.to_string()),
ProofExpr::Atom(s) => ProofExpr::Atom(s.clone()),
ProofExpr::TypedVar { name, typename } => ProofExpr::TypedVar {
name: if name == from {
to.to_string()
} else {
name.clone()
},
typename: typename.clone(),
},
ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
name: name.clone(),
args: args
.iter()
.map(|a| substitute_var_in_term(a, from, to))
.collect(),
world: world.clone(),
},
ProofExpr::Identity(l, r) => ProofExpr::Identity(
substitute_var_in_term(l, from, to),
substitute_var_in_term(r, from, to),
),
ProofExpr::And(l, r) => ProofExpr::And(
Box::new(substitute_var_in_expr(l, from, to)),
Box::new(substitute_var_in_expr(r, from, to)),
),
ProofExpr::Or(l, r) => ProofExpr::Or(
Box::new(substitute_var_in_expr(l, from, to)),
Box::new(substitute_var_in_expr(r, from, to)),
),
ProofExpr::Implies(l, r) => ProofExpr::Implies(
Box::new(substitute_var_in_expr(l, from, to)),
Box::new(substitute_var_in_expr(r, from, to)),
),
ProofExpr::Iff(l, r) => ProofExpr::Iff(
Box::new(substitute_var_in_expr(l, from, to)),
Box::new(substitute_var_in_expr(r, from, to)),
),
ProofExpr::Not(inner) => {
ProofExpr::Not(Box::new(substitute_var_in_expr(inner, from, to)))
}
ProofExpr::ForAll { variable, body } if variable != from => ProofExpr::ForAll {
variable: variable.clone(),
body: Box::new(substitute_var_in_expr(body, from, to)),
},
ProofExpr::Exists { variable, body } if variable != from => ProofExpr::Exists {
variable: variable.clone(),
body: Box::new(substitute_var_in_expr(body, from, to)),
},
ProofExpr::Lambda { variable, body } if variable != from => ProofExpr::Lambda {
variable: variable.clone(),
body: Box::new(substitute_var_in_expr(body, from, to)),
},
ProofExpr::App(f, a) => ProofExpr::App(
Box::new(substitute_var_in_expr(f, from, to)),
Box::new(substitute_var_in_expr(a, from, to)),
),
ProofExpr::Term(t) => ProofExpr::Term(substitute_var_in_term(t, from, to)),
ProofExpr::Ctor { name, args } => ProofExpr::Ctor {
name: name.clone(),
args: args
.iter()
.map(|a| substitute_var_in_expr(a, from, to))
.collect(),
},
other => other.clone(),
}
}
fn substitute_var_in_term(term: &ProofTerm, from: &str, to: &str) -> ProofTerm {
match term {
ProofTerm::Variable(s) if s == from => ProofTerm::Variable(to.to_string()),
ProofTerm::Variable(s) => ProofTerm::Variable(s.clone()),
ProofTerm::Constant(s) => ProofTerm::Constant(s.clone()),
ProofTerm::BoundVarRef(s) if s == from => ProofTerm::BoundVarRef(to.to_string()),
ProofTerm::BoundVarRef(s) => ProofTerm::BoundVarRef(s.clone()),
ProofTerm::Function(name, args) => ProofTerm::Function(
name.clone(),
args.iter()
.map(|a| substitute_var_in_term(a, from, to))
.collect(),
),
ProofTerm::Group(terms) => ProofTerm::Group(
terms
.iter()
.map(|t| substitute_var_in_term(t, from, to))
.collect(),
),
}
}