sim-lib-lang-clojure 0.1.0

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

use sim_codec::{Input, decode_tree_with_codec};
use sim_kernel::{
    Cx, Expr, NumberLiteral, ReadPolicy, Ref, Symbol, TrustLevel, Value,
    capability::{control_capture_capability, control_prompt_capability},
    control::{control_aborted_status, control_result_status},
};
use sim_lib_namespace::{ImportOptions, Namespace};
use sim_lib_sequence::{TransducerPipeline, force_sequence_bounded};
use sim_lib_standard_core::ProfileRegistry;

use crate::*;

use sim_kernel::testing::bare_cx as cx;

fn number(cx: &mut Cx, value: u64) -> Value {
    cx.factory()
        .number_literal(Symbol::qualified("test", "u64"), value.to_string())
        .unwrap()
}

fn number_from_value(cx: &mut Cx, value: &Value) -> u64 {
    let Expr::Number(NumberLiteral { canonical, .. }) = value.object().as_expr(cx).unwrap() else {
        panic!("expected number value");
    };
    canonical.parse().unwrap()
}

#[test]
fn edn_reader_decodes_core_data_with_locations() {
    let mut cx = cx();
    let codec_id = cx.registry_mut().fresh_codec_id();
    cx.load_lib(&ClojureEdnCodecLib::new(codec_id)).unwrap();

    let tree = decode_tree_with_codec(
        &mut cx,
        &clojure_edn_reader_symbol(),
        Input::Text("{:a [1 2] :flag true}".to_owned()),
        read_policy(),
        "unit.edn",
    )
    .unwrap();

    assert_eq!(tree.origin.as_ref().unwrap().source.0.as_str(), "unit.edn");
    let Expr::Map(entries) = &tree.expr else {
        panic!("expected EDN map");
    };
    assert_eq!(entries.len(), 2);
    assert_eq!(tree.children.len(), 4);
    assert_eq!(
        entries[0].0,
        Expr::Symbol(Symbol::qualified("keyword", "a"))
    );
    assert!(matches!(entries[0].1, Expr::Vector(_)));
}

#[test]
fn clojure_data_uses_sequence_organ() {
    let mut cx = cx();
    let expr = Expr::Vector(vec![
        Expr::Number(NumberLiteral {
            domain: Symbol::qualified("numbers", "i64"),
            canonical: "1".to_owned(),
        }),
        Expr::Number(NumberLiteral {
            domain: Symbol::qualified("numbers", "i64"),
            canonical: "2".to_owned(),
        }),
    ]);
    let vector = edn_expr_to_value(&mut cx, &expr).unwrap();
    assert_eq!(vector.object().as_expr(&mut cx).unwrap(), expr);

    let one = number(&mut cx, 1);
    let two = number(&mut cx, 2);
    let three = number(&mut cx, 3);
    let source = clojure_profile_sequence(&mut cx, vec![one, two, three]).unwrap();
    let pipeline = TransducerPipeline::new().map(Arc::new(|cx, value| {
        let next = number_from_value(cx, &value) + 1;
        Ok(number(cx, next))
    }));
    let zero = number(&mut cx, 0);
    let sum = clojure_transduce(
        &mut cx,
        &source,
        pipeline,
        zero,
        Arc::new(|cx, acc, value| {
            let left = number_from_value(cx, &acc);
            let right = number_from_value(cx, &value);
            Ok(number(cx, left + right))
        }),
    )
    .unwrap();

    assert_eq!(number_from_value(&mut cx, &sum), 9);
    assert!(force_sequence_bounded(&mut cx, &source, 0, "already consumed").is_ok());
}

#[test]
fn clojure_core_namespace_exports_sequence_and_recur() {
    let source = clojure_core_namespace().unwrap();
    let mut user = Namespace::module(Symbol::qualified("user", "unit"));
    user.import_from(
        &source,
        &Symbol::new("transduce"),
        ImportOptions::new().rename(Symbol::new("xduce")),
    )
    .unwrap();
    user.import_from(&source, &Symbol::new("recur"), ImportOptions::new())
        .unwrap();

    assert_eq!(
        user.resolve(&Symbol::new("xduce")).unwrap().target(),
        &Symbol::qualified("sequence", "transduce.v1")
    );
    assert_eq!(
        user.resolve(&Symbol::new("recur")).unwrap().target(),
        &Symbol::qualified("control", "abort.v1")
    );
}

#[test]
fn recur_uses_control_prompt() {
    let mut cx = cx();
    sim_lib_control::install_control_policy(&mut cx);
    cx.grant(control_prompt_capability());
    cx.grant(control_capture_capability());
    let result = clojure_loop_prompt(&mut cx, Ref::Symbol(Symbol::new("input")), |cx| {
        clojure_recur(cx, Ref::Symbol(Symbol::new("next-bindings")))
    })
    .unwrap();

    assert_eq!(
        control_result_status(&cx, &result).unwrap(),
        Some(control_aborted_status())
    );
}

#[test]
fn clojure_profile_publishes_per_organ_fidelity() {
    let mut cx = cx();
    let mut registry = ProfileRegistry::new();
    let profile = install_clojure_core_profile(&mut cx, &mut registry).unwrap();

    let badges = cx
        .query_facts(sim_kernel::ClaimPattern {
            subject: Some(Ref::Symbol(profile.symbol.clone())),
            predicate: Some(Symbol::qualified("standard", "fidelity-badge")),
            object: None,
            include_revoked: false,
        })
        .unwrap();
    assert_eq!(badges.len(), 3);
    for badge in [
        clojure_sequence_fidelity_symbol(),
        clojure_namespace_fidelity_symbol(),
        clojure_control_fidelity_symbol(),
    ] {
        assert!(
            badges
                .iter()
                .any(|claim| claim.object == Ref::Symbol(badge.clone()))
        );
    }
    assert!(registry.profile(&profile.symbol).is_some());
}

fn read_policy() -> ReadPolicy {
    ReadPolicy {
        trust: TrustLevel::TrustedSource,
        capabilities: sim_kernel::CapabilitySet::new(),
    }
}