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::{
    AndShape, Bindings, ListShape, NotShape, OrShape, RepeatShape, Shape, TableExtraPolicy,
    TableFieldSpec, TableShape,
};

use crate::shapes::shape_ref_arc;

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

pub(super) fn shape_and_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    let shapes_value = exact_arg(prepared, 1, "shape:and")?.clone();
    let parts = shape_list(cx, shapes_value.clone())?;
    build_runtime_shape(
        "and",
        Arc::new(AndShape::new(parts)),
        vec![shapes_value.object().as_expr(cx)?],
    )
}

pub(super) fn shape_or_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    let shapes_value = exact_arg(prepared, 1, "shape:or")?.clone();
    let choices = shape_list(cx, shapes_value.clone())?;
    build_runtime_shape(
        "or",
        Arc::new(OrShape::new(choices)),
        vec![shapes_value.object().as_expr(cx)?],
    )
}

pub(super) fn shape_not_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    let inner_value = exact_arg(prepared, 1, "shape:not")?.clone();
    let inner = shape_ref_arc(&inner_value)?;
    build_runtime_shape(
        "not",
        Arc::new(NotShape::new(inner)),
        vec![inner_value.object().as_expr(cx)?],
    )
}

pub(super) fn shape_list_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    let shapes_value = exact_arg(prepared, 1, "shape:list")?.clone();
    let items = shape_list(cx, shapes_value.clone())?;
    build_runtime_shape(
        "list",
        Arc::new(ListShape::new(items)),
        vec![shapes_value.object().as_expr(cx)?],
    )
}

pub(super) fn shape_list_rest_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 2, "shape:list-rest")?;
    let prefix_value = prepared.get(0).unwrap().clone();
    let rest_value = prepared.get(1).unwrap().clone();
    let prefix = shape_list(cx, prefix_value.clone())?;
    let rest = shape_ref_arc(&rest_value)?;
    build_runtime_shape(
        "list-rest",
        Arc::new(ListShape::with_rest(prefix, rest)),
        vec![
            prefix_value.object().as_expr(cx)?,
            rest_value.object().as_expr(cx)?,
        ],
    )
}

pub(super) fn shape_table_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 2, "shape:table")?;
    let key_value = prepared.get(0).unwrap().clone();
    let shape_value = prepared.get(1).unwrap().clone();
    let key = value_to_symbol(cx, key_value.clone())?;
    let shape = shape_ref_arc(&shape_value)?;
    build_runtime_shape(
        "table",
        Arc::new(TableShape::single(key.clone(), shape)),
        vec![Expr::Symbol(key), shape_value.object().as_expr(cx)?],
    )
}

pub(super) fn shape_table_open_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    table_with_extra(cx, prepared, "table-open", TableExtraPolicy::Allow)
}

pub(super) fn shape_table_required_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    table_with_extra(cx, prepared, "table-required", TableExtraPolicy::Allow)
}

pub(super) fn shape_table_closed_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    table_with_extra(cx, prepared, "table-closed", TableExtraPolicy::Reject)
}

fn table_with_extra(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    name: &str,
    extra: TableExtraPolicy,
) -> Result<Value> {
    let fields_value = exact_arg(prepared, 1, &format!("shape:{name}"))?.clone();
    let fields = table_fields(cx, fields_value.clone())?;
    build_runtime_shape(
        name,
        Arc::new(TableShape::new(fields, extra)),
        vec![fields_value.object().as_expr(cx)?],
    )
}

pub(super) fn shape_repeat_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    let body_value = exact_arg(prepared, 1, "shape:repeat")?.clone();
    let body = shape_ref_arc(&body_value)?;
    build_runtime_shape(
        "repeat",
        Arc::new(RepeatShape::new(body)),
        vec![body_value.object().as_expr(cx)?],
    )
}

pub(super) fn shape_repeat_bounds_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 3, "shape:repeat-bounds")?;
    let body_value = prepared.get(0).unwrap().clone();
    let min_value = prepared.get(1).unwrap().clone();
    let max_value = prepared.get(2).unwrap().clone();
    let body = shape_ref_arc(&body_value)?;
    let min = value_to_usize(cx, min_value.clone(), "shape:repeat-bounds min")?;
    let max = value_to_optional_usize(cx, max_value.clone(), "shape:repeat-bounds max")?;
    if matches!(max, Some(max) if max < min) {
        return Err(Error::Eval(
            "shape:repeat-bounds max must be greater than or equal to min".to_owned(),
        ));
    }
    build_runtime_shape(
        "repeat-bounds",
        Arc::new(RepeatShape::with_bounds(body, min, max)),
        vec![
            body_value.object().as_expr(cx)?,
            min_value.object().as_expr(cx)?,
            max_value.object().as_expr(cx)?,
        ],
    )
}

pub(super) fn shape_without_impl(
    cx: &mut Cx,
    prepared: &PreparedArgs,
    _bindings: Bindings,
) -> Result<Value> {
    exact_len(prepared, 2, "shape:without")?;
    let left_value = prepared.get(0).unwrap().clone();
    let right_value = prepared.get(1).unwrap().clone();
    let left = shape_ref_arc(&left_value)?;
    let right = shape_ref_arc(&right_value)?;
    build_runtime_shape(
        "without",
        Arc::new(AndShape::new(vec![left, Arc::new(NotShape::new(right))])),
        vec![
            left_value.object().as_expr(cx)?,
            right_value.object().as_expr(cx)?,
        ],
    )
}

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

fn exact_arg<'a>(prepared: &'a PreparedArgs, expected: usize, name: &str) -> Result<&'a Value> {
    exact_len(prepared, expected, name)?;
    prepared
        .get(0)
        .ok_or_else(|| Error::Eval(format!("{name} missing argument")))
}

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

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

fn table_fields(cx: &mut Cx, value: Value) -> Result<Vec<TableFieldSpec>> {
    value_list_items(cx, value)?
        .into_iter()
        .map(|entry| {
            let parts = value_list_items(cx, entry)?;
            let [key_value, shape_value] = parts.as_slice() else {
                return Err(Error::Eval(
                    "shape:table field must be a two-item list".to_owned(),
                ));
            };
            Ok(TableFieldSpec {
                key: value_to_symbol(cx, key_value.clone())?,
                shape: shape_ref_arc(shape_value)?,
                required: true,
            })
        })
        .collect()
}

fn value_to_optional_usize(cx: &mut Cx, value: Value, context: &str) -> Result<Option<usize>> {
    if matches!(value.object().as_expr(cx)?, Expr::Nil) {
        Ok(None)
    } else {
        value_to_usize(cx, value, context).map(Some)
    }
}

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