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