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, CORE_FUNCTION_CLASS_ID, Callable, ClassRef, Cx, EagerPolicy, Error, EvalPolicyRef, Expr,
    HybridPolicy, LazyPolicy, NeedPolicy, Object, QuoteMode, RawArgs, Result, StrictByShapePolicy,
    Symbol, Value,
};

#[derive(Clone)]
pub(crate) struct WithEvalPolicyFunction;

impl Object for WithEvalPolicyFunction {
    fn display(&self, _cx: &mut Cx) -> Result<String> {
        Ok("#<function core/with-eval-policy>".to_owned())
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

impl sim_kernel::ObjectCompat for WithEvalPolicyFunction {
    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
        if let Some(value) = cx
            .registry()
            .class_by_symbol(&Symbol::qualified("core", "Function"))
        {
            return Ok(value.clone());
        }
        cx.factory().class_stub(
            CORE_FUNCTION_CLASS_ID,
            Symbol::qualified("core", "Function"),
        )
    }

    fn as_callable(&self) -> Option<&dyn Callable> {
        Some(self)
    }
}

impl Callable for WithEvalPolicyFunction {
    fn call(&self, _cx: &mut Cx, _args: Args) -> Result<Value> {
        Err(Error::Eval(
            "with-eval-policy must be called with unevaluated policy and body".to_owned(),
        ))
    }

    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
        let args = args.into_exprs();
        let Some((policy_expr, body)) = args.split_first() else {
            return Err(Error::Eval(
                "with-eval-policy expects a policy and body expression".to_owned(),
            ));
        };
        if body.is_empty() {
            return Err(Error::Eval(
                "with-eval-policy expects at least one body expression".to_owned(),
            ));
        }
        let policy = eval_policy_from_expr(policy_expr)?;
        let saved = cx.eval_policy_ref();
        cx.set_eval_policy(policy);
        let result = eval_body(cx, body);
        cx.set_eval_policy(saved);
        result
    }
}

fn eval_body(cx: &mut Cx, body: &[Expr]) -> Result<Value> {
    let mut last = cx.factory().nil()?;
    for expr in body {
        last = cx.eval_expr(expr.clone())?;
    }
    Ok(last)
}

fn eval_policy_from_expr(expr: &Expr) -> Result<EvalPolicyRef> {
    match expr {
        Expr::Symbol(symbol) => eval_policy_from_symbol(symbol),
        Expr::String(value) => eval_policy_from_text(value),
        Expr::Quote {
            mode: QuoteMode::Quote,
            expr,
        } => eval_policy_from_expr(expr),
        _ => Err(Error::Eval(
            "with-eval-policy policy must be a symbol or string".to_owned(),
        )),
    }
}

fn eval_policy_from_symbol(symbol: &Symbol) -> Result<EvalPolicyRef> {
    if let Some(namespace) = &symbol.namespace
        && namespace.as_ref() != "core"
    {
        return Err(Error::Eval(format!(
            "unknown eval policy namespace `{namespace}`"
        )));
    }
    eval_policy_from_name(symbol.name.as_ref())
}

fn eval_policy_from_text(value: &str) -> Result<EvalPolicyRef> {
    let name = value
        .rsplit_once('/')
        .map(|(_, name)| name)
        .unwrap_or(value);
    eval_policy_from_name(name)
}

fn eval_policy_from_name(name: &str) -> Result<EvalPolicyRef> {
    match name {
        "eager" => Ok(Arc::new(EagerPolicy)),
        "lazy" => Ok(Arc::new(LazyPolicy)),
        "lazy-by-need" | "need" => Ok(Arc::new(NeedPolicy)),
        "strict-by-shape" => Ok(Arc::new(StrictByShapePolicy)),
        "hybrid" => Ok(Arc::new(HybridPolicy)),
        other => Err(Error::Eval(format!("unknown eval policy `{other}`"))),
    }
}