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
26pub fn expr_tree_lib_symbol() -> Symbol {
28 Symbol::qualified("lib", "expr-tree")
29}
30
31pub 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
97pub 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
149pub 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
157pub fn expr_tree_operation_symbols() -> Vec<Symbol> {
159 operation_specs()
160 .into_iter()
161 .map(OperationSpec::symbol)
162 .collect()
163}
164
165pub 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}