use super::canonical;
use hashbrown::HashTable;
use hashbrown::hash_table::Entry;
use std::convert::TryFrom;
use std::fmt;
use std::hash::{BuildHasher, RandomState};
use std::sync::Arc;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct SymbolId(u32);
impl SymbolId {
const MAX_VALID_RAW: u32 = u32::MAX - 1;
#[must_use]
#[inline]
pub fn is_canonical(self) -> bool {
canonical::text_of(self.0).is_some()
}
#[must_use]
#[inline]
pub const fn get(self) -> u32 {
self.0
}
#[must_use]
#[inline]
pub const fn from_raw(raw: u32) -> Self {
Self(raw)
}
}
impl fmt::Debug for SymbolId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match canonical::text_of(self.0) {
Some(text) => write!(f, "SymbolId({text:?})"),
None => write!(f, "SymbolId(local {})", self.0),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, thiserror::Error)]
#[error("identifier dictionary is full")]
pub struct DictionaryFull;
#[derive(Clone, Copy)]
struct Extent {
start: u32,
end: u32,
}
#[derive(Clone, Copy)]
#[repr(transparent)]
struct LocalOrdinal(u32);
#[derive(Clone)]
struct Storage {
arena: String,
extents: Vec<Extent>,
table: HashTable<LocalOrdinal>,
}
#[derive(Clone)]
pub struct Interner {
storage: Arc<Storage>,
hasher: RandomState,
limit: u32,
}
impl Interner {
pub(crate) fn clear(&mut self) {
let storage = Arc::make_mut(&mut self.storage);
storage.arena.clear();
storage.extents.clear();
storage.table.clear();
}
#[must_use]
pub fn retained_bytes(&self) -> usize {
let storage = &self.storage;
let extents = storage
.extents
.capacity()
.saturating_mul(std::mem::size_of::<Extent>());
let table = storage
.table
.capacity()
.saturating_mul(std::mem::size_of::<LocalOrdinal>());
storage
.arena
.capacity()
.saturating_add(extents)
.saturating_add(table)
}
pub const DEFAULT_LIMIT: u32 = 1_000_000;
#[must_use]
pub fn new() -> Self {
Self {
storage: Arc::new(Storage {
arena: String::new(),
extents: Vec::new(),
table: HashTable::new(),
}),
hasher: RandomState::new(),
limit: Self::DEFAULT_LIMIT,
}
}
#[must_use]
pub fn with_capacity(identifiers: usize) -> Self {
let mut interner = Self::new();
let storage = Arc::make_mut(&mut interner.storage);
storage.extents.reserve(identifiers);
storage.table.reserve(identifiers, |_| 0u64);
interner
}
#[must_use]
pub const fn with_limit(mut self, limit: u32) -> Self {
self.limit = limit;
self
}
pub fn intern(&mut self, text: &str) -> Result<SymbolId, DictionaryFull> {
if let Some(ordinal) = canonical::ordinal_of(text) {
return Ok(SymbolId(ordinal));
}
if Arc::strong_count(&self.storage) > 1
&& let Some(id) = self.get(text)
{
return Ok(id);
}
let Self {
storage,
hasher,
limit,
} = self;
let Storage {
arena,
extents,
table,
} = Arc::make_mut(storage);
let hash = hasher.hash_one(text);
let pending = pending_local(extents.len(), *limit, arena.len(), text.len());
let (local, id, extent) = match pending {
Ok(pending) => pending,
Err(error) => {
return match find_local(table, hash, arena, extents, text) {
Some(local) => local_to_id(local).ok_or(error),
None => Err(error),
};
}
};
match table.entry(
hash,
|&other| extent_text(arena, extents, other) == Some(text),
|&other| {
match extent_text(arena, extents, other) {
Some(existing) => hasher.hash_one(existing),
None => hasher.hash_one(""),
}
},
) {
Entry::Occupied(occupied) => local_to_id(*occupied.get()).ok_or(DictionaryFull),
Entry::Vacant(vacant) => {
arena.push_str(text);
extents.push(extent);
let _ = vacant.insert(local);
Ok(id)
}
}
}
#[must_use]
pub fn get(&self, text: &str) -> Option<SymbolId> {
if let Some(ordinal) = canonical::ordinal_of(text) {
return Some(SymbolId(ordinal));
}
let hash = self.hasher.hash_one(text);
find_local(
&self.storage.table,
hash,
&self.storage.arena,
&self.storage.extents,
text,
)
.and_then(local_to_id)
}
#[must_use]
pub fn resolve(&self, id: SymbolId) -> Option<&str> {
if let Some(text) = canonical::text_of(id.0) {
return Some(text);
}
let local = id_to_local(id)?;
extent_text(&self.storage.arena, &self.storage.extents, local)
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.storage.extents.len()
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.storage.extents.is_empty()
}
#[must_use]
#[inline]
pub fn arena_len(&self) -> usize {
self.storage.arena.len()
}
pub fn iter(&self) -> impl Iterator<Item = (SymbolId, &str)> {
let canonical = canonical::CANONICAL
.iter()
.enumerate()
.filter_map(|(position, text)| {
u32::try_from(position)
.ok()
.map(|position| (SymbolId(position), *text))
});
let local = self
.storage
.extents
.iter()
.enumerate()
.filter_map(|(position, _extent)| {
let local = u32::try_from(position).ok().map(LocalOrdinal)?;
let symbol = local_to_id(local)?;
extent_text(&self.storage.arena, &self.storage.extents, local)
.map(|text| (symbol, text))
});
canonical.chain(local)
}
}
impl Default for Interner {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Interner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Interner")
.field("local", &self.storage.extents.len())
.field("arena_bytes", &self.storage.arena.len())
.finish_non_exhaustive()
}
}
#[inline]
fn pending_local(
local_count: usize,
limit: u32,
arena_len: usize,
text_len: usize,
) -> Result<(LocalOrdinal, SymbolId, Extent), DictionaryFull> {
let local = match u32::try_from(local_count) {
Ok(local) if local < limit => LocalOrdinal(local),
Ok(_) | Err(_) => return Err(DictionaryFull),
};
let id = local_to_id(local).ok_or(DictionaryFull)?;
let extent = extent_for_append(arena_len, text_len).ok_or(DictionaryFull)?;
Ok((local, id, extent))
}
#[inline]
fn extent_for_append(arena_len: usize, text_len: usize) -> Option<Extent> {
let end = arena_len.checked_add(text_len)?;
Some(Extent {
start: u32::try_from(arena_len).ok()?,
end: u32::try_from(end).ok()?,
})
}
#[inline]
fn find_local(
table: &HashTable<LocalOrdinal>,
hash: u64,
arena: &str,
extents: &[Extent],
text: &str,
) -> Option<LocalOrdinal> {
table
.find(hash, |&local| {
extent_text(arena, extents, local) == Some(text)
})
.copied()
}
#[inline]
fn local_to_id(local: LocalOrdinal) -> Option<SymbolId> {
let canonical_count = u32::try_from(canonical::CANONICAL.len()).ok()?;
let raw = canonical_count.checked_add(local.0)?;
if raw > SymbolId::MAX_VALID_RAW {
None
} else {
Some(SymbolId(raw))
}
}
#[inline]
fn id_to_local(id: SymbolId) -> Option<LocalOrdinal> {
if id.0 > SymbolId::MAX_VALID_RAW {
return None;
}
let canonical_count = u32::try_from(canonical::CANONICAL.len()).ok()?;
id.0.checked_sub(canonical_count).map(LocalOrdinal)
}
#[inline]
fn extent_text<'a>(arena: &'a str, extents: &[Extent], local: LocalOrdinal) -> Option<&'a str> {
let local_index = usize::try_from(local.0).ok()?;
let extent = extents.get(local_index)?;
let start = usize::try_from(extent.start).ok()?;
let end = usize::try_from(extent.end).ok()?;
arena.get(start..end)
}
#[cfg(test)]
#[path = "interner_tests.rs"]
mod tests;