use oxilean_kernel::Node;
use oxilean_kernel::{BinderInfo, Declaration, EnvError, Environment, Expr, Level, Literal, Name};
fn app(f: Expr, a: Expr) -> Expr {
Expr::App(Node::new(f), Node::new(a))
}
fn app2(f: Expr, a: Expr, b: Expr) -> Expr {
app(app(f, a), b)
}
fn pi(name: &str, dom: Expr, body: Expr) -> Expr {
Expr::Pi(
BinderInfo::Default,
Name::str(name),
Node::new(dom),
Node::new(body),
)
}
fn cst(s: &str) -> Expr {
Expr::Const(Name::str(s), vec![])
}
fn bvar(n: u32) -> Expr {
Expr::BVar(n)
}
fn prop() -> Expr {
Expr::Sort(Level::zero())
}
fn type1() -> Expr {
Expr::Sort(Level::succ(Level::zero()))
}
fn int_const() -> Expr {
cst("Int")
}
fn nat_const() -> Expr {
cst("Nat")
}
fn forall1_int(body: Expr) -> Expr {
pi("a", int_const(), body)
}
fn forall2_int(body: Expr) -> Expr {
pi("a", int_const(), pi("b", int_const(), body))
}
fn forall3_int(body: Expr) -> Expr {
pi(
"a",
int_const(),
pi("b", int_const(), pi("c", int_const(), body)),
)
}
fn int_eq_expr(a: Expr, b: Expr) -> Expr {
let eq_const = Expr::Const(Name::str("Eq"), vec![Level::succ(Level::zero())]);
app(app(app(eq_const, int_const()), a), b)
}
fn bool_eq_false_expr(lhs: Expr) -> Expr {
let eq_const = Expr::Const(Name::str("Eq"), vec![Level::succ(Level::zero())]);
app(app(app(eq_const, cst("Bool")), lhs), cst("Bool.false"))
}
fn int_zero() -> Expr {
app(cst("Int.ofNat"), Expr::Lit(Literal::nat(0)))
}
fn int_one() -> Expr {
app(cst("Int.ofNat"), Expr::Lit(Literal::nat(1)))
}
fn ty_le_refl() -> Expr {
forall1_int(app2(cst("Int.le"), bvar(0), bvar(0)))
}
fn ty_le_trans() -> Expr {
forall3_int(pi(
"h1",
app2(cst("Int.le"), bvar(2), bvar(1)),
pi(
"h2",
app2(cst("Int.le"), bvar(2), bvar(1)),
app2(cst("Int.le"), bvar(4), bvar(2)),
),
))
}
fn ty_le_antisymm() -> Expr {
forall2_int(pi(
"h1",
app2(cst("Int.le"), bvar(1), bvar(0)),
pi(
"h2",
app2(cst("Int.le"), bvar(1), bvar(2)),
int_eq_expr(bvar(3), bvar(2)),
),
))
}
fn ty_lt_irrefl() -> Expr {
forall1_int(app(cst("Not"), app2(cst("Int.lt"), bvar(0), bvar(0))))
}
fn ty_lt_iff_add_one_le() -> Expr {
forall2_int(app2(
cst("Iff"),
app2(cst("Int.lt"), bvar(1), bvar(0)),
app2(
cst("Int.le"),
app2(cst("Int.add"), bvar(1), int_one()),
bvar(0),
),
))
}
fn ty_le_of_lt() -> Expr {
forall2_int(pi(
"h",
app2(cst("Int.lt"), bvar(1), bvar(0)),
app2(cst("Int.le"), bvar(2), bvar(1)),
))
}
fn ty_add_le_add() -> Expr {
pi(
"a",
int_const(),
pi(
"b",
int_const(),
pi(
"c",
int_const(),
pi(
"d",
int_const(),
pi(
"h1",
app2(cst("Int.le"), bvar(3), bvar(2)),
pi(
"h2",
app2(cst("Int.le"), bvar(2), bvar(1)),
app2(
cst("Int.le"),
app2(cst("Int.add"), bvar(5), bvar(3)),
app2(cst("Int.add"), bvar(4), bvar(2)),
),
),
),
),
),
),
)
}
fn ty_mul_le_mul_of_nonneg_left() -> Expr {
forall3_int(pi(
"h1",
app2(cst("Int.le"), bvar(2), bvar(1)),
pi(
"h2",
app2(cst("Int.le"), int_zero(), bvar(1)),
app2(
cst("Int.le"),
app2(cst("Int.mul"), bvar(2), bvar(4)),
app2(cst("Int.mul"), bvar(2), bvar(3)),
),
),
))
}
fn ty_le_of_eq() -> Expr {
forall2_int(pi(
"h",
int_eq_expr(bvar(1), bvar(0)),
app2(cst("Int.le"), bvar(2), bvar(1)),
))
}
fn ty_le_total() -> Expr {
forall2_int(app2(
cst("Or"),
app2(cst("Int.le"), bvar(1), bvar(0)),
app2(cst("Int.le"), bvar(0), bvar(1)),
))
}
fn ty_le_of_int_le() -> Expr {
Expr::Pi(
BinderInfo::Implicit,
Name::str("inst"),
Node::new(app(cst("LE"), int_const())),
Node::new(pi(
"a",
int_const(),
pi(
"b",
int_const(),
pi(
"h",
app2(cst("Int.le"), bvar(1), bvar(0)),
app(
app(app(app(cst("LE.le"), int_const()), bvar(3)), bvar(2)),
bvar(1),
),
),
),
)),
)
}
fn ty_int_le_of_le() -> Expr {
Expr::Pi(
BinderInfo::Implicit,
Name::str("inst"),
Node::new(app(cst("LE"), int_const())),
Node::new(pi(
"a",
int_const(),
pi(
"b",
int_const(),
pi(
"h",
app(
app(app(app(cst("LE.le"), int_const()), bvar(2)), bvar(1)),
bvar(0),
),
app2(cst("Int.le"), bvar(2), bvar(1)),
),
),
)),
)
}
fn ty_absurd_le_zero() -> Expr {
pi(
"k",
int_const(),
pi(
"h1",
app2(cst("Int.lt"), int_zero(), bvar(0)),
pi(
"h2",
app2(cst("Int.le"), bvar(1), int_zero()),
cst("False"),
),
),
)
}
fn ty_zero_lt_one() -> Expr {
app2(cst("Int.lt"), int_zero(), int_one())
}
fn ty_lt_of_le_of_lt() -> Expr {
forall3_int(pi(
"h1",
app2(cst("Int.le"), bvar(2), bvar(1)),
pi(
"h2",
app2(cst("Int.lt"), bvar(2), bvar(1)),
app2(cst("Int.lt"), bvar(4), bvar(2)),
),
))
}
fn ty_lt_of_lt_of_le() -> Expr {
forall3_int(pi(
"h1",
app2(cst("Int.lt"), bvar(2), bvar(1)),
pi(
"h2",
app2(cst("Int.le"), bvar(2), bvar(1)),
app2(cst("Int.lt"), bvar(4), bvar(2)),
),
))
}
fn ty_lt_trans() -> Expr {
forall3_int(pi(
"h1",
app2(cst("Int.lt"), bvar(2), bvar(1)),
pi(
"h2",
app2(cst("Int.lt"), bvar(2), bvar(1)),
app2(cst("Int.lt"), bvar(4), bvar(2)),
),
))
}
fn ty_lt_irrefl_false() -> Expr {
forall1_int(pi("h", app2(cst("Int.lt"), bvar(0), bvar(0)), cst("False")))
}
fn ty_not_le_of_ble_false() -> Expr {
forall2_int(pi(
"h1",
bool_eq_false_expr(app2(cst("Int.ble"), bvar(1), bvar(0))),
pi("h2", app2(cst("Int.le"), bvar(2), bvar(1)), cst("False")),
))
}
fn ty_not_lt_of_blt_false() -> Expr {
forall2_int(pi(
"h1",
bool_eq_false_expr(app2(cst("Int.blt"), bvar(1), bvar(0))),
pi("h2", app2(cst("Int.lt"), bvar(2), bvar(1)), cst("False")),
))
}
pub fn register_omega_helper(env: &mut Environment) -> Result<(), EnvError> {
let lemmas: &[(&str, Vec<Name>, Expr)] = &[
("Int.le_refl", vec![], ty_le_refl()),
("Int.le_trans", vec![], ty_le_trans()),
("Int.le_antisymm", vec![], ty_le_antisymm()),
("Int.lt_irrefl", vec![], ty_lt_irrefl()),
("Int.lt_iff_add_one_le", vec![], ty_lt_iff_add_one_le()),
("Int.le_of_lt", vec![], ty_le_of_lt()),
("Int.add_le_add", vec![], ty_add_le_add()),
(
"Int.mul_le_mul_of_nonneg_left",
vec![],
ty_mul_le_mul_of_nonneg_left(),
),
("Int.le_of_eq", vec![], ty_le_of_eq()),
("Int.le_total", vec![], ty_le_total()),
("le_of_int_le", vec![], ty_le_of_int_le()),
("int_le_of_le", vec![], ty_int_le_of_le()),
("Int.absurd_le_zero", vec![], ty_absurd_le_zero()),
("Int.zero_lt_one", vec![], ty_zero_lt_one()),
("Int.lt_of_le_of_lt", vec![], ty_lt_of_le_of_lt()),
("Int.lt_of_lt_of_le", vec![], ty_lt_of_lt_of_le()),
("Int.lt_trans", vec![], ty_lt_trans()),
("Int.lt_irrefl'", vec![], ty_lt_irrefl_false()),
("Int.not_le_of_ble_false", vec![], ty_not_le_of_ble_false()),
("Int.not_lt_of_blt_false", vec![], ty_not_lt_of_blt_false()),
];
for (name, univ_params, ty) in lemmas {
let n = Name::str(*name);
if env.contains(&n) {
continue;
}
env.add(Declaration::Axiom {
name: n,
univ_params: univ_params.clone(),
ty: ty.clone(),
})?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
const OMEGA_LEMMAS: &[&str] = &[
"Int.le_refl",
"Int.le_trans",
"Int.le_antisymm",
"Int.lt_irrefl",
"Int.lt_iff_add_one_le",
"Int.le_of_lt",
"Int.add_le_add",
"Int.mul_le_mul_of_nonneg_left",
"Int.le_of_eq",
"Int.le_total",
"le_of_int_le",
"int_le_of_le",
"Int.absurd_le_zero",
"Int.zero_lt_one",
"Int.lt_of_le_of_lt",
"Int.lt_of_lt_of_le",
"Int.lt_trans",
"Int.lt_irrefl'",
"Int.not_le_of_ble_false",
"Int.not_lt_of_blt_false",
];
#[test]
fn test_register_omega_helper_success() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("registration should succeed");
}
#[test]
fn test_all_lemmas_present() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("registration should succeed");
for name in OMEGA_LEMMAS {
assert!(
env.contains(&Name::str(*name)),
"lemma {} should be registered",
name
);
}
}
#[test]
fn test_duplicate_registration_is_idempotent() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("first registration");
register_omega_helper(&mut env).expect("second registration should succeed");
for name in OMEGA_LEMMAS {
assert!(
env.contains(&Name::str(*name)),
"lemma {} should still be registered after second call",
name
);
}
}
#[test]
fn test_coexistence_with_preregistered_lemmas() {
let mut env = Environment::new();
let pre_existing = &[
("Int.le_refl", ty_le_refl()),
("Int.le_trans", ty_le_trans()),
("Int.le_antisymm", ty_le_antisymm()),
];
for (name, ty) in pre_existing {
env.add(Declaration::Axiom {
name: Name::str(*name),
univ_params: vec![],
ty: ty.clone(),
})
.expect("pre-registration should succeed");
}
register_omega_helper(&mut env).expect("registration with pre-existing should succeed");
for name in OMEGA_LEMMAS {
assert!(
env.contains(&Name::str(*name)),
"lemma {} should be present after coexistence test",
name
);
}
}
#[test]
fn test_le_refl_type_structure() {
let ty = ty_le_refl();
assert!(
matches!(ty, Expr::Pi(_, _, _, _)),
"le_refl type should be a Pi"
);
}
#[test]
fn test_lt_irrefl_type_structure() {
let ty = ty_lt_irrefl();
assert!(
matches!(ty, Expr::Pi(_, _, _, _)),
"lt_irrefl type should be a Pi"
);
}
#[test]
fn test_add_le_add_type_structure() {
let ty = ty_add_le_add();
assert!(
matches!(ty, Expr::Pi(_, _, _, _)),
"add_le_add type should be a Pi"
);
}
#[test]
fn test_le_total_type_structure() {
let ty = ty_le_total();
assert!(
matches!(ty, Expr::Pi(_, _, _, _)),
"le_total type should be a Pi"
);
}
#[test]
fn test_env_contains_after_registration() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("registration should succeed");
assert!(env.find(&Name::str("Int.le_refl")).is_some());
assert!(env.find(&Name::str("Int.le_total")).is_some());
assert!(env.find(&Name::str("Int.add_le_add")).is_some());
assert!(env
.find(&Name::str("Int.mul_le_mul_of_nonneg_left"))
.is_some());
}
#[test]
fn test_lemma_count_in_fresh_env() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("registration should succeed");
assert_eq!(env.len(), OMEGA_LEMMAS.len());
}
#[test]
fn test_bridge_axioms_present() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("registration should succeed");
assert!(env.contains(&Name::str("le_of_int_le")));
assert!(env.contains(&Name::str("int_le_of_le")));
}
#[test]
fn test_le_of_int_le_type_structure() {
let ty = ty_le_of_int_le();
assert!(
matches!(ty, Expr::Pi(BinderInfo::Implicit, _, _, _)),
"le_of_int_le should start with an implicit Pi (inst binder)"
);
}
#[test]
fn test_int_le_of_le_type_structure() {
let ty = ty_int_le_of_le();
assert!(
matches!(ty, Expr::Pi(BinderInfo::Implicit, _, _, _)),
"int_le_of_le should start with an implicit Pi (inst binder)"
);
}
#[test]
fn test_bridge_axioms_idempotent() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("first registration");
register_omega_helper(&mut env).expect("second registration should succeed");
assert!(env.contains(&Name::str("le_of_int_le")));
assert!(env.contains(&Name::str("int_le_of_le")));
}
#[test]
fn test_absurd_le_zero_registered() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("registration should succeed");
assert!(
env.contains(&Name::str("Int.absurd_le_zero")),
"Int.absurd_le_zero should be registered"
);
}
#[test]
fn test_zero_lt_one_registered() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("registration should succeed");
assert!(
env.contains(&Name::str("Int.zero_lt_one")),
"Int.zero_lt_one should be registered"
);
}
#[test]
fn test_absurd_le_zero_type_is_pi() {
let ty = ty_absurd_le_zero();
assert!(
matches!(ty, Expr::Pi(BinderInfo::Default, _, _, _)),
"absurd_le_zero type should start with a default Pi binder (k : Int)"
);
if let Expr::Pi(_, _, _, body1) = ty {
assert!(
matches!(*body1, Expr::Pi(BinderInfo::Default, _, _, _)),
"second binder (h1) should be a default Pi"
);
if let Expr::Pi(_, _, _, body2) = (*body1).clone() {
assert!(
matches!(*body2, Expr::Pi(BinderInfo::Default, _, _, _)),
"third binder (h2) should be a default Pi"
);
if let Expr::Pi(_, _, _, concl) = (*body2).clone() {
assert!(
matches!(*concl, Expr::Const(_, _)),
"conclusion of absurd_le_zero should be Const(\"False\")"
);
}
}
}
}
#[test]
fn test_zero_lt_one_type_is_ground_app() {
let ty = ty_zero_lt_one();
assert!(
matches!(ty, Expr::App(_, _)),
"zero_lt_one type should be a ground App (Int.lt 0 1), no binders"
);
}
#[test]
fn test_closing_axioms_idempotent() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("first registration");
register_omega_helper(&mut env).expect("second registration should succeed (idempotent)");
assert!(env.contains(&Name::str("Int.absurd_le_zero")));
assert!(env.contains(&Name::str("Int.zero_lt_one")));
assert_eq!(env.len(), OMEGA_LEMMAS.len());
}
#[test]
fn test_strict_transitivity_axioms_present() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("registration should succeed");
assert!(
env.contains(&Name::str("Int.lt_of_le_of_lt")),
"Int.lt_of_le_of_lt should be registered"
);
assert!(
env.contains(&Name::str("Int.lt_of_lt_of_le")),
"Int.lt_of_lt_of_le should be registered"
);
assert!(
env.contains(&Name::str("Int.lt_trans")),
"Int.lt_trans should be registered"
);
}
#[test]
fn test_lt_irrefl_false_registered() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("registration should succeed");
assert!(
env.contains(&Name::str("Int.lt_irrefl'")),
"Int.lt_irrefl' should be registered"
);
}
#[test]
fn test_lt_of_le_of_lt_type_structure() {
let ty = ty_lt_of_le_of_lt();
assert!(
matches!(ty, Expr::Pi(BinderInfo::Default, _, _, _)),
"lt_of_le_of_lt type should start with a default Pi binder"
);
}
#[test]
fn test_lt_of_lt_of_le_type_structure() {
let ty = ty_lt_of_lt_of_le();
assert!(
matches!(ty, Expr::Pi(BinderInfo::Default, _, _, _)),
"lt_of_lt_of_le type should start with a default Pi binder"
);
}
#[test]
fn test_lt_trans_type_structure() {
let ty = ty_lt_trans();
assert!(
matches!(ty, Expr::Pi(BinderInfo::Default, _, _, _)),
"lt_trans type should start with a default Pi binder"
);
}
#[test]
fn test_lt_irrefl_false_type_structure() {
let ty = ty_lt_irrefl_false();
assert!(
matches!(ty, Expr::Pi(BinderInfo::Default, _, _, _)),
"lt_irrefl' type should start with a default Pi binder (a : Int)"
);
if let Expr::Pi(_, _, _, body) = ty {
assert!(
matches!(*body, Expr::Pi(BinderInfo::Default, _, _, _)),
"second binder (h) should be a default Pi"
);
if let Expr::Pi(_, _, _, concl) = (*body).clone() {
assert!(
matches!(*concl, Expr::Const(_, _)),
"conclusion of lt_irrefl' should be Const(\"False\")"
);
}
}
}
#[test]
fn test_lemma_count_is_20() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("registration should succeed");
assert_eq!(env.len(), 20, "expected 20 omega lemmas");
assert_eq!(
OMEGA_LEMMAS.len(),
20,
"OMEGA_LEMMAS slice should list 20 entries"
);
}
#[test]
fn test_strict_transitivity_axioms_idempotent() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("first registration");
register_omega_helper(&mut env).expect("second registration should succeed");
assert!(env.contains(&Name::str("Int.lt_of_le_of_lt")));
assert!(env.contains(&Name::str("Int.lt_of_lt_of_le")));
assert!(env.contains(&Name::str("Int.lt_trans")));
assert!(env.contains(&Name::str("Int.lt_irrefl'")));
assert_eq!(env.len(), 20);
}
#[test]
fn test_bool_reflection_axioms_present() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("registration should succeed");
assert!(
env.contains(&Name::str("Int.not_le_of_ble_false")),
"Int.not_le_of_ble_false should be registered"
);
assert!(
env.contains(&Name::str("Int.not_lt_of_blt_false")),
"Int.not_lt_of_blt_false should be registered"
);
}
#[test]
fn test_not_le_of_ble_false_type_structure() {
let ty = ty_not_le_of_ble_false();
assert!(
matches!(ty, Expr::Pi(BinderInfo::Default, _, _, _)),
"not_le_of_ble_false type should start with a default Pi binder (a : Int)"
);
if let Expr::Pi(_, _, _, body1) = ty {
assert!(
matches!(*body1, Expr::Pi(BinderInfo::Default, _, _, _)),
"second binder (b) should be a default Pi"
);
if let Expr::Pi(_, _, _, body2) = (*body1).clone() {
assert!(
matches!(*body2, Expr::Pi(BinderInfo::Default, _, _, _)),
"third binder (h1: Eq Bool ...) should be a default Pi"
);
if let Expr::Pi(_, _, _, body3) = (*body2).clone() {
assert!(
matches!(*body3, Expr::Pi(BinderInfo::Default, _, _, _)),
"fourth binder (h2: Int.le ...) should be a default Pi"
);
if let Expr::Pi(_, _, _, concl) = (*body3).clone() {
assert!(
matches!(*concl, Expr::Const(_, _)),
"conclusion should be Const(\"False\")"
);
}
}
}
}
}
#[test]
fn test_not_lt_of_blt_false_type_structure() {
let ty = ty_not_lt_of_blt_false();
assert!(
matches!(ty, Expr::Pi(BinderInfo::Default, _, _, _)),
"not_lt_of_blt_false type should start with a default Pi binder (a : Int)"
);
if let Expr::Pi(_, _, _, body1) = ty {
if let Expr::Pi(_, _, _, body2) = (*body1).clone() {
if let Expr::Pi(_, _, _, body3) = (*body2).clone() {
if let Expr::Pi(_, _, _, concl) = (*body3).clone() {
assert!(
matches!(*concl, Expr::Const(_, _)),
"conclusion of not_lt_of_blt_false should be Const(\"False\")"
);
}
}
}
}
}
#[test]
fn test_bool_reflection_axioms_idempotent() {
let mut env = Environment::new();
register_omega_helper(&mut env).expect("first registration");
register_omega_helper(&mut env).expect("second registration should succeed");
assert!(env.contains(&Name::str("Int.not_le_of_ble_false")));
assert!(env.contains(&Name::str("Int.not_lt_of_blt_false")));
assert_eq!(env.len(), 20);
}
}