Skip to main content

prima_core/
symbol.rs

1use std::sync::{OnceLock, RwLock};
2
3use dashmap::DashMap;
4
5/// Symbol identifier (spec §7): built-in symbols and user symbols share the same registry.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
7pub struct SymbolId(pub u32);
8
9/// Symbol registry: name → `SymbolId`, with the display name taken from the TeX name (e.g. `pi → \pi`, spec §7).
10/// Lazily initialized; built-in symbols are registered on the first access to `SymbolTable::global()`.
11pub struct SymbolTable {
12    names: RwLock<Vec<String>>,
13    map: DashMap<String, SymbolId>,
14}
15
16impl SymbolTable {
17    pub fn new() -> SymbolTable {
18        SymbolTable {
19            names: RwLock::new(Vec::new()),
20            map: DashMap::new(),
21        }
22    }
23
24    pub fn global() -> &'static SymbolTable {
25        static TABLE: OnceLock<SymbolTable> = OnceLock::new();
26        TABLE.get_or_init(|| {
27            let t = SymbolTable::new();
28            crate::builtins::register(&t);
29            t
30        })
31    }
32
33    pub fn intern(&self, name: &str) -> SymbolId {
34        if let Some(id) = self.map.get(name) {
35            return *id;
36        }
37        let mut names = self.names.write().unwrap();
38        if let Some(id) = self.map.get(name) {
39            return *id;
40        }
41        let id = SymbolId(names.len() as u32);
42        names.push(name.to_string());
43        self.map.insert(name.to_string(), id);
44        id
45    }
46
47    /// Register a symbol with an explicit display name (TeX name; spec §7: built-in symbols are independent of TeX, TeX is only a view).
48    pub fn intern_display(&self, name: &str, display: &str) -> SymbolId {
49        if let Some(id) = self.map.get(name) {
50            return *id;
51        }
52        let mut names = self.names.write().unwrap();
53        if let Some(id) = self.map.get(name) {
54            return *id;
55        }
56        let id = SymbolId(names.len() as u32);
57        names.push(display.to_string());
58        self.map.insert(name.to_string(), id);
59        id
60    }
61
62    pub fn name(&self, id: SymbolId) -> Option<String> {
63        self.names.read().unwrap().get(id.0 as usize).cloned()
64    }
65}
66
67impl Default for SymbolTable {
68    fn default() -> Self {
69        Self::new()
70    }
71}