use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::hash::{BuildHasher, Hasher};
use hashbrown::HashTable;
use rustc_hash::FxBuildHasher;
use crate::flat_reader::FlatReader;
use crate::reader::Reader;
use crate::sym::Sym;
pub struct LocalLexicon<S = FxBuildHasher> {
dedup: HashTable<Sym>,
offsets: Vec<u32>,
buffer: String,
hasher: S,
}
struct StorageRollback<'a> {
offsets: &'a mut Vec<u32>,
buffer: &'a mut String,
index: usize,
}
impl Drop for StorageRollback<'_> {
fn drop(&mut self) {
self.offsets.truncate(self.index + 1);
self.buffer.truncate(self.offsets[self.index] as usize);
}
}
impl Default for LocalLexicon {
fn default() -> Self {
Self::new()
}
}
impl<S> core::fmt::Debug for LocalLexicon<S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("LocalLexicon")
.field("len", &(self.offsets.len() - 1))
.finish_non_exhaustive()
}
}
impl LocalLexicon {
#[must_use]
pub fn new() -> Self {
Self::with_hasher(FxBuildHasher)
}
#[must_use]
#[cfg_attr(test, mutants::skip)] pub fn with_capacity(strings: usize, bytes: usize) -> Self {
Self::with_capacity_and_hasher(strings, bytes, FxBuildHasher)
}
}
impl<S: BuildHasher> LocalLexicon<S> {
pub fn with_hasher(hasher: S) -> Self {
Self::with_capacity_and_hasher(0, 0, hasher)
}
pub fn with_capacity_and_hasher(strings: usize, bytes: usize, hasher: S) -> Self {
let mut offsets = Vec::with_capacity(strings.saturating_add(1));
offsets.push(0);
Self {
dedup: HashTable::with_capacity(strings),
offsets,
buffer: String::with_capacity(bytes),
hasher,
}
}
#[inline]
#[cfg_attr(test, mutants::skip)] fn hash_bytes(&self, bytes: &[u8]) -> u64 {
let mut hasher = self.hasher.build_hasher();
hasher.write(bytes);
hasher.finish()
}
#[inline]
fn hash_str(&self, s: &str) -> u64 {
self.hash_bytes(s.as_bytes())
}
#[inline]
fn str_at<'a>(offsets: &[u32], buffer: &'a str, index: usize) -> &'a str {
crate::storage::str_at(offsets, buffer.as_bytes(), index)
}
#[inline]
pub fn intern(&mut self, s: impl AsRef<str>) -> Sym {
let s = s.as_ref();
let h = self.hash_str(s);
let offsets = &self.offsets;
let buffer = &self.buffer;
if let Some(&sym) = self.dedup.find(h, |&sym| Self::str_at(offsets, buffer, sym.dense()) == s) {
return sym;
}
self.insert_new(h, s)
}
#[inline]
fn insert_new(&mut self, h: u64, s: &str) -> Sym {
let index = self.offsets.len() - 1;
let buffer_len = self.buffer.len();
let end = buffer_len
.checked_add(s.len())
.and_then(|n| u32::try_from(n).ok())
.expect("internity: buffer exceeds u32");
let storage = StorageRollback {
offsets: &mut self.offsets,
buffer: &mut self.buffer,
index,
};
storage.buffer.push_str(s);
storage.offsets.push(end);
let sym = Sym::pack_dense(index);
let offsets = &*storage.offsets;
let buffer = &*storage.buffer;
let hasher = &self.hasher;
self.dedup.insert_unique(h, sym, |&sym| {
let s = Self::str_at(offsets, buffer, sym.dense());
let mut hh = hasher.build_hasher();
hh.write(s.as_bytes());
hh.finish()
});
core::mem::forget(storage);
sym
}
#[inline]
pub fn intern_bytes(&mut self, bytes: &[u8]) -> Result<Sym, core::str::Utf8Error> {
let h = self.hash_bytes(bytes);
let offsets = &self.offsets;
let buffer = &self.buffer;
if let Some(&sym) = self
.dedup
.find(h, |&sym| Self::str_at(offsets, buffer, sym.dense()).as_bytes() == bytes)
{
return Ok(sym);
}
let s = core::str::from_utf8(bytes)?;
Ok(self.insert_new(h, s))
}
#[inline]
pub fn get(&self, s: impl AsRef<str>) -> Option<Sym> {
let s = s.as_ref();
let h = self.hash_str(s);
let offsets = &self.offsets;
let buffer = &self.buffer;
self.dedup.find(h, |&sym| Self::str_at(offsets, buffer, sym.dense()) == s).copied()
}
#[inline]
#[must_use]
pub fn resolve(&self, sym: Sym) -> &str {
self.try_resolve(sym).expect("internity: Sym is out of range for this interner")
}
#[inline]
#[must_use]
pub fn try_resolve(&self, sym: Sym) -> Option<&str> {
let index = sym.dense();
crate::storage::resolve(&self.offsets, self.buffer.as_bytes(), index)
}
#[must_use]
pub fn len(&self) -> usize {
self.offsets.len() - 1
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.offsets.len() == 1
}
pub fn iter(&self) -> impl Iterator<Item = (Sym, &str)> + '_ {
let offsets = &self.offsets;
let bytes = self.buffer.as_bytes();
(0..offsets.len() - 1).map(move |i| (Sym::pack_dense(i), crate::storage::str_at(offsets, bytes, i)))
}
#[must_use]
pub fn freeze(self) -> impl Reader {
self.into_reader()
}
fn into_reader(self) -> FlatReader {
FlatReader::new(self.offsets.into_boxed_slice(), self.buffer.into_bytes().into_boxed_slice())
}
pub(crate) fn into_boxed_reader(self) -> Box<dyn Reader> {
Box::new(self.into_reader())
}
}
impl<S> crate::reader::Sealed for LocalLexicon<S> {}
impl<S: BuildHasher + Send + Sync> Reader for LocalLexicon<S> {
fn try_resolve(&self, sym: Sym) -> Option<&str> {
Self::try_resolve(self, sym)
}
fn len(&self) -> usize {
Self::len(self)
}
fn iter(&self) -> Box<dyn Iterator<Item = (Sym, &str)> + '_> {
Box::new(Self::iter(self))
}
}
impl<S: BuildHasher, T: AsRef<str>> Extend<T> for LocalLexicon<S> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for s in iter {
self.intern(s.as_ref());
}
}
}
impl<T: AsRef<str>> FromIterator<T> for LocalLexicon {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let iter = iter.into_iter();
let (strings, _) = iter.size_hint();
let mut lexicon = Self::with_capacity(strings, 0);
lexicon.extend(iter);
lexicon
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::LocalLexicon;
#[test]
fn from_iter_preallocates_from_lower_size_hint() {
let lexicon: LocalLexicon = (0..64).map(|_| "same").collect();
assert!(lexicon.dedup.capacity() >= 64);
assert!(lexicon.offsets.capacity() >= 65);
}
}