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 first_handle_seed: sim_kernel::HandleSeed,
100}
101
102impl ExprTreeLib {
103 #[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
160pub 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
168pub fn expr_tree_operation_symbols() -> Vec<Symbol> {
170 operation_specs()
171 .into_iter()
172 .map(OperationSpec::symbol)
173 .collect()
174}
175
176pub 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}