oxihuman_core/
symbol_table.rs1#![allow(dead_code)]
4
5use std::collections::HashMap;
8
9#[allow(dead_code)]
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct SymbolId(pub u32);
13
14#[allow(dead_code)]
16pub struct SymbolTable {
17 name_to_id: HashMap<String, SymbolId>,
18 id_to_name: Vec<String>,
19 scope: String,
20}
21
22#[allow(dead_code)]
23impl SymbolTable {
24 pub fn new(scope: &str) -> Self {
25 Self {
26 name_to_id: HashMap::new(),
27 id_to_name: Vec::new(),
28 scope: scope.to_string(),
29 }
30 }
31
32 pub fn intern(&mut self, name: &str) -> SymbolId {
34 if let Some(&id) = self.name_to_id.get(name) {
35 return id;
36 }
37 let id = SymbolId(self.id_to_name.len() as u32);
38 self.name_to_id.insert(name.to_string(), id);
39 self.id_to_name.push(name.to_string());
40 id
41 }
42
43 pub fn lookup(&self, id: SymbolId) -> Option<&str> {
45 self.id_to_name.get(id.0 as usize).map(|s| s.as_str())
46 }
47
48 pub fn find(&self, name: &str) -> Option<SymbolId> {
50 self.name_to_id.get(name).copied()
51 }
52
53 pub fn contains(&self, name: &str) -> bool {
54 self.name_to_id.contains_key(name)
55 }
56
57 pub fn len(&self) -> usize {
58 self.id_to_name.len()
59 }
60
61 pub fn is_empty(&self) -> bool {
62 self.id_to_name.is_empty()
63 }
64
65 pub fn scope(&self) -> &str {
66 &self.scope
67 }
68
69 pub fn all_names(&self) -> &[String] {
70 &self.id_to_name
71 }
72
73 pub fn clear(&mut self) {
74 self.name_to_id.clear();
75 self.id_to_name.clear();
76 }
77
78 pub fn ids_with_prefix(&self, prefix: &str) -> Vec<SymbolId> {
80 self.name_to_id
81 .iter()
82 .filter(|(k, _)| k.starts_with(prefix))
83 .map(|(_, &v)| v)
84 .collect()
85 }
86}
87
88impl Default for SymbolTable {
89 fn default() -> Self {
90 Self::new("global")
91 }
92}
93
94pub fn new_symbol_table(scope: &str) -> SymbolTable {
95 SymbolTable::new(scope)
96}
97
98pub fn sym_intern(table: &mut SymbolTable, name: &str) -> SymbolId {
99 table.intern(name)
100}
101
102pub fn sym_lookup(table: &SymbolTable, id: SymbolId) -> Option<&str> {
103 table.lookup(id)
104}
105
106pub fn sym_find(table: &SymbolTable, name: &str) -> Option<SymbolId> {
107 table.find(name)
108}
109
110pub fn sym_len(table: &SymbolTable) -> usize {
111 table.len()
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn new_is_empty() {
120 let t = new_symbol_table("test");
121 assert!(t.is_empty());
122 }
123
124 #[test]
125 fn intern_returns_same_id() {
126 let mut t = new_symbol_table("test");
127 let id1 = sym_intern(&mut t, "foo");
128 let id2 = sym_intern(&mut t, "foo");
129 assert_eq!(id1, id2);
130 }
131
132 #[test]
133 fn different_names_get_different_ids() {
134 let mut t = new_symbol_table("test");
135 let id1 = sym_intern(&mut t, "a");
136 let id2 = sym_intern(&mut t, "b");
137 assert_ne!(id1, id2);
138 }
139
140 #[test]
141 fn lookup_roundtrip() {
142 let mut t = new_symbol_table("test");
143 let id = sym_intern(&mut t, "hello");
144 assert_eq!(sym_lookup(&t, id), Some("hello"));
145 }
146
147 #[test]
148 fn find_existing() {
149 let mut t = new_symbol_table("test");
150 sym_intern(&mut t, "x");
151 assert!(sym_find(&t, "x").is_some());
152 }
153
154 #[test]
155 fn find_missing_returns_none() {
156 let t = new_symbol_table("test");
157 assert!(sym_find(&t, "ghost").is_none());
158 }
159
160 #[test]
161 fn len_grows() {
162 let mut t = new_symbol_table("test");
163 sym_intern(&mut t, "a");
164 sym_intern(&mut t, "b");
165 sym_intern(&mut t, "a"); assert_eq!(sym_len(&t), 2);
167 }
168
169 #[test]
170 fn clear_empties() {
171 let mut t = new_symbol_table("test");
172 sym_intern(&mut t, "x");
173 t.clear();
174 assert!(t.is_empty());
175 }
176
177 #[test]
178 fn ids_with_prefix() {
179 let mut t = new_symbol_table("test");
180 sym_intern(&mut t, "mesh_body");
181 sym_intern(&mut t, "mesh_hair");
182 sym_intern(&mut t, "texture_skin");
183 let ids = t.ids_with_prefix("mesh_");
184 assert_eq!(ids.len(), 2);
185 }
186
187 #[test]
188 fn scope_is_stored() {
189 let t = new_symbol_table("physics");
190 assert_eq!(t.scope(), "physics");
191 }
192}