Skip to main content

abyss_core/
semantic.rs

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        // Should not pop the last scope
85        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}