use std::hash::BuildHasher;
use hashbrown::{DefaultHashBuilder, HashTable};
use crate::{HashMap, ItemKind};
pub(crate) type Sym = u32;
pub(crate) type PathId = u32;
#[derive(Default)]
pub(crate) struct Interner {
syms: HashMap<Box<str>, Sym>,
names: Vec<Box<str>>,
arena: Vec<Sym>,
ends: Vec<u32>,
paths: HashTable<PathId>,
state: DefaultHashBuilder,
kinds: Vec<ItemKind>,
parents: Vec<PathId>,
orphans: HashTable<(Box<[Sym]>, Vec<PathId>)>,
children: HashMap<(PathId, Sym), PathId>,
scratch: Vec<Sym>,
}
const NO_PARENT: PathId = PathId::MAX;
impl Interner {
pub(crate) fn sym(&mut self, name: &str) -> Sym {
if let Some(&sym) = self.syms.get(name) {
return sym;
}
let sym = Sym::try_from(self.names.len()).expect("fewer than 2^32 identifiers");
self.names.push(name.into());
self.syms.insert(name.into(), sym);
sym
}
pub(crate) fn find_sym(&self, name: &str) -> Option<Sym> {
self.syms.get(name).copied()
}
pub(crate) fn name(&self, sym: Sym) -> &str {
&self.names[index(sym)]
}
pub(crate) fn path(&mut self, segments: &[Sym], kind: ItemKind) -> PathId {
self.intern(segments, kind, None)
}
fn intern(&mut self, segments: &[Sym], kind: ItemKind, parent: Option<PathId>) -> PathId {
let hash = self.state.hash_one(segments);
if let Some(id) = self.find_hashed(hash, segments) {
return id;
}
let id = PathId::try_from(self.ends.len())
.ok()
.filter(|&id| id != NO_PARENT)
.expect("fewer than 2^32 - 1 paths");
let parent = parent.or_else(|| {
let (_, above) = segments.split_last()?;
if above.is_empty() {
return None;
}
let above_hash = self.state.hash_one(above);
let found = self.find_hashed(above_hash, above);
if found.is_none() {
let Self { orphans, state, .. } = self;
orphans
.entry(
above_hash,
|(waiting_for, _)| **waiting_for == *above,
|(waiting_for, _)| state.hash_one(&**waiting_for),
)
.or_insert_with(|| (above.into(), Vec::new()))
.into_mut()
.1
.push(id);
}
found
});
self.arena.extend_from_slice(segments);
let end = u32::try_from(self.arena.len()).expect("fewer than 2^32 segments");
self.ends.push(end);
let Self {
arena,
ends,
paths,
state,
kinds,
parents,
orphans,
..
} = self;
paths.insert_unique(hash, id, |&known| state.hash_one(span(arena, ends, known)));
kinds.push(kind);
parents.push(parent.unwrap_or(NO_PARENT));
if let Ok(waiting) = orphans.find_entry(hash, |(waiting_for, _)| **waiting_for == *segments)
{
let ((_, children), _) = waiting.remove();
for child in children {
parents[index(child)] = id;
}
}
id
}
pub(crate) fn find_path(&self, segments: &[Sym]) -> Option<PathId> {
self.find_hashed(self.state.hash_one(segments), segments)
}
fn find_hashed(&self, hash: u64, segments: &[Sym]) -> Option<PathId> {
self.paths
.find(hash, |&known| {
span(&self.arena, &self.ends, known) == segments
})
.copied()
}
pub(crate) fn path_count(&self) -> usize {
self.ends.len()
}
pub(crate) fn child(&mut self, parent: PathId, last: Sym, kind: ItemKind) -> PathId {
if let Some(&id) = self.children.get(&(parent, last)) {
return id;
}
let mut path = std::mem::take(&mut self.scratch);
path.clear();
path.extend_from_slice(self.segments(parent));
path.push(last);
let id = self.intern(&path, kind, Some(parent));
self.scratch = path;
self.children.insert((parent, last), id);
id
}
pub(crate) fn parent(&self, id: PathId) -> Option<PathId> {
Some(self.parents[index(id)]).filter(|&parent| parent != NO_PARENT)
}
pub(crate) fn prefixes<'a>(&'a self, segments: &'a [Sym]) -> Prefixes<'a> {
Prefixes {
interner: self,
segments,
len: segments.len(),
next: None,
}
}
pub(crate) fn ancestors(&self, id: PathId) -> Prefixes<'_> {
let segments = self.segments(id);
Prefixes {
interner: self,
segments,
len: segments.len().saturating_sub(1),
next: self.parent(id),
}
}
pub(crate) fn segments(&self, id: PathId) -> &[Sym] {
span(&self.arena, &self.ends, id)
}
pub(crate) fn kind(&self, id: PathId) -> ItemKind {
self.kinds[index(id)]
}
pub(crate) fn display(&self, id: PathId) -> String {
self.join(self.segments(id))
}
pub(crate) fn join(&self, segments: &[Sym]) -> String {
let mut out = String::new();
for (i, &sym) in segments.iter().enumerate() {
if i > 0 {
out.push_str("::");
}
out.push_str(self.name(sym));
}
out
}
}
pub(crate) struct Prefixes<'a> {
interner: &'a Interner,
segments: &'a [Sym],
len: usize,
next: Option<PathId>,
}
impl Iterator for Prefixes<'_> {
type Item = (usize, PathId);
fn next(&mut self) -> Option<(usize, PathId)> {
while self.len > 0 {
let len = self.len;
let found = self
.next
.or_else(|| self.interner.find_path(&self.segments[..len]));
self.len -= 1;
self.next = found.and_then(|id| self.interner.parent(id));
if let Some(id) = found {
return Some((len, id));
}
}
None
}
}
fn index(id: u32) -> usize {
usize::try_from(id).expect("u32 ids fit in usize on supported targets")
}
fn span<'a>(arena: &'a [Sym], ends: &[u32], id: PathId) -> &'a [Sym] {
let id = index(id);
let start = id.checked_sub(1).map_or(0, |before| index(ends[before]));
&arena[start..index(ends[id])]
}
#[cfg(test)]
mod tests {
use super::Interner;
use crate::ItemKind;
#[test]
fn a_path_interned_before_its_parent_is_adopted_when_the_parent_arrives() {
let mut interner = Interner::default();
let [a, b, c, d] = ["a", "b", "c", "d"].map(|name| interner.sym(name));
let abc = interner.path(&[a, b, c], ItemKind::Function);
assert_eq!(interner.parent(abc), None);
let ab = interner.path(&[a, b], ItemKind::Module);
assert_eq!(interner.parent(abc), Some(ab));
assert_eq!(interner.parent(ab), None);
let root = interner.path(&[a], ItemKind::Module);
assert_eq!(interner.parent(ab), Some(root));
assert_eq!(interner.child(ab, c, ItemKind::Function), abc);
let abd = interner.child(ab, d, ItemKind::Function);
assert_eq!(interner.parent(abd), Some(ab));
}
#[test]
fn ancestors_skip_a_prefix_never_interned_and_find_the_shorter_ones() {
let mut interner = Interner::default();
let [a, b, c, d] = ["a", "b", "c", "d"].map(|name| interner.sym(name));
let root = interner.path(&[a], ItemKind::Module);
let abc = interner.path(&[a, b, c], ItemKind::Module);
let abcd = interner.path(&[a, b, c, d], ItemKind::Function);
let found: Vec<_> = interner.ancestors(abcd).collect();
assert_eq!(found, [(3, abc), (1, root)]);
let prefixes: Vec<_> = interner.prefixes(&[a, b, c, d]).collect();
assert_eq!(prefixes, [(4, abcd), (3, abc), (1, root)]);
}
}