Skip to main content

sim_lib_mutation/
runtime_table.rs

1use std::{
2    collections::BTreeMap,
3    sync::{Arc, RwLock},
4};
5
6use sim_kernel::{Cx, Error, Expr, Object, ObjectCompat, Result, Value};
7
8use crate::{RuntimeKey, RuntimeKeyPolicy, standard_mutate_capability};
9
10/// A mutable table keyed by arbitrary runtime values.
11///
12/// Unlike [`MutableTable`](crate::MutableTable), this table does not implement
13/// the kernel symbol-keyed table contract. It is the shared substrate for guest
14/// language hash/map/table values whose keys can be booleans, numbers, strings,
15/// symbols, or object identities. Writes require
16/// [`standard_mutate_capability`]; reads are always allowed.
17#[sim_citizen_derive::non_citizen(
18    reason = "mutable runtime-keyed table handle; reconstruct from entries plus key policy",
19    kind = "handle",
20    descriptor = "core/Expr"
21)]
22pub struct MutableRuntimeTable<P> {
23    policy: P,
24    entries: RwLock<BTreeMap<RuntimeKey, Value>>,
25}
26
27impl<P: RuntimeKeyPolicy> MutableRuntimeTable<P> {
28    /// Creates an empty runtime-keyed table.
29    pub fn new(policy: P) -> Self {
30        Self {
31            policy,
32            entries: RwLock::new(BTreeMap::new()),
33        }
34    }
35
36    /// Creates a runtime-keyed table seeded with `entries`.
37    ///
38    /// Seeding is construction, not mutation, so it does not require the mutate
39    /// capability. Later duplicate keys replace earlier entries.
40    pub fn with_entries(cx: &mut Cx, policy: P, entries: Vec<(Value, Value)>) -> Result<Self> {
41        let table = Self::new(policy);
42        let mut keyed = BTreeMap::new();
43        for (key, value) in entries {
44            keyed.insert(table.key_for_write(cx, &key)?, value);
45        }
46        *table.write_entries()? = keyed;
47        Ok(table)
48    }
49
50    /// Returns the table's key policy.
51    pub fn policy(&self) -> &P {
52        &self.policy
53    }
54
55    /// Reads a value using the configured key policy.
56    pub fn get(&self, cx: &mut Cx, key: &Value) -> Result<Option<Value>> {
57        let Some(key) = self.policy.key_for(cx, key)? else {
58            return Ok(None);
59        };
60        self.get_runtime_key(&key)
61    }
62
63    /// Reads a value using an already-derived runtime key.
64    pub fn get_runtime_key(&self, key: &RuntimeKey) -> Result<Option<Value>> {
65        Ok(self.read_entries()?.get(key).cloned())
66    }
67
68    /// Writes a value using the configured key policy.
69    pub fn set(&self, cx: &mut Cx, key: Value, value: Value) -> Result<()> {
70        let key = self.key_for_write(cx, &key)?;
71        self.set_runtime_key(cx, key, value)
72    }
73
74    /// Writes a value using an already-derived runtime key.
75    pub fn set_runtime_key(&self, cx: &mut Cx, key: RuntimeKey, value: Value) -> Result<()> {
76        cx.require(&standard_mutate_capability())?;
77        self.write_entries()?.insert(key, value);
78        Ok(())
79    }
80
81    /// Deletes a value using the configured key policy.
82    pub fn del(&self, cx: &mut Cx, key: &Value) -> Result<Option<Value>> {
83        let Some(key) = self.policy.key_for(cx, key)? else {
84            return Ok(None);
85        };
86        self.del_runtime_key(cx, &key)
87    }
88
89    /// Deletes a value using an already-derived runtime key.
90    pub fn del_runtime_key(&self, cx: &mut Cx, key: &RuntimeKey) -> Result<Option<Value>> {
91        cx.require(&standard_mutate_capability())?;
92        Ok(self.write_entries()?.remove(key))
93    }
94
95    /// Returns entries in deterministic key order.
96    pub fn entries_in_key_order(&self) -> Result<Vec<(RuntimeKey, Value)>> {
97        Ok(self
98            .read_entries()?
99            .iter()
100            .map(|(key, value)| (key.clone(), value.clone()))
101            .collect())
102    }
103
104    /// Returns the number of entries.
105    pub fn len(&self) -> Result<usize> {
106        Ok(self.read_entries()?.len())
107    }
108
109    /// Returns whether the table is empty.
110    pub fn is_empty(&self) -> Result<bool> {
111        Ok(self.len()? == 0)
112    }
113
114    /// Clears the table after checking mutation authority.
115    pub fn clear(&self, cx: &mut Cx) -> Result<()> {
116        cx.require(&standard_mutate_capability())?;
117        self.write_entries()?.clear();
118        Ok(())
119    }
120
121    fn key_for_write(&self, cx: &mut Cx, key: &Value) -> Result<RuntimeKey> {
122        self.policy
123            .key_for(cx, key)?
124            .ok_or_else(|| Error::Eval("runtime table key is not allowed by policy".to_owned()))
125    }
126
127    fn read_entries(&self) -> Result<std::sync::RwLockReadGuard<'_, BTreeMap<RuntimeKey, Value>>> {
128        self.entries
129            .read()
130            .map_err(|_| Error::PoisonedLock("runtime-keyed mutation table"))
131    }
132
133    fn write_entries(
134        &self,
135    ) -> Result<std::sync::RwLockWriteGuard<'_, BTreeMap<RuntimeKey, Value>>> {
136        self.entries
137            .write()
138            .map_err(|_| Error::PoisonedLock("runtime-keyed mutation table"))
139    }
140}
141
142impl<P: RuntimeKeyPolicy + 'static> Object for MutableRuntimeTable<P> {
143    fn display(&self, _cx: &mut Cx) -> Result<String> {
144        Ok(format!("#<runtime-mutation-table {}>", self.len()?))
145    }
146
147    fn as_any(&self) -> &dyn std::any::Any {
148        self
149    }
150}
151
152impl<P: RuntimeKeyPolicy + 'static> ObjectCompat for MutableRuntimeTable<P> {
153    fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
154        Ok(Expr::Map(
155            self.entries_in_key_order()?
156                .into_iter()
157                .map(|(key, value)| Ok((key.as_expr(), value.object().as_expr(cx)?)))
158                .collect::<Result<Vec<_>>>()?,
159        ))
160    }
161
162    fn truth(&self, _cx: &mut Cx) -> Result<bool> {
163        Ok(!self.is_empty()?)
164    }
165}
166
167/// Constructs a [`MutableRuntimeTable`] and wraps it as a runtime [`Value`].
168pub fn mutable_runtime_table<P>(
169    cx: &mut Cx,
170    policy: P,
171    entries: Vec<(Value, Value)>,
172) -> Result<Value>
173where
174    P: RuntimeKeyPolicy + 'static,
175{
176    let table = MutableRuntimeTable::with_entries(cx, policy, entries)?;
177    cx.factory().opaque(Arc::new(table))
178}
179
180/// Borrows a [`MutableRuntimeTable`] with policy `P` from `value`.
181pub fn mutable_runtime_table_value<P>(value: &Value) -> Result<&MutableRuntimeTable<P>>
182where
183    P: RuntimeKeyPolicy + 'static,
184{
185    value
186        .object()
187        .downcast_ref::<MutableRuntimeTable<P>>()
188        .ok_or(Error::TypeMismatch {
189            expected: "runtime-keyed mutation table",
190            found: "non-table",
191        })
192}