Skip to main content

telltale_vm/
intern.rs

1//! String interning for hot runtime paths.
2
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6/// Stable identifier for interned runtime strings.
7pub type StringId = u32;
8
9/// Runtime symbol table used to intern repeated role/label strings.
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11pub struct SymbolTable {
12    symbols: Vec<String>,
13    index: BTreeMap<String, StringId>,
14}
15
16impl SymbolTable {
17    /// Create an empty symbol table.
18    #[must_use]
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    /// Intern a string and return its stable id.
24    ///
25    /// # Panics
26    ///
27    /// Panics if the symbol table exceeds `u32::MAX` entries.
28    pub fn intern(&mut self, value: &str) -> StringId {
29        if let Some(id) = self.index.get(value) {
30            return *id;
31        }
32        let id = u32::try_from(self.symbols.len()).expect("symbol table overflow");
33        let owned = value.to_string();
34        self.symbols.push(owned.clone());
35        self.index.insert(owned, id);
36        id
37    }
38
39    /// Resolve an id to a string, if present.
40    #[must_use]
41    #[allow(clippy::as_conversions)]
42    pub fn resolve(&self, id: StringId) -> Option<&str> {
43        // u32 -> usize is always safe on 32-bit or larger platforms
44        self.symbols.get(id as usize).map(String::as_str)
45    }
46
47    /// Number of interned symbols.
48    #[must_use]
49    pub fn len(&self) -> usize {
50        self.symbols.len()
51    }
52
53    /// Whether the table is empty.
54    #[must_use]
55    pub fn is_empty(&self) -> bool {
56        self.symbols.is_empty()
57    }
58}