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, DefaultFactory, EagerPolicy, Expr, NumberLiteral, Symbol};

use crate::runtime::install_core_runtime;

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

fn number_expr(text: &str) -> Expr {
    Expr::Number(NumberLiteral {
        domain: Symbol::qualified("numbers", "f64"),
        canonical: text.to_owned(),
    })
}

fn number_value(cx: &mut sim_kernel::Cx, text: &str) -> sim_kernel::Value {
    cx.factory()
        .number_literal(Symbol::qualified("numbers", "f64"), text.to_owned())
        .unwrap()
}

fn expr_kind_shape(cx: &mut sim_kernel::Cx, kind: &str) -> sim_kernel::Value {
    let kind = cx.factory().symbol(Symbol::new(kind)).unwrap();
    cx.call_class(
        &Symbol::qualified("core", "ExprKindShape"),
        Args::new(vec![kind]),
    )
    .unwrap()
}

#[test]
fn runtime_combiner_constructors_are_registered() {
    let mut cx = cx();
    let any = cx.resolve_shape(&Symbol::qualified("core", "Any")).unwrap();
    let number = expr_kind_shape(&mut cx, "number");
    let parts = cx.factory().list(vec![any, number]).unwrap();
    let shape = cx
        .call_function(&Symbol::qualified("shape", "and"), Args::new(vec![parts]))
        .unwrap();

    let matched = shape
        .object()
        .as_shape()
        .unwrap()
        .check_expr(&mut cx, &number_expr("1"))
        .unwrap();
    assert!(matched.accepted);

    let matched = shape
        .object()
        .as_shape()
        .unwrap()
        .check_expr(&mut cx, &Expr::String("no".to_owned()))
        .unwrap();
    assert!(!matched.accepted);
    assert!(matched.diagnostics[0].message.starts_with("shape-and:"));
}

#[test]
fn runtime_alias_constructors_are_registered() {
    let mut cx = cx();
    let any = cx.resolve_shape(&Symbol::qualified("core", "Any")).unwrap();
    let number = expr_kind_shape(&mut cx, "number");
    let choices = cx.factory().list(vec![number]).unwrap();
    let none = cx
        .call_function(&Symbol::qualified("shape", "none"), Args::new(vec![any]))
        .unwrap();
    let any_shape = cx
        .call_function(&Symbol::qualified("shape", "any"), Args::new(vec![choices]))
        .unwrap();
    let without = cx
        .call_function(
            &Symbol::qualified("shape", "without"),
            Args::new(vec![any_shape, none]),
        )
        .unwrap();

    assert!(without.object().as_shape().is_some());
}

#[test]
fn runtime_list_rest_constructor_builds_variadic_list_shape() {
    let mut cx = cx();
    let any = cx.resolve_shape(&Symbol::qualified("core", "Any")).unwrap();
    let number = expr_kind_shape(&mut cx, "number");
    let prefix = cx.factory().list(vec![any]).unwrap();
    let shape = cx
        .call_function(
            &Symbol::qualified("shape", "list-rest"),
            Args::new(vec![prefix, number]),
        )
        .unwrap();

    let matched = shape
        .object()
        .as_shape()
        .unwrap()
        .check_expr(
            &mut cx,
            &Expr::List(vec![Expr::Bool(true), number_expr("1"), number_expr("2")]),
        )
        .unwrap();

    assert!(matched.accepted);
}

#[test]
fn runtime_table_and_repeat_constructors_work() {
    let mut cx = cx();
    let number = expr_kind_shape(&mut cx, "number");
    let key = cx.factory().symbol(Symbol::new("n")).unwrap();
    let field = cx.factory().list(vec![key, number.clone()]).unwrap();
    let fields = cx.factory().list(vec![field]).unwrap();
    let table_shape = cx
        .call_function(
            &Symbol::qualified("shape", "table-closed"),
            Args::new(vec![fields]),
        )
        .unwrap();
    let extra = number_value(&mut cx, "2");
    let required = number_value(&mut cx, "1");
    let table = cx
        .factory()
        .table(vec![
            (Symbol::new("n"), required),
            (Symbol::new("extra"), extra),
        ])
        .unwrap();

    let matched = table_shape
        .object()
        .as_shape()
        .unwrap()
        .check_value(&mut cx, table)
        .unwrap();
    assert!(!matched.accepted);
    assert!(matched.diagnostics[0].message.starts_with("shape-table:"));

    let min = number_value(&mut cx, "1");
    let max = number_value(&mut cx, "2");
    let repeat_shape = cx
        .call_function(
            &Symbol::qualified("shape", "repeat-bounds"),
            Args::new(vec![number, min, max]),
        )
        .unwrap();
    let matched = repeat_shape
        .object()
        .as_shape()
        .unwrap()
        .check_expr(
            &mut cx,
            &Expr::Vector(vec![number_expr("1"), number_expr("2")]),
        )
        .unwrap();
    assert!(matched.accepted);
}

#[test]
fn runtime_shape_constructors_encode_as_expr_calls() {
    let mut cx = cx();
    let any = cx.resolve_shape(&Symbol::qualified("core", "Any")).unwrap();
    let parts = cx.factory().list(vec![any]).unwrap();
    let shape = cx
        .call_function(&Symbol::qualified("shape", "and"), Args::new(vec![parts]))
        .unwrap();

    let expr = shape.object().as_expr(&mut cx).unwrap();

    assert!(matches!(
        expr,
        Expr::Call { operator, .. }
            if *operator == Expr::Symbol(Symbol::qualified("shape", "and"))
    ));
}

#[cfg(feature = "codec-lisp")]
#[test]
fn runtime_shape_constructor_round_trips_through_lisp_codec() {
    use sim_codec::{Input, Output, decode_with_codec, encode_with_codec};
    use sim_codec_lisp::LispCodecLib;
    use sim_kernel::{EncodeOptions, ReadPolicy};

    let mut cx = cx();
    let codec_id = cx.registry_mut().fresh_codec_id();
    cx.load_lib(&LispCodecLib::new(codec_id).unwrap()).unwrap();
    let any = cx.resolve_shape(&Symbol::qualified("core", "Any")).unwrap();
    let parts = cx.factory().list(vec![any]).unwrap();
    let shape = cx
        .call_function(&Symbol::qualified("shape", "and"), Args::new(vec![parts]))
        .unwrap();
    let expr = shape.object().as_expr(&mut cx).unwrap();
    let output = encode_with_codec(
        &mut cx,
        &Symbol::qualified("codec", "lisp"),
        &expr,
        EncodeOptions::default(),
    )
    .unwrap();
    let input = match output {
        Output::Text(text) => Input::Text(text),
        Output::Bytes(bytes) => Input::Bytes(bytes),
    };
    let decoded = decode_with_codec(
        &mut cx,
        &Symbol::qualified("codec", "lisp"),
        input,
        ReadPolicy::default(),
    )
    .unwrap();
    let value = cx.eval_expr(decoded).unwrap();

    assert!(value.object().as_shape().is_some());
}