1use 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#[derive(Clone, Debug, PartialEq)]
24pub enum TableExpected {
25 Absent,
27 Value(Expr),
29}
30
31#[derive(Clone)]
33pub enum TableReplacement {
34 Delete,
36 Value(Value),
38}
39
40#[derive(Clone, Debug, PartialEq)]
42pub enum TableObserved {
43 Absent,
45 Value(Expr),
47}
48
49#[derive(Clone, Debug, PartialEq)]
51pub struct TableCompareExchange {
52 pub exchanged: bool,
54 pub observed: TableObserved,
56}
57
58pub trait Table: RuntimeObject {
60 fn backend_symbol(&self) -> Symbol;
62
63 fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value>;
65
66 fn set(&self, cx: &mut Cx, key: Symbol, value: Value) -> Result<()>;
68
69 fn has(&self, cx: &mut Cx, key: Symbol) -> Result<bool>;
71
72 fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value>;
74
75 fn keys(&self, cx: &mut Cx) -> Result<Vec<Symbol>>;
77
78 fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>>;
80
81 fn len(&self, cx: &mut Cx) -> Result<usize>;
83
84 fn is_empty(&self, cx: &mut Cx) -> Result<bool> {
86 Ok(self.len(cx)? == 0)
87 }
88
89 fn clear(&self, cx: &mut Cx) -> Result<()>;
91
92 fn compare_exchange(
97 &self,
98 _cx: &mut Cx,
99 _key: Symbol,
100 _expected: TableExpected,
101 _replacement: TableReplacement,
102 ) -> Result<TableCompareExchange> {
103 Err(Error::Eval(format!(
104 "table/compare-exchange unsupported by {}",
105 self.backend_symbol()
106 )))
107 }
108
109 fn as_table_expr(&self, cx: &mut Cx) -> Result<Expr> {
111 let entries = self.entries(cx)?;
112 let mut pairs = Vec::with_capacity(entries.len());
113 for (key, value) in entries {
114 pairs.push((Expr::Symbol(key), value.object().as_expr(cx)?));
115 }
116 Ok(Expr::Map(pairs))
117 }
118
119 fn table_eq(&self, cx: &mut Cx, other: &dyn Table) -> Result<bool> {
121 let mut left = self.entries(cx)?;
122 let mut right = other.entries(cx)?;
123 left.sort_by(|a, b| a.0.cmp(&b.0));
124 right.sort_by(|a, b| a.0.cmp(&b.0));
125 Ok(left == right)
126 }
127}
128
129pub trait Dir: Table {
131 fn mkdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value>;
133
134 fn opendir(&self, cx: &mut Cx, name: Symbol) -> Result<Option<Value>>;
136
137 fn rmdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value>;
139
140 fn is_dir(&self, cx: &mut Cx, name: Symbol) -> Result<bool>;
142}
143
144pub struct AssocTable {
149 entries: RwLock<Vec<(Symbol, Value)>>,
150}
151
152impl AssocTable {
153 pub fn new() -> Self {
155 Self {
156 entries: RwLock::new(Vec::new()),
157 }
158 }
159
160 pub fn with_entries(entries: Vec<(Symbol, Value)>) -> Self {
162 Self {
163 entries: RwLock::new(entries),
164 }
165 }
166
167 fn read_entries(&self) -> Result<RwLockReadGuard<'_, Vec<(Symbol, Value)>>> {
168 self.entries.read().map_err(|_| poisoned_table_error())
169 }
170
171 fn write_entries(&self) -> Result<RwLockWriteGuard<'_, Vec<(Symbol, Value)>>> {
172 self.entries.write().map_err(|_| poisoned_table_error())
173 }
174}
175
176impl Default for AssocTable {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182impl Object for AssocTable {
183 fn display(&self, _cx: &mut Cx) -> Result<String> {
184 Ok(format!("table[{}]", self.read_entries()?.len()))
185 }
186
187 fn as_any(&self) -> &dyn std::any::Any {
188 self
189 }
190}
191
192impl crate::ObjectCompat for AssocTable {
193 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
194 let symbol = Symbol::qualified("core", "Table");
195 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
196 return Ok(value.clone());
197 }
198 cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
199 }
200 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
201 self.as_table_expr(cx)
202 }
203 fn truth(&self, _cx: &mut Cx) -> Result<bool> {
204 Ok(!self.read_entries()?.is_empty())
205 }
206 fn as_table_impl(&self) -> Option<&dyn Table> {
207 Some(self)
208 }
209}
210
211impl Table for AssocTable {
212 fn backend_symbol(&self) -> Symbol {
213 Symbol::qualified("core", "Table")
214 }
215
216 fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
217 let guard = self.read_entries()?;
218 match guard.iter().find(|(candidate, _)| *candidate == key) {
219 Some((_, value)) => Ok(value.clone()),
220 None => cx.factory().nil(),
221 }
222 }
223
224 fn set(&self, _cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
225 let mut guard = self.write_entries()?;
226 if let Some((_, slot)) = guard.iter_mut().find(|(candidate, _)| *candidate == key) {
227 *slot = value;
228 } else {
229 guard.push((key, value));
230 }
231 Ok(())
232 }
233
234 fn has(&self, _cx: &mut Cx, key: Symbol) -> Result<bool> {
235 Ok(self
236 .read_entries()?
237 .iter()
238 .any(|(candidate, _)| *candidate == key))
239 }
240
241 fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
242 let mut guard = self.write_entries()?;
243 if let Some(index) = guard.iter().position(|(candidate, _)| *candidate == key) {
244 Ok(guard.remove(index).1)
245 } else {
246 cx.factory().nil()
247 }
248 }
249
250 fn keys(&self, _cx: &mut Cx) -> Result<Vec<Symbol>> {
251 Ok(self
252 .read_entries()?
253 .iter()
254 .map(|(key, _)| key.clone())
255 .collect())
256 }
257
258 fn entries(&self, _cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
259 Ok(self.read_entries()?.clone())
260 }
261
262 fn len(&self, _cx: &mut Cx) -> Result<usize> {
263 Ok(self.read_entries()?.len())
264 }
265
266 fn clear(&self, _cx: &mut Cx) -> Result<()> {
267 self.write_entries()?.clear();
268 Ok(())
269 }
270
271 fn compare_exchange(
272 &self,
273 cx: &mut Cx,
274 key: Symbol,
275 expected: TableExpected,
276 replacement: TableReplacement,
277 ) -> Result<TableCompareExchange> {
278 let replacement = match replacement {
279 TableReplacement::Delete => None,
280 TableReplacement::Value(value) => Some((value.object().as_expr(cx)?, value)),
281 };
282 let mut guard = self.write_entries()?;
283 let index = guard.iter().position(|(candidate, _)| *candidate == key);
284 let observed = match index {
285 Some(index) => TableObserved::Value(guard[index].1.object().as_expr(cx)?),
286 None => TableObserved::Absent,
287 };
288 let matches = match (&expected, &observed) {
289 (TableExpected::Absent, TableObserved::Absent) => true,
290 (TableExpected::Value(left), TableObserved::Value(right)) => left == right,
291 _ => false,
292 };
293 if matches {
294 match (index, replacement) {
295 (Some(index), None) => {
296 guard.remove(index);
297 }
298 (None, None) => {}
299 (Some(index), Some((_, value))) => guard[index].1 = value,
300 (None, Some((_, value))) => guard.push((key, value)),
301 }
302 }
303 Ok(TableCompareExchange {
304 exchanged: matches,
305 observed,
306 })
307 }
308}
309
310fn poisoned_table_error() -> Error {
311 Error::Eval("assoc table lock poisoned".to_owned())
312}
313
314pub trait TableBackend: Send + Sync {
316 fn name(&self) -> &str;
318
319 fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value>;
321}
322
323pub struct TableRegistry {
325 backends: BTreeMap<String, Arc<dyn TableBackend>>,
326 active: String,
327}
328
329impl TableRegistry {
330 pub fn new() -> Self {
332 let mut registry = Self {
333 backends: BTreeMap::new(),
334 active: "assoc".to_owned(),
335 };
336 registry.register(Arc::new(AssocBackend));
337 registry.register(Arc::new(CatalogBackend));
338 registry
339 }
340
341 pub fn register(&mut self, backend: Arc<dyn TableBackend>) {
343 self.backends.insert(backend.name().to_owned(), backend);
344 }
345
346 pub fn set_active(&mut self, name: &str) -> Result<()> {
348 if self.backends.contains_key(name) {
349 self.active = name.to_owned();
350 Ok(())
351 } else {
352 Err(Error::Eval(format!("unknown table backend: {name}")))
353 }
354 }
355
356 pub fn active(&self) -> &str {
358 &self.active
359 }
360
361 pub fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value> {
363 self.backend()?.new_table(cx, entries)
364 }
365
366 fn backend(&self) -> Result<&Arc<dyn TableBackend>> {
367 self.backends
368 .get(&self.active)
369 .ok_or_else(|| Error::Eval("active table backend missing".to_owned()))
370 }
371}
372
373impl Default for TableRegistry {
374 fn default() -> Self {
375 Self::new()
376 }
377}
378
379struct AssocBackend;
380
381impl TableBackend for AssocBackend {
382 fn name(&self) -> &str {
383 "assoc"
384 }
385
386 fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value> {
387 cx.factory()
388 .opaque(Arc::new(AssocTable::with_entries(entries)))
389 }
390}
391
392#[cfg(test)]
393#[path = "table_tests.rs"]
394mod table_tests;