sim-nest 0.1.5

The SIM constellation umbrella facade: one crate that re-exports the runtime kernel, codecs, number domains, and libraries behind features.
Documentation
use std::{collections::BTreeSet, sync::Arc};

use sim_kernel::{Args, Cx, DefaultFactory, Expr, NoopEvalPolicy, Symbol, Value};

use crate::runtime::install_core_runtime;

use super::support::table_value;

#[test]
fn browse_neighbors_extracts_card_edges_and_leaf_cards() {
    let mut cx = test_cx();
    let core_help = symbol_value(&cx, Symbol::qualified("core", "help"));
    let neighbors = call(
        &mut cx,
        Symbol::qualified("core", "browse-neighbors"),
        vec![core_help],
    );
    let symbols = symbol_set(&expr(&mut cx, &neighbors));

    assert!(symbols.contains(&Symbol::qualified("core", "help")));
    assert!(symbols.contains(&Symbol::qualified("core/help", "args")));
    assert!(symbols.contains(&Symbol::qualified("core/help", "result")));
    assert!(symbols.contains(&Symbol::new("core")));
    assert!(symbols.contains(&Symbol::qualified("browse", "Help")));
    assert!(symbols.contains(&Symbol::qualified("capability", "browse.internal")));
    assert!(symbols.contains(&Symbol::qualified("core", "call.v1")));

    assert_card_kind(
        &mut cx,
        Symbol::qualified("capability", "browse.internal"),
        Symbol::qualified("browse", "capability"),
    );
    assert_card_kind(
        &mut cx,
        Symbol::qualified("core", "call.v1"),
        Symbol::qualified("browse", "op"),
    );
    assert_card_kind(
        &mut cx,
        Symbol::qualified("core/help", "args"),
        Symbol::qualified("core", "shape"),
    );
}

#[test]
fn browse_path_returns_ref_list_or_nil() {
    let mut cx = test_cx();
    let core_help = symbol_value(&cx, Symbol::qualified("core", "help"));
    let core_card = symbol_value(&cx, Symbol::qualified("core", "Card"));
    let path = call(
        &mut cx,
        Symbol::qualified("core", "browse-path"),
        vec![core_help, core_card],
    );
    let path = expr(&mut cx, &path);
    let Expr::List(items) = &path else {
        panic!("reachable path should be a list");
    };
    assert_eq!(
        items.first(),
        Some(&Expr::Symbol(Symbol::qualified("core", "help")))
    );
    assert_eq!(
        items.last(),
        Some(&Expr::Symbol(Symbol::qualified("core", "Card")))
    );
    assert!(items.iter().all(is_ref_expr));

    let core_help = symbol_value(&cx, Symbol::qualified("core", "help"));
    let missing_target = symbol_value(&cx, Symbol::qualified("external", "missing"));
    let missing = call(
        &mut cx,
        Symbol::qualified("core", "browse-path"),
        vec![core_help, missing_target],
    );
    assert_eq!(expr(&mut cx, &missing), Expr::Nil);
}

#[cfg(feature = "codec-lisp")]
#[test]
fn root_graph_path_reaches_function_shape_codec_test_and_capability() {
    let mut cx = test_cx();
    let codec_id = cx.registry_mut().fresh_codec_id();
    let lib = crate::codec_lisp::LispCodecLib::new(codec_id).unwrap();
    cx.load_lib(&lib).unwrap();

    for (target, kind) in [
        (
            Symbol::qualified("core", "help"),
            Symbol::qualified("core", "function"),
        ),
        (
            Symbol::qualified("browse", "Help"),
            Symbol::qualified("core", "shape"),
        ),
        (
            Symbol::qualified("codec", "lisp"),
            Symbol::qualified("core", "codec"),
        ),
        (
            Symbol::qualified("browse-example", "core-card"),
            Symbol::qualified("core", "test"),
        ),
        (
            Symbol::qualified("capability", "browse.internal"),
            Symbol::qualified("browse", "capability"),
        ),
    ] {
        let root = symbol_value(&cx, Symbol::qualified("browse", "catalog"));
        let target_value = symbol_value(&cx, target.clone());
        let path = call(
            &mut cx,
            Symbol::qualified("core", "browse-path"),
            vec![root, target_value],
        );
        assert_path_reaches(&mut cx, path, &target);
        assert_card_kind(&mut cx, target, kind);
    }
}

#[test]
fn zero_arg_browse_neighbors_starts_at_root_catalog() {
    let mut cx = test_cx();
    let neighbors = call(
        &mut cx,
        Symbol::qualified("core", "browse-neighbors"),
        Vec::new(),
    );
    let symbols = symbol_set(&expr(&mut cx, &neighbors));

    assert!(symbols.contains(&Symbol::qualified("core", "functions")));
    assert!(symbols.contains(&Symbol::qualified("core", "shapes")));
    assert!(symbols.contains(&Symbol::qualified("core", "tests")));
    assert!(symbols.contains(&Symbol::qualified("capability", "browse.internal")));
}

fn test_cx() -> Cx {
    let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
    install_core_runtime(&mut cx);
    cx
}

fn call(cx: &mut Cx, symbol: Symbol, args: Vec<Value>) -> Value {
    cx.call_function(&symbol, Args::new(args))
        .unwrap_or_else(|err| panic!("{symbol} failed: {err}"))
}

fn symbol_value(cx: &Cx, symbol: Symbol) -> Value {
    cx.factory().symbol(symbol).unwrap()
}

fn expr(cx: &mut Cx, value: &Value) -> Expr {
    value.object().as_expr(cx).unwrap()
}

fn symbol_set(expr: &Expr) -> BTreeSet<Symbol> {
    let Expr::List(items) = expr else {
        panic!("expected list");
    };
    items
        .iter()
        .filter_map(|item| match item {
            Expr::Symbol(symbol) => Some(symbol.clone()),
            _ => None,
        })
        .collect()
}

fn assert_card_kind(cx: &mut Cx, subject: Symbol, expected: Symbol) {
    let subject_value = symbol_value(cx, subject.clone());
    let card = call(cx, Symbol::qualified("core", "browse"), vec![subject_value]);
    let card = expr(cx, &card);
    assert_eq!(
        table_value(&card, &Symbol::new("kind")),
        Some(&Expr::Symbol(expected)),
        "{subject} should browse with the expected kind"
    );
}

fn assert_path_reaches(cx: &mut Cx, path: Value, target: &Symbol) {
    let path = expr(cx, &path);
    let Expr::List(items) = path else {
        panic!("path to {target} should be a list");
    };
    assert_eq!(items.last(), Some(&Expr::Symbol(target.clone())));
    assert!(items.iter().all(is_ref_expr));
}

fn is_ref_expr(expr: &Expr) -> bool {
    match expr {
        Expr::Symbol(_) => true,
        Expr::Extension { tag, .. } => tag == &Symbol::qualified("core", "ref"),
        _ => false,
    }
}