use std::hash::Hash;
use std::path::PathBuf;
use std::sync::Arc;
use elsa::sync::FrozenMap;
pub struct SharedTable<K, V> {
map: Arc<FrozenMap<K, Box<V>>>,
}
impl<K, V> SharedTable<K, V>
where
K: Eq + Hash,
{
pub fn new() -> Self {
Self {
map: Arc::new(FrozenMap::new()),
}
}
pub fn insert(&self, key: K, value: V) {
self.map.insert(key, Box::new(value));
}
pub fn get(&self, key: &K) -> Option<&V> {
self.map.get(key)
}
pub fn contains(&self, key: &K) -> bool {
self.map.get(key).is_some()
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.len() == 0
}
pub fn as_vec(&self) -> Vec<(K, &V)>
where
K: Clone,
{
self.map
.keys_cloned()
.into_iter()
.map(|k| {
let v = self.map.get(&k).unwrap();
(k, v)
})
.collect()
}
}
impl<K, V> std::fmt::Debug for SharedTable<K, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SharedTable").finish_non_exhaustive()
}
}
impl<K, V> Default for SharedTable<K, V>
where
K: Eq + Hash,
{
fn default() -> Self {
Self::new()
}
}
impl<K, V> Clone for SharedTable<K, V> {
fn clone(&self) -> Self {
Self {
map: Arc::clone(&self.map),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FileId {
path: PathBuf,
}
impl FileId {
pub fn new(path: PathBuf) -> Self {
Self { path }
}
pub fn path(&self) -> &PathBuf {
&self.path
}
}
#[derive(Debug, Clone)]
pub struct SourceFile {
pub path: PathBuf,
pub source: String,
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
#[test]
fn shared_table_insert_and_get() {
let t = SharedTable::new();
t.insert("a", 1);
assert_eq!(t.get(&"a"), Some(&1));
}
#[test]
fn shared_table_get_missing_returns_none() {
let t: SharedTable<&str, i32> = SharedTable::new();
assert_eq!(t.get(&"x"), None);
}
#[test]
fn shared_table_first_insert_wins() {
let t = SharedTable::new();
t.insert("a", 1);
t.insert("a", 2);
assert_eq!(t.get(&"a"), Some(&1));
}
#[test]
fn shared_table_contains_true() {
let t = SharedTable::new();
t.insert("a", 1);
assert!(t.contains(&"a"));
}
#[test]
fn shared_table_contains_false() {
let t: SharedTable<&str, i32> = SharedTable::new();
assert!(!t.contains(&"a"));
}
#[test]
fn shared_table_clone_shares_state() {
let t = SharedTable::new();
let t2 = t.clone();
t.insert("a", 1);
assert_eq!(t2.get(&"a"), Some(&1));
}
#[test]
fn shared_table_original_sees_clone_inserts() {
let t = SharedTable::new();
let t2 = t.clone();
t2.insert("b", 42);
assert_eq!(t.get(&"b"), Some(&42));
}
#[test]
fn shared_table_empty_new() {
let t: SharedTable<String, String> = SharedTable::new();
assert_eq!(t.get(&"anything".to_string()), None);
}
#[test]
fn shared_table_multiple_keys() {
let t = SharedTable::new();
for i in 0..100 {
t.insert(i, i * 10);
}
for i in 0..100 {
assert_eq!(t.get(&i), Some(&(i * 10)));
}
}
#[test]
fn shared_table_get_returns_ref() {
let t = SharedTable::new();
t.insert("a", vec![1, 2, 3]);
let v = t.get(&"a").unwrap();
assert_eq!(v, &vec![1, 2, 3]);
let v2 = t.get(&"a").unwrap();
assert_eq!(v, v2);
}
#[test]
fn shared_table_len() {
let t = SharedTable::new();
t.insert("a", 1);
t.insert("b", 2);
assert_eq!(t.len(), 2);
}
#[test]
fn shared_table_len_empty() {
let t: SharedTable<&str, i32> = SharedTable::new();
assert_eq!(t.len(), 0);
assert!(t.is_empty());
}
#[test]
fn file_id_equality() {
let a = FileId::new(PathBuf::from("/a/b.relux"));
let b = FileId::new(PathBuf::from("/a/b.relux"));
assert_eq!(a, b);
}
#[test]
fn file_id_inequality() {
let a = FileId::new(PathBuf::from("/a/b.relux"));
let b = FileId::new(PathBuf::from("/a/c.relux"));
assert_ne!(a, b);
}
#[test]
fn file_id_hash_consistency() {
let a = FileId::new(PathBuf::from("/a/b.relux"));
let b = FileId::new(PathBuf::from("/a/b.relux"));
let mut ha = DefaultHasher::new();
a.hash(&mut ha);
let mut hb = DefaultHasher::new();
b.hash(&mut hb);
assert_eq!(ha.finish(), hb.finish());
}
#[test]
fn file_id_absolute_vs_relative() {
let abs = FileId::new(PathBuf::from("/a/b"));
let rel = FileId::new(PathBuf::from("a/b"));
assert_ne!(abs, rel);
}
}