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, ShapeMatch, Symbol};

use crate::{
    LogicConfig, LogicDb,
    builtins::{BuiltinBinding, BuiltinTable},
    query::{query_all, query_all_with_builtins},
    unify::occurs_check,
};

fn capture<'a>(answer: &'a ShapeMatch, name: &str) -> &'a Expr {
    answer
        .captures
        .exprs()
        .iter()
        .find_map(|(symbol, expr)| (symbol == &Symbol::new(name)).then_some(expr))
        .unwrap()
}

fn number(text: &str) -> Expr {
    Expr::Number(sim_kernel::NumberLiteral {
        domain: Symbol::qualified("numbers", "i64"),
        canonical: text.to_owned(),
    })
}

#[test]
fn standard_table_lists_is_and_findall_with_organs() {
    let table = BuiltinTable::standard();
    let keys = table.keys().cloned().collect::<Vec<_>>();

    assert!(keys.contains(&Symbol::new("is")));
    assert!(keys.contains(&Symbol::new("findall")));
    for key in ["member", "append", "length", "select"] {
        assert!(keys.contains(&Symbol::new(key)));
        assert_eq!(
            table.organ_of(&Symbol::new(key)),
            Some(&Symbol::new("sequence"))
        );
    }
    for key in ["#=", "#<", "dif"] {
        assert!(keys.contains(&Symbol::new(key)));
        assert_eq!(
            table.organ_of(&Symbol::new(key)),
            Some(&Symbol::new("control"))
        );
    }
    assert_eq!(
        table.organ_of(&Symbol::new("is")),
        Some(&Symbol::qualified("numbers", "arith"))
    );
    assert_eq!(
        table.organ_of(&Symbol::new("findall")),
        Some(&Symbol::new("sequence"))
    );
}

#[test]
fn standard_table_routes_existing_constraint_bindings() {
    let mut cx = Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
    let answers = query_all(
        &mut cx,
        &LogicDb::new(),
        &LogicConfig::default(),
        Expr::List(vec![
            Expr::Symbol(Symbol::new("between")),
            number("1"),
            number("2"),
            Expr::Local(Symbol::new("X")),
        ]),
        Some(10),
    )
    .unwrap();

    assert_eq!(answers.len(), 2);
}

#[test]
fn registering_a_new_key_resolves_without_resolver_change() {
    let mut table = BuiltinTable::standard();
    table.register(BuiltinBinding {
        key: Symbol::new("bind-ok"),
        organ: Symbol::qualified("test", "binding"),
        solve: Arc::new(|_cx, ctx, args, env| {
            let [out] = args else {
                return Err(sim_kernel::Error::Eval(
                    "bind-ok expects one argument".to_owned(),
                ));
            };
            let mut next = env.clone();
            if next.unify(
                out,
                &Expr::Symbol(Symbol::new("ok")),
                occurs_check(ctx.config),
            )? {
                Ok(vec![next])
            } else {
                Ok(Vec::new())
            }
        }),
    });

    let mut cx = Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
    let answers = query_all_with_builtins(
        &mut cx,
        &LogicDb::new(),
        &LogicConfig::default(),
        Expr::List(vec![
            Expr::Symbol(Symbol::new("bind-ok")),
            Expr::Local(Symbol::new("X")),
        ]),
        Some(1),
        table,
    )
    .unwrap();

    assert_eq!(answers.len(), 1);
    assert_eq!(capture(&answers[0], "X"), &Expr::Symbol(Symbol::new("ok")));
}