6096c03caa
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
90 lines
2.5 KiB
Rust
90 lines
2.5 KiB
Rust
use std::collections::HashMap;
|
|
|
|
/// An interned string identifier. Copy-cheap, O(1) hash and equality.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
pub struct Symbol(u64);
|
|
|
|
/// Bidirectional string ↔ Symbol mapping.
|
|
#[derive(Clone, Default)]
|
|
pub struct SymbolTable {
|
|
to_id: HashMap<String, Symbol>,
|
|
to_str: Vec<String>,
|
|
}
|
|
|
|
/// The interned strings, sorted: which `Symbol` each got is interning order,
|
|
/// not content, so two equal stores print alike (improvise-djcn).
|
|
impl std::fmt::Debug for SymbolTable {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let SymbolTable { to_id, to_str: _ } = self;
|
|
let interned: std::collections::BTreeSet<&String> = to_id.keys().collect();
|
|
f.debug_set().entries(interned).finish()
|
|
}
|
|
}
|
|
|
|
impl SymbolTable {
|
|
#[allow(dead_code)]
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
/// Intern a string, returning its Symbol. Returns existing Symbol if
|
|
/// already interned.
|
|
pub fn intern(&mut self, s: &str) -> Symbol {
|
|
if let Some(&id) = self.to_id.get(s) {
|
|
return id;
|
|
}
|
|
let id = Symbol(self.to_str.len() as u64);
|
|
self.to_str.push(s.to_string());
|
|
self.to_id.insert(s.to_string(), id);
|
|
id
|
|
}
|
|
|
|
/// Look up the Symbol for a string without interning.
|
|
pub fn get(&self, s: &str) -> Option<Symbol> {
|
|
self.to_id.get(s).copied()
|
|
}
|
|
|
|
/// Resolve a Symbol back to its string.
|
|
pub fn resolve(&self, sym: Symbol) -> &str {
|
|
&self.to_str[sym.0 as usize]
|
|
}
|
|
|
|
/// Intern a (category, item) pair.
|
|
pub fn intern_pair(&mut self, cat: &str, item: &str) -> (Symbol, Symbol) {
|
|
(self.intern(cat), self.intern(item))
|
|
}
|
|
|
|
/// Intern a full coordinate list.
|
|
pub fn intern_coords(&mut self, coords: &[(String, String)]) -> Vec<(Symbol, Symbol)> {
|
|
coords.iter().map(|(c, i)| self.intern_pair(c, i)).collect()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn intern_returns_same_id() {
|
|
let mut t = SymbolTable::new();
|
|
let a = t.intern("hello");
|
|
let b = t.intern("hello");
|
|
assert_eq!(a, b);
|
|
}
|
|
|
|
#[test]
|
|
fn different_strings_different_ids() {
|
|
let mut t = SymbolTable::new();
|
|
let a = t.intern("hello");
|
|
let b = t.intern("world");
|
|
assert_ne!(a, b);
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_roundtrips() {
|
|
let mut t = SymbolTable::new();
|
|
let s = t.intern("test");
|
|
assert_eq!(t.resolve(s), "test");
|
|
}
|
|
}
|