diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index f38b6eaa..191f5917 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -1,5 +1,6 @@ use crate::module::CompiledModule; use crate::opcode::{self, OpCode}; +use crate::symbol_table::SymbolTable; pub use oxide_parser::{BinaryOperator, Expression, Statement, UnaryOperator}; @@ -20,6 +21,8 @@ struct CompileCtx { constants: Vec, next_reg: u8, max_regs: u8, + #[allow(dead_code)] + symbols: SymbolTable, } impl CompileCtx { @@ -29,6 +32,7 @@ impl CompileCtx { constants: Vec::new(), next_reg: 0, max_regs: 0, + symbols: SymbolTable::new(), } } @@ -57,6 +61,37 @@ impl CompileCtx { self.constants.push(c); idx as u16 } + + #[allow(dead_code)] + fn push_scope(&mut self) { + self.symbols.push_scope(); + } + + #[allow(dead_code)] + fn pop_scope(&mut self) { + self.symbols.pop_scope(); + } + + #[allow(dead_code)] + fn declare(&mut self, name: &str, reg: u8) -> Result<(), String> { + self.symbols.declare(name, reg) + } + + #[allow(dead_code)] + fn lookup(&self, name: &str) -> Result { + self.symbols.lookup(name) + } + + #[allow(dead_code)] + fn lookup_or_global(&mut self, name: &str) -> u8 { + let reg = self.alloc_reg(); + self.symbols.lookup_or_global(name, reg) + } + + #[allow(dead_code)] + fn init_var(&mut self, name: &str) { + self.symbols.init_var(name); + } } impl Compiler { diff --git a/crates/oxide_compiler/src/lib.rs b/crates/oxide_compiler/src/lib.rs index 00ab9d87..fbe66017 100644 --- a/crates/oxide_compiler/src/lib.rs +++ b/crates/oxide_compiler/src/lib.rs @@ -3,3 +3,4 @@ pub mod compiler; pub mod module; pub mod opcode; +pub mod symbol_table; diff --git a/crates/oxide_compiler/src/symbol_table.rs b/crates/oxide_compiler/src/symbol_table.rs new file mode 100644 index 00000000..24472c93 --- /dev/null +++ b/crates/oxide_compiler/src/symbol_table.rs @@ -0,0 +1,146 @@ +use std::collections::HashMap; + +struct Binding { + reg: u8, + initialized: bool, +} + +pub struct SymbolTable { + scopes: Vec>, +} + +impl Default for SymbolTable { + fn default() -> Self { + Self::new() + } +} + +impl SymbolTable { + pub fn new() -> Self { + Self { + scopes: vec![HashMap::new()], + } + } + + pub fn push_scope(&mut self) { + self.scopes.push(HashMap::new()); + } + + pub fn pop_scope(&mut self) { + self.scopes.pop(); + } + + pub fn declare(&mut self, name: &str, reg: u8) -> Result<(), String> { + let current = self.scopes.last_mut().unwrap(); + if current.contains_key(name) { + return Err(format!("Identifier '{name}' has already been declared")); + } + current.insert( + name.to_string(), + Binding { + reg, + initialized: false, + }, + ); + Ok(()) + } + + pub fn lookup(&self, name: &str) -> Result { + for scope in self.scopes.iter().rev() { + if let Some(b) = scope.get(name) { + if b.initialized { + return Ok(b.reg); + } + return Err(format!("Cannot access '{name}' before initialization")); + } + } + Err(format!("Identifier '{name}' is not defined")) + } + + pub fn lookup_or_global(&mut self, name: &str, reg_for_new: u8) -> u8 { + if let Ok(reg) = self.lookup(name) { + return reg; + } + self.scopes[0].insert( + name.to_string(), + Binding { + reg: reg_for_new, + initialized: true, + }, + ); + reg_for_new + } + + pub fn init_var(&mut self, name: &str) { + let current = self.scopes.last_mut().unwrap(); + if let Some(b) = current.get_mut(name) { + b.initialized = true; + } + } +} + +#[cfg(test)] +mod tests { + use super::SymbolTable; + + #[test] + fn global_scope_exists() { + let st = SymbolTable::new(); + assert!(st.lookup("x").is_err()); + } + + #[test] + fn declare_and_lookup() { + let mut st = SymbolTable::new(); + st.declare("x", 0).unwrap(); + st.init_var("x"); + assert_eq!(st.lookup("x").unwrap(), 0); + } + + #[test] + fn tdz_error() { + let mut st = SymbolTable::new(); + st.declare("x", 0).unwrap(); + assert!(st + .lookup("x") + .unwrap_err() + .contains("before initialization")); + } + + #[test] + fn duplicate_declaration_error() { + let mut st = SymbolTable::new(); + st.declare("x", 0).unwrap(); + assert!(st.declare("x", 1).is_err()); + } + + #[test] + fn nested_scopes_shadow() { + let mut st = SymbolTable::new(); + st.declare("x", 0).unwrap(); + st.init_var("x"); + st.push_scope(); + st.declare("x", 1).unwrap(); + st.init_var("x"); + assert_eq!(st.lookup("x").unwrap(), 1); + st.pop_scope(); + assert_eq!(st.lookup("x").unwrap(), 0); + } + + #[test] + fn lookup_or_global_auto_create() { + let mut st = SymbolTable::new(); + let reg = st.lookup_or_global("x", 5); + assert_eq!(reg, 5); + assert_eq!(st.lookup("x").unwrap(), 5); + } + + #[test] + fn lookup_or_global_existing() { + let mut st = SymbolTable::new(); + st.declare("x", 3).unwrap(); + st.init_var("x"); + let reg = st.lookup_or_global("x", 99); + assert_eq!(reg, 3); + } +}