use std::{
collections::BTreeMap,
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
use crate::{
catalog::CatalogBackend,
env::Cx,
error::{Error, Result},
expr::Expr,
id::{CORE_TABLE_CLASS_ID, Symbol},
object::{ClassRef, Object},
value::{RuntimeObject, Value},
};
pub trait Table: RuntimeObject {
fn backend_symbol(&self) -> Symbol;
fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value>;
fn set(&self, cx: &mut Cx, key: Symbol, value: Value) -> Result<()>;
fn has(&self, cx: &mut Cx, key: Symbol) -> Result<bool>;
fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value>;
fn keys(&self, cx: &mut Cx) -> Result<Vec<Symbol>>;
fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>>;
fn len(&self, cx: &mut Cx) -> Result<usize>;
fn is_empty(&self, cx: &mut Cx) -> Result<bool> {
Ok(self.len(cx)? == 0)
}
fn clear(&self, cx: &mut Cx) -> Result<()>;
fn as_table_expr(&self, cx: &mut Cx) -> Result<Expr> {
let entries = self.entries(cx)?;
let mut pairs = Vec::with_capacity(entries.len());
for (key, value) in entries {
pairs.push((Expr::Symbol(key), value.object().as_expr(cx)?));
}
Ok(Expr::Map(pairs))
}
fn table_eq(&self, cx: &mut Cx, other: &dyn Table) -> Result<bool> {
let mut left = self.entries(cx)?;
let mut right = other.entries(cx)?;
left.sort_by(|a, b| a.0.cmp(&b.0));
right.sort_by(|a, b| a.0.cmp(&b.0));
Ok(left == right)
}
}
pub trait Dir: Table {
fn mkdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value>;
fn opendir(&self, cx: &mut Cx, name: Symbol) -> Result<Option<Value>>;
fn rmdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value>;
fn is_dir(&self, cx: &mut Cx, name: Symbol) -> Result<bool>;
}
pub struct AssocTable {
entries: RwLock<Vec<(Symbol, Value)>>,
}
impl AssocTable {
pub fn new() -> Self {
Self {
entries: RwLock::new(Vec::new()),
}
}
pub fn with_entries(entries: Vec<(Symbol, Value)>) -> Self {
Self {
entries: RwLock::new(entries),
}
}
fn read_entries(&self) -> Result<RwLockReadGuard<'_, Vec<(Symbol, Value)>>> {
self.entries.read().map_err(|_| poisoned_table_error())
}
fn write_entries(&self) -> Result<RwLockWriteGuard<'_, Vec<(Symbol, Value)>>> {
self.entries.write().map_err(|_| poisoned_table_error())
}
}
impl Default for AssocTable {
fn default() -> Self {
Self::new()
}
}
impl Object for AssocTable {
fn display(&self, _cx: &mut Cx) -> Result<String> {
Ok(format!("table[{}]", self.read_entries()?.len()))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl crate::ObjectCompat for AssocTable {
fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
let symbol = Symbol::qualified("core", "Table");
if let Some(value) = cx.registry().class_by_symbol(&symbol) {
return Ok(value.clone());
}
cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
}
fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
self.as_table_expr(cx)
}
fn truth(&self, _cx: &mut Cx) -> Result<bool> {
Ok(!self.read_entries()?.is_empty())
}
fn as_table_impl(&self) -> Option<&dyn Table> {
Some(self)
}
}
impl Table for AssocTable {
fn backend_symbol(&self) -> Symbol {
Symbol::qualified("core", "Table")
}
fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
let guard = self.read_entries()?;
match guard.iter().find(|(candidate, _)| *candidate == key) {
Some((_, value)) => Ok(value.clone()),
None => cx.factory().nil(),
}
}
fn set(&self, _cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
let mut guard = self.write_entries()?;
if let Some((_, slot)) = guard.iter_mut().find(|(candidate, _)| *candidate == key) {
*slot = value;
} else {
guard.push((key, value));
}
Ok(())
}
fn has(&self, _cx: &mut Cx, key: Symbol) -> Result<bool> {
Ok(self
.read_entries()?
.iter()
.any(|(candidate, _)| *candidate == key))
}
fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
let mut guard = self.write_entries()?;
if let Some(index) = guard.iter().position(|(candidate, _)| *candidate == key) {
Ok(guard.remove(index).1)
} else {
cx.factory().nil()
}
}
fn keys(&self, _cx: &mut Cx) -> Result<Vec<Symbol>> {
Ok(self
.read_entries()?
.iter()
.map(|(key, _)| key.clone())
.collect())
}
fn entries(&self, _cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
Ok(self.read_entries()?.clone())
}
fn len(&self, _cx: &mut Cx) -> Result<usize> {
Ok(self.read_entries()?.len())
}
fn clear(&self, _cx: &mut Cx) -> Result<()> {
self.write_entries()?.clear();
Ok(())
}
}
fn poisoned_table_error() -> Error {
Error::Eval("assoc table lock poisoned".to_owned())
}
pub trait TableBackend: Send + Sync {
fn name(&self) -> &str;
fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value>;
}
pub struct TableRegistry {
backends: BTreeMap<String, Arc<dyn TableBackend>>,
active: String,
}
impl TableRegistry {
pub fn new() -> Self {
let mut registry = Self {
backends: BTreeMap::new(),
active: "assoc".to_owned(),
};
registry.register(Arc::new(AssocBackend));
registry.register(Arc::new(CatalogBackend));
registry
}
pub fn register(&mut self, backend: Arc<dyn TableBackend>) {
self.backends.insert(backend.name().to_owned(), backend);
}
pub fn set_active(&mut self, name: &str) -> Result<()> {
if self.backends.contains_key(name) {
self.active = name.to_owned();
Ok(())
} else {
Err(Error::Eval(format!("unknown table backend: {name}")))
}
}
pub fn active(&self) -> &str {
&self.active
}
pub fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value> {
self.backend()?.new_table(cx, entries)
}
fn backend(&self) -> Result<&Arc<dyn TableBackend>> {
self.backends
.get(&self.active)
.ok_or_else(|| Error::Eval("active table backend missing".to_owned()))
}
}
impl Default for TableRegistry {
fn default() -> Self {
Self::new()
}
}
struct AssocBackend;
impl TableBackend for AssocBackend {
fn name(&self) -> &str {
"assoc"
}
fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value> {
cx.factory()
.opaque(Arc::new(AssocTable::with_entries(entries)))
}
}
#[cfg(test)]
#[path = "table_tests.rs"]
mod table_tests;