use indexmap::{IndexMap, map::Entry};
use smartstring::alias::String;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum SymTabError {
#[error("invalid symbol index {index} (table length {len})")]
InvalidIndex {
index: usize,
len: usize,
},
}
#[derive(Debug)]
pub struct SymTab {
tab: IndexMap<String, i64>,
}
impl SymTab {
pub fn new() -> Self {
Self {
tab: IndexMap::new(),
}
}
pub fn len(&self) -> usize {
self.tab.len()
}
pub fn intern(&mut self, name: impl AsRef<str>) -> usize {
match self.tab.entry(String::from(name.as_ref())) {
Entry::Occupied(o) => o.index(),
Entry::Vacant(v) => {
let o = v.insert_entry(0);
o.index()
}
}
}
pub fn set(&mut self, index: usize, new_value: i64) -> Result<(), SymTabError> {
let n = self.tab.len();
let (_, value) = self
.tab
.get_index_mut(index)
.ok_or(SymTabError::InvalidIndex { index, len: n })?;
*value = new_value;
Ok(())
}
pub fn get(&self, index: usize) -> Result<i64, SymTabError> {
let (_, value) = self.tab.get_index(index).ok_or(SymTabError::InvalidIndex {
index,
len: self.tab.len(),
})?;
Ok(*value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_table_is_empty() {
let st = SymTab::new();
assert_eq!(st.len(), 0);
}
#[test]
fn intern_assigns_sequential_indices() {
let mut st = SymTab::new();
let a = st.intern("a");
let b = st.intern("b");
let c = st.intern("c");
assert_eq!((a, b, c), (0, 1, 2));
assert_eq!(st.len(), 3);
}
#[test]
fn re_intern_returns_same_index_and_preserves_value() {
let mut st = SymTab::new();
let i = st.intern("x");
st.set(i, 42).unwrap();
let j = st.intern("x"); assert_eq!(i, j);
assert_eq!(st.get(i).unwrap(), 42);
}
#[test]
fn set_and_get_roundtrip() {
let mut st = SymTab::new();
let i = st.intern("n");
st.set(i, -7).unwrap();
assert_eq!(st.get(i).unwrap(), -7);
st.set(i, 123).unwrap();
assert_eq!(st.get(i).unwrap(), 123);
}
#[test]
fn get_invalid_index_errors() {
let mut st = SymTab::new();
let _ = st.intern("only_one");
match st.get(5) {
Err(SymTabError::InvalidIndex { index, len }) => {
assert_eq!(index, 5);
assert_eq!(len, 1);
}
other => panic!("expected InvalidIndex, got {:?}", other),
}
}
#[test]
fn set_invalid_index_errors() {
let st_len;
let mut st = SymTab::new();
let _ = st.intern("z");
st_len = st.len();
let err = st.set(999, 1).unwrap_err();
match err {
SymTabError::InvalidIndex { index, len } => {
assert_eq!(index, 999);
assert_eq!(len, st_len);
}
}
}
#[test]
fn intern_same_name_multiple_times_does_not_change_len() {
let mut st = SymTab::new();
let first = st.intern("dup");
let second = st.intern("dup");
let third = st.intern("dup");
assert_eq!(first, second);
assert_eq!(second, third);
assert_eq!(st.len(), 1);
}
#[test]
fn many_symbols_have_distinct_indices() {
let mut st = SymTab::new();
let mut seen = std::collections::BTreeSet::new();
for n in 0..100 {
let name = format!("v{n}");
let idx = st.intern(name);
assert!(seen.insert(idx), "duplicate index {}", idx);
}
assert_eq!(st.len(), 100);
assert_eq!(seen.len(), 100);
}
}