slang_shared/symbol_table.rs
1use slang_types::types::TypeId;
2use std::collections::HashMap;
3
4/// Represents the specific data for each symbol kind
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum SymbolData {
7 /// A type symbol (primitive types, structs, enums, etc.)
8 Type,
9 /// A variable symbol with mutability information
10 Variable { is_mutable: bool },
11 /// A function symbol (function declarations, built-in functions, etc.)
12 Function,
13}
14
15/// Represents a symbol in the symbol table
16///
17/// A symbol contains all the information needed to identify and work with
18/// a named entity in the language, including its name, specific data, and type.
19#[derive(Debug, Clone)]
20pub struct Symbol {
21 /// The name of the symbol as it appears in source code
22 pub name: String,
23 /// The specific data for this symbol kind
24 pub data: SymbolData,
25 /// The type ID associated with this symbol
26 pub type_id: TypeId,
27}
28
29impl Symbol {
30 /// Returns the kind of this symbol for compatibility
31 pub fn kind(&self) -> SymbolKind {
32 match &self.data {
33 SymbolData::Type => SymbolKind::Type,
34 SymbolData::Variable { .. } => SymbolKind::Variable,
35 SymbolData::Function => SymbolKind::Function,
36 }
37 }
38
39 /// Returns whether this symbol is mutable (only meaningful for variables)
40 pub fn is_mutable(&self) -> bool {
41 match &self.data {
42 SymbolData::Variable { is_mutable } => *is_mutable,
43 _ => false,
44 }
45 }
46
47 /// Returns true if this is a variable symbol
48 pub fn is_variable(&self) -> bool {
49 matches!(self.data, SymbolData::Variable { .. })
50 }
51
52 /// Returns true if this is a function symbol
53 pub fn is_function(&self) -> bool {
54 matches!(self.data, SymbolData::Function)
55 }
56
57 /// Returns true if this is a type symbol
58 pub fn is_type(&self) -> bool {
59 matches!(self.data, SymbolData::Type)
60 }
61}
62
63/// Legacy enum for compatibility with existing code
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
65pub enum SymbolKind {
66 Type,
67 Variable,
68 Function,
69}
70
71/// Represents a lexical scope containing symbols
72#[derive(Debug, Clone)]
73pub struct Scope {
74 /// Map of symbol names to symbols in this scope
75 symbols: HashMap<String, Symbol>,
76}
77
78/// A symbol table for managing symbols during compilation
79///
80/// The symbol table stores all named entities (variables, types, functions)
81/// in the current scope. It provides functionality to define new symbols
82/// and look up existing ones by name. Each symbol is associated with its
83/// kind and type information.
84///
85/// ### Example
86/// ```
87/// use slang_shared::{SymbolTable, SymbolData};
88/// use slang_types::TypeId;
89///
90/// let mut table = SymbolTable::new();
91/// let type_id = TypeId::new(); // Example type ID
92///
93/// // Define a variable symbol
94/// table.define("my_var".to_string(), SymbolData::Variable { is_mutable: true }, type_id).unwrap();
95///
96/// // Look up the symbol
97/// let symbol = table.lookup("my_var").unwrap();
98/// assert_eq!(symbol.name, "my_var");
99/// ```
100#[derive(Default)]
101pub struct SymbolTable {
102 /// Stack of scopes, with the innermost scope at the end
103 scopes: Vec<Scope>,
104}
105
106impl SymbolTable {
107 /// Creates a new symbol table with a global scope
108 ///
109 /// ### Returns
110 /// A new SymbolTable instance with an empty global scope
111 ///
112 /// ### Example
113 /// ```
114 /// use slang_shared::SymbolTable;
115 ///
116 /// let table = SymbolTable::new();
117 /// assert!(table.lookup("nonexistent").is_none());
118 /// ```
119 pub fn new() -> Self {
120 SymbolTable {
121 scopes: vec![Scope {
122 symbols: HashMap::new(),
123 }],
124 }
125 }
126
127 /// Begins a new scope by pushing it onto the scope stack
128 ///
129 /// Used when entering a block, function, or other lexical scope.
130 pub fn begin_scope(&mut self) {
131 self.scopes.push(Scope {
132 symbols: HashMap::new(),
133 });
134 }
135
136 /// Ends the current scope by popping it from the scope stack
137 ///
138 /// Used when exiting a block, function, or other lexical scope.
139 /// Will panic if attempting to end the global scope.
140 pub fn end_scope(&mut self) {
141 if self.scopes.len() > 1 {
142 self.scopes.pop();
143 } else {
144 panic!("Cannot end the global scope");
145 }
146 }
147
148 /// Defines a new symbol in the current (innermost) scope
149 ///
150 /// Attempts to add a new symbol with the given name, data, and type.
151 /// If a symbol with the same name already exists in the current scope,
152 /// returns an error with a descriptive message.
153 ///
154 /// ### Arguments
155 /// * `name` - The name of the symbol to define
156 /// * `data` - The specific data for this symbol kind
157 /// * `type_id` - The type ID associated with this symbol
158 ///
159 /// ### Returns
160 /// * `Ok(())` if the symbol was successfully defined
161 /// * `Err(String)` with an error message if the name is already taken
162 ///
163 /// ### Example
164 /// ```
165 /// use slang_shared::{SymbolTable, SymbolData};
166 /// use slang_types::TypeId;
167 ///
168 /// let mut table = SymbolTable::new();
169 /// let type_id = TypeId::new();
170 ///
171 /// // Define a new variable
172 /// assert!(table.define("x".to_string(), SymbolData::Variable { is_mutable: true }, type_id.clone()).is_ok());
173 ///
174 /// // Try to define the same name again - should fail
175 /// assert!(table.define("x".to_string(), SymbolData::Variable { is_mutable: false }, type_id).is_err());
176 /// ```
177 pub fn define(
178 &mut self,
179 name: String,
180 data: SymbolData,
181 type_id: TypeId,
182 ) -> Result<(), String> {
183 // Check if symbol already exists in current scope
184 if let Some(current_scope) = self.scopes.last() {
185 if let Some(existing_symbol) = current_scope.symbols.get(&name) {
186 let error_message = match (&existing_symbol.data, &data) {
187 (SymbolData::Type, _) => {
188 format!("Type '{}' is already defined in the current scope.", name)
189 }
190 (SymbolData::Function, _) => format!(
191 "Function '{}' is already defined in the current scope.",
192 name
193 ),
194 (SymbolData::Variable { .. }, _) => format!(
195 "Variable '{}' is already defined in the current scope.",
196 name
197 ),
198 };
199 return Err(error_message);
200 }
201 }
202
203 // Add symbol to current scope
204 if let Some(current_scope) = self.scopes.last_mut() {
205 current_scope.symbols.insert(
206 name.clone(),
207 Symbol {
208 name,
209 data,
210 type_id,
211 },
212 );
213 }
214 Ok(())
215 }
216
217 /// Looks up a symbol by name in all scopes, starting from innermost
218 ///
219 /// Searches for a symbol with the given name starting from the innermost
220 /// (current) scope and working outward. Returns a reference to the first
221 /// matching symbol found.
222 ///
223 /// ### Arguments
224 /// * `name` - The name of the symbol to look up
225 ///
226 /// ### Returns
227 /// * `Some(&Symbol)` if a symbol with the given name exists
228 /// * `None` if no symbol with the given name is found
229 ///
230 /// ### Example
231 /// ```
232 /// use slang_shared::{SymbolTable, SymbolData};
233 /// use slang_types::TypeId;
234 ///
235 /// let mut table = SymbolTable::new();
236 /// let type_id = TypeId::new();
237 ///
238 /// table.define("my_function".to_string(), SymbolData::Function, type_id).unwrap();
239 ///
240 /// let symbol = table.lookup("my_function").unwrap();
241 /// assert!(matches!(symbol.data, SymbolData::Function));
242 /// assert_eq!(symbol.name, "my_function");
243 ///
244 /// assert!(table.lookup("nonexistent").is_none());
245 /// ```
246 pub fn lookup(&self, name: &str) -> Option<&Symbol> {
247 // Search from innermost to outermost scope
248 for scope in self.scopes.iter().rev() {
249 if let Some(symbol) = scope.symbols.get(name) {
250 return Some(symbol);
251 }
252 }
253 None
254 }
255}