Skip to main content

sim_lib_expr_tree/
operation.rs

1use std::sync::Arc;
2
3use sim_kernel::{
4    AbiVersion, Args, Callable, CapabilityName, ClassRef, Cx, Dependency, Export, Lib, LibManifest,
5    LibTarget, Linker, LoadCx, Object, ObjectCompat, RawArgs, Result, ShapeRef, Symbol, Value,
6    Version,
7};
8
9use crate::{
10    capability::{
11        expr_tree_calculate_capability, expr_tree_mount_capability, expr_tree_read_capability,
12        expr_tree_write_capability,
13    },
14    citizen::{
15        durable_policy_class_symbol, durable_source_class_symbol, expr_tree_citizen_registry,
16    },
17    dispatch::dispatch,
18    handle::TreeRuntime,
19    projection::cards_for_contracts,
20    shape::{
21        argument_shape, operation_args_shape_symbol, operation_result_shape_symbol, result_shape,
22    },
23    source::RawSource,
24};
25
26/// Stable manifest id for the loadable expression-tree library.
27pub fn expr_tree_lib_symbol() -> Symbol {
28    Symbol::qualified("lib", "expr-tree")
29}
30
31/// Value export containing one Card projection per operation.
32pub fn expr_tree_operation_cards_symbol() -> Symbol {
33    Symbol::qualified("expr-tree", "operation-cards")
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub(crate) enum OperationKind {
38    Open,
39    NewCell,
40    NewDir,
41    Mount,
42    Unmount,
43    Move,
44    Rename,
45    Delete,
46    SetExpr,
47    SetCalcPolicy,
48    SetCodecPolicy,
49    Ref,
50    List,
51    Calculate,
52    Recalculate,
53    RecalculateRecursive,
54    Cancel,
55    Refresh,
56    Status,
57    Explain,
58    Watch,
59}
60
61#[derive(Clone, Copy)]
62pub(crate) enum CapabilityKind {
63    Read,
64    Write,
65    Calculate,
66    Mount,
67}
68
69impl CapabilityKind {
70    pub(crate) fn name(self) -> CapabilityName {
71        match self {
72            Self::Read => expr_tree_read_capability(),
73            Self::Write => expr_tree_write_capability(),
74            Self::Calculate => expr_tree_calculate_capability(),
75            Self::Mount => expr_tree_mount_capability(),
76        }
77    }
78}
79
80#[derive(Clone, Copy)]
81pub(crate) struct OperationSpec {
82    pub(crate) kind: OperationKind,
83    pub(crate) name: &'static str,
84    pub(crate) min_args: usize,
85    pub(crate) max_args: usize,
86    pub(crate) capability: CapabilityKind,
87    pub(crate) args_detail: &'static str,
88    pub(crate) result_detail: &'static str,
89}
90
91impl OperationSpec {
92    pub(crate) fn symbol(self) -> Symbol {
93        Symbol::qualified("expr-tree", self.name)
94    }
95}
96
97/// Host-registered expression-tree runtime library.
98pub struct ExprTreeLib {
99    first_handle_seed: sim_kernel::HandleSeed,
100}
101
102impl ExprTreeLib {
103    /// Creates the runtime library with the first namespace reserved for its
104    /// expression-tree contexts.
105    #[must_use]
106    pub fn new(first_handle_seed: sim_kernel::HandleSeed) -> Self {
107        Self { first_handle_seed }
108    }
109}
110
111impl Lib for ExprTreeLib {
112    fn manifest(&self) -> LibManifest {
113        LibManifest {
114            id: expr_tree_lib_symbol(),
115            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
116            abi: AbiVersion { major: 0, minor: 1 },
117            target: LibTarget::HostRegistered,
118            requires: vec![Dependency {
119                id: Symbol::qualified("codec", "lisp"),
120                minimum_version: None,
121            }],
122            capabilities: Vec::new(),
123            exports: expr_tree_exports(),
124        }
125    }
126
127    fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
128        expr_tree_citizen_registry()?.install_all(linker)?;
129        let runtime = Arc::new(TreeRuntime::new(self.first_handle_seed));
130        let mut contracts = Vec::new();
131        for spec in operation_specs() {
132            let args_shape =
133                argument_shape(spec.name, spec.min_args, spec.max_args, spec.args_detail);
134            let result_shape = result_shape(spec.name, spec.result_detail);
135            linker.shape_value(operation_args_shape_symbol(spec.name), args_shape.clone())?;
136            linker.shape_value(
137                operation_result_shape_symbol(spec.name),
138                result_shape.clone(),
139            )?;
140            linker.function_value(
141                spec.symbol(),
142                cx.factory().opaque(Arc::new(OperationFunction {
143                    spec,
144                    runtime: Arc::clone(&runtime),
145                    args_shape: args_shape.clone(),
146                    result_shape: result_shape.clone(),
147                }))?,
148            )?;
149            contracts.push((spec, args_shape, result_shape));
150        }
151        let cards = cards_for_contracts(cx.factory(), &contracts)?;
152        linker.value(
153            expr_tree_operation_cards_symbol(),
154            cx.factory().list(cards)?,
155        )?;
156        Ok(())
157    }
158}
159
160/// Installs [`ExprTreeLib`] exactly once using the caller-owned handle namespace.
161pub fn install_expr_tree_lib(cx: &mut Cx, first_handle_seed: sim_kernel::HandleSeed) -> Result<()> {
162    if cx.registry().lib(&expr_tree_lib_symbol()).is_none() {
163        cx.load_lib(&ExprTreeLib::new(first_handle_seed))?;
164    }
165    Ok(())
166}
167
168/// Returns every stable operation symbol in product-contract order.
169pub fn expr_tree_operation_symbols() -> Vec<Symbol> {
170    operation_specs()
171        .into_iter()
172        .map(OperationSpec::symbol)
173        .collect()
174}
175
176/// Returns the manifest exports for classes, operations, Shapes, and Cards.
177pub fn expr_tree_exports() -> Vec<Export> {
178    let mut exports = vec![
179        Export::Class {
180            symbol: durable_source_class_symbol(),
181            class_id: None,
182        },
183        Export::Class {
184            symbol: durable_policy_class_symbol(),
185            class_id: None,
186        },
187        Export::Value {
188            symbol: expr_tree_operation_cards_symbol(),
189        },
190    ];
191    for spec in operation_specs() {
192        exports.push(Export::Function {
193            symbol: spec.symbol(),
194            function_id: None,
195        });
196        exports.push(Export::Shape {
197            symbol: operation_args_shape_symbol(spec.name),
198            shape_id: None,
199        });
200        exports.push(Export::Shape {
201            symbol: operation_result_shape_symbol(spec.name),
202            shape_id: None,
203        });
204    }
205    exports
206}
207
208struct OperationFunction {
209    spec: OperationSpec,
210    runtime: Arc<TreeRuntime>,
211    args_shape: Value,
212    result_shape: Value,
213}
214
215impl Object for OperationFunction {
216    fn display(&self, _cx: &mut Cx) -> Result<String> {
217        Ok(format!("#<function {}>", self.spec.symbol()))
218    }
219
220    fn as_any(&self) -> &dyn std::any::Any {
221        self
222    }
223}
224
225impl ObjectCompat for OperationFunction {
226    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
227        cx.resolve_class(&Symbol::qualified("core", "Function"))
228    }
229
230    fn as_callable(&self) -> Option<&dyn Callable> {
231        Some(self)
232    }
233}
234
235impl Callable for OperationFunction {
236    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
237        self.invoke(cx, args.into_vec())
238    }
239
240    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
241        let expressions = args.into_exprs();
242        if matches!(
243            self.spec.kind,
244            OperationKind::NewCell | OperationKind::SetExpr
245        ) {
246            let source_index = expressions.len().saturating_sub(1);
247            let mut values = Vec::with_capacity(expressions.len());
248            for (index, expression) in expressions.into_iter().enumerate() {
249                if index == source_index {
250                    values.push(cx.factory().opaque(Arc::new(RawSource(expression)))?);
251                } else {
252                    values.push(cx.eval_expr(expression)?);
253                }
254            }
255            self.invoke(cx, values)
256        } else {
257            let values = expressions
258                .into_iter()
259                .map(|expression| cx.eval_expr(expression))
260                .collect::<Result<Vec<_>>>()?;
261            self.invoke(cx, values)
262        }
263    }
264
265    fn browse_args_shape(&self, _cx: &mut Cx) -> Result<Option<ShapeRef>> {
266        Ok(Some(self.args_shape.clone()))
267    }
268
269    fn browse_result_shape(&self, _cx: &mut Cx) -> Result<Option<ShapeRef>> {
270        Ok(Some(self.result_shape.clone()))
271    }
272}
273
274impl OperationFunction {
275    fn invoke(&self, cx: &mut Cx, values: Vec<Value>) -> Result<Value> {
276        if !(self.spec.min_args..=self.spec.max_args).contains(&values.len()) {
277            return Err(crate::dispatch::bounded_error(
278                self.spec.name,
279                format!(
280                    "expected {}..={} arguments, found {}",
281                    self.spec.min_args,
282                    self.spec.max_args,
283                    values.len()
284                ),
285            ));
286        }
287        cx.require(&self.spec.capability.name())?;
288        dispatch(self.spec.kind, &self.runtime, cx, values)
289    }
290}
291
292pub(crate) fn operation_specs() -> Vec<OperationSpec> {
293    use CapabilityKind::{Calculate as C, Mount as M, Read as R, Write as W};
294    use OperationKind::*;
295
296    vec![
297        spec(
298            Open,
299            "open",
300            1,
301            1,
302            R,
303            "storage name",
304            "opaque live tree handle",
305        ),
306        spec(
307            NewCell,
308            "new-cell",
309            4,
310            4,
311            W,
312            "tree, parent path, optional name, raw source Expr",
313            "canonical cell path",
314        ),
315        spec(
316            NewDir,
317            "new-dir",
318            3,
319            3,
320            W,
321            "tree, parent path, optional name",
322            "canonical directory path",
323        ),
324        spec(
325            Mount,
326            "mount",
327            5,
328            5,
329            M,
330            "tree, path, backend, table-or-dir, epoch",
331            "canonical mount path",
332        ),
333        spec(
334            Unmount,
335            "unmount",
336            2,
337            2,
338            M,
339            "tree and mount path",
340            "true after removal",
341        ),
342        spec(
343            Move,
344            "move",
345            3,
346            3,
347            W,
348            "tree, source path, target path",
349            "canonical target path",
350        ),
351        spec(
352            Rename,
353            "rename",
354            3,
355            3,
356            W,
357            "tree, path, new segment",
358            "canonical target path",
359        ),
360        spec(
361            Delete,
362            "delete",
363            2,
364            2,
365            W,
366            "tree and empty-dir-or-cell path",
367            "true after removal",
368        ),
369        spec(
370            SetExpr,
371            "set-expr",
372            3,
373            3,
374            W,
375            "tree, cell path, raw source Expr",
376            "canonical cell path",
377        ),
378        spec(
379            SetCalcPolicy,
380            "set-calc-policy",
381            3,
382            3,
383            W,
384            "tree, owner path, policy record or map",
385            "durable policy Citizen",
386        ),
387        spec(
388            SetCodecPolicy,
389            "set-codec-policy",
390            3,
391            3,
392            W,
393            "tree, owner path, policy record or map",
394            "durable policy Citizen",
395        ),
396        spec(
397            Ref,
398            "ref",
399            2,
400            3,
401            R,
402            "tree, path reference, optional base directory",
403            "ordinary current cell Value",
404        ),
405        spec(
406            List,
407            "list",
408            2,
409            2,
410            R,
411            "tree and directory path",
412            "bounded entry-card list",
413        ),
414        spec(
415            Calculate,
416            "calculate",
417            2,
418            2,
419            C,
420            "tree and cell path",
421            "verified ordinary Value",
422        ),
423        spec(
424            Recalculate,
425            "recalculate",
426            2,
427            2,
428            C,
429            "tree and cell path",
430            "root-forced ordinary Value",
431        ),
432        spec(
433            RecalculateRecursive,
434            "recalculate-recursive",
435            2,
436            2,
437            C,
438            "tree and cell path",
439            "recursively forced ordinary Value",
440        ),
441        spec(
442            Cancel,
443            "cancel",
444            2,
445            2,
446            C,
447            "tree and request-id text",
448            "whether queued work was cancelled",
449        ),
450        spec(
451            Refresh,
452            "refresh",
453            1,
454            1,
455            C,
456            "tree",
457            "bounded refresh evidence table",
458        ),
459        spec(
460            Status,
461            "status",
462            2,
463            2,
464            R,
465            "tree and cell path",
466            "non-evaluating status symbol",
467        ),
468        spec(
469            Explain,
470            "explain",
471            2,
472            2,
473            R,
474            "tree and cell path",
475            "bounded receipt and durable-record Card",
476        ),
477        spec(
478            Watch,
479            "watch",
480            1,
481            1,
482            R,
483            "tree",
484            "standard bounded Stream value",
485        ),
486    ]
487}
488
489fn spec(
490    kind: OperationKind,
491    name: &'static str,
492    min_args: usize,
493    max_args: usize,
494    capability: CapabilityKind,
495    args_detail: &'static str,
496    result_detail: &'static str,
497) -> OperationSpec {
498    OperationSpec {
499        kind,
500        name,
501        min_args,
502        max_args,
503        capability,
504        args_detail,
505        result_detail,
506    }
507}