1use crate::ast::Type;
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, PartialEq)]
5pub struct SymbolInfo {
6 pub var_type: Type,
7 pub is_mutable: bool,
8}
9
10#[derive(Debug, Clone, Default)]
11pub struct SymbolTable {
12 scopes: Vec<HashMap<String, SymbolInfo>>,
13}
14
15impl SymbolTable {
16 pub fn new() -> Self {
17 Self {
18 scopes: vec![HashMap::new()],
19 }
20 }
21
22 pub fn push_scope(&mut self) {
23 self.scopes.push(HashMap::new());
24 }
25
26 pub fn pop_scope(&mut self) {
27 if self.scopes.len() > 1 {
28 self.scopes.pop();
29 }
30 }
31
32 pub fn define(&mut self, name: String, var_type: Type, is_mutable: bool) -> Result<(), String> {
33 if let Some(scope) = self.scopes.last_mut() {
34 if scope.contains_key(&name) {
35 return Err(format!("Symbol '{}' already defined in this scope", name));
36 }
37 scope.insert(
38 name,
39 SymbolInfo {
40 var_type,
41 is_mutable,
42 },
43 );
44 Ok(())
45 } else {
46 Err("No scope available".to_string())
47 }
48 }
49
50 pub fn lookup(&self, name: &str) -> Option<&SymbolInfo> {
51 for scope in self.scopes.iter().rev() {
52 if let Some(info) = scope.get(name) {
53 return Some(info);
54 }
55 }
56 None
57 }
58
59 pub fn lookup_mut(&mut self, name: &str) -> Option<&mut SymbolInfo> {
60 for scope in self.scopes.iter_mut().rev() {
61 if let Some(info) = scope.get_mut(name) {
62 return Some(info);
63 }
64 }
65 None
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn test_symbol_table_scope_management() {
75 let mut table = SymbolTable::new();
76 assert_eq!(table.scopes.len(), 1);
77
78 table.push_scope();
79 assert_eq!(table.scopes.len(), 2);
80
81 table.pop_scope();
82 assert_eq!(table.scopes.len(), 1);
83
84 table.pop_scope();
86 assert_eq!(table.scopes.len(), 1);
87 }
88
89 #[test]
90 fn test_define_and_lookup() {
91 let mut table = SymbolTable::new();
92 table
93 .define("x".to_string(), Type::Arcana, false)
94 .expect("failed to define symbol");
95
96 let info = table.lookup("x").expect("symbol not found");
97 assert_eq!(info.var_type, Type::Arcana);
98 assert!(!info.is_mutable);
99
100 assert!(table.lookup("y").is_none());
101 }
102
103 #[test]
104 fn test_define_duplicate_in_same_scope() {
105 let mut table = SymbolTable::new();
106 table
107 .define("x".to_string(), Type::Arcana, false)
108 .expect("failed to define symbol");
109
110 let result = table.define("x".to_string(), Type::Aether, true);
111 assert!(result.is_err());
112 }
113
114 #[test]
115 fn test_shadowing() {
116 let mut table = SymbolTable::new();
117 table
118 .define("x".to_string(), Type::Arcana, false)
119 .expect("failed to define symbol");
120
121 table.push_scope();
122 table
123 .define("x".to_string(), Type::Aether, true)
124 .expect("failed to shadow symbol");
125
126 let info = table.lookup("x").expect("symbol not found");
127 assert_eq!(info.var_type, Type::Aether);
128 assert!(info.is_mutable);
129
130 table.pop_scope();
131 let info = table.lookup("x").expect("symbol not found");
132 assert_eq!(info.var_type, Type::Arcana);
133 assert!(!info.is_mutable);
134 }
135
136 #[test]
137 fn test_lookup_mut() {
138 let mut table = SymbolTable::new();
139 table
140 .define("x".to_string(), Type::Arcana, true)
141 .expect("failed to define symbol");
142
143 if let Some(info) = table.lookup_mut("x") {
144 info.is_mutable = false;
145 }
146
147 let info = table.lookup("x").expect("symbol not found");
148 assert!(!info.is_mutable);
149 }
150}