Skip to main content

sim_lib_mutation/
table.rs

1use std::{
2    collections::BTreeMap,
3    sync::{Arc, RwLock},
4};
5
6use sim_kernel::{
7    Cx, Error, Expr, Object, ObjectCompat, Result, Symbol, Table, Value, object::ClassRef,
8};
9
10use crate::standard_mutate_capability;
11
12/// A mutable, symbol-keyed table backing the kernel [`Table`] contract.
13///
14/// Implements [`Table`] so it behaves as a core table object, but its writing
15/// verbs ([`set`](Table::set), [`del`](Table::del), [`clear`](Table::clear))
16/// require [`standard_mutate_capability`]; reads are always allowed.
17#[sim_citizen_derive::non_citizen(
18    reason = "mutable table handle; reconstruct from table entries plus mutation policy",
19    kind = "handle",
20    descriptor = "core/Table"
21)]
22pub struct MutableTable {
23    entries: RwLock<BTreeMap<Symbol, Value>>,
24}
25
26impl MutableTable {
27    /// Create a table seeded with `entries` (later duplicates win).
28    pub fn new(entries: Vec<(Symbol, Value)>) -> Self {
29        Self {
30            entries: RwLock::new(entries.into_iter().collect()),
31        }
32    }
33}
34
35impl Object for MutableTable {
36    fn display(&self, cx: &mut Cx) -> Result<String> {
37        Ok(format!("#<mutation-table {}>", self.len(cx)?))
38    }
39
40    fn as_any(&self) -> &dyn std::any::Any {
41        self
42    }
43}
44
45impl ObjectCompat for MutableTable {
46    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
47        cx.factory().class_stub(
48            sim_kernel::CORE_TABLE_CLASS_ID,
49            Symbol::qualified("core", "Table"),
50        )
51    }
52
53    fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
54        self.as_table_expr(cx)
55    }
56
57    fn as_table_impl(&self) -> Option<&dyn Table> {
58        Some(self)
59    }
60}
61
62impl Table for MutableTable {
63    fn backend_symbol(&self) -> Symbol {
64        Symbol::qualified("mutation", "table")
65    }
66
67    fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
68        self.entries
69            .read()
70            .map_err(|_| Error::PoisonedLock("mutation table"))?
71            .get(&key)
72            .cloned()
73            .map_or_else(|| cx.factory().nil(), Ok)
74    }
75
76    fn set(&self, cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
77        cx.require(&standard_mutate_capability())?;
78        self.entries
79            .write()
80            .map_err(|_| Error::PoisonedLock("mutation table"))?
81            .insert(key, value);
82        Ok(())
83    }
84
85    fn has(&self, _cx: &mut Cx, key: Symbol) -> Result<bool> {
86        Ok(self
87            .entries
88            .read()
89            .map_err(|_| Error::PoisonedLock("mutation table"))?
90            .contains_key(&key))
91    }
92
93    fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
94        cx.require(&standard_mutate_capability())?;
95        self.entries
96            .write()
97            .map_err(|_| Error::PoisonedLock("mutation table"))?
98            .remove(&key)
99            .map_or_else(|| cx.factory().nil(), Ok)
100    }
101
102    fn keys(&self, _cx: &mut Cx) -> Result<Vec<Symbol>> {
103        Ok(self
104            .entries
105            .read()
106            .map_err(|_| Error::PoisonedLock("mutation table"))?
107            .keys()
108            .cloned()
109            .collect())
110    }
111
112    fn entries(&self, _cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
113        Ok(self
114            .entries
115            .read()
116            .map_err(|_| Error::PoisonedLock("mutation table"))?
117            .iter()
118            .map(|(key, value)| (key.clone(), value.clone()))
119            .collect())
120    }
121
122    fn len(&self, _cx: &mut Cx) -> Result<usize> {
123        Ok(self
124            .entries
125            .read()
126            .map_err(|_| Error::PoisonedLock("mutation table"))?
127            .len())
128    }
129
130    fn clear(&self, cx: &mut Cx) -> Result<()> {
131        cx.require(&standard_mutate_capability())?;
132        self.entries
133            .write()
134            .map_err(|_| Error::PoisonedLock("mutation table"))?
135            .clear();
136        Ok(())
137    }
138}
139
140/// Construct a [`MutableTable`] from `entries` and wrap it as a runtime [`Value`].
141pub fn mutable_table(cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value> {
142    cx.factory().opaque(Arc::new(MutableTable::new(entries)))
143}
144
145/// Borrow the [`MutableTable`] behind `value`, or error if it is not one.
146pub fn mutable_table_value(value: &Value) -> Result<&MutableTable> {
147    value
148        .object()
149        .downcast_ref::<MutableTable>()
150        .ok_or(Error::TypeMismatch {
151            expected: "mutable table",
152            found: "non-table",
153        })
154}