use std::sync::Arc;
use rustc_hash::FxHashMap;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
pub(crate) struct SymbolId(pub u32);
pub(crate) struct SymRecord {
pub text: Arc<str>,
pub local: SymbolId,
pub has_prefix: bool,
}
#[derive(Default)]
pub(crate) struct SymbolTable {
map: FxHashMap<Box<str>, SymbolId>,
records: Vec<SymRecord>,
pairs: FxHashMap<(u32, u32), SymbolId>,
}
impl SymbolTable {
pub fn intern(&mut self, s: &str) -> SymbolId {
if let Some(&id) = self.map.get(s) {
return id;
}
let local_sym = match s.split_once(':') {
Some((p, l)) if !p.is_empty() && !l.is_empty() => Some(self.intern(l)),
_ => None,
};
let id = SymbolId(self.records.len() as u32);
let (local, has_prefix) = match local_sym {
Some(l) => (l, true),
None => (id, false),
};
let text: Arc<str> = Arc::from(s);
self.records.push(SymRecord {
text,
local,
has_prefix,
});
self.map.insert(Box::from(s), id);
id
}
pub fn intern_pair(&mut self, ns: SymbolId, local: SymbolId) -> SymbolId {
if let Some(&id) = self.pairs.get(&(ns.0, local.0)) {
return id;
}
let id = SymbolId(self.records.len() as u32);
let text = self.records[local.0 as usize].text.clone();
self.records.push(SymRecord {
text,
local,
has_prefix: false,
});
self.pairs.insert((ns.0, local.0), id);
id
}
#[inline]
pub fn arc(&self, id: SymbolId) -> &Arc<str> {
&self.records[id.0 as usize].text
}
#[inline]
#[allow(dead_code)]
pub fn record(&self, id: SymbolId) -> &SymRecord {
&self.records[id.0 as usize]
}
#[inline]
pub fn local(&self, id: SymbolId) -> SymbolId {
self.records[id.0 as usize].local
}
#[inline]
#[allow(dead_code)]
pub fn has_prefix(&self, id: SymbolId) -> bool {
self.records[id.0 as usize].has_prefix
}
#[inline]
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.records.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intern_is_idempotent() {
let mut t = SymbolTable::default();
let a = t.intern("gml:Point");
let b = t.intern("gml:Point");
assert_eq!(a, b);
}
#[test]
fn prefixed_links_to_local() {
let mut t = SymbolTable::default();
let q = t.intern("gml:Point");
let l = t.intern("Point");
assert_eq!(t.local(q), l);
assert!(t.has_prefix(q));
assert!(!t.has_prefix(l));
assert_eq!(t.local(l), l);
}
#[test]
fn arc_is_pooled() {
let mut t = SymbolTable::default();
let a = t.intern("dem:lod");
let b = t.intern("dem:lod");
assert!(Arc::ptr_eq(t.arc(a), t.arc(b)));
assert_eq!(t.arc(a).as_ref(), "dem:lod");
}
#[test]
fn unprefixed_local_is_self() {
let mut t = SymbolTable::default();
let a = t.intern("root");
assert_eq!(t.local(a), a);
assert!(!t.has_prefix(a));
}
}