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::{Cx, Error, Expr, PreparedArgs, Result, Symbol, Value};
use sim_shape::{
    Bindings, ShapeProbe, ShapeRelation, ShapeRelationKind, VennShapeSet, relate_shapes,
};

use crate::shapes::{shape_ref_arc, shape_ref_as_shape};

use super::{build_shape, shape_number, value_list_items, value_to_symbol};

pub(super) fn shape_compare_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 2, "shape:compare")?;
    let left = shape_ref_as_shape(prepared.get(0).unwrap())?;
    let right = shape_ref_as_shape(prepared.get(1).unwrap())?;
    let relation = relate_shapes(cx, left, right, &[])?;
    relation_value(cx, relation)
}

pub(super) fn shape_compare_with_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 3, "shape:compare-with")?;
    let probes_value = prepared.get(2).unwrap().clone();
    let probes = parse_probes(cx, probes_value)?;
    let left = shape_ref_as_shape(prepared.get(0).unwrap())?;
    let right = shape_ref_as_shape(prepared.get(1).unwrap())?;
    let relation = relate_shapes(cx, left, right, &probes)?;
    relation_value(cx, relation)
}

pub(super) fn shape_venn_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 1, "shape:venn")?;
    let members_value = prepared.get(0).unwrap().clone();
    let members = parse_members(cx, members_value)?;
    cx.factory().opaque(Arc::new(VennShapeSet::new(members)))
}

pub(super) fn shape_venn_union_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 1, "shape:venn-union")?;
    let venn_value = prepared.get(0).unwrap().clone();
    let shape = venn_ref(&venn_value)?.union();
    runtime_shape(
        "venn-union",
        shape,
        vec![venn_evidence_expr(cx, &venn_value)],
    )
}

pub(super) fn shape_venn_intersection_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 1, "shape:venn-intersection")?;
    let venn_value = prepared.get(0).unwrap().clone();
    let shape = venn_ref(&venn_value)?.intersection();
    runtime_shape(
        "venn-intersection",
        shape,
        vec![venn_evidence_expr(cx, &venn_value)],
    )
}

pub(super) fn shape_venn_only_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 2, "shape:venn-only")?;
    let venn_value = prepared.get(0).unwrap().clone();
    let name_value = prepared.get(1).unwrap().clone();
    let name = value_to_symbol(cx, name_value.clone())?;
    let shape = venn_ref(&venn_value)?.only(&name)?;
    runtime_shape(
        "venn-only",
        shape,
        vec![
            venn_evidence_expr(cx, &venn_value),
            name_value.object().as_expr(cx)?,
        ],
    )
}

pub(super) fn shape_venn_outside_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 1, "shape:venn-outside")?;
    let venn_value = prepared.get(0).unwrap().clone();
    let shape = venn_ref(&venn_value)?.outside_all();
    runtime_shape(
        "venn-outside",
        shape,
        vec![venn_evidence_expr(cx, &venn_value)],
    )
}

pub(super) fn shape_venn_exactly_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 2, "shape:venn-exactly")?;
    let venn_value = prepared.get(0).unwrap().clone();
    let names_value = prepared.get(1).unwrap().clone();
    let names = value_list_items(cx, names_value.clone())?
        .into_iter()
        .map(|value| value_to_symbol(cx, value))
        .collect::<Result<Vec<_>>>()?;
    let shape = venn_ref(&venn_value)?.exactly(&names)?;
    runtime_shape(
        "venn-exactly",
        shape,
        vec![
            venn_evidence_expr(cx, &venn_value),
            names_value.object().as_expr(cx)?,
        ],
    )
}

fn venn_evidence_expr(cx: &mut Cx, value: &Value) -> Expr {
    match value.object().as_expr(cx) {
        Ok(expr) => expr,
        Err(_) => Expr::Symbol(Symbol::qualified("shape", "venn")),
    }
}

fn relation_value(cx: &mut Cx, relation: ShapeRelation) -> Result<Value> {
    let witnesses = relation
        .witnesses
        .into_iter()
        .map(|witness| {
            cx.factory().table(vec![
                (Symbol::new("label"), cx.factory().string(witness.label)?),
                (
                    Symbol::new("accepted-left"),
                    cx.factory().bool(witness.accepted_left)?,
                ),
                (
                    Symbol::new("accepted-right"),
                    cx.factory().bool(witness.accepted_right)?,
                ),
                (Symbol::new("note"), cx.factory().string(witness.note)?),
            ])
        })
        .collect::<Result<Vec<_>>>()?;
    let witness_count = witnesses.len();
    let diagnostics = relation
        .diagnostics
        .into_iter()
        .map(|diagnostic| cx.factory().string(diagnostic.message))
        .collect::<Result<Vec<_>>>()?;
    let kind = cx.factory().symbol(relation_kind_symbol(relation.kind))?;
    let proven = cx.factory().bool(relation.proven)?;
    let left = cx.factory().string(relation.left.label)?;
    let right = cx.factory().string(relation.right.label)?;
    let witness_count = shape_number(cx, witness_count)?;
    let witnesses = cx.factory().list(witnesses)?;
    let diagnostics = cx.factory().list(diagnostics)?;
    cx.factory().table(vec![
        (Symbol::new("kind"), kind),
        (Symbol::new("proven"), proven),
        (Symbol::new("left"), left),
        (Symbol::new("right"), right),
        (Symbol::new("witness-count"), witness_count),
        (Symbol::new("witnesses"), witnesses),
        (Symbol::new("diagnostics"), diagnostics),
    ])
}

fn relation_kind_symbol(kind: ShapeRelationKind) -> Symbol {
    let name = match kind {
        ShapeRelationKind::Equal => "equal",
        ShapeRelationKind::LeftSubshape => "left-subshape",
        ShapeRelationKind::RightSubshape => "right-subshape",
        ShapeRelationKind::Overlap => "overlap",
        ShapeRelationKind::Disjoint => "disjoint",
        ShapeRelationKind::Unknown => "unknown",
    };
    Symbol::qualified("shape", name)
}

fn parse_probes(cx: &mut Cx, value: Value) -> Result<Vec<ShapeProbe>> {
    value_list_items(cx, value)?
        .into_iter()
        .map(|entry| {
            let parts = value_list_items(cx, entry)?;
            match parts.as_slice() {
                [label, value] => Ok(ShapeProbe::Value {
                    label: label_string(cx, label.clone())?,
                    value: value.clone(),
                }),
                [tag, label, value] => {
                    let tag = value_to_symbol(cx, tag.clone())?;
                    match tag.name.as_ref() {
                        "value" => Ok(ShapeProbe::Value {
                            label: label_string(cx, label.clone())?,
                            value: value.clone(),
                        }),
                        "expr" => Ok(ShapeProbe::Expr {
                            label: label_string(cx, label.clone())?,
                            expr: value.object().as_expr(cx)?,
                        }),
                        other => Err(Error::Eval(format!(
                            "shape:compare-with unknown probe kind {other}"
                        ))),
                    }
                }
                _ => Err(Error::Eval(
                    "shape:compare-with probes must be (label value) or (kind label value)"
                        .to_owned(),
                )),
            }
        })
        .collect()
}

fn parse_members(cx: &mut Cx, value: Value) -> Result<Vec<(Symbol, Arc<dyn sim_shape::Shape>)>> {
    value_list_items(cx, value)?
        .into_iter()
        .map(|entry| {
            let parts = value_list_items(cx, entry)?;
            let [name_value, shape_value] = parts.as_slice() else {
                return Err(Error::Eval(
                    "shape:venn member must be a two-item list".to_owned(),
                ));
            };
            Ok((
                value_to_symbol(cx, name_value.clone())?,
                shape_ref_arc(shape_value)?,
            ))
        })
        .collect()
}

fn label_string(cx: &mut Cx, value: Value) -> Result<String> {
    Ok(match value.object().as_expr(cx)? {
        Expr::Symbol(symbol) => symbol.to_string(),
        Expr::String(text) => text,
        other => format!("{other:?}"),
    })
}

fn venn_ref(value: &Value) -> Result<&VennShapeSet> {
    value
        .object()
        .downcast_ref::<VennShapeSet>()
        .ok_or(Error::TypeMismatch {
            expected: "shape-venn",
            found: "non-shape-venn",
        })
}

fn runtime_shape(name: &str, shape: Arc<dyn sim_shape::Shape>, args: Vec<Expr>) -> Result<Value> {
    Ok(build_shape(Symbol::qualified("shape", name), shape, args))
}

fn exact_len(prepared: &PreparedArgs, expected: usize, name: &str) -> Result<()> {
    if prepared.len() == expected {
        Ok(())
    } else {
        Err(Error::Eval(format!(
            "{name} expects {expected} argument(s), got {}",
            prepared.len()
        )))
    }
}