rhai/src/types/interner.rs
2022-08-29 14:27:05 +08:00

165 lines
4.3 KiB
Rust

use crate::func::hashing::get_hasher;
use crate::ImmutableString;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
collections::BTreeMap,
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
ops::AddAssign,
};
/// Maximum number of strings interned.
pub const MAX_INTERNED_STRINGS: usize = 256;
/// Maximum length of strings interned.
pub const MAX_STRING_LEN: usize = 24;
/// _(internals)_ A factory of identifiers from text strings.
/// Exported under the `internals` feature only.
///
/// Normal identifiers, property getters and setters are interned separately.
#[derive(Clone, Hash)]
pub struct StringsInterner<'a> {
/// Maximum number of strings interned.
pub capacity: usize,
/// Maximum string length.
pub max_string_len: usize,
/// Normal strings.
strings: BTreeMap<u64, ImmutableString>,
/// Take care of the lifetime parameter.
dummy: PhantomData<&'a ()>,
}
impl Default for StringsInterner<'_> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for StringsInterner<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.strings.values()).finish()
}
}
impl StringsInterner<'_> {
/// Create a new [`StringsInterner`].
#[inline(always)]
#[must_use]
pub fn new() -> Self {
Self {
capacity: MAX_INTERNED_STRINGS,
max_string_len: MAX_STRING_LEN,
strings: BTreeMap::new(),
dummy: PhantomData,
}
}
/// Get an identifier from a text string, adding it to the interner if necessary.
#[inline(always)]
#[must_use]
pub fn get<S: AsRef<str> + Into<ImmutableString>>(&mut self, text: S) -> ImmutableString {
self.get_with_mapper(Into::into, text)
}
/// Get an identifier from a text string, adding it to the interner if necessary.
#[inline]
#[must_use]
pub fn get_with_mapper<S: AsRef<str>>(
&mut self,
mapper: fn(S) -> ImmutableString,
text: S,
) -> ImmutableString {
let key = text.as_ref();
if key.len() > MAX_STRING_LEN {
return mapper(text);
}
let hasher = &mut get_hasher();
key.hash(hasher);
let key = hasher.finish();
if !self.strings.is_empty() && self.strings.contains_key(&key) {
return self.strings.get(&key).unwrap().clone();
}
let value = mapper(text);
if value.strong_count() > 1 {
return value;
}
self.strings.insert(key, value.clone());
// If the interner is over capacity, remove the longest entry
if self.strings.len() > self.capacity {
// Leave some buffer to grow when shrinking the cache.
// We leave at least two entries, one for the empty string, and one for the string
// that has just been inserted.
let max = if self.capacity < 5 {
2
} else {
self.capacity - 3
};
while self.strings.len() > max {
let (_, n) = self.strings.iter().fold((0, 0), |(x, n), (&k, v)| {
if k != key && v.len() > x {
(v.len(), k)
} else {
(x, n)
}
});
self.strings.remove(&n);
}
}
value
}
/// Number of strings interned.
#[inline(always)]
#[must_use]
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.strings.len()
}
/// Returns `true` if there are no interned strings.
#[inline(always)]
#[must_use]
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.strings.is_empty()
}
/// Clear all interned strings.
#[inline(always)]
#[allow(dead_code)]
pub fn clear(&mut self) {
self.strings.clear();
}
}
impl AddAssign<Self> for StringsInterner<'_> {
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {
self.strings.extend(rhs.strings.into_iter());
}
}
impl AddAssign<&Self> for StringsInterner<'_> {
#[inline(always)]
fn add_assign(&mut self, rhs: &Self) {
self.strings
.extend(rhs.strings.iter().map(|(&k, v)| (k, v.clone())));
}
}