sim-kernel 0.1.0-rc.1

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

use crate::{
    Claim, ClaimPattern, DefaultFactory, Expr, NoopEvalPolicy, Ref, Symbol,
    card::card_fixed_predicates, card::card_for_ref, card::card_for_ref_with_fallback,
    card::card_for_value, card::minimal_card,
};

fn cx() -> crate::Cx {
    crate::Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory))
}

fn table_value<'a>(expr: &'a Expr, key: &str) -> Option<&'a Expr> {
    let Expr::Map(entries) = expr else {
        return None;
    };
    entries.iter().find_map(|(entry_key, entry_value)| {
        let Expr::Symbol(entry_key) = entry_key else {
            return None;
        };
        (entry_key == &Symbol::new(key)).then_some(entry_value)
    })
}

fn table_keys(expr: &Expr) -> Vec<Symbol> {
    let Expr::Map(entries) = expr else {
        panic!("expected map expression");
    };
    entries
        .iter()
        .map(|(entry_key, _)| match entry_key {
            Expr::Symbol(symbol) => symbol.clone(),
            _ => panic!("expected symbol key"),
        })
        .collect()
}

#[test]
fn card_v1_field_order_is_stable() {
    let mut cx = cx();
    let subject = Ref::Symbol(Symbol::qualified("test", "ordered"));

    let card = card_for_ref(&mut cx, subject).unwrap();
    let expr = card.object().as_expr(&mut cx).unwrap();
    let keys = table_keys(&expr);

    assert_eq!(
        &keys[..10],
        &[
            Symbol::new("subject"),
            Symbol::new("kind"),
            Symbol::new("help"),
            Symbol::new("args"),
            Symbol::new("result"),
            Symbol::new("tests"),
            Symbol::new("ops"),
            Symbol::new("requires"),
            Symbol::new("see-also"),
            Symbol::new("shape-known"),
        ]
    );
    assert_eq!(
        card_fixed_predicates(),
        vec![
            Symbol::qualified("core", "subject"),
            Symbol::qualified("core", "kind"),
            Symbol::qualified("core", "help"),
            Symbol::qualified("core", "args"),
            Symbol::qualified("core", "result"),
            Symbol::qualified("core", "tests"),
            Symbol::qualified("core", "ops"),
            Symbol::qualified("core", "requires"),
            Symbol::qualified("core", "see-also"),
            Symbol::qualified("core", "shape-known"),
        ]
    );
}

#[test]
fn unknown_ref_produces_minimal_card() {
    let mut cx = cx();
    let subject = Ref::Symbol(Symbol::qualified("missing", "thing"));

    let card = card_for_ref(&mut cx, subject).unwrap();
    let expr = card.object().as_expr(&mut cx).unwrap();

    assert_eq!(
        table_value(&expr, "subject"),
        Some(&Expr::Symbol(Symbol::qualified("missing", "thing")))
    );
    assert_eq!(
        table_value(&expr, "kind"),
        Some(&Expr::Symbol(Symbol::qualified("core", "unknown")))
    );
    assert_eq!(
        table_value(&expr, "help"),
        Some(&Expr::String(String::new()))
    );
    assert_eq!(
        table_value(&expr, "args"),
        Some(&Expr::Symbol(Symbol::qualified("core", "Any")))
    );
    assert_eq!(
        table_value(&expr, "result"),
        Some(&Expr::Symbol(Symbol::qualified("core", "Any")))
    );
    assert_eq!(table_value(&expr, "tests"), Some(&Expr::List(Vec::new())));
    assert_eq!(table_value(&expr, "ops"), Some(&Expr::List(Vec::new())));
    assert_eq!(
        table_value(&expr, "requires"),
        Some(&Expr::List(Vec::new()))
    );
    assert_eq!(
        table_value(&expr, "see-also"),
        Some(&Expr::List(Vec::new()))
    );
    assert_eq!(table_value(&expr, "shape-known"), Some(&Expr::Bool(false)));
}

#[test]
fn every_value_produces_a_minimal_card() {
    let mut cx = cx();
    let value = cx.factory().string("hello".to_owned()).unwrap();

    let card = card_for_value(&mut cx, value).unwrap();
    let expr = card.object().as_expr(&mut cx).unwrap();

    assert!(table_value(&expr, "subject").is_some());
    assert_eq!(
        table_value(&expr, "kind"),
        Some(&Expr::Symbol(Symbol::qualified("core", "unknown")))
    );
    assert_eq!(
        table_value(&expr, "args"),
        Some(&Expr::Symbol(Symbol::qualified("core", "Any")))
    );
    assert_eq!(
        table_value(&expr, "result"),
        Some(&Expr::Symbol(Symbol::qualified("core", "Any")))
    );
    assert_eq!(table_value(&expr, "shape-known"), Some(&Expr::Bool(false)));
}

#[test]
fn visible_claims_override_minimal_card_defaults() {
    let mut cx = cx();
    let subject = Ref::Symbol(Symbol::qualified("test", "callable"));
    cx.insert_fact(Claim::public(
        subject.clone(),
        Symbol::qualified("core", "kind"),
        Ref::Symbol(Symbol::qualified("core", "function")),
    ))
    .unwrap();
    let help = crate::Claim::content_object(
        cx.datum_store_mut(),
        subject.clone(),
        Symbol::qualified("core", "help"),
        crate::Datum::String("callable help".to_owned()),
    )
    .unwrap();
    cx.insert_fact(help).unwrap();
    cx.insert_fact(Claim::public(
        subject.clone(),
        Symbol::qualified("core", "tests"),
        Ref::Symbol(Symbol::qualified("test", "callable-test")),
    ))
    .unwrap();

    let card = card_for_ref(&mut cx, subject).unwrap();
    let expr = card.object().as_expr(&mut cx).unwrap();

    assert_eq!(
        table_value(&expr, "kind"),
        Some(&Expr::Symbol(Symbol::qualified("core", "function")))
    );
    assert_eq!(
        table_value(&expr, "help"),
        Some(&Expr::String("callable help".to_owned()))
    );
    let Some(Expr::List(tests)) = table_value(&expr, "tests") else {
        panic!("tests should be a list");
    };
    assert_eq!(
        tests,
        &[Expr::Symbol(Symbol::qualified("test", "callable-test"))]
    );
}

#[test]
fn fallback_table_preserves_existing_metadata_fields() {
    let mut cx = cx();
    let subject = Ref::Symbol(Symbol::qualified("test", "old-function"));
    let fallback = cx
        .factory()
        .table(vec![
            (
                Symbol::new("symbol"),
                cx.factory().string("test/old-function".to_owned()).unwrap(),
            ),
            (
                Symbol::new("case-count"),
                cx.factory()
                    .number_literal(Symbol::qualified("numbers", "f64"), "2".to_owned())
                    .unwrap(),
            ),
        ])
        .unwrap();

    let card = card_for_ref_with_fallback(
        &mut cx,
        subject,
        Some(fallback),
        Some(Symbol::qualified("core", "function")),
    )
    .unwrap();
    let expr = card.object().as_expr(&mut cx).unwrap();

    assert_eq!(
        table_value(&expr, "kind"),
        Some(&Expr::Symbol(Symbol::qualified("core", "function")))
    );
    assert_eq!(
        table_value(&expr, "symbol"),
        Some(&Expr::String("test/old-function".to_owned()))
    );
    assert!(table_value(&expr, "case-count").is_some());

    let claims = cx
        .query_facts(ClaimPattern {
            subject: Some(Ref::Symbol(Symbol::qualified("test", "old-function"))),
            predicate: Some(Symbol::qualified("core", "kind")),
            object: Some(Ref::Symbol(Symbol::qualified("core", "function"))),
            include_revoked: false,
        })
        .unwrap();
    assert_eq!(claims.len(), 1);
}

#[test]
fn symbolic_subject_is_card_identity() {
    let mut cx = cx();
    let subject = Ref::Symbol(Symbol::qualified("core", "help"));

    let card = minimal_card(&mut cx, subject).unwrap();
    let expr = card.object().as_expr(&mut cx).unwrap();

    assert_eq!(
        table_value(&expr, "subject"),
        Some(&Expr::Symbol(Symbol::qualified("core", "help")))
    );
    assert!(table_value(&expr, "class-id").is_none());
}