sim-kernel 0.1.4

Small protocol kernel contracts for the expandable SIM Rust runtime.
Documentation
use std::sync::{Arc, Mutex};

use crate::{
    Callable, DefaultFactory, Error, NoopEvalPolicy, ShapeRef, Symbol, Value,
    object::{Args, RawArgs},
    shape::{
        ExprKind, MatchScore, Shape, ShapeBindings, ShapeCallTarget, ShapeDoc, ShapeMatch,
        call_shape, shape_is_subshape_of,
    },
};

struct AnyKernelShape;

impl Shape for AnyKernelShape {
    fn check_value(&self, _cx: &mut crate::Cx, _value: Value) -> crate::Result<ShapeMatch> {
        Ok(ShapeMatch::accept(MatchScore::exact(7)))
    }

    fn check_expr(&self, _cx: &mut crate::Cx, _expr: &crate::Expr) -> crate::Result<ShapeMatch> {
        Ok(ShapeMatch::accept(MatchScore::exact(5)))
    }

    fn describe(&self, _cx: &mut crate::Cx) -> crate::Result<ShapeDoc> {
        Ok(ShapeDoc::new("kernel-any"))
    }
}

struct ParentShape {
    symbol: Symbol,
    parents: Arc<Mutex<Vec<ShapeRef>>>,
}

impl Shape for ParentShape {
    fn symbol(&self) -> Option<Symbol> {
        Some(self.symbol.clone())
    }

    fn parents(&self, _cx: &mut crate::Cx) -> crate::Result<Vec<ShapeRef>> {
        Ok(self.parents.lock().unwrap().clone())
    }

    fn check_value(&self, _cx: &mut crate::Cx, _value: Value) -> crate::Result<ShapeMatch> {
        Ok(ShapeMatch::reject("not relevant"))
    }

    fn check_expr(&self, _cx: &mut crate::Cx, _expr: &crate::Expr) -> crate::Result<ShapeMatch> {
        Ok(ShapeMatch::reject("not relevant"))
    }

    fn describe(&self, _cx: &mut crate::Cx) -> crate::Result<ShapeDoc> {
        Ok(ShapeDoc::new(self.symbol.to_string()))
    }
}

fn cx() -> crate::Cx {
    crate::Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory))
}

fn parent_shape(cx: &mut crate::Cx, name: &str) -> (ShapeRef, Arc<Mutex<Vec<ShapeRef>>>) {
    let parents = Arc::new(Mutex::new(Vec::new()));
    let value = cx
        .factory()
        .opaque(Arc::new(ParentShape {
            symbol: Symbol::qualified("test", name),
            parents: parents.clone(),
        }))
        .unwrap();
    (value, parents)
}

#[test]
fn expr_kind_names_and_matches_live_in_kernel() {
    assert!(ExprKind::String.matches(&crate::Expr::String("ok".to_owned())));
    assert_eq!(ExprKind::String.name(), "string");
}

#[test]
fn shape_bindings_can_populate_env_from_kernel() {
    let mut cx = cx();
    let mut bindings = ShapeBindings::new();
    bindings.bind_expr(
        crate::Symbol::new("s"),
        crate::Expr::String("hello".to_owned()),
    );
    bindings.bind_value(crate::Symbol::new("n"), cx.factory().bool(true).unwrap());
    bindings.into_env(&mut cx).unwrap();
    assert!(cx.env().get(&crate::Symbol::new("s")).is_some());
    assert!(cx.env().get(&crate::Symbol::new("n")).is_some());
}

#[test]
fn kernel_shape_call_returns_match_table() {
    let mut cx = cx();
    let value = cx.factory().string("ok".to_owned()).unwrap();
    let matched = call_shape(&mut cx, &AnyKernelShape, ShapeCallTarget::Value(value)).unwrap();
    let table = matched.object().as_table(&mut cx).unwrap();
    let table = table.object().as_table_impl().unwrap();
    let _ = table.get(&mut cx, crate::Symbol::new("accepted")).unwrap();
    let _ = table.get(&mut cx, crate::Symbol::new("score")).unwrap();
}

#[test]
fn summed_rejects_stay_below_accept() {
    let mut score = MatchScore::reject();
    for _ in 0..64 {
        score += MatchScore::reject();
    }
    assert!(score < MatchScore::exact(0));
}

#[test]
fn shape_callable_accepts_exactly_one_argument() {
    let mut cx = cx();
    let shape = AnyKernelShape;
    let value = cx.factory().string("ok".to_owned()).unwrap();

    let matched = shape
        .call(&mut cx, Args::new(vec![value.clone()]))
        .expect("one evaluated value should match");
    assert!(matched.object().truth(&mut cx).unwrap());

    let error = shape.call(&mut cx, Args::new(Vec::new())).unwrap_err();
    assert!(matches!(error, Error::Eval(message) if message == "shape call expects 1 argument"));

    let error = shape
        .call(&mut cx, Args::new(vec![value.clone(), value]))
        .unwrap_err();
    assert!(matches!(error, Error::Eval(message) if message == "shape call expects 1 argument"));

    let error = shape
        .call_exprs(&mut cx, RawArgs::new(Vec::new()))
        .unwrap_err();
    assert!(matches!(error, Error::Eval(message) if message == "shape call expects 1 expression"));
}

#[test]
fn subshape_walk_treats_self_parent_cycle_as_not_proven() {
    let mut cx = cx();
    let (child, parents) = parent_shape(&mut cx, "cyclic-child");
    let (target, _) = parent_shape(&mut cx, "target");
    parents.lock().unwrap().push(child.clone());

    let child_shape = child.object().as_shape().unwrap();
    let target_shape = target.object().as_shape().unwrap();

    assert!(!shape_is_subshape_of(&mut cx, child_shape, target_shape).unwrap());
}

#[test]
fn subshape_walk_treats_two_shape_parent_cycle_as_not_proven() {
    let mut cx = cx();
    let (left, left_parents) = parent_shape(&mut cx, "cycle-left");
    let (right, right_parents) = parent_shape(&mut cx, "cycle-right");
    let (target, _) = parent_shape(&mut cx, "target");
    left_parents.lock().unwrap().push(right.clone());
    right_parents.lock().unwrap().push(left.clone());

    let left_shape = left.object().as_shape().unwrap();
    let target_shape = target.object().as_shape().unwrap();

    assert!(!shape_is_subshape_of(&mut cx, left_shape, target_shape).unwrap());
}