use logicaffeine_proof::verify::{
check_derivation_with_defs, dependency_graph, prove_certify_check_with_defs, Definition,
};
use logicaffeine_proof::{DerivationTree, InferenceRule, ProofExpr, ProofTerm};
fn konst(name: &str) -> ProofTerm {
ProofTerm::Constant(name.to_string())
}
fn pred(name: &str, args: Vec<ProofTerm>) -> ProofExpr {
ProofExpr::Predicate { name: name.to_string(), args, world: None }
}
fn and(l: ProofExpr, r: ProofExpr) -> ProofExpr {
ProofExpr::And(Box::new(l), Box::new(r))
}
#[test]
fn definition_unfolds_at_kernel_root() {
let def = Definition {
name: "glorp".to_string(),
params: vec!["x".to_string()],
definiens: and(
pred("shiny", vec![konst("x")]),
pred("round", vec![konst("x")]),
),
};
let shiny_a = pred("shiny", vec![konst("A")]);
let round_a = pred("round", vec![konst("A")]);
let glorp_a = pred("glorp", vec![konst("A")]);
let tree = DerivationTree::new(
and(shiny_a.clone(), round_a.clone()),
InferenceRule::ConjunctionIntro,
vec![
DerivationTree::leaf(shiny_a.clone(), InferenceRule::PremiseMatch),
DerivationTree::leaf(round_a.clone(), InferenceRule::PremiseMatch),
],
);
let result = check_derivation_with_defs(&[shiny_a, round_a], &glorp_a, &[def], tree);
assert!(
result.verified,
"glorp(a) should δ-reconcile with shiny(a) ∧ round(a): {:?}",
result.verification_error
);
}
#[test]
fn multi_argument_definition_unfolds() {
let def = Definition {
name: "between".to_string(),
params: vec!["x".to_string(), "y".to_string(), "z".to_string()],
definiens: and(
pred("near", vec![konst("x"), konst("y")]),
pred("near", vec![konst("y"), konst("z")]),
),
};
let near_ab = pred("near", vec![konst("A"), konst("B")]);
let near_bc = pred("near", vec![konst("B"), konst("C")]);
let between_abc = pred("between", vec![konst("A"), konst("B"), konst("C")]);
let tree = DerivationTree::new(
and(near_ab.clone(), near_bc.clone()),
InferenceRule::ConjunctionIntro,
vec![
DerivationTree::leaf(near_ab.clone(), InferenceRule::PremiseMatch),
DerivationTree::leaf(near_bc.clone(), InferenceRule::PremiseMatch),
],
);
let result = check_derivation_with_defs(&[near_ab, near_bc], &between_abc, &[def], tree);
assert!(
result.verified,
"between(a,b,c) should δ-reconcile with near(a,b) ∧ near(b,c): {:?}",
result.verification_error
);
}
#[test]
fn engine_proves_defined_goal_via_expansion() {
let def = Definition {
name: "glorp".to_string(),
params: vec!["x".to_string()],
definiens: and(
pred("shiny", vec![konst("x")]),
pred("round", vec![konst("x")]),
),
};
let shiny_a = pred("shiny", vec![konst("A")]);
let round_a = pred("round", vec![konst("A")]);
let glorp_a = pred("glorp", vec![konst("A")]);
let result = prove_certify_check_with_defs(&[shiny_a, round_a], &glorp_a, &[def]);
assert!(
result.verified,
"engine should prove glorp(A) by expanding the definition for search: {:?}",
result.verification_error
);
}
#[test]
fn definition_using_another_definition_proves() {
let happy = Definition {
name: "happy".to_string(),
params: vec!["x".to_string()],
definiens: and(
pred("shiny", vec![konst("x")]),
pred("round", vec![konst("x")]),
),
};
let great = Definition {
name: "great".to_string(),
params: vec!["x".to_string()],
definiens: and(
pred("happy", vec![konst("x")]),
pred("tall", vec![konst("x")]),
),
};
let premises = [
pred("shiny", vec![konst("A")]),
pred("round", vec![konst("A")]),
pred("tall", vec![konst("A")]),
];
let goal = pred("great", vec![konst("A")]);
let result = prove_certify_check_with_defs(&premises, &goal, &[happy, great]);
assert!(
result.verified,
"great(A) should prove by unfolding great → happy → primitives: {:?}",
result.verification_error
);
}
#[test]
fn mutually_recursive_definitions_are_rejected() {
let ping = Definition {
name: "ping".to_string(),
params: vec!["x".to_string()],
definiens: pred("pong", vec![konst("x")]),
};
let pong = Definition {
name: "pong".to_string(),
params: vec!["x".to_string()],
definiens: pred("ping", vec![konst("x")]),
};
let goal = pred("ping", vec![konst("A")]);
let result = prove_certify_check_with_defs(&[], &goal, &[ping, pong]);
assert!(!result.verified, "a mutually-recursive pair must not verify");
let msg = result.verification_error.unwrap_or_default().to_lowercase();
assert!(
msg.contains("circular"),
"expected a circular-definition error, got: {msg}"
);
}
#[test]
fn dependency_graph_records_uses_edges() {
let happy = Definition {
name: "happy".to_string(),
params: vec!["x".to_string()],
definiens: and(
pred("shiny", vec![konst("x")]),
pred("round", vec![konst("x")]),
),
};
let great = Definition {
name: "great".to_string(),
params: vec!["x".to_string()],
definiens: and(
pred("happy", vec![konst("x")]),
pred("tall", vec![konst("x")]),
),
};
let goal = pred("great", vec![konst("A")]);
let graph = dependency_graph(&[happy, great], &[], &goal);
let uses_of = |name: &str| -> Vec<String> {
graph
.def_uses
.iter()
.find(|(n, _)| n == name)
.map(|(_, u)| u.clone())
.unwrap_or_default()
};
assert_eq!(uses_of("great"), vec!["happy".to_string()], "great uses happy");
assert!(uses_of("happy").is_empty(), "happy uses only primitives");
assert_eq!(
graph.theorem_uses,
vec!["great".to_string()],
"the theorem uses great"
);
}
#[test]
fn existential_definition_proves_by_witness() {
let def = Definition {
name: "grounded".to_string(),
params: vec!["x".to_string()],
definiens: ProofExpr::Exists {
variable: "y".to_string(),
body: Box::new(pred(
"supports",
vec![ProofTerm::Variable("y".to_string()), konst("x")],
)),
},
};
let premise = pred("supports", vec![konst("B"), konst("A")]);
let goal = pred("grounded", vec![konst("A")]);
let result = prove_certify_check_with_defs(&[premise], &goal, &[def]);
assert!(
result.verified,
"grounded(A) should prove with witness B: {:?}",
result.verification_error
);
}
#[test]
fn universal_definition_in_premise_instantiates() {
let def = Definition {
name: "everywhere".to_string(),
params: vec!["x".to_string()],
definiens: ProofExpr::ForAll {
variable: "y".to_string(),
body: Box::new(pred(
"at",
vec![konst("x"), ProofTerm::Variable("y".to_string())],
)),
},
};
let premise = pred("everywhere", vec![konst("A")]);
let goal = pred("at", vec![konst("A"), konst("C")]);
let result = prove_certify_check_with_defs(&[premise], &goal, &[def]);
assert!(
result.verified,
"at(A, C) should follow from everywhere(A) by instantiation: {:?}",
result.verification_error
);
}
#[test]
fn parameter_shadowed_by_inner_binder_is_not_captured() {
let def = Definition {
name: "foo".to_string(),
params: vec!["y".to_string()],
definiens: ProofExpr::Exists {
variable: "y".to_string(),
body: Box::new(pred("near", vec![ProofTerm::Variable("y".to_string())])),
},
};
let premise = pred("near", vec![konst("B")]);
let goal = pred("foo", vec![konst("A")]);
let result = prove_certify_check_with_defs(&[premise], &goal, &[def]);
assert!(
result.verified,
"foo(A) should unfold to ∃y.near(y) (shadowed param, not captured): {:?}",
result.verification_error
);
}
#[test]
fn disjunctive_definition_proves_via_one_disjunct() {
let def = Definition {
name: "colorful".to_string(),
params: vec!["x".to_string()],
definiens: ProofExpr::Or(
Box::new(pred("red", vec![konst("x")])),
Box::new(pred("blue", vec![konst("x")])),
),
};
let premise = pred("red", vec![konst("A")]);
let goal = pred("colorful", vec![konst("A")]);
let result = prove_certify_check_with_defs(&[premise], &goal, &[def]);
assert!(
result.verified,
"colorful(A) should prove from red(A) via the left disjunct: {:?}",
result.verification_error
);
}
#[test]
fn recursive_definition_is_rejected() {
let def = Definition {
name: "loop".to_string(),
params: vec!["x".to_string()],
definiens: pred("loop", vec![konst("x")]),
};
let goal = pred("loop", vec![konst("a")]);
let result = prove_certify_check_with_defs(&[], &goal, &[def]);
assert!(!result.verified, "a recursive definition must not verify");
let msg = result.verification_error.unwrap_or_default().to_lowercase();
assert!(
msg.contains("recurs"),
"expected a recursion error, got: {msg}"
);
}