use std::cell::Cell;
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::fmt::Formatter;
use std::ptr::null;
type NumIndex = u32;
#[derive(Debug, Default)]
pub struct AtomTable(UnsafeCell<Inner>);
#[derive(Default)]
struct Inner {
strings: Vec<String>,
map: HashMap<&'static str, NumIndex>,
strings_u16: Vec<Vec<u16>>,
map_u16: HashMap<&'static [u16], NumIndex>,
strings_bytes: Vec<Vec<u8>>,
map_bytes: HashMap<&'static [u8], NumIndex>,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Atom(NumIndex);
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct AtomU16(NumIndex);
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct AtomBytes(NumIndex);
thread_local! {
static DEBUG_TABLE: Cell<* const AtomTable> = Cell::new(null());
}
impl std::fmt::Debug for Atom {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut t = f.debug_tuple("Atom");
t.field(&self.0);
DEBUG_TABLE.with(|debug_table| {
let p = debug_table.get();
if let Some(r) = unsafe { p.as_ref() } {
if let Some(value) = r.try_str(*self) {
t.field(&value);
}
}
});
t.finish()
}
}
impl std::fmt::Debug for AtomU16 {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut t = f.debug_tuple("Atom");
t.field(&self.0);
DEBUG_TABLE.with(|debug_table| {
let p = debug_table.get();
if let Some(r) = unsafe { p.as_ref() } {
if let Some(value) = r.try_str_u16(*self) {
t.field(&value);
}
}
});
t.finish()
}
}
impl std::fmt::Debug for AtomBytes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut t = f.debug_tuple("AtomBytes");
t.field(&self.0);
DEBUG_TABLE.with(|debug_table| {
let p = debug_table.get();
if let Some(r) = unsafe { p.as_ref() } {
if let Some(value) = r.try_bytes(*self) {
t.field(&value);
}
}
});
t.finish()
}
}
pub const INVALID_ATOM: Atom = Atom(NumIndex::MAX);
pub const INVALID_ATOM_BYTES: AtomBytes = AtomBytes(NumIndex::MAX);
impl Inner {
fn add_atom<V: Into<String> + AsRef<str>>(&mut self, value: V) -> Atom {
if let Some(index) = self.map.get(value.as_ref()) {
return Atom(*index);
}
self.add(value.into())
}
fn add(&mut self, owned: String) -> Atom {
let index = self.strings.len();
assert!(index < INVALID_ATOM.0 as usize, "More than 4GB atoms?");
let key: *const str = owned.as_str();
self.strings.push(owned);
self.map.insert(unsafe { &*key }, index as NumIndex);
Atom(index as NumIndex)
}
#[inline]
fn str(&self, ident: Atom) -> &str {
self.strings[ident.0 as usize].as_str()
}
fn try_str(&self, ident: Atom) -> Option<&str> {
if (ident.0 as usize) < self.strings.len() {
Some(self.str(ident))
} else {
None
}
}
fn add_atom_u16<V: Into<Vec<u16>> + AsRef<[u16]>>(&mut self, value: V) -> AtomU16 {
if let Some(index) = self.map_u16.get(value.as_ref()) {
return AtomU16(*index);
}
self.add_u16(value.into())
}
fn add_u16(&mut self, owned: Vec<u16>) -> AtomU16 {
let index = self.strings_u16.len();
assert!(index < INVALID_ATOM.0 as usize, "More than 4GB atoms?");
let key: *const [u16] = owned.as_slice();
self.strings_u16.push(owned);
self.map_u16.insert(unsafe { &*key }, index as NumIndex);
AtomU16(index as NumIndex)
}
#[inline]
fn str_u16(&self, ident: AtomU16) -> &[u16] {
self.strings_u16[ident.0 as usize].as_slice()
}
fn try_str_u16(&self, ident: AtomU16) -> Option<&[u16]> {
if (ident.0 as usize) < self.strings_u16.len() {
Some(self.str_u16(ident))
} else {
None
}
}
fn add_atom_bytes<V: Into<Vec<u8>> + AsRef<[u8]>>(&mut self, value: V) -> AtomBytes {
if let Some(index) = self.map_bytes.get(value.as_ref()) {
return AtomBytes(*index);
}
self.add_bytes(value.into())
}
fn add_bytes(&mut self, owned: Vec<u8>) -> AtomBytes {
let index = self.strings_bytes.len();
assert!(index < INVALID_ATOM_BYTES.0 as usize, "More than 4GB atoms?");
let key: *const [u8] = owned.as_slice();
self.strings_bytes.push(owned);
self.map_bytes.insert(unsafe { &*key }, index as NumIndex);
AtomBytes(index as NumIndex)
}
#[inline]
fn bytes(&self, ident: AtomBytes) -> &[u8] {
self.strings_bytes[ident.0 as usize].as_slice()
}
fn try_bytes(&self, ident: AtomBytes) -> Option<&[u8]> {
if (ident.0 as usize) < self.strings_bytes.len() {
Some(self.bytes(ident))
} else {
None
}
}
}
impl AtomTable {
pub fn new() -> AtomTable {
Default::default()
}
pub fn atom<V: Into<String> + AsRef<str>>(&self, value: V) -> Atom {
unsafe { &mut *self.0.get() }.add_atom(value)
}
#[inline]
pub fn str(&self, ident: Atom) -> &str {
unsafe { &*self.0.get() }.str(ident)
}
#[inline]
pub fn try_str(&self, ident: Atom) -> Option<&str> {
unsafe { &*self.0.get() }.try_str(ident)
}
pub fn atom_u16<V: Into<Vec<u16>> + AsRef<[u16]>>(&self, value: V) -> AtomU16 {
unsafe { &mut *self.0.get() }.add_atom_u16(value)
}
#[inline]
pub fn str_u16(&self, ident: AtomU16) -> &[u16] {
unsafe { &*self.0.get() }.str_u16(ident)
}
#[inline]
pub fn try_str_u16(&self, ident: AtomU16) -> Option<&[u16]> {
unsafe { &*self.0.get() }.try_str_u16(ident)
}
pub fn atom_bytes<V: Into<Vec<u8>> + AsRef<[u8]>>(&self, value: V) -> AtomBytes {
unsafe { &mut *self.0.get() }.add_atom_bytes(value)
}
#[inline]
pub fn bytes(&self, ident: AtomBytes) -> &[u8] {
unsafe { &*self.0.get() }.bytes(ident)
}
#[inline]
pub fn try_bytes(&self, ident: AtomBytes) -> Option<&[u8]> {
unsafe { &*self.0.get() }.try_bytes(ident)
}
pub fn in_debug_context<R, F: FnOnce() -> R>(&self, f: F) -> R {
DEBUG_TABLE.with(|debug_table| {
let prev_table = debug_table.replace(self);
let res = f();
debug_assert!(
debug_table.get() == self,
"debug context unexpectedly changed"
);
debug_table.set(prev_table);
res
})
}
pub unsafe fn unsafe_set_debug_context(ptr: *const Self) -> *const Self {
DEBUG_TABLE.with(|debug_table| debug_table.replace(ptr))
}
}
impl std::ops::Index<Atom> for AtomTable {
type Output = str;
fn index(&self, index: Atom) -> &Self::Output {
self.str(index)
}
}
impl std::ops::Index<AtomU16> for AtomTable {
type Output = [u16];
fn index(&self, index: AtomU16) -> &Self::Output {
self.str_u16(index)
}
}
impl std::ops::Index<AtomBytes> for AtomTable {
type Output = [u8];
fn index(&self, index: AtomBytes) -> &Self::Output {
self.bytes(index)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tab() {
let idtab = AtomTable::new();
let id_foo = idtab.atom("foo");
let p_foo: *const str = idtab.str(id_foo);
let id_bar = idtab.atom("bar");
assert_ne!(id_foo, id_bar);
assert_eq!(idtab.atom("foo"), id_foo);
assert_eq!(idtab.atom("bar"), id_bar);
assert_eq!(idtab.atom(String::from("foo")), id_foo);
assert_eq!(idtab.atom(String::from("bar")), id_bar);
assert_eq!(idtab.str(id_foo), "foo");
assert_eq!(idtab.str(id_bar), "bar");
assert_eq!(idtab.str(id_foo) as *const str, p_foo);
}
#[test]
fn test_bytes() {
let tab = AtomTable::new();
let foo = tab.atom_bytes(b"foo".as_slice());
let bar = tab.atom_bytes(b"bar".as_slice());
assert_ne!(foo, bar);
assert_eq!(tab.atom_bytes(b"foo".as_slice()), foo);
assert_eq!(tab.atom_bytes(Vec::from(*b"bar")), bar);
assert_eq!(tab.bytes(foo), b"foo");
assert_eq!(&tab[bar], b"bar");
let p_foo: *const [u8] = tab.bytes(foo);
let _ = tab.atom_bytes(b"baz".as_slice());
assert_eq!(tab.bytes(foo) as *const [u8], p_foo);
}
#[test]
fn test_bytes_ill_formed_utf8() {
let tab = AtomTable::new();
let lone_surrogate: &[u8] = &[0xed, 0xa0, 0x80];
let a = tab.atom_bytes(lone_surrogate);
assert_eq!(tab.bytes(a), lone_surrogate);
assert_eq!(tab.atom_bytes(lone_surrogate), a);
let s = tab.atom("foo");
let b = tab.atom_bytes(b"foo".as_slice());
assert_eq!(tab.str(s), "foo");
assert_eq!(tab.bytes(b), b"foo");
}
#[test]
fn test_bytes_try_and_invalid() {
let tab = AtomTable::new();
let a = tab.atom_bytes(b"x".as_slice());
assert_eq!(tab.try_bytes(a), Some(b"x".as_slice()));
assert_eq!(tab.try_bytes(INVALID_ATOM_BYTES), None);
}
}