use proptest::prelude::*;
use trit_core::algebra::Algebra;
use trit_core::bits::TriBit;
use trit_core::parser::Parser;
use trit_core::value::Ternary;
use trit_eval::eval::{Assignment, decode_assignment, eval, truth_table};
fn arb_ternary() -> impl Strategy<Value = Ternary> {
prop_oneof![
Just(Ternary::False),
Just(Ternary::Unknown),
Just(Ternary::True),
]
}
fn arb_algebra() -> impl Strategy<Value = Algebra> {
prop_oneof![
Just(Algebra::Kleene),
Just(Algebra::Lukasiewicz),
Just(Algebra::Bochvar),
]
}
proptest! {
#[test]
fn bitwise_matches_scalar(a in arb_ternary(), b in arb_ternary(), alg_s in arb_algebra()) {
let ta = TriBit::constant(a);
let tb = TriBit::constant(b);
prop_assert_eq!(ta.and(tb, alg_s).get(0), alg_s.and(a, b));
prop_assert_eq!(ta.or(tb, alg_s).get(0), alg_s.or(a, b));
prop_assert_eq!(ta.strong_add(tb, alg_s).get(0), alg_s.strong_add(a, b));
prop_assert_eq!(ta.strong_and(tb, alg_s).get(0), alg_s.strong_and(a, b));
prop_assert_eq!(ta.implies(tb, alg_s).get(0), alg_s.implies(a, b));
prop_assert_eq!(ta.equiv(tb, alg_s).get(0), alg_s.equiv(a, b));
}
#[test]
fn negation_involutive(a in arb_ternary()) {
let neg = Algebra::Kleene.not(a);
prop_assert_eq!(Algebra::Kleene.not(neg), a);
if a.is_unknown() {
prop_assert!(neg.is_unknown());
}
}
#[test]
fn lattice_laws(a in arb_ternary(), b in arb_ternary()) {
for alg_s in [Algebra::Kleene, Algebra::Lukasiewicz] {
prop_assert_eq!(alg_s.and(a, b), alg_s.and(b, a));
prop_assert_eq!(alg_s.or(a, b), alg_s.or(b, a));
prop_assert_eq!(alg_s.and(a, a), a);
prop_assert_eq!(alg_s.or(a, a), a);
}
}
#[test]
fn bochvar_is_poisonous(a in arb_ternary(), b in arb_ternary()) {
let alg_s = Algebra::Bochvar;
if a.is_unknown() || b.is_unknown() {
prop_assert!(alg_s.and(a, b).is_unknown());
prop_assert!(alg_s.or(a, b).is_unknown());
prop_assert!(alg_s.strong_add(a, b).is_unknown());
prop_assert!(alg_s.strong_and(a, b).is_unknown());
}
}
#[test]
fn truth_table_matches_pointwise(
a in arb_ternary(),
b in arb_ternary(),
c in arb_ternary(),
alg_s in arb_algebra(),
) {
let expr = Parser::parse("(a & b) | (!a & c)").unwrap();
let vars = expr.variables();
let tt = truth_table(&expr, alg_s);
prop_assert_eq!(tt.values.len(), 27);
let mut asg = Assignment::new();
asg.set("a", a).set("b", b).set("c", c);
for (i, expected) in tt.values.iter().enumerate() {
let row = decode_assignment(i as u64, &vars);
if row.get("a") == Some(a) && row.get("b") == Some(b) && row.get("c") == Some(c) {
prop_assert_eq!(eval(&expr, &asg, alg_s), *expected);
}
}
}
#[test]
fn simplify_preserves_semantics(a in arb_ternary(), b in arb_ternary(), alg_s in arb_algebra()) {
let expr = Parser::parse("a & b | a & !b").unwrap();
let simplified = trit_eval::simplify::simplify(&expr, alg_s);
let mut asg = Assignment::new();
asg.set("a", a).set("b", b);
prop_assert_eq!(eval(&expr, &asg, alg_s), eval(&simplified, &asg, alg_s));
}
#[test]
fn parser_roundtrip(src in "[a-z]{1,3}") {
if let Ok(expr) = Parser::parse(&src) {
for v in expr.variables() {
prop_assert!(v.chars().all(|c| c.is_alphanumeric() || c == '_'));
prop_assert!(!v.is_empty());
}
}
}
#[test]
fn equivalence_matches_truth_table(alg_s in arb_algebra()) {
let e1 = Parser::parse("a & b").unwrap();
let e2 = Parser::parse("b & a").unwrap();
let r = trit_eval::eval::equivalent(&e1, &e2, alg_s);
prop_assert!(r.equivalent);
let e3 = Parser::parse("a & !a").unwrap();
let r2 = trit_eval::eval::equivalent(&e1, &e3, alg_s);
prop_assert!(!r2.equivalent);
}
}
#[test]
fn lukasiewicz_identities() {
let alg = Algebra::Lukasiewicz;
assert_eq!(
alg.strong_add(Ternary::Unknown, Ternary::Unknown),
Ternary::True
);
assert_eq!(
alg.strong_and(Ternary::Unknown, Ternary::Unknown),
Ternary::False
);
assert_eq!(
alg.implies(Ternary::Unknown, Ternary::Unknown),
Ternary::True
);
}