1use std::{
7 collections::{BTreeMap, BTreeSet},
8 sync::{Arc, Mutex},
9};
10
11use sim_kernel::{
12 Cx, Error, Expr, Object, ObjectEncode, ObjectEncoding, Result, Symbol, Value,
13 capability::{
14 table_db_capability, table_db_mkdir_capability, table_db_read_capability,
15 table_db_rmdir_capability, table_db_write_capability,
16 },
17 id::CORE_TABLE_CLASS_ID,
18 object::ClassRef,
19 table::{Dir, Table},
20};
21
22use crate::citizen::db_dir_class_symbol;
23
24struct Store {
25 values: BTreeMap<(String, Symbol), Value>,
26 dirs: BTreeSet<String>,
27}
28
29#[derive(Clone)]
42pub struct DbDir {
43 store: Arc<Mutex<Store>>,
44 path: String,
45}
46
47impl DbDir {
48 pub fn open() -> Self {
51 let mut dirs = BTreeSet::new();
52 dirs.insert(String::new());
53 Self {
54 store: Arc::new(Mutex::new(Store {
55 values: BTreeMap::new(),
56 dirs,
57 })),
58 path: String::new(),
59 }
60 }
61
62 fn with_store(store: Arc<Mutex<Store>>, path: String) -> Self {
63 Self { store, path }
64 }
65
66 fn lock(&self) -> Result<std::sync::MutexGuard<'_, Store>> {
67 self.store
68 .lock()
69 .map_err(|_| Error::Eval("table/db lock poisoned".into()))
70 }
71
72 fn child_path(&self, name: &Symbol) -> Result<String> {
73 let segment = name.name.as_ref();
74 if !sim_table_core::is_legal_table_segment(segment) {
75 return Err(Error::Eval(format!("table/db: illegal name {segment:?}")));
76 }
77 Ok(if self.path.is_empty() {
78 segment.to_owned()
79 } else {
80 format!("{}/{segment}", self.path)
81 })
82 }
83
84 fn direct_subdirs(&self, store: &Store) -> Vec<Symbol> {
85 let prefix = if self.path.is_empty() {
86 None
87 } else {
88 Some(format!("{}/", self.path))
89 };
90 let mut names = BTreeSet::new();
91 for path in &store.dirs {
92 if path.is_empty() || *path == self.path {
93 continue;
94 }
95 let Some(rest) = prefix
96 .as_ref()
97 .map_or_else(|| Some(path.as_str()), |prefix| path.strip_prefix(prefix))
98 else {
99 continue;
100 };
101 if let Some((head, tail)) = rest.split_once('/') {
102 if !head.is_empty() && !tail.is_empty() {
103 names.insert(Symbol::new(head));
104 }
105 } else if !rest.is_empty() {
106 names.insert(Symbol::new(rest));
107 }
108 }
109 names.into_iter().collect()
110 }
111
112 fn descriptor_path(&self) -> Vec<String> {
113 if self.path.is_empty() {
114 Vec::new()
115 } else {
116 self.path
117 .split('/')
118 .map(std::borrow::ToOwned::to_owned)
119 .collect()
120 }
121 }
122}
123
124impl Default for DbDir {
125 fn default() -> Self {
126 Self::open()
127 }
128}
129
130impl Object for DbDir {
131 fn display(&self, _cx: &mut Cx) -> Result<String> {
132 if self.path.is_empty() {
133 Ok("table/db[/]".to_owned())
134 } else {
135 Ok(format!("table/db[/{}]", self.path))
136 }
137 }
138
139 fn as_any(&self) -> &dyn std::any::Any {
140 self
141 }
142}
143
144impl sim_kernel::ObjectCompat for DbDir {
145 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
146 let symbol = db_dir_class_symbol();
147 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
148 return Ok(value.clone());
149 }
150 let symbol = Symbol::qualified("core", "Table");
151 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
152 return Ok(value.clone());
153 }
154 cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
155 }
156 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
157 self.as_table_expr(cx)
158 }
159 fn truth(&self, cx: &mut Cx) -> Result<bool> {
160 Ok(!self.is_empty(cx)?)
161 }
162 fn as_table_impl(&self) -> Option<&dyn Table> {
163 Some(self)
164 }
165 fn as_dir(&self) -> Option<&dyn Dir> {
166 Some(self)
167 }
168 fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
169 Some(self)
170 }
171}
172
173impl ObjectEncode for DbDir {
174 fn object_encoding(&self, _cx: &mut Cx) -> Result<ObjectEncoding> {
175 Ok(ObjectEncoding::Constructor {
176 class: db_dir_class_symbol(),
177 args: vec![
178 Expr::Symbol(Symbol::new("v0")),
179 sim_table_core::citizen_fields::path_segments::encode(&self.descriptor_path()),
180 ],
181 })
182 }
183}
184
185impl sim_citizen::Citizen for DbDir {
186 fn citizen_symbol() -> Symbol {
187 db_dir_class_symbol()
188 }
189
190 fn citizen_version() -> u32 {
191 0
192 }
193
194 fn citizen_arity() -> usize {
195 1
196 }
197
198 fn citizen_fields() -> &'static [&'static str] {
199 &["path"]
200 }
201}
202
203impl Table for DbDir {
204 fn backend_symbol(&self) -> Symbol {
205 Symbol::qualified("table", "db")
206 }
207
208 fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
209 cx.require(&table_db_read_capability())?;
210 let value = self.lock()?.values.get(&(self.path.clone(), key)).cloned();
211 match value {
212 Some(value) => Ok(value),
213 None => cx.factory().nil(),
214 }
215 }
216
217 fn set(&self, cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
218 cx.require(&table_db_write_capability())?;
219 let path = self.child_path(&key)?;
220 let mut store = self.lock()?;
221 if store.dirs.contains(&path) {
222 return Err(Error::Eval(format!("table/db: {key} is a directory")));
223 }
224 store.values.insert((self.path.clone(), key), value);
225 Ok(())
226 }
227
228 fn has(&self, cx: &mut Cx, key: Symbol) -> Result<bool> {
229 cx.require(&table_db_read_capability())?;
230 let path = self.child_path(&key)?;
231 let store = self.lock()?;
232 Ok(store.values.contains_key(&(self.path.clone(), key)) || store.dirs.contains(&path))
233 }
234
235 fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
236 cx.require(&table_db_write_capability())?;
237 let value = self.lock()?.values.remove(&(self.path.clone(), key));
238 match value {
239 Some(value) => Ok(value),
240 None => cx.factory().nil(),
241 }
242 }
243
244 fn keys(&self, cx: &mut Cx) -> Result<Vec<Symbol>> {
245 cx.require(&table_db_read_capability())?;
246 let store = self.lock()?;
247 let mut keys = BTreeSet::new();
248 for (path, key) in store.values.keys() {
249 if *path == self.path {
250 keys.insert(key.clone());
251 }
252 }
253 for key in self.direct_subdirs(&store) {
254 keys.insert(key);
255 }
256 Ok(keys.into_iter().collect())
257 }
258
259 fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
260 cx.require(&table_db_read_capability())?;
261 let store = self.lock()?;
262 Ok(store
263 .values
264 .iter()
265 .filter(|((path, _), _)| *path == self.path)
266 .map(|((_, key), value)| (key.clone(), value.clone()))
267 .collect())
268 }
269
270 fn len(&self, cx: &mut Cx) -> Result<usize> {
271 Ok(self.entries(cx)?.len())
272 }
273
274 fn clear(&self, cx: &mut Cx) -> Result<()> {
275 cx.require(&table_db_write_capability())?;
276 self.lock()?
277 .values
278 .retain(|(path, _), _| *path != self.path);
279 Ok(())
280 }
281}
282
283impl Dir for DbDir {
284 fn mkdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value> {
285 cx.require(&table_db_mkdir_capability())?;
286 let path = self.child_path(&name)?;
287 let mut store = self.lock()?;
288 if store
289 .values
290 .contains_key(&(self.path.clone(), name.clone()))
291 {
292 return Err(Error::Eval(format!("table/db: {name} is a file")));
293 }
294 store.dirs.insert(path.clone());
295 cx.factory()
296 .opaque(Arc::new(Self::with_store(self.store.clone(), path)))
297 }
298
299 fn opendir(&self, cx: &mut Cx, name: Symbol) -> Result<Option<Value>> {
300 cx.require(&table_db_read_capability())?;
301 let path = self.child_path(&name)?;
302 let store = self.lock()?;
303 if store.dirs.contains(&path) {
304 return Ok(Some(
305 cx.factory()
306 .opaque(Arc::new(Self::with_store(self.store.clone(), path)))?,
307 ));
308 }
309 if store
310 .values
311 .contains_key(&(self.path.clone(), name.clone()))
312 {
313 return Err(Error::Eval(format!("table/db: {name} is not a directory")));
314 }
315 Ok(None)
316 }
317
318 fn rmdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value> {
319 cx.require(&table_db_rmdir_capability())?;
320 let path = self.child_path(&name)?;
321 let mut store = self.lock()?;
322 if !store.dirs.contains(&path) {
323 return Err(Error::Eval(format!("table/db: {name} is not a directory")));
324 }
325 let prefix = format!("{path}/");
326 store
327 .values
328 .retain(|(entry_path, _), _| *entry_path != path && !entry_path.starts_with(&prefix));
329 store
330 .dirs
331 .retain(|dir_path| *dir_path != path && !dir_path.starts_with(&prefix));
332 cx.factory().nil()
333 }
334
335 fn is_dir(&self, cx: &mut Cx, name: Symbol) -> Result<bool> {
336 cx.require(&table_db_read_capability())?;
337 let path = self.child_path(&name)?;
338 Ok(self.lock()?.dirs.contains(&path))
339 }
340}
341
342pub fn install_db_dir_lib(cx: &mut Cx) -> Result<Value> {
373 cx.require(&table_db_capability())?;
374 cx.factory().opaque(Arc::new(DbDir::open()))
375}