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 {
96 entries: RwLock<Vec<(Symbol, Value)>>,
97}
98
99impl AssocTable {
100 pub fn new() -> Self {
102 Self {
103 entries: RwLock::new(Vec::new()),
104 }
105 }
106
107 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
223pub trait TableBackend: Send + Sync {
225 fn name(&self) -> &str;
227
228 fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value>;
230}
231
232pub struct TableRegistry {
234 backends: BTreeMap<String, Arc<dyn TableBackend>>,
235 active: String,
236}
237
238impl TableRegistry {
239 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 pub fn register(&mut self, backend: Arc<dyn TableBackend>) {
252 self.backends.insert(backend.name().to_owned(), backend);
253 }
254
255 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 pub fn active(&self) -> &str {
267 &self.active
268 }
269
270 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;