parlex_calc/
symtab.rs

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