From e83a621c9e08242de22eef49839b83c23da52510 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 6 Jun 2026 09:37:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E6=9C=9F=E7=AC=A6=E5=8F=B7=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/compiler.rs | 35 ++++++ crates/oxide_compiler/src/lib.rs | 1 + crates/oxide_compiler/src/symbol_table.rs | 146 ++++++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 crates/oxide_compiler/src/symbol_table.rs 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); + } +}