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}