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
22pub trait Table: RuntimeObject {
24 fn backend_symbol(&self) -> Symbol;
26
27 fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value>;
29
30 fn set(&self, cx: &mut Cx, key: Symbol, value: Value) -> Result<()>;
32
33 fn has(&self, cx: &mut Cx, key: Symbol) -> Result<bool>;
35
36 fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value>;
38
39 fn keys(&self, cx: &mut Cx) -> Result<Vec<Symbol>>;
41
42 fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>>;
44
45 fn len(&self, cx: &mut Cx) -> Result<usize>;
47
48 fn is_empty(&self, cx: &mut Cx) -> Result<bool> {
50 Ok(self.len(cx)? == 0)
51 }
52
53 fn clear(&self, cx: &mut Cx) -> Result<()>;
55
56 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 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
76pub trait Dir: Table {
78 fn mkdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value>;
80
81 fn opendir(&self, cx: &mut Cx, name: Symbol) -> Result<Option<Value>>;
83
84 fn rmdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value>;
86
87 fn is_dir(&self, cx: &mut Cx, name: Symbol) -> Result<bool>;
89}
90
91pub struct AssocTable {
98 entries: RwLock<Vec<(Symbol, Value)>>,
99}
100
101impl AssocTable {
102 pub fn new() -> Self {
104 Self {
105 entries: RwLock::new(Vec::new()),
106 }
107 }
108
109 pub fn with_entries(entries: Vec<(Symbol, Value)>) -> Self {
111 Self {
112 entries: RwLock::new(entries),
113 }
114 }
115
116 fn read_entries(&self) -> Result<RwLockReadGuard<'_, Vec<(Symbol, Value)>>> {
117 self.entries.read().map_err(|_| poisoned_table_error())
118 }
119
120 fn write_entries(&self) -> Result<RwLockWriteGuard<'_, Vec<(Symbol, Value)>>> {
121 self.entries.write().map_err(|_| poisoned_table_error())
122 }
123}
124
125impl Default for AssocTable {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131impl Object for AssocTable {
132 fn display(&self, _cx: &mut Cx) -> Result<String> {
133 Ok(format!("table[{}]", self.read_entries()?.len()))
134 }
135
136 fn as_any(&self) -> &dyn std::any::Any {
137 self
138 }
139}
140
141impl crate::ObjectCompat for AssocTable {
142 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
143 let symbol = Symbol::qualified("core", "Table");
144 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
145 return Ok(value.clone());
146 }
147 cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
148 }
149 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
150 self.as_table_expr(cx)
151 }
152 fn truth(&self, _cx: &mut Cx) -> Result<bool> {
153 Ok(!self.read_entries()?.is_empty())
154 }
155 fn as_table_impl(&self) -> Option<&dyn Table> {
156 Some(self)
157 }
158}
159
160impl Table for AssocTable {
161 fn backend_symbol(&self) -> Symbol {
162 Symbol::qualified("core", "Table")
163 }
164
165 fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
166 let guard = self.read_entries()?;
167 match guard.iter().find(|(candidate, _)| *candidate == key) {
168 Some((_, value)) => Ok(value.clone()),
169 None => cx.factory().nil(),
170 }
171 }
172
173 fn set(&self, _cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
174 let mut guard = self.write_entries()?;
175 if let Some((_, slot)) = guard.iter_mut().find(|(candidate, _)| *candidate == key) {
176 *slot = value;
177 } else {
178 guard.push((key, value));
179 }
180 Ok(())
181 }
182
183 fn has(&self, _cx: &mut Cx, key: Symbol) -> Result<bool> {
184 Ok(self
185 .read_entries()?
186 .iter()
187 .any(|(candidate, _)| *candidate == key))
188 }
189
190 fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
191 let mut guard = self.write_entries()?;
192 if let Some(index) = guard.iter().position(|(candidate, _)| *candidate == key) {
193 Ok(guard.remove(index).1)
194 } else {
195 cx.factory().nil()
196 }
197 }
198
199 fn keys(&self, _cx: &mut Cx) -> Result<Vec<Symbol>> {
200 Ok(self
201 .read_entries()?
202 .iter()
203 .map(|(key, _)| key.clone())
204 .collect())
205 }
206
207 fn entries(&self, _cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
208 Ok(self.read_entries()?.clone())
209 }
210
211 fn len(&self, _cx: &mut Cx) -> Result<usize> {
212 Ok(self.read_entries()?.len())
213 }
214
215 fn clear(&self, _cx: &mut Cx) -> Result<()> {
216 self.write_entries()?.clear();
217 Ok(())
218 }
219}
220
221fn poisoned_table_error() -> Error {
222 Error::Eval("assoc table lock poisoned".to_owned())
223}
224
225pub trait TableBackend: Send + Sync {
227 fn name(&self) -> &str;
229
230 fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value>;
232}
233
234pub struct TableRegistry {
236 backends: BTreeMap<String, Arc<dyn TableBackend>>,
237 active: String,
238}
239
240impl TableRegistry {
241 pub fn new() -> Self {
243 let mut registry = Self {
244 backends: BTreeMap::new(),
245 active: "assoc".to_owned(),
246 };
247 registry.register(Arc::new(AssocBackend));
248 registry.register(Arc::new(CatalogBackend));
249 registry
250 }
251
252 pub fn register(&mut self, backend: Arc<dyn TableBackend>) {
254 self.backends.insert(backend.name().to_owned(), backend);
255 }
256
257 pub fn set_active(&mut self, name: &str) -> Result<()> {
259 if self.backends.contains_key(name) {
260 self.active = name.to_owned();
261 Ok(())
262 } else {
263 Err(Error::Eval(format!("unknown table backend: {name}")))
264 }
265 }
266
267 pub fn active(&self) -> &str {
269 &self.active
270 }
271
272 pub fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value> {
274 self.backend()?.new_table(cx, entries)
275 }
276
277 fn backend(&self) -> Result<&Arc<dyn TableBackend>> {
278 self.backends
279 .get(&self.active)
280 .ok_or_else(|| Error::Eval("active table backend missing".to_owned()))
281 }
282}
283
284impl Default for TableRegistry {
285 fn default() -> Self {
286 Self::new()
287 }
288}
289
290struct AssocBackend;
291
292impl TableBackend for AssocBackend {
293 fn name(&self) -> &str {
294 "assoc"
295 }
296
297 fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value> {
298 cx.factory()
299 .opaque(Arc::new(AssocTable::with_entries(entries)))
300 }
301}
302
303#[cfg(test)]
304#[path = "table_tests.rs"]
305mod table_tests;