Skip to main content

sim_kernel/
table.rs

1//! The table contract: the pluggable [`Table`] and [`Dir`] backend protocol.
2//!
3//! The kernel defines the table/directory protocols and a backend registry;
4//! concrete table representations are libs loaded against it, with `AssocTable`
5//! provided as a baseline backend rather than kernel-fixed behavior.
6
7use std::{
8    collections::BTreeMap,
9    sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
10};
11
12use crate::{
13    catalog::CatalogBackend,
14    env::Cx,
15    error::{Error, Result},
16    expr::Expr,
17    id::{CORE_TABLE_CLASS_ID, Symbol},
18    object::{ClassRef, Object},
19    value::{RuntimeObject, Value},
20};
21
22/// Universal map surface. Keys are symbols; values are runtime values.
23pub trait Table: RuntimeObject {
24    /// Symbol identifying the backend representation.
25    fn backend_symbol(&self) -> Symbol;
26
27    /// Looks up `key`, returning nil when absent.
28    fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value>;
29
30    /// Inserts or replaces the value for `key`.
31    fn set(&self, cx: &mut Cx, key: Symbol, value: Value) -> Result<()>;
32
33    /// Whether `key` is present.
34    fn has(&self, cx: &mut Cx, key: Symbol) -> Result<bool>;
35
36    /// Removes `key`, returning its prior value or nil.
37    fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value>;
38
39    /// All keys, in backend order.
40    fn keys(&self, cx: &mut Cx) -> Result<Vec<Symbol>>;
41
42    /// All key/value pairs, in backend order.
43    fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>>;
44
45    /// Number of entries.
46    fn len(&self, cx: &mut Cx) -> Result<usize>;
47
48    /// Whether the table has no entries.
49    fn is_empty(&self, cx: &mut Cx) -> Result<bool> {
50        Ok(self.len(cx)? == 0)
51    }
52
53    /// Removes all entries.
54    fn clear(&self, cx: &mut Cx) -> Result<()>;
55
56    /// Projects the table to an [`Expr::Map`].
57    fn as_table_expr(&self, cx: &mut Cx) -> Result<Expr> {
58        let entries = self.entries(cx)?;
59        let mut pairs = Vec::with_capacity(entries.len());
60        for (key, value) in entries {
61            pairs.push((Expr::Symbol(key), value.object().as_expr(cx)?));
62        }
63        Ok(Expr::Map(pairs))
64    }
65
66    /// Order-insensitive equality against another table's entries.
67    fn table_eq(&self, cx: &mut Cx, other: &dyn Table) -> Result<bool> {
68        let mut left = self.entries(cx)?;
69        let mut right = other.entries(cx)?;
70        left.sort_by(|a, b| a.0.cmp(&b.0));
71        right.sort_by(|a, b| a.0.cmp(&b.0));
72        Ok(left == right)
73    }
74}
75
76/// Hierarchical table surface for backends that support nested subtables.
77pub trait Dir: Table {
78    /// Creates a nested subtable under `name`, returning it.
79    fn mkdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value>;
80
81    /// Opens the subtable at `name`, or `Ok(None)` when absent.
82    fn opendir(&self, cx: &mut Cx, name: Symbol) -> Result<Option<Value>>;
83
84    /// Removes the subtable at `name`, returning it.
85    fn rmdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value>;
86
87    /// Whether `name` resolves to a subtable.
88    fn is_dir(&self, cx: &mut Cx, name: Symbol) -> Result<bool>;
89}
90
91/// Baseline table backend backed by an association list under a lock.
92// sim-non-citizen(reason = "kernel table backing object; canonical form is native table entries", kind = "private", descriptor = "")
93// AssocTable is the built-in bootstrap table backend. It implements the same
94// Table/TableBackend contracts as loadable table backends.
95pub struct AssocTable {
96    entries: RwLock<Vec<(Symbol, Value)>>,
97}
98
99impl AssocTable {
100    /// Builds an empty table.
101    pub fn new() -> Self {
102        Self {
103            entries: RwLock::new(Vec::new()),
104        }
105    }
106
107    /// Builds a table seeded with the given entries.
108    pub fn with_entries(entries: Vec<(Symbol, Value)>) -> Self {
109        Self {
110            entries: RwLock::new(entries),
111        }
112    }
113
114    fn read_entries(&self) -> Result<RwLockReadGuard<'_, Vec<(Symbol, Value)>>> {
115        self.entries.read().map_err(|_| poisoned_table_error())
116    }
117
118    fn write_entries(&self) -> Result<RwLockWriteGuard<'_, Vec<(Symbol, Value)>>> {
119        self.entries.write().map_err(|_| poisoned_table_error())
120    }
121}
122
123impl Default for AssocTable {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl Object for AssocTable {
130    fn display(&self, _cx: &mut Cx) -> Result<String> {
131        Ok(format!("table[{}]", self.read_entries()?.len()))
132    }
133
134    fn as_any(&self) -> &dyn std::any::Any {
135        self
136    }
137}
138
139impl crate::ObjectCompat for AssocTable {
140    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
141        let symbol = Symbol::qualified("core", "Table");
142        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
143            return Ok(value.clone());
144        }
145        cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
146    }
147    fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
148        self.as_table_expr(cx)
149    }
150    fn truth(&self, _cx: &mut Cx) -> Result<bool> {
151        Ok(!self.read_entries()?.is_empty())
152    }
153    fn as_table_impl(&self) -> Option<&dyn Table> {
154        Some(self)
155    }
156}
157
158impl Table for AssocTable {
159    fn backend_symbol(&self) -> Symbol {
160        Symbol::qualified("core", "Table")
161    }
162
163    fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
164        let guard = self.read_entries()?;
165        match guard.iter().find(|(candidate, _)| *candidate == key) {
166            Some((_, value)) => Ok(value.clone()),
167            None => cx.factory().nil(),
168        }
169    }
170
171    fn set(&self, _cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
172        let mut guard = self.write_entries()?;
173        if let Some((_, slot)) = guard.iter_mut().find(|(candidate, _)| *candidate == key) {
174            *slot = value;
175        } else {
176            guard.push((key, value));
177        }
178        Ok(())
179    }
180
181    fn has(&self, _cx: &mut Cx, key: Symbol) -> Result<bool> {
182        Ok(self
183            .read_entries()?
184            .iter()
185            .any(|(candidate, _)| *candidate == key))
186    }
187
188    fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
189        let mut guard = self.write_entries()?;
190        if let Some(index) = guard.iter().position(|(candidate, _)| *candidate == key) {
191            Ok(guard.remove(index).1)
192        } else {
193            cx.factory().nil()
194        }
195    }
196
197    fn keys(&self, _cx: &mut Cx) -> Result<Vec<Symbol>> {
198        Ok(self
199            .read_entries()?
200            .iter()
201            .map(|(key, _)| key.clone())
202            .collect())
203    }
204
205    fn entries(&self, _cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
206        Ok(self.read_entries()?.clone())
207    }
208
209    fn len(&self, _cx: &mut Cx) -> Result<usize> {
210        Ok(self.read_entries()?.len())
211    }
212
213    fn clear(&self, _cx: &mut Cx) -> Result<()> {
214        self.write_entries()?.clear();
215        Ok(())
216    }
217}
218
219fn poisoned_table_error() -> Error {
220    Error::Eval("assoc table lock poisoned".to_owned())
221}
222
223/// Factory protocol for constructing tables in a particular representation.
224pub trait TableBackend: Send + Sync {
225    /// Stable name the backend is registered and selected under.
226    fn name(&self) -> &str;
227
228    /// Builds a table from an initial set of entries.
229    fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value>;
230}
231
232/// Registry of named table backends with one active default.
233pub struct TableRegistry {
234    backends: BTreeMap<String, Arc<dyn TableBackend>>,
235    active: String,
236}
237
238impl TableRegistry {
239    /// Builds a registry preloaded with the `assoc` and catalog backends.
240    pub fn new() -> Self {
241        let mut registry = Self {
242            backends: BTreeMap::new(),
243            active: "assoc".to_owned(),
244        };
245        registry.register(Arc::new(AssocBackend));
246        registry.register(Arc::new(CatalogBackend));
247        registry
248    }
249
250    /// Registers a backend under its own name, replacing any prior one.
251    pub fn register(&mut self, backend: Arc<dyn TableBackend>) {
252        self.backends.insert(backend.name().to_owned(), backend);
253    }
254
255    /// Selects the active backend by name, erroring if it is unknown.
256    pub fn set_active(&mut self, name: &str) -> Result<()> {
257        if self.backends.contains_key(name) {
258            self.active = name.to_owned();
259            Ok(())
260        } else {
261            Err(Error::Eval(format!("unknown table backend: {name}")))
262        }
263    }
264
265    /// Name of the currently active backend.
266    pub fn active(&self) -> &str {
267        &self.active
268    }
269
270    /// Builds a table using the active backend.
271    pub fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value> {
272        self.backend()?.new_table(cx, entries)
273    }
274
275    fn backend(&self) -> Result<&Arc<dyn TableBackend>> {
276        self.backends
277            .get(&self.active)
278            .ok_or_else(|| Error::Eval("active table backend missing".to_owned()))
279    }
280}
281
282impl Default for TableRegistry {
283    fn default() -> Self {
284        Self::new()
285    }
286}
287
288struct AssocBackend;
289
290impl TableBackend for AssocBackend {
291    fn name(&self) -> &str {
292        "assoc"
293    }
294
295    fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value> {
296        cx.factory()
297            .opaque(Arc::new(AssocTable::with_entries(entries)))
298    }
299}
300
301#[cfg(test)]
302#[path = "table_tests.rs"]
303mod table_tests;