use std::collections::HashMap;
use std::num::NonZeroU32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct SymbolId(NonZeroU32);
impl SymbolId {
#[must_use]
pub fn as_u32(self) -> u32 {
self.0.get()
}
}
#[derive(Debug)]
pub struct Dict {
map: HashMap<String, SymbolId>,
strings: Vec<String>,
}
impl Dict {
#[must_use]
pub fn new() -> Self {
Self {
map: HashMap::new(),
strings: vec![String::new()],
}
}
#[allow(clippy::cast_possible_truncation, clippy::expect_used)]
pub fn intern(&mut self, s: &str) -> SymbolId {
if let Some(&id) = self.map.get(s) {
return id;
}
let index = self.strings.len() as u32;
let id = SymbolId(NonZeroU32::new(index).expect("symbol index overflow"));
self.strings.push(s.to_owned());
self.map.insert(s.to_owned(), id);
id
}
#[must_use]
pub fn resolve(&self, id: SymbolId) -> &str {
&self.strings[id.0.get() as usize]
}
#[must_use]
pub fn len(&self) -> usize {
self.strings.len() - 1 }
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl Default for Dict {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intern_returns_same_id_for_same_string() {
let mut dict = Dict::new();
let a = dict.intern("hello");
let b = dict.intern("hello");
assert_eq!(a, b);
}
#[test]
fn test_intern_returns_different_ids_for_different_strings() {
let mut dict = Dict::new();
let a = dict.intern("hello");
let b = dict.intern("world");
assert_ne!(a, b);
}
#[test]
fn test_resolve_returns_original_string() {
let mut dict = Dict::new();
let id = dict.intern("test string");
assert_eq!(dict.resolve(id), "test string");
}
#[test]
fn test_len_and_is_empty() {
let mut dict = Dict::new();
assert!(dict.is_empty());
assert_eq!(dict.len(), 0);
dict.intern("a");
assert!(!dict.is_empty());
assert_eq!(dict.len(), 1);
dict.intern("b");
assert_eq!(dict.len(), 2);
dict.intern("a");
assert_eq!(dict.len(), 2);
}
#[test]
fn test_empty_string_interning() {
let mut dict = Dict::new();
let id = dict.intern("");
assert_eq!(dict.resolve(id), "");
}
#[test]
fn test_symbol_id_as_u32() {
let mut dict = Dict::new();
let id = dict.intern("first");
assert_eq!(id.as_u32(), 1); }
}