use std::collections::HashMap;
use crate::error::DomError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct AtomId(u32);
impl AtomId {
#[inline]
pub fn to_u32(self) -> u32 {
self.0
}
#[inline]
pub fn from_u32(raw: u32) -> Self {
Self(raw)
}
}
#[derive(Debug, Default, Clone)]
pub struct Interner {
strings: Vec<String>,
index: HashMap<String, AtomId>,
}
impl Interner {
pub fn new() -> Self {
Self::default()
}
pub fn intern(&mut self, name: &str) -> AtomId {
if let Some(id) = self.index.get(name) {
return *id;
}
let id =
AtomId(u32::try_from(self.strings.len()).expect("more than u32::MAX interned names"));
self.strings.push(name.to_owned());
self.index.insert(name.to_owned(), id);
id
}
pub fn get(&self, name: &str) -> Option<AtomId> {
self.index.get(name).copied()
}
pub fn resolve(&self, atom: AtomId) -> Result<&str, DomError> {
self.strings
.get(atom.0 as usize)
.map(String::as_str)
.ok_or(DomError::UnknownAtom(atom))
}
pub fn len(&self) -> usize {
self.strings.len()
}
pub fn is_empty(&self) -> bool {
self.strings.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn interning_the_same_name_twice_gives_one_id() {
let mut names = Interner::new();
let first = names.intern("div");
let second = names.intern("div");
assert_eq!(first, second);
assert_eq!(names.len(), 1);
}
#[test]
fn distinct_names_get_distinct_ids_and_resolve_back() {
let mut names = Interner::new();
let div = names.intern("div");
let span = names.intern("span");
assert_ne!(div, span);
assert_eq!(names.resolve(div).unwrap(), "div");
assert_eq!(names.resolve(span).unwrap(), "span");
}
#[test]
fn get_does_not_intern() {
let mut names = Interner::new();
assert_eq!(names.get("div"), None);
assert!(names.is_empty());
names.intern("div");
assert!(names.get("div").is_some());
}
#[test]
fn an_id_from_another_interner_is_rejected_or_wrong() {
let mut a = Interner::new();
let mut b = Interner::new();
a.intern("div");
let a_span = a.intern("span");
assert_eq!(b.resolve(a_span), Err(DomError::UnknownAtom(a_span)));
b.intern("span");
b.intern("div");
assert_eq!(b.resolve(a_span).unwrap(), "div");
}
}