use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, RwLock};
use std::hash::Hash;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Symbol(u32);
impl Symbol {
pub fn id(&self) -> u32 {
self.0
}
}
pub struct StringInterner {
string_to_symbol: HashMap<Arc<str, std::collections::HashMap<Arc<str, Arc<str>>, Symbol>>>,
symbol_to_string: Vec<Arc<str>>,
stats: InternerStats,
}
#[derive(Debug, Clone, Default)]
pub struct InternerStats {
pub total_intern_calls: u64,
pub cache_hits: u64,
pub new_strings: u64,
pub memory_saved_bytes: u64,
pub pool_size: usize,
}
impl StringInterner {
pub fn new() -> Self {
Self {
string_to_symbol: HashMap::with_capacity(1024),
symbol_to_string: Vec::with_capacity(1024),
stats: InternerStats::default(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
string_to_symbol: HashMap::with_capacity(capacity),
symbol_to_string: Vec::with_capacity(capacity),
stats: InternerStats::default(),
}
}
pub fn intern(&mut self, s: &str) -> Symbol {
self.stats.total_intern_calls += 1;
if let Some(&symbol) = self.string_to_symbol.get(s) {
self.stats.cache_hits += 1;
self.stats.memory_saved_bytes += s.len() as u64;
return symbol;
}
let symbol: _ = Symbol(self.symbol_to_string.len() as u32);
let arc_str: Arc<str> = Arc::from(s);
self.string_to_symbol.insert(arc_str.clone(), symbol);
self.symbol_to_string.push(arc_str);
self.stats.new_strings += 1;
self.stats.pool_size = self.symbol_to_string.len();
symbol
}
pub fn resolve(&self, symbol: Symbol) -> Option<&str> {
self.symbol_to_string.get(symbol.0 as usize).map(|s| &**s)
}
pub fn stats(&self) -> &InternerStats {
&self.stats
}
pub fn hit_rate(&self) -> f64 {
if self.stats.total_intern_calls == 0 {
0.0
} else {
self.stats.cache_hits as f64 / self.stats.total_intern_calls as f64
}
}
pub fn len(&self) -> usize {
self.symbol_to_string.len()
}
pub fn is_empty(&self) -> bool {
self.symbol_to_string.is_empty()
}
pub fn prefill_common_strings(&mut self) {
let common_strings: _ = [
"undefined", "null", "true", "false",
"length", "prototype", "constructor", "__proto__",
"toString", "valueOf", "hasOwnProperty",
"push", "pop", "shift", "unshift", "slice", "splice",
"map", "filter", "reduce", "forEach", "find", "indexOf",
"includes", "concat", "join", "reverse", "sort",
"substring", "substr", "split", "trim", "toLowerCase", "toUpperCase",
"charAt", "charCodeAt", "replace", "match", "search",
"keys", "values", "entries", "assign", "freeze",
"console", "log", "error", "warn", "info", "debug",
"process", "env", "argv", "cwd", "exit",
"module", "exports", "require", "import", "from",
"on", "once", "emit", "off", "removeListener",
"then", "catch", "finally", "resolve", "reject",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
];
for s in common_strings {
self.intern(s);
}
}
}
impl Default for StringInterner {
fn default() -> Self {
Self::new()
}
}
pub struct GlobalInterner {
inner: RwLock<StringInterner>,
}
impl GlobalInterner {
pub fn new() -> Self {
let mut interner = StringInterner::with_capacity(2048);
interner.prefill_common_strings();
Self {
inner: RwLock::new(interner),
}
}
pub fn intern(&self, s: &str) -> Symbol {
self.inner.write().unwrap().intern(s)
}
pub fn resolve(&self, symbol: Symbol) -> Option<String> {
self.inner.read().unwrap().resolve(symbol).map(|s| s.to_string())
}
pub fn stats(&self) -> InternerStats {
self.inner.read().unwrap().stats().clone()
}
pub fn hit_rate(&self) -> f64 {
self.inner.read().unwrap().hit_rate()
}
pub fn len(&self) -> usize {
self.inner.read().unwrap().len()
}
pub fn is_empty(&self) -> bool {
self.inner.read().unwrap().is_empty()
}
}
impl Default for GlobalInterner {
fn default() -> Self {
Self::new()
}
}
pub static GLOBAL_INTERNER: Lazy<GlobalInterner> = Lazy::new(|| GlobalInterner::new());
pub fn intern(s: &str) -> Symbol {
GLOBAL_INTERNER.intern(s)
}
pub fn resolve(symbol: Symbol) -> Option<String> {
GLOBAL_INTERNER.resolve(symbol)
}
pub fn interner_stats() -> InternerStats {
GLOBAL_INTERNER.stats()
}
#[cfg(test)]
mod tests {
#[test]
fn test_string_interner_creation() {
let interner: _ = StringInterner::new();
assert_eq!(interner.len(), 0);
assert!(interner.is_empty());
}
#[test]
fn test_intern_and_resolve() {
let mut interner = StringInterner::new();
let symbol: _ = interner.intern("hello");
assert_eq!(interner.resolve(symbol), Some("hello"));
let symbol2: _ = interner.intern("world");
assert_eq!(interner.resolve(symbol2), Some("world"));
assert_ne!(symbol, symbol2);
assert_eq!(interner.len(), 2);
}
#[test]
fn test_intern_deduplication() {
let mut interner = StringInterner::new();
let s1: _ = interner.intern("hello");
let s2: _ = interner.intern("hello");
let s3: _ = interner.intern("hello");
assert_eq!(s1, s2);
assert_eq!(s2, s3);
assert_eq!(interner.len(), 1);
let stats: _ = interner.stats();
assert_eq!(stats.total_intern_calls, 3);
assert_eq!(stats.cache_hits, 2);
assert_eq!(stats.new_strings, 1);
}
#[test]
fn test_hit_rate() {
let mut interner = StringInterner::new();
for _ in 0..10 {
interner.intern("test");
}
assert!((interner.hit_rate() - 0.9).abs() < 0.01);
}
#[test]
fn test_prefill_common_strings() {
let mut interner = StringInterner::new();
interner.prefill_common_strings();
assert!(interner.len() > 50);
let symbol: _ = interner.intern("length");
assert_eq!(interner.resolve(symbol), Some("length"));
}
#[test]
fn test_global_interner() {
let s1: _ = GLOBAL_INTERNER.intern("global_test");
let s2: _ = GLOBAL_INTERNER.intern("global_test");
assert_eq!(s1, s2);
assert_eq!(GLOBAL_INTERNER.resolve(s1), Some("global_test".to_string()));
}
#[test]
fn test_memory_saved_estimation() {
let mut interner = StringInterner::new();
let long_string: _ = "this_is_a_very_long_string_that_takes_up_memory";
for _ in 0..100 {
interner.intern(long_string);
}
let stats: _ = interner.stats();
assert!(stats.memory_saved_bytes > 0);
assert_eq!(stats.memory_saved_bytes, (long_string.len() * 99) as u64);
}
#[test]
fn test_symbol_id() {
let mut interner = StringInterner::new();
let s0: _ = interner.intern("first");
let s1: _ = interner.intern("second");
let s2: _ = interner.intern("third");
assert_eq!(s0.id(), 0);
assert_eq!(s1.id(), 1);
assert_eq!(s2.id(), 2);
}
#[test]
fn test_convenience_functions() {
let sym: _ = intern("convenience_test");
let resolved: _ = resolve(sym);
assert_eq!(resolved, Some("convenience_test".to_string()));
let stats: _ = interner_stats();
assert!(stats.total_intern_calls > 0);
}
}