sim-lib-binding 0.1.0-rc.1

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

use sim_kernel::{
    Args, Cx, Error, Expr, NumberLiteral, Ref, Result, Symbol,
    card::{card_for_ref, card_kind_predicate},
    force_list_to_vec,
    standard::standard_organ_kind,
};

use crate::*;

use sim_kernel::testing::bare_cx as cx;

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

fn bool_value(cx: &mut Cx, value: bool) -> sim_kernel::Value {
    cx.factory().bool(value).unwrap()
}

fn symbol_value(cx: &mut Cx, name: &str) -> sim_kernel::Value {
    cx.factory().symbol(Symbol::new(name)).unwrap()
}

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

fn bool_from_value(cx: &mut Cx, value: &sim_kernel::Value) -> bool {
    let Expr::Bool(value) = value.object().as_expr(cx).unwrap() else {
        panic!("expected bool value");
    };
    value
}

fn call_value(
    cx: &mut Cx,
    function: sim_kernel::Value,
    args: Vec<sim_kernel::Value>,
) -> Result<sim_kernel::Value> {
    let Some(callable) = function.object().as_callable() else {
        return Err(Error::TypeMismatch {
            expected: "callable binding value",
            found: "non-callable",
        });
    };
    callable.call(cx, Args::new(args))
}

#[test]
fn lexical_let_star_sees_prior_bindings() {
    let mut cx = cx();
    let env = LexicalEnv::new();
    let x = Symbol::new("x");
    let y = Symbol::new("y");

    let result = eval_let_star(
        &mut cx,
        &env,
        vec![
            {
                let x = x.clone();
                (
                    x,
                    Box::new(|cx: &mut Cx, _env: &LexicalEnv| Ok(number(cx, 2)))
                        as BindingInitializer,
                )
            },
            {
                let x = x.clone();
                let y = y.clone();
                (
                    y,
                    Box::new(move |cx: &mut Cx, env: &LexicalEnv| {
                        let left = number_from_value(cx, &env.lookup(&x)?);
                        Ok(number(cx, left + 3))
                    }) as BindingInitializer,
                )
            },
        ],
        |_cx, env| env.lookup(&y),
    )
    .unwrap();

    assert_eq!(number_from_value(&mut cx, &result), 5);
}

#[test]
fn letrec_handles_mutual_recursion() {
    let mut cx = cx();
    let root = LexicalEnv::new();
    let even = Symbol::new("even?");
    let odd = Symbol::new("odd?");

    let result = eval_letrec(
        &mut cx,
        &root,
        vec![
            {
                let even = even.clone();
                let odd = odd.clone();
                (
                    even.clone(),
                    Box::new(move |cx: &mut Cx, env: &LexicalEnv| {
                        let captured = env.clone();
                        let name = even.clone();
                        let peer = odd.clone();
                        lexical_function_value(
                            cx,
                            name,
                            captured,
                            Arc::new(move |cx, env, args| {
                                let n = number_from_value(cx, &args[0]);
                                if n == 0 {
                                    return Ok(bool_value(cx, true));
                                }
                                let next = number(cx, n - 1);
                                let peer_function = env.lookup(&peer)?;
                                call_value(cx, peer_function, vec![next])
                            }),
                        )
                    }) as BindingInitializer,
                )
            },
            {
                let even = even.clone();
                let odd = odd.clone();
                (
                    odd.clone(),
                    Box::new(move |cx: &mut Cx, env: &LexicalEnv| {
                        let captured = env.clone();
                        let name = odd.clone();
                        let peer = even.clone();
                        lexical_function_value(
                            cx,
                            name,
                            captured,
                            Arc::new(move |cx, env, args| {
                                let n = number_from_value(cx, &args[0]);
                                if n == 0 {
                                    return Ok(bool_value(cx, false));
                                }
                                let next = number(cx, n - 1);
                                let peer_function = env.lookup(&peer)?;
                                call_value(cx, peer_function, vec![next])
                            }),
                        )
                    }) as BindingInitializer,
                )
            },
        ],
        |cx, env| {
            let arg = number(cx, 8);
            let function = env.lookup(&even)?;
            call_value(cx, function, vec![arg])
        },
    )
    .unwrap();

    assert!(bool_from_value(&mut cx, &result));
}

#[test]
fn dynamic_binding_is_restored_after_escape() {
    let mut cx = cx();
    let env = DynamicEnv::new();
    let fluid = Symbol::new("fluid");

    let outer = symbol_value(&mut cx, "outer");
    let inner = symbol_value(&mut cx, "inner");
    let escaped = env.with_bindings(vec![(fluid.clone(), outer.clone())], || {
        assert_eq!(env.lookup(&fluid)?.unwrap(), outer);
        let result: Result<()> = env.with_bindings(vec![(fluid.clone(), inner)], || {
            assert!(env.lookup(&fluid)?.is_some());
            Err(Error::Eval("simulated non-local escape".to_owned()))
        });
        assert!(result.is_err());
        assert_eq!(env.lookup(&fluid)?.unwrap(), outer);
        Ok(())
    });

    escaped.unwrap();
    assert!(env.lookup(&fluid).unwrap().is_none());
}

#[test]
fn parameters_respect_control_dynamic_extent() {
    let mut cx = cx();
    let parameter = Parameter::new(
        Symbol::new("current-output"),
        symbol_value(&mut cx, "default"),
    );
    let temporary = symbol_value(&mut cx, "temporary");

    let result: Result<()> = parameter.with_value(temporary, || {
        assert_eq!(
            parameter.get()?.object().as_expr(&mut cx).unwrap(),
            Expr::Symbol(Symbol::new("temporary"))
        );
        Err(Error::Eval("simulated control escape".to_owned()))
    });

    assert!(result.is_err());
    assert_eq!(
        parameter.get().unwrap().object().as_expr(&mut cx).unwrap(),
        Expr::Symbol(Symbol::new("default"))
    );
}

#[test]
fn profile_options_select_binding_and_hygiene_modes() {
    let modes = BindingProfileModes::from_options(&[
        (
            Symbol::new("scope"),
            Expr::Symbol(Symbol::qualified("binding", "dynamic")),
        ),
        (
            Symbol::new("hygiene"),
            Expr::Symbol(Symbol::qualified("binding", "explicit")),
        ),
    ])
    .unwrap();

    assert_eq!(modes.scope, BindingScopeMode::Dynamic);
    assert_eq!(modes.hygiene, HygieneMode::Explicit);
    assert_eq!(
        BindingProfileModes::default().scope,
        BindingScopeMode::Lexical
    );
}

#[test]
fn binding_organ_claims_project_to_card() {
    let mut cx = cx();
    publish_binding_organ_claims(&mut cx).unwrap();

    let claims = cx
        .query_facts(sim_kernel::ClaimPattern {
            subject: Some(Ref::Symbol(binding_organ_symbol())),
            predicate: Some(card_kind_predicate()),
            object: Some(Ref::Symbol(standard_organ_kind())),
            include_revoked: false,
        })
        .unwrap();
    assert_eq!(claims.len(), 1);

    let card = card_for_ref(&mut cx, Ref::Symbol(binding_organ_symbol())).unwrap();
    let table = card.object().as_table(&mut cx).unwrap();
    let entries = table.object().as_table_impl().unwrap();
    let ops = entries.get(&mut cx, Symbol::new("ops")).unwrap();
    let list = ops.object().as_list().unwrap();
    let values = force_list_to_vec(&mut cx, list, "binding organ ops").unwrap();

    assert!(values.into_iter().any(|value| {
        value.object().as_expr(&mut cx).unwrap()
            == Expr::Symbol(Symbol::qualified("binding", "letrec.v1"))
    }));
}