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::sync::Arc;

use sim_kernel::{
    Args, ContentId, Cx, Datum, DatumStore, DefaultFactory, EagerPolicy, Expr, Ref, Symbol, Value,
    card::ref_value, datum_content_algorithm,
};

use crate::runtime::{
    SimTest, TestExpected,
    browse::schema::{
        CoverageBuilder, FacetBuilder, HelpBuilder, RedactionBuilder, TestReportBuilder,
    },
    install_core_runtime,
};

use super::support::table_value;

#[test]
fn content_ref_browse_resolves_stored_datum_value() {
    let mut cx = test_cx();
    let id = cx
        .datum_store_mut()
        .intern(Datum::String("stored browse prose".to_owned()))
        .unwrap();
    let subject = ref_value(&mut cx, &Ref::Content(id)).unwrap();

    let card = cx
        .call_function(
            &Symbol::qualified("core", "browse"),
            Args::new(vec![subject]),
        )
        .unwrap();
    let expr = card.object().as_expr(&mut cx).unwrap();

    assert_eq!(
        table_value(&expr, &Symbol::new("kind")),
        Some(&Expr::Symbol(Symbol::qualified("browse", "content")))
    );
    let content = facet_payload(&expr, &Symbol::qualified("browse", "content"));
    assert_eq!(
        table_value(content, &Symbol::new("datum-kind")),
        Some(&Expr::Symbol(Symbol::qualified("datum", "string")))
    );
    assert_eq!(
        table_value(content, &Symbol::new("value")),
        Some(&Expr::String("stored browse prose".to_owned()))
    );
}

#[test]
fn missing_content_ref_browses_as_missing_ref_card() {
    let mut cx = test_cx();
    let id = ContentId::from_bytes(datum_content_algorithm(), [7; 32]);
    let subject = ref_value(&mut cx, &Ref::Content(id)).unwrap();

    let card = cx
        .call_function(
            &Symbol::qualified("core", "browse"),
            Args::new(vec![subject]),
        )
        .unwrap();
    let expr = card.object().as_expr(&mut cx).unwrap();

    assert_eq!(
        table_value(&expr, &Symbol::new("kind")),
        Some(&Expr::Symbol(Symbol::qualified("browse", "missing-ref")))
    );
    let missing = facet_payload(&expr, &Symbol::qualified("browse", "missing-ref"));
    assert!(matches!(
        table_value(missing, &Symbol::new("missing-ref")),
        Some(Expr::Extension { tag, .. }) if tag == &Symbol::qualified("core", "ref")
    ));
    assert_eq!(
        table_value(&expr, &Symbol::new("shape-known")),
        Some(&Expr::Bool(false))
    );
}

#[cfg(any(
    feature = "codec-lisp",
    feature = "codec-json",
    feature = "codec-binary",
    feature = "codec-binary-base64",
    feature = "codec-algol"
))]
#[test]
fn browse_schema_values_roundtrip_through_enabled_codecs() {
    let mut cx = test_cx();
    let codecs = install_enabled_codecs(&mut cx);
    let samples = schema_samples(&mut cx);

    for (label, expr) in samples {
        for codec in &codecs {
            let decoded = codec_roundtrip(&mut cx, codec, &expr);
            assert!(
                decoded.canonical_eq(&expr),
                "{label} did not round-trip through {codec}: {decoded:?}"
            );
        }
    }
}

#[cfg(any(
    feature = "codec-lisp",
    feature = "codec-json",
    feature = "codec-binary",
    feature = "codec-binary-base64",
    feature = "codec-algol"
))]
#[test]
fn test_expr_and_expected_codec_ids_survive_codec_roundtrips() {
    let mut cx = test_cx();
    let codecs = install_enabled_codecs(&mut cx);
    let expr = registered_codec_id_test_expr(&mut cx);
    assert_codec_id_fields(&expr);

    for codec in &codecs {
        let decoded = codec_roundtrip(&mut cx, codec, &expr);
        assert_codec_id_fields(&decoded);
    }
}

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

fn facet_payload<'a>(card: &'a Expr, name: &Symbol) -> &'a Expr {
    let Some(Expr::List(facets)) = table_value(card, &Symbol::new("facets")) else {
        panic!("Card should contain facets");
    };
    facets
        .iter()
        .find_map(|facet| {
            let facet_name = table_value(facet, &Symbol::new("name"))?;
            (facet_name == &Expr::Symbol(name.clone()))
                .then(|| table_value(facet, &Symbol::new("value")).expect("facet value"))
        })
        .unwrap_or_else(|| panic!("missing facet {name}"))
}

#[cfg(any(
    feature = "codec-lisp",
    feature = "codec-json",
    feature = "codec-binary",
    feature = "codec-binary-base64",
    feature = "codec-algol"
))]
fn schema_samples(cx: &mut Cx) -> Vec<(&'static str, Expr)> {
    let subject = cx
        .factory()
        .symbol(Symbol::qualified("core", "help"))
        .unwrap();
    let card = cx
        .call_function(
            &Symbol::qualified("core", "browse"),
            Args::new(vec![subject]),
        )
        .unwrap();
    let help_subject = cx
        .factory()
        .symbol(Symbol::qualified("core", "help"))
        .unwrap();
    let help = HelpBuilder::new(help_subject).build(cx).unwrap();
    let test = registered_codec_id_test_expr(cx);
    let coverage = CoverageBuilder {
        tests: 1,
        examples: 1,
        runnable: 1,
        passed: Some(1),
        failed: Some(0),
        skipped: Some(0),
        last_run: None,
        stale: false,
    }
    .build(cx)
    .unwrap();
    let mut facet_builder = FacetBuilder::new(Symbol::qualified("browse", "codec-roundtrip"));
    facet_builder.kind = Symbol::new("roundtrip");
    facet_builder.value = Some(
        cx.factory()
            .string("facet payload survives codec data mode".to_owned())
            .unwrap(),
    );
    let facet = facet_builder.build(cx).unwrap();
    let mut redaction_builder = RedactionBuilder::unavailable();
    redaction_builder.summary = "hidden payload".to_owned();
    let redaction = redaction_builder.build(cx).unwrap();
    let mut report_builder = TestReportBuilder::new(Symbol::qualified("test", "codec-ids"));
    report_builder.passed = true;
    report_builder.mode = Symbol::qualified("test", "value");
    report_builder.detail = Some("passed".to_owned());
    let report = report_builder.build(cx).unwrap();

    vec![
        ("Card", value_expr(cx, card)),
        ("Help", value_expr(cx, help)),
        ("Test", test),
        ("Coverage", value_expr(cx, coverage)),
        ("Facet", value_expr(cx, facet)),
        ("Redaction", value_expr(cx, redaction)),
        ("TestReport", value_expr(cx, report)),
    ]
}

#[cfg(any(
    feature = "codec-lisp",
    feature = "codec-json",
    feature = "codec-binary",
    feature = "codec-binary-base64",
    feature = "codec-algol"
))]
fn registered_codec_id_test_expr(cx: &mut Cx) -> Expr {
    let name = Symbol::qualified("test", "codec-ids");
    let test = SimTest::new(
        name.clone(),
        Symbol::qualified("test", "runtime"),
        Expr::List(vec![
            Expr::Symbol(Symbol::qualified("core", "quote")),
            Expr::String("lisp-authored expr".to_owned()),
        ]),
        TestExpected::Value(Expr::Map(vec![(
            Expr::Symbol(Symbol::new("expected")),
            Expr::String("json-authored expected".to_owned()),
        )])),
        vec![Symbol::qualified("core", "browse")],
    )
    .with_expr_codec(Symbol::qualified("codec", "lisp"))
    .with_expected_codec(Symbol::qualified("codec", "json"));
    cx.registry_mut()
        .register_test(
            name.clone(),
            Symbol::qualified("test", "runtime"),
            Arc::new(test),
            vec![Symbol::qualified("core", "browse")],
        )
        .unwrap();

    let tests = cx
        .call_function(&Symbol::qualified("core", "tests"), Args::new(Vec::new()))
        .unwrap();
    let Expr::List(entries) = tests.object().as_expr(cx).unwrap() else {
        panic!("core/tests should return a list");
    };
    entries
        .into_iter()
        .find(|entry| table_value(entry, &Symbol::new("name")) == Some(&Expr::Symbol(name.clone())))
        .expect("registered codec id test")
}

#[cfg(any(
    feature = "codec-lisp",
    feature = "codec-json",
    feature = "codec-binary",
    feature = "codec-binary-base64",
    feature = "codec-algol"
))]
fn assert_codec_id_fields(expr: &Expr) {
    assert_eq!(
        table_value(expr, &Symbol::new("expr-codec")),
        Some(&Expr::Symbol(Symbol::qualified("codec", "lisp")))
    );
    assert_eq!(
        table_value(expr, &Symbol::new("expected-codec")),
        Some(&Expr::Symbol(Symbol::qualified("codec", "json")))
    );
}

#[cfg(any(
    feature = "codec-lisp",
    feature = "codec-json",
    feature = "codec-binary",
    feature = "codec-binary-base64",
    feature = "codec-algol"
))]
fn value_expr(cx: &mut Cx, value: Value) -> Expr {
    value.object().as_expr(cx).unwrap()
}

#[cfg(any(
    feature = "codec-lisp",
    feature = "codec-json",
    feature = "codec-binary",
    feature = "codec-binary-base64",
    feature = "codec-algol"
))]
fn codec_roundtrip(cx: &mut Cx, codec: &Symbol, expr: &Expr) -> Expr {
    let output =
        sim_codec::encode_with_codec(cx, codec, expr, sim_kernel::EncodeOptions::default())
            .unwrap();
    let input = match output {
        sim_codec::Output::Text(text) => sim_codec::Input::Text(text),
        sim_codec::Output::Bytes(bytes) => sim_codec::Input::Bytes(bytes),
    };
    sim_codec::decode_with_codec(cx, codec, input, sim_kernel::ReadPolicy::default()).unwrap()
}

#[cfg(any(
    feature = "codec-lisp",
    feature = "codec-json",
    feature = "codec-binary",
    feature = "codec-binary-base64",
    feature = "codec-algol"
))]
fn install_enabled_codecs(cx: &mut Cx) -> Vec<Symbol> {
    let mut symbols = Vec::new();

    #[cfg(feature = "codec-lisp")]
    {
        let symbol = Symbol::qualified("codec", "lisp");
        if cx.registry().codec_by_symbol(&symbol).is_none() {
            let lib =
                crate::codec_lisp::LispCodecLib::new(cx.registry_mut().fresh_codec_id()).unwrap();
            cx.load_lib(&lib).unwrap();
        }
        symbols.push(symbol);
    }

    #[cfg(feature = "codec-json")]
    {
        let symbol = Symbol::qualified("codec", "json");
        if cx.registry().codec_by_symbol(&symbol).is_none() {
            let lib = crate::codec_json::JsonCodecLib::new(cx.registry_mut().fresh_codec_id());
            cx.load_lib(&lib).unwrap();
        }
        symbols.push(symbol);
    }

    #[cfg(feature = "codec-binary")]
    {
        let symbol = Symbol::qualified("codec", "binary");
        if cx.registry().codec_by_symbol(&symbol).is_none() {
            let lib = crate::codec_binary::BinaryCodecLib::new(cx.registry_mut().fresh_codec_id());
            cx.load_lib(&lib).unwrap();
        }
        symbols.push(symbol);
    }

    #[cfg(feature = "codec-binary-base64")]
    {
        let symbol = Symbol::qualified("codec", "binary-base64");
        if cx.registry().codec_by_symbol(&symbol).is_none() {
            let lib = crate::codec_binary_base64::BinaryBase64CodecLib::new(
                cx.registry_mut().fresh_codec_id(),
            );
            cx.load_lib(&lib).unwrap();
        }
        symbols.push(symbol);
    }

    #[cfg(feature = "codec-algol")]
    {
        let symbol = Symbol::qualified("codec", "algol");
        if cx.registry().codec_by_symbol(&symbol).is_none() {
            let lib = crate::codec_algol::AlgolCodecLib::new(cx.registry_mut().fresh_codec_id());
            cx.load_lib(&lib).unwrap();
        }
        symbols.push(symbol);
    }

    assert!(!symbols.is_empty());
    symbols
}