sim-lib-logic 0.1.0

SIM workspace package for sim lib logic.
Documentation
use std::sync::Arc;

use sim_kernel::{Cx, DefaultFactory, EagerPolicy, Expr, Symbol};

use crate::{LogicConfig, LogicEnv, model::OccursCheck, unify::unify_exprs};

#[test]
fn unify_binds_repeated_variables_across_lists() {
    let mut cx = Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
    let left = Expr::List(vec![
        Expr::Symbol(Symbol::new("pair")),
        Expr::Local(Symbol::new("x")),
        Expr::Local(Symbol::new("x")),
    ]);
    let right = Expr::List(vec![
        Expr::Symbol(Symbol::new("pair")),
        Expr::Number(sim_kernel::NumberLiteral {
            domain: Symbol::qualified("numbers", "i64"),
            canonical: "1".to_owned(),
        }),
        Expr::Number(sim_kernel::NumberLiteral {
            domain: Symbol::qualified("numbers", "i64"),
            canonical: "1".to_owned(),
        }),
    ]);
    let matched = unify_exprs(&mut cx, &LogicConfig::default(), &left, &right).unwrap();
    assert!(matched.accepted);
    assert_eq!(matched.captures.exprs().len(), 1);
}

#[test]
fn occurs_check_rejects_cycles() {
    let mut env = LogicEnv::new();
    let value = Expr::List(vec![
        Expr::Symbol(Symbol::new("loop")),
        Expr::Local(Symbol::new("x")),
    ]);
    let err = env
        .bind(Symbol::new("x"), value, OccursCheck::Always)
        .unwrap_err();
    assert!(format!("{err}").contains("occurs check"));
}

#[test]
fn shape_unify_binds_logic_variable() {
    let mut env = LogicEnv::new();
    let pattern = Expr::List(vec![
        Expr::Symbol(Symbol::new("parent")),
        Expr::Symbol(Symbol::new("tom")),
        Expr::Local(Symbol::new("X")),
    ]);
    let subject = Expr::List(vec![
        Expr::Symbol(Symbol::new("parent")),
        Expr::Symbol(Symbol::new("tom")),
        Expr::Symbol(Symbol::new("bob")),
    ]);
    assert!(env.unify(&pattern, &subject, OccursCheck::Always).unwrap());
    assert_eq!(
        env.get(&Symbol::new("X")),
        Some(&Expr::Symbol(Symbol::new("bob")))
    );
}

#[test]
fn shape_unify_fails_on_mismatch() {
    let mut env = LogicEnv::new();
    let pattern = Expr::List(vec![
        Expr::Symbol(Symbol::new("parent")),
        Expr::Symbol(Symbol::new("tom")),
        Expr::Local(Symbol::new("X")),
    ]);
    let subject = Expr::List(vec![
        Expr::Symbol(Symbol::new("child")),
        Expr::Symbol(Symbol::new("tom")),
        Expr::Symbol(Symbol::new("bob")),
    ]);
    assert!(!env.unify(&pattern, &subject, OccursCheck::Always).unwrap());
    assert_eq!(env.get(&Symbol::new("X")), None);
}

#[test]
fn shape_unify_repeated_variable_requires_same_subject() {
    let mut accepted = LogicEnv::new();
    let pattern = Expr::List(vec![
        Expr::Symbol(Symbol::new("same")),
        Expr::Local(Symbol::new("X")),
        Expr::Local(Symbol::new("X")),
    ]);
    let same_subject = Expr::List(vec![
        Expr::Symbol(Symbol::new("same")),
        Expr::Symbol(Symbol::new("bob")),
        Expr::Symbol(Symbol::new("bob")),
    ]);
    assert!(
        accepted
            .unify(&pattern, &same_subject, OccursCheck::Always)
            .unwrap()
    );
    assert_eq!(
        accepted.get(&Symbol::new("X")),
        Some(&Expr::Symbol(Symbol::new("bob")))
    );

    let mut rejected = LogicEnv::new();
    let different_subject = Expr::List(vec![
        Expr::Symbol(Symbol::new("same")),
        Expr::Symbol(Symbol::new("bob")),
        Expr::Symbol(Symbol::new("alice")),
    ]);
    assert!(
        !rejected
            .unify(&pattern, &different_subject, OccursCheck::Always)
            .unwrap()
    );
    assert_eq!(rejected.get(&Symbol::new("X")), None);
}

#[test]
fn unify_returns_false_on_mismatch() {
    let mut env = LogicEnv::new();
    let accepted = env
        .unify(
            &Expr::Symbol(Symbol::new("a")),
            &Expr::Symbol(Symbol::new("b")),
            OccursCheck::Always,
        )
        .unwrap();
    assert!(!accepted);
}