sim-lib-expr-tree-server 0.2.0

Authoritative bounded EvalSite server for SIM expression-tree sessions.
Documentation
use std::{
    collections::VecDeque,
    sync::{Arc, Mutex},
};

use sim_kernel::{
    CapabilityName, Consistency, Cx, EvalFabric, EvalMode, EvalRequest, Expr, Symbol,
};
use sim_lib_server::{ServerAddress, WallClock, WallTimestamp};
use sim_value::{access, build};

use crate::{ExpressionTreeServer, ExpressionTreeServerLimits, SessionId};

pub(crate) fn full_cx() -> Cx {
    runtime_cx(&[
        sim_lib_expr_tree::expr_tree_read_capability(),
        sim_lib_expr_tree::expr_tree_write_capability(),
        sim_lib_expr_tree::expr_tree_calculate_capability(),
        sim_lib_expr_tree::expr_tree_mount_capability(),
    ])
}

pub(crate) fn read_cx() -> Cx {
    runtime_cx(&[sim_lib_expr_tree::expr_tree_read_capability()])
}

fn runtime_cx(capabilities: &[CapabilityName]) -> Cx {
    let mut cx = sim_kernel::testing::eager_cx();
    let codec_id = cx.registry_mut().fresh_codec_id();
    cx.load_lib(&sim_codec_lisp::LispCodecLib::new(codec_id).unwrap())
        .unwrap();
    for capability in capabilities {
        cx.grant(capability.clone());
    }
    sim_lib_expr_tree::install_expr_tree_lib(&mut cx).unwrap();
    cx
}

pub(crate) fn request(expr: Expr) -> EvalRequest {
    EvalRequest {
        expr,
        result_shape: None,
        required_capabilities: Vec::new(),
        deadline: None,
        consistency: Consistency::LocalFirst,
        mode: EvalMode::Eval,
        answer_limit: None,
        stream_buffer: None,
        stream: false,
        trace: false,
    }
}

pub(crate) fn realize_expr(server: &ExpressionTreeServer, cx: &mut Cx, expr: Expr) -> Expr {
    server
        .realize(cx, request(expr))
        .unwrap()
        .value
        .object()
        .as_expr(cx)
        .unwrap()
}

pub(crate) fn runtime_call(session: &SessionId, name: &str, args: Vec<Expr>) -> Expr {
    let mut all = vec![Expr::Symbol(session.resource())];
    all.extend(args);
    Expr::Call {
        operator: Box::new(Expr::Symbol(Symbol::qualified("expr-tree", name))),
        args: all,
    }
}

pub(crate) fn web_commit(
    server: &ExpressionTreeServer,
    cx: &mut Cx,
    session: &SessionId,
    operation: Expr,
    expected: Expr,
) -> Expr {
    realize_expr(
        server,
        cx,
        build::map(vec![
            (
                "op",
                Expr::Symbol(Symbol::qualified("web-session", "commit")),
            ),
            ("resource", Expr::Symbol(session.resource())),
            ("operation", operation),
            ("expected-current", expected),
        ]),
    )
}

pub(crate) fn error_code(expr: &Expr) -> Option<String> {
    access::field_sym(expr, "error").map(|symbol| symbol.name.to_string())
}

pub(crate) fn snapshot_revision(expr: &Expr) -> u64 {
    match access::field(expr, "revision").unwrap() {
        Expr::Number(number) => number.canonical.parse().unwrap(),
        other => panic!("unexpected revision {other:?}"),
    }
}

pub(crate) fn target(session: &SessionId, revision: u64, path: &str) -> Expr {
    build::map(vec![
        ("tree", Expr::Symbol(session.resource())),
        ("revision", build::uint(revision)),
        ("path", build::text(path)),
    ])
}

pub(crate) fn server_with(
    limits: ExpressionTreeServerLimits,
    clock: Arc<dyn WallClock>,
) -> ExpressionTreeServer {
    ExpressionTreeServer::new(
        ServerAddress::Local,
        vec![Symbol::qualified("codec", "lisp")],
        clock,
        limits,
    )
    .unwrap()
}

pub(crate) struct ScriptClock {
    values: Mutex<VecDeque<u64>>,
}

impl ScriptClock {
    pub(crate) fn new(values: impl IntoIterator<Item = u64>) -> Self {
        Self {
            values: Mutex::new(values.into_iter().collect()),
        }
    }
}

impl WallClock for ScriptClock {
    fn now(&self) -> sim_kernel::Result<WallTimestamp> {
        self.values
            .lock()
            .unwrap()
            .pop_front()
            .map(WallTimestamp::from_unix_millis)
            .ok_or_else(|| sim_kernel::Error::HostError("clock exhausted".to_owned()))
    }
}