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::{
    AcceptOnNoDiagnosticsHook, Bindings, DiscardOnDiagnosticPrefixHook, HookedShape, MatchHook,
    ScoreFloorHook, Shape, TraceMarkHook, hook_ref_arc, hook_value,
};

use crate::shapes::shape_ref_arc;

use super::{build_shape, value_list_items};

pub(super) fn shape_hook_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 2, "shape:hook")?;
    let inner_value = prepared.get(0).unwrap().clone();
    let hooks_value = prepared.get(1).unwrap().clone();
    let inner = shape_ref_arc(&inner_value)?;
    let hooks = hook_list(cx, hooks_value.clone())?;
    build_runtime_shape(
        "hook",
        Arc::new(HookedShape::new(inner, hooks)),
        vec![
            inner_value.object().as_expr(cx)?,
            hooks_value.object().as_expr(cx)?,
        ],
    )
}

pub(super) fn shape_hook_trace_impl(
    _cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 0, "shape:hook-trace")?;
    Ok(hook_value(Arc::new(TraceMarkHook)))
}

pub(super) fn shape_hook_score_floor_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 1, "shape:hook-score-floor")?;
    let floor = value_to_i32(
        cx,
        prepared.get(0).unwrap().clone(),
        "shape:hook-score-floor",
    )?;
    Ok(hook_value(Arc::new(ScoreFloorHook::new(floor))))
}

pub(super) fn shape_hook_accept_on_no_diagnostics_impl(
    _cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 0, "shape:hook-accept-on-no-diagnostics")?;
    Ok(hook_value(Arc::new(AcceptOnNoDiagnosticsHook)))
}

pub(super) fn shape_hook_discard_on_diagnostic_prefix_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 1, "shape:hook-discard-on-diagnostic-prefix")?;
    let prefix = value_to_string(cx, prepared.get(0).unwrap().clone())?;
    Ok(hook_value(Arc::new(DiscardOnDiagnosticPrefixHook::new(
        prefix,
    ))))
}

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

fn hook_list(cx: &mut Cx, value: Value) -> Result<Vec<Arc<dyn MatchHook>>> {
    value_list_items(cx, value)?
        .into_iter()
        .map(|item| hook_ref_arc(&item))
        .collect()
}

fn value_to_i32(cx: &mut Cx, value: Value, context: &str) -> Result<i32> {
    let Expr::Number(number) = value.object().as_expr(cx)? else {
        return Err(Error::Eval(format!("{context} expects a number")));
    };
    number
        .canonical
        .parse::<i32>()
        .map_err(|_| Error::Eval(format!("{context} expects an integer")))
}

fn value_to_string(cx: &mut Cx, value: Value) -> Result<String> {
    Ok(match value.object().as_expr(cx)? {
        Expr::String(text) => text,
        Expr::Symbol(symbol) => symbol.to_string(),
        _ => {
            return Err(Error::Eval(
                "shape:hook-discard-on-diagnostic-prefix expects string or symbol".to_owned(),
            ));
        }
    })
}

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()
        )))
    }
}