sim-lib-agent 0.1.1

Agent runtime surfaces for SIM.
Documentation
use sim_kernel::{
    CapabilityName, Cx, Error, Expr, ObjectEncoding, Result, ShapeRef, Symbol, Value,
};
use sim_shape::{Shape, parse_shape_expr, shape_value_with_encoding};

pub(crate) fn expr_to_value(cx: &mut Cx, expr: &Expr) -> Result<Value> {
    match expr {
        Expr::Nil => cx.factory().nil(),
        Expr::Bool(value) => cx.factory().bool(*value),
        Expr::Number(number) => cx
            .factory()
            .number_literal(number.domain.clone(), number.canonical.clone()),
        Expr::Symbol(symbol) => cx.factory().symbol(symbol.clone()),
        Expr::String(text) => cx.factory().string(text.clone()),
        Expr::Bytes(bytes) => cx.factory().bytes(bytes.clone()),
        Expr::List(items) | Expr::Vector(items) => {
            let values = items
                .iter()
                .map(|item| expr_to_value(cx, item))
                .collect::<Result<Vec<_>>>()?;
            cx.factory().list(values)
        }
        Expr::Map(entries) => {
            let values = entries
                .iter()
                .map(|(key, value)| {
                    let Expr::Symbol(key) = key else {
                        return Err(Error::TypeMismatch {
                            expected: "symbol table key",
                            found: "non-symbol",
                        });
                    };
                    Ok((key.clone(), expr_to_value(cx, value)?))
                })
                .collect::<Result<Vec<_>>>()?;
            cx.factory().table(values)
        }
        _ => cx.factory().expr(expr.clone()),
    }
}

pub(crate) fn parse_capabilities_expr(expr: &Expr) -> Result<Vec<CapabilityName>> {
    match expr {
        Expr::Nil => Ok(Vec::new()),
        Expr::List(items) | Expr::Vector(items) => items.iter().map(capability_from_expr).collect(),
        Expr::Symbol(_) | Expr::String(_) => Ok(vec![capability_from_expr(expr)?]),
        _ => Err(Error::TypeMismatch {
            expected: "capability list",
            found: "non-list",
        }),
    }
}

pub(crate) fn capability_from_expr(expr: &Expr) -> Result<CapabilityName> {
    match expr {
        Expr::Symbol(symbol) => Ok(CapabilityName::new(symbol.to_string())),
        Expr::String(text) => Ok(CapabilityName::new(text.clone())),
        _ => Err(Error::TypeMismatch {
            expected: "capability symbol or string",
            found: "non-capability",
        }),
    }
}

pub(crate) fn shape_from_expr(
    _cx: &mut Cx,
    expr: &Expr,
    tool_name: &Symbol,
    slot: &str,
) -> Result<ShapeRef> {
    let shape = parse_shape_expr(literal_expr(expr))?;
    let symbol = Symbol::qualified(tool_name.to_string(), format!("{slot}-shape"));
    Ok(shape_value_with_encoding(
        symbol.clone(),
        shape,
        ObjectEncoding::Constructor {
            class: Symbol::qualified("core", "ExactExprShape"),
            args: vec![literal_expr(expr).clone()],
        },
    ))
}

pub(crate) fn shape_protocol(shape: &ShapeRef) -> Result<&dyn Shape> {
    shape.object().as_shape().ok_or(Error::TypeMismatch {
        expected: "shape",
        found: "non-shape",
    })
}

pub(crate) fn shape_id(shape: &ShapeRef) -> sim_kernel::ShapeId {
    shape_protocol(shape)
        .ok()
        .and_then(|shape| shape.id())
        .unwrap_or(sim_kernel::ShapeId(0))
}

pub(crate) fn installed_codecs(cx: &Cx) -> Vec<Symbol> {
    cx.registry().codecs().keys().cloned().collect()
}

pub(crate) fn keyword(expr: &Expr) -> Result<String> {
    let Expr::Symbol(symbol) = expr else {
        return Err(Error::TypeMismatch {
            expected: "keyword symbol",
            found: "non-symbol",
        });
    };
    let Some(keyword) = symbol.name.strip_prefix(':') else {
        return Err(Error::Eval(format!(
            "expected keyword option, found {symbol}"
        )));
    };
    Ok(keyword.to_owned())
}

pub(crate) fn literal_expr(expr: &Expr) -> &Expr {
    match expr {
        Expr::Quote { expr, .. } => expr,
        _ => expr,
    }
}

pub(crate) fn symbol_of(expr: &Expr, message: &'static str) -> Result<Symbol> {
    match expr {
        Expr::Symbol(symbol) => Ok(symbol.clone()),
        _ => Err(Error::Eval(message.to_owned())),
    }
}

pub(crate) fn string_literal(expr: &Expr, message: &'static str) -> Result<String> {
    match literal_expr(expr) {
        Expr::String(text) => Ok(text.clone()),
        _ => Err(Error::Eval(message.to_owned())),
    }
}

pub(crate) fn string_from_value(
    cx: &mut Cx,
    value: Value,
    message: &'static str,
) -> Result<String> {
    match value.object().as_expr(cx)? {
        Expr::String(text) => Ok(text),
        _ => Err(Error::Eval(message.to_owned())),
    }
}

pub(crate) fn stringish_from_value(
    cx: &mut Cx,
    value: Value,
    message: &'static str,
) -> Result<String> {
    match value.object().as_expr(cx)? {
        Expr::String(text) => Ok(text),
        Expr::Symbol(symbol) => Ok(symbol.to_string()),
        _ => Err(Error::Eval(message.to_owned())),
    }
}

pub(crate) fn u32_from_expr(expr: &Expr, message: &'static str) -> Result<u32> {
    match expr {
        Expr::Number(number) => number
            .canonical
            .parse::<u32>()
            .map_err(|_| Error::Eval(message.to_owned())),
        _ => Err(Error::Eval(message.to_owned())),
    }
}

pub(crate) fn u32_from_value(cx: &mut Cx, value: Value, message: &'static str) -> Result<u32> {
    u32_from_expr(&value.object().as_expr(cx)?, message)
}

pub(crate) fn symbol_from_eval(cx: &mut Cx, expr: &Expr, message: &'static str) -> Result<Symbol> {
    let value = cx.eval_expr(expr.clone())?;
    symbol_from_value(cx, value, message)
}

pub(crate) fn symbol_from_value(
    cx: &mut Cx,
    value: Value,
    message: &'static str,
) -> Result<Symbol> {
    match value.object().as_expr(cx)? {
        Expr::Symbol(symbol) => Ok(symbol),
        Expr::String(text) => Ok(Symbol::new(text)),
        _ => Err(Error::Eval(message.to_owned())),
    }
}

pub(crate) fn f64_from_value(cx: &mut Cx, value: Value, message: &'static str) -> Result<f64> {
    match value.object().as_expr(cx)? {
        Expr::Number(number) => number
            .canonical
            .parse::<f64>()
            .map_err(|_| Error::Eval(message.to_owned())),
        _ => Err(Error::Eval(message.to_owned())),
    }
}