parlex_calc/
symtab.rs

1//! # symtab
2//!
3//! A minimal, flat symbol table built on [`indexmap::IndexMap`].
4//!
5//! This table assigns each unique string name a stable integer index.
6//! You can use it to *intern* symbols (insert if missing and get index)
7//! and later access their values by index.
8//!
9//! ## Example
10//! ```rust
11//! # use parlex_calc::SymTab;
12//! let mut st = SymTab::new();
13//! let i = st.intern("foo"); // inserts "foo" at index 0
14//! assert_eq!(st.get(i).unwrap(), 0);
15//! st.set(i, 42).unwrap();
16//! assert_eq!(st.get(i).unwrap(), 42);
17//! assert_eq!(st.intern("foo"), i); // same index, not reinserted
18//! ```
19
20use indexmap::{IndexMap, map::Entry};
21use thiserror::Error;
22use smartstring::alias::String;
23
24/// Errors that can occur when operating on a [`SymTab`].
25#[derive(Debug, Error)]
26pub enum SymTabError {
27    /// Attempted to access an invalid index (out of bounds).
28    #[error("invalid symbol index {index} (table length {len})")]
29    InvalidIndex {
30        /// The index that was requested.
31        index: usize,
32        /// The number of entries currently in the table.
33        len: usize,
34    },
35}
36
37/// A simple symbol table that maps string names to integer values.
38///
39/// Each inserted name receives a stable index corresponding to its insertion order.
40/// Re-inserting the same name returns the existing index.
41#[derive(Debug)]
42pub struct SymTab {
43    tab: IndexMap<String, i64>,
44}
45
46impl SymTab {
47    /// Creates a new, empty symbol table.
48    pub fn new() -> Self {
49        Self {
50            tab: IndexMap::new(),
51        }
52    }
53
54    /// Returns the number of entries currently stored in the symbol table.
55    ///
56    /// Each entry corresponds to a unique symbol (e.g., variable or identifier)
57    /// that has been interned.
58    ///
59    /// # Example
60    /// ```rust
61    /// # use parlex_calc::SymTab;
62    /// let mut symtab = SymTab::new();
63    /// assert_eq!(symtab.len(), 0);
64    /// symtab.intern("foo");
65    /// assert_eq!(symtab.len(), 1);
66    /// symtab.intern("baz");
67    /// assert_eq!(symtab.len(), 2);
68    /// symtab.intern("foo");
69    /// assert_eq!(symtab.len(), 2);
70    /// ```
71    pub fn len(&self) -> usize {
72        self.tab.len()
73    }
74
75    /// Inserts the given name if it doesn't exist and returns its index.
76    ///
77    /// If the name already exists, this returns the existing index
78    /// without modifying the stored value.
79    ///
80    /// # Examples
81    /// ```
82    /// # use parlex_calc::SymTab;
83    /// let mut st = SymTab::new();
84    /// let i = st.intern("a");
85    /// assert_eq!(i, 0);
86    /// assert_eq!(st.intern("a"), 0); // existing index
87    /// ```
88    pub fn intern(&mut self, name: impl AsRef<str>) -> usize {
89        match self.tab.entry(String::from(name.as_ref())) {
90            Entry::Occupied(o) => o.index(),
91            Entry::Vacant(v) => {
92                let o = v.insert_entry(0);
93                o.index()
94            }
95        }
96    }
97
98    /// Updates the value at the given index.
99    ///
100    /// Returns [`Err`] if the index is out of bounds.
101    ///
102    /// # Examples
103    /// ```
104    /// # use parlex_calc::SymTab;
105    /// let mut st = SymTab::new();
106    /// let i = st.intern("x");
107    /// st.set(i, 123).unwrap();
108    /// assert_eq!(st.get(i).unwrap(), 123);
109    /// ```
110    pub fn set(&mut self, index: usize, new_value: i64) -> Result<(), SymTabError> {
111        let n = self.tab.len();
112        let (_, value) = self
113            .tab
114            .get_index_mut(index)
115            .ok_or(SymTabError::InvalidIndex { index, len: n })?;
116        *value = new_value;
117        Ok(())
118    }
119
120    /// Returns the value stored at the given index.
121    ///
122    /// Returns [`Err`] if the index is out of bounds.
123    pub fn get(&self, index: usize) -> Result<i64, SymTabError> {
124        let (_, value) = self.tab.get_index(index).ok_or(SymTabError::InvalidIndex {
125            index,
126            len: self.tab.len(),
127        })?;
128        Ok(*value)
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn new_table_is_empty() {
138        let st = SymTab::new();
139        assert_eq!(st.len(), 0);
140    }
141
142    #[test]
143    fn intern_assigns_sequential_indices() {
144        let mut st = SymTab::new();
145        let a = st.intern("a");
146        let b = st.intern("b");
147        let c = st.intern("c");
148        assert_eq!((a, b, c), (0, 1, 2));
149        assert_eq!(st.len(), 3);
150    }
151
152    #[test]
153    fn re_intern_returns_same_index_and_preserves_value() {
154        let mut st = SymTab::new();
155        let i = st.intern("x");
156        st.set(i, 42).unwrap();
157
158        let j = st.intern("x"); // should NOT overwrite the stored value
159        assert_eq!(i, j);
160        assert_eq!(st.get(i).unwrap(), 42);
161    }
162
163    #[test]
164    fn set_and_get_roundtrip() {
165        let mut st = SymTab::new();
166        let i = st.intern("n");
167        st.set(i, -7).unwrap();
168        assert_eq!(st.get(i).unwrap(), -7);
169
170        st.set(i, 123).unwrap();
171        assert_eq!(st.get(i).unwrap(), 123);
172    }
173
174    #[test]
175    fn get_invalid_index_errors() {
176        let mut st = SymTab::new();
177        let _ = st.intern("only_one");
178        match st.get(5) {
179            Err(SymTabError::InvalidIndex { index, len }) => {
180                assert_eq!(index, 5);
181                assert_eq!(len, 1);
182            }
183            other => panic!("expected InvalidIndex, got {:?}", other),
184        }
185    }
186
187    #[test]
188    fn set_invalid_index_errors() {
189        let st_len;
190        let mut st = SymTab::new();
191        let _ = st.intern("z");
192        st_len = st.len();
193        let err = st.set(999, 1).unwrap_err();
194        match err {
195            SymTabError::InvalidIndex { index, len } => {
196                assert_eq!(index, 999);
197                assert_eq!(len, st_len);
198            }
199        }
200    }
201
202    #[test]
203    fn intern_same_name_multiple_times_does_not_change_len() {
204        let mut st = SymTab::new();
205        let first = st.intern("dup");
206        let second = st.intern("dup");
207        let third = st.intern("dup");
208        assert_eq!(first, second);
209        assert_eq!(second, third);
210        assert_eq!(st.len(), 1);
211    }
212
213    #[test]
214    fn many_symbols_have_distinct_indices() {
215        let mut st = SymTab::new();
216        let mut seen = std::collections::BTreeSet::new();
217        for n in 0..100 {
218            let name = format!("v{n}");
219            let idx = st.intern(name);
220            assert!(seen.insert(idx), "duplicate index {}", idx);
221        }
222        assert_eq!(st.len(), 100);
223        assert_eq!(seen.len(), 100);
224    }
225}
226