From 4224055860f8f8d85a2e0a9c8c6d3bc8ea77263b Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Fri, 16 May 2025 10:56:57 -0500 Subject: [PATCH] Refactor name-analysis code into separate module/files. --- src/ast/mod.rs | 2 +- src/bin/dmc/name_analysis.rs | 3 +- src/compile/mod.rs | 1 - src/compile/name_analysis.rs | 540 ------------------------------ src/diagnostic/mod.rs | 3 + src/lib.rs | 3 +- src/name_analysis/fqn_context.rs | 38 +++ src/name_analysis/gather.rs | 154 +++++++++ src/name_analysis/mod.rs | 76 +++++ src/name_analysis/resolve.rs | 137 ++++++++ src/name_analysis/symbol.rs | 59 ++++ src/name_analysis/symbol_table.rs | 90 +++++ 12 files changed, 562 insertions(+), 544 deletions(-) delete mode 100644 src/compile/mod.rs delete mode 100644 src/compile/name_analysis.rs create mode 100644 src/diagnostic/mod.rs create mode 100644 src/name_analysis/fqn_context.rs create mode 100644 src/name_analysis/gather.rs create mode 100644 src/name_analysis/mod.rs create mode 100644 src/name_analysis/resolve.rs create mode 100644 src/name_analysis/symbol.rs create mode 100644 src/name_analysis/symbol_table.rs diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 9548221..1dd5c9a 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1,6 +1,6 @@ -use crate::compile::name_analysis::Symbol; use pest::Parser; use std::range::Range; +use crate::name_analysis::symbol::Symbol; pub mod build; pub mod named; diff --git a/src/bin/dmc/name_analysis.rs b/src/bin/dmc/name_analysis.rs index 4aad6b3..ae524f3 100644 --- a/src/bin/dmc/name_analysis.rs +++ b/src/bin/dmc/name_analysis.rs @@ -2,7 +2,8 @@ use codespan_reporting::files::SimpleFiles; use codespan_reporting::term; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; use deimos::ast::build::build_ast; -use deimos::compile::name_analysis::{analyze_names, SymbolTable}; +use deimos::name_analysis::analyze_names; +use deimos::name_analysis::symbol_table::SymbolTable; use deimos::parser::{DeimosParser, Rule}; use pest::Parser; use std::path::Path; diff --git a/src/compile/mod.rs b/src/compile/mod.rs deleted file mode 100644 index 66315a3..0000000 --- a/src/compile/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod name_analysis; diff --git a/src/compile/name_analysis.rs b/src/compile/name_analysis.rs deleted file mode 100644 index bf774e5..0000000 --- a/src/compile/name_analysis.rs +++ /dev/null @@ -1,540 +0,0 @@ -use crate::ast::named::Named; -use crate::ast::*; -use codespan_reporting::diagnostic::{Diagnostic, Label}; -use std::collections::HashMap; -use std::fmt::Display; - -#[derive(Debug, Clone)] -pub enum Symbol { - Function(FunctionSymbol), - Variable(VariableSymbol), -} - -impl Symbol { - pub fn name(&self) -> &str { - match self { - Symbol::Function(s) => s.declared_name.as_str(), - Symbol::Variable(s) => s.name.as_str(), - } - } -} - -impl Display for Symbol { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use Symbol::*; - match self { - Function(function_symbol) => write!(f, "{}", function_symbol), - Variable(variable_symbol) => write!(f, "{}", variable_symbol), - } - } -} - -#[derive(Debug, Clone)] -pub struct FunctionSymbol { - pub fqn: String, - pub declared_name: String, - pub is_public: bool, -} - -impl Display for FunctionSymbol { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "FunctionSymbol(fqn = {}, declared_name = {}, is_public = {})", - self.fqn, self.declared_name, self.is_public - ) - } -} - -#[derive(Debug, Clone)] -pub struct VariableSymbol { - pub name: String, - pub is_mutable: bool, -} - -impl Display for VariableSymbol { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "VariableSymbol(name = {}, is_mutable = {})", - self.name, self.is_mutable - ) - } -} - -#[derive(Default)] -pub struct Scope { - parent: Option, - symbols: HashMap, - debug_name: String, -} - -#[derive(Default)] -pub struct SymbolTable { - scopes: Vec, - current_scope_id: usize, -} - -/// Contains a vec of scopes, like a flattened tree -impl SymbolTable { - pub fn new() -> Self { - let mut t = SymbolTable::default(); - t.scopes.push(Scope::default()); - t.current_scope_id = 0; - t - } - - pub fn current_scope_id(&self) -> usize { - self.current_scope_id - } - - pub fn push_scope(&mut self, debug_name: &str) { - let id = self.scopes.len(); - self.scopes.push(Scope { - symbols: HashMap::new(), - parent: Some(self.current_scope_id), - debug_name: debug_name.to_string(), - }); - self.current_scope_id = id; - } - - pub fn pop_scope(&mut self) { - if let Some(parent_id) = self.scopes[self.current_scope_id].parent { - self.current_scope_id = parent_id; - } - } - - pub fn insert(&mut self, name: String, symbol: Symbol) -> Result<(), String> { - if let Some(current_symbol) = self.scopes[self.current_scope_id].symbols.get(&name) { - Err(format!( - "Symbol '{}' already defined in current scope.", - current_symbol.name() - )) - } else { - self.scopes[self.current_scope_id] - .symbols - .insert(name, symbol); - Ok(()) - } - } - - pub fn lookup(&self, name: &str, scope_id: usize) -> Result<&Symbol, String> { - let mut scope_opt = Some(&self.scopes[scope_id]); - while let Some(scope) = scope_opt { - if let Some(symbol) = scope.symbols.get(name) { - return Ok(symbol); - } - scope_opt = if let Some(parent_id) = scope.parent { - Some(&self.scopes[parent_id]) - } else { - None - }; - } - Err(format!("Symbol '{}' not found in current scope.", name)) - } -} - -impl Display for SymbolTable { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - for (i, scope) in self.scopes.iter().enumerate() { - writeln!(f, "Scope {} {}", i, scope.debug_name)?; - for (name, symbol) in &scope.symbols { - writeln!(f, " {}({})", name, symbol)?; - } - } - Ok(()) - } -} - -struct FqnContext { - stack: Vec, -} - -impl FqnContext { - fn new() -> Self { - FqnContext { stack: Vec::new() } - } - - fn push(&mut self, name: String) { - self.stack.push(name); - } - - fn pop(&mut self) { - self.stack.pop(); - } - - fn current(&self) -> String { - let mut acc = String::new(); - for (i, name) in self.stack.iter().enumerate() { - acc.push_str(name); - if i != self.stack.len() - 1 { - acc.push_str("::") - } - } - acc - } - - fn resolve(&self, name: &str) -> String { - let mut acc = String::new(); - if !self.stack.is_empty() { - acc.push_str(&self.current()); - acc.push_str("::"); - } - acc.push_str(name); - acc - } -} - -pub fn analyze_names( - file_id: usize, - compilation_unit: &mut CompilationUnit, - symbol_table: &mut SymbolTable, -) -> Vec> { - let mut diagnostics = vec![]; - - let mut fqn_context = FqnContext::new(); - if let Some(namespace) = &compilation_unit.namespace { - fqn_context.push(namespace.name().to_string()); - } - - for declaration in &mut compilation_unit.declarations { - diagnostics.extend(gather_module_level_declaration( - file_id, - declaration, - symbol_table, - &mut fqn_context, - )); - } - - assert_eq!(symbol_table.current_scope_id, 0); - - for declaration in &mut compilation_unit.declarations { - diagnostics.extend(resolve_module_level_declaration(file_id, declaration, symbol_table)); - } - - diagnostics -} - -fn gather_module_level_declaration( - file_id: usize, - declaration: &mut ModuleLevelDeclaration, - symbol_table: &mut SymbolTable, - fqn_context: &mut FqnContext, -) -> Vec> { - use ModuleLevelDeclaration::*; - match declaration { - Function(function_definition) => { - gather_function_definition(file_id, function_definition, symbol_table, fqn_context) - } - _ => todo!(), - } -} - -fn gather_function_definition( - file_id: usize, - function: &mut FunctionDefinition, - symbol_table: &mut SymbolTable, - fqn_context: &mut FqnContext, -) -> Vec> { - let mut diagnostics = vec![]; - - let declared_name = function.identifier.name().to_string(); - let resolved_name = fqn_context.resolve(&declared_name); - - let insert_result = symbol_table.insert( - declared_name.clone(), - Symbol::Function(FunctionSymbol { - fqn: resolved_name.clone(), - declared_name, - is_public: function.is_public, - }), - ); - if let Err(msg) = insert_result { - diagnostics.push( - Diagnostic::error() - .with_message(msg) - .with_label(Label::primary(file_id, function.identifier.range)), - ) - } - - function - .identifier - .set_scope_id(symbol_table.current_scope_id()); - - symbol_table.push_scope(&format!("FunctionScope({})", resolved_name)); - // TODO: params - diagnostics.extend(gather_function_body( - file_id, - &mut function.body, - symbol_table, - fqn_context, - )); - symbol_table.pop_scope(); - - diagnostics -} - -fn gather_function_body( - file_id: usize, - function_body: &mut FunctionBody, - symbol_table: &mut SymbolTable, - fqn_context: &mut FqnContext, -) -> Vec> { - use FunctionBody::*; - match function_body { - Block(block) => gather_block_statement(file_id, block, symbol_table, fqn_context), - _ => todo!(), - } -} - -fn gather_block_statement( - file_id: usize, - block: &mut BlockStatement, - symbol_table: &mut SymbolTable, - fqn_context: &mut FqnContext, -) -> Vec> { - let mut diagnostics = vec![]; - symbol_table.push_scope("BlockStatementScope"); - for statement in &mut block.statements { - diagnostics.extend(gather_statement( - file_id, - statement, - symbol_table, - fqn_context, - )); - } - symbol_table.pop_scope(); - diagnostics -} - -fn gather_statement( - file_id: usize, - statement: &mut Statement, - symbol_table: &mut SymbolTable, - fqn_context: &mut FqnContext, -) -> Vec> { - use Statement::*; - match statement { - BlockStatement(block) => gather_block_statement(file_id, block, symbol_table, fqn_context), - VariableDeclarationStatement(variable_declaration) => { - gather_variable_declaration(file_id, variable_declaration, symbol_table) - } - AssignStatement(assign_statement) => { - gather_assign_statement(assign_statement, symbol_table, fqn_context) - } - _ => todo!(), - } -} - -fn gather_variable_declaration( - file_id: usize, - variable_declaration: &mut VariableDeclarationStatement, - symbol_table: &mut SymbolTable, -) -> Vec> { - let mut diagnostics = vec![]; - let variable_name = variable_declaration.identifier.name().to_string(); - - let insert_result = symbol_table.insert( - variable_name.clone(), - Symbol::Variable(VariableSymbol { - name: variable_name, - is_mutable: variable_declaration.is_mutable, - }), - ); - - if let Err(msg) = insert_result { - diagnostics.push( - Diagnostic::error() - .with_message(msg) - .with_label(Label::primary( - file_id, - variable_declaration.identifier.range, - )), - ); - } - - variable_declaration - .identifier - .set_scope_id(symbol_table.current_scope_id()); - - diagnostics -} - -fn gather_assign_statement( - assign_statement: &mut AssignStatement, - symbol_table: &mut SymbolTable, - fqn_context: &mut FqnContext, -) -> Vec> { - let mut diagnostics = vec![]; - diagnostics.extend(gather_expression( - &mut assign_statement.lhs, - symbol_table, - fqn_context, - )); - diagnostics.extend(gather_expression( - &mut assign_statement.rhs, - symbol_table, - fqn_context, - )); - diagnostics -} - -fn gather_expression( - expression: &mut Expression, - symbol_table: &mut SymbolTable, - fqn_context: &mut FqnContext, -) -> Vec> { - use Expression::*; - match expression { - FullyQualifiedName(fully_qualified_name) => { - gather_fully_qualified_name(fully_qualified_name, symbol_table) - } - _ => { - vec![] - } - } -} - -fn gather_fully_qualified_name( - fully_qualified_name: &mut FullyQualifiedName, - symbol_table: &mut SymbolTable, -) -> Vec> { - fully_qualified_name.set_scope_id(symbol_table.current_scope_id()); - vec![] -} - -/* Resolve */ - -fn resolve_module_level_declaration( - file_id: usize, - declaration: &mut ModuleLevelDeclaration, - symbol_table: &mut SymbolTable, -) -> Vec> { - use ModuleLevelDeclaration::*; - match declaration { - Function(function_definition) => { - resolve_function_definition(file_id, function_definition, symbol_table) - } - _ => todo!(), - } -} - -fn resolve_function_definition( - file_id: usize, - function_definition: &mut FunctionDefinition, - symbol_table: &mut SymbolTable, -) -> Vec> { - resolve_function_body(file_id, &mut function_definition.body, symbol_table) -} - -fn resolve_function_body( - file_id: usize, - function_body: &mut FunctionBody, - symbol_table: &mut SymbolTable, -) -> Vec> { - use FunctionBody::*; - match function_body { - Block(block) => resolve_block(file_id, block, symbol_table), - _ => todo!(), - } -} - -fn resolve_block( - file_id: usize, - block_statement: &mut BlockStatement, - symbol_table: &mut SymbolTable, -) -> Vec> { - block_statement - .statements - .iter_mut() - .flat_map(|statement| resolve_statement(file_id, statement, symbol_table)) - .collect() -} - -fn resolve_statement( - file_id: usize, - statement: &mut Statement, - symbol_table: &mut SymbolTable, -) -> Vec> { - use Statement::*; - match statement { - BlockStatement(block) => resolve_block(file_id, block, symbol_table), - VariableDeclarationStatement(variable_declaration) => { - resolve_variable_declaration(file_id, variable_declaration, symbol_table) - } - AssignStatement(assign_statement) => { - resolve_assign_statement(file_id, assign_statement, symbol_table) - } - CallStatement(call_statement) => resolve_call_statement(file_id, call_statement, symbol_table), - _ => todo!(), - } -} - -fn resolve_variable_declaration( - file_id: usize, - variable_declaration: &mut VariableDeclarationStatement, - symbol_table: &mut SymbolTable, -) -> Vec> { - if let Some(initializer) = &mut variable_declaration.initializer { - resolve_expression(file_id, initializer, symbol_table) - } else { - vec![] - } -} - -fn resolve_assign_statement( - file_id: usize, - assign_statement: &mut AssignStatement, - symbol_table: &mut SymbolTable, -) -> Vec> { - let mut diagnostics = vec![]; - diagnostics.extend(resolve_expression(file_id, &mut assign_statement.lhs, symbol_table)); - diagnostics.extend(resolve_expression(file_id, &mut assign_statement.rhs, symbol_table)); - diagnostics -} - -fn resolve_call_statement( - file_id: usize, - call_statement: &mut CallStatement, - symbol_table: &mut SymbolTable, -) -> Vec> { - resolve_expression(file_id, &mut call_statement.0, symbol_table) -} - -fn resolve_expression( - file_id: usize, - expression: &mut Expression, - symbol_table: &mut SymbolTable, -) -> Vec> { - use Expression::*; - match expression { - FullyQualifiedName(fqn) => resolve_fully_qualified_name(file_id, fqn, symbol_table), - Literal(_) => vec![], - _ => todo!(), - } -} - -fn resolve_fully_qualified_name( - file_id: usize, - fully_qualified_name: &mut FullyQualifiedName, - symbol_table: &mut SymbolTable, -) -> Vec> { - let lookup_result = symbol_table.lookup( - fully_qualified_name.name().as_ref(), - fully_qualified_name.scope_id().expect(&format!( - "FullyQualifiedName has no scope_id set: {:?}", - fully_qualified_name - )), - ); - match lookup_result { - Ok(symbol) => { - fully_qualified_name.set_symbol(symbol.clone()); - vec![] - } - Err(e) => vec![ - Diagnostic::error() - .with_message(e) - .with_label(Label::primary(file_id, fully_qualified_name.range)) - ], - } -} diff --git a/src/diagnostic/mod.rs b/src/diagnostic/mod.rs new file mode 100644 index 0000000..b1f1ebc --- /dev/null +++ b/src/diagnostic/mod.rs @@ -0,0 +1,3 @@ +use codespan_reporting::diagnostic::Diagnostic; + +pub type DmDiagnostic = Diagnostic; diff --git a/src/lib.rs b/src/lib.rs index 44f09d1..916222f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ #![feature(new_range_api)] #![allow(warnings)] pub mod ast; -pub mod compile; +pub mod diagnostic; pub mod module; +pub mod name_analysis; pub mod object_file; pub mod parser; pub mod util; diff --git a/src/name_analysis/fqn_context.rs b/src/name_analysis/fqn_context.rs new file mode 100644 index 0000000..c5f51f0 --- /dev/null +++ b/src/name_analysis/fqn_context.rs @@ -0,0 +1,38 @@ +pub(super) struct FqnContext { + stack: Vec, +} + +impl FqnContext { + pub fn new() -> Self { + FqnContext { stack: Vec::new() } + } + + pub fn push(&mut self, name: String) { + self.stack.push(name); + } + + pub fn pop(&mut self) { + self.stack.pop(); + } + + pub fn current(&self) -> String { + let mut acc = String::new(); + for (i, name) in self.stack.iter().enumerate() { + acc.push_str(name); + if i != self.stack.len() - 1 { + acc.push_str("::") + } + } + acc + } + + pub fn resolve(&self, name: &str) -> String { + let mut acc = String::new(); + if !self.stack.is_empty() { + acc.push_str(&self.current()); + acc.push_str("::"); + } + acc.push_str(name); + acc + } +} diff --git a/src/name_analysis/gather.rs b/src/name_analysis/gather.rs new file mode 100644 index 0000000..b6d7fdc --- /dev/null +++ b/src/name_analysis/gather.rs @@ -0,0 +1,154 @@ +use crate::ast::named::Named; +use crate::ast::*; +use crate::name_analysis::fqn_context::FqnContext; +use crate::name_analysis::symbol::{FunctionSymbol, Symbol, VariableSymbol}; +use crate::name_analysis::symbol_table::SymbolTable; +use crate::name_analysis::DiagnosticsContainer; +use codespan_reporting::diagnostic::{Diagnostic, Label}; + +pub(super) fn gather_module_level_declaration( + declaration: &mut ModuleLevelDeclaration, + symbol_table: &mut SymbolTable, + fqn_context: &mut FqnContext, + diagnostics: &mut DiagnosticsContainer, +) { + use ModuleLevelDeclaration::*; + match declaration { + Function(function_definition) => { + gather_function_definition(function_definition, symbol_table, fqn_context, diagnostics) + } + _ => todo!(), + } +} + +fn gather_function_definition( + function: &mut FunctionDefinition, + symbol_table: &mut SymbolTable, + fqn_context: &mut FqnContext, + diagnostics: &mut DiagnosticsContainer, +) { + let declared_name = function.identifier.name().to_string(); + let resolved_name = fqn_context.resolve(&declared_name); + + let insert_result = symbol_table.insert( + declared_name.clone(), + Symbol::Function(FunctionSymbol { + fqn: resolved_name.clone(), + declared_name, + is_public: function.is_public, + }), + ); + + if let Err(msg) = insert_result { + diagnostics.add(|file_id| { + Diagnostic::error().with_label(Label::primary(file_id, function.identifier.range)) + }); + } + + function + .identifier + .set_scope_id(symbol_table.current_scope_id()); + + symbol_table.push_scope(&format!("FunctionScope({})", resolved_name)); + // TODO: params + gather_function_body(&mut function.body, symbol_table, fqn_context, diagnostics); + symbol_table.pop_scope(); +} + +fn gather_function_body( + function_body: &mut FunctionBody, + symbol_table: &mut SymbolTable, + fqn_context: &mut FqnContext, + diagnostics: &mut DiagnosticsContainer, +) { + use crate::ast::FunctionBody::*; + match function_body { + Block(block) => gather_block_statement(block, symbol_table, fqn_context, diagnostics), + _ => todo!(), + } +} + +fn gather_block_statement( + block: &mut BlockStatement, + symbol_table: &mut SymbolTable, + fqn_context: &mut FqnContext, + diagnostics: &mut DiagnosticsContainer, +) { + symbol_table.push_scope("BlockStatementScope"); + for statement in &mut block.statements { + gather_statement(statement, symbol_table, fqn_context, diagnostics); + } + symbol_table.pop_scope(); +} + +fn gather_statement( + statement: &mut Statement, + symbol_table: &mut SymbolTable, + fqn_context: &mut FqnContext, + diagnostics: &mut DiagnosticsContainer, +) { + use crate::ast::Statement::*; + match statement { + BlockStatement(block) => { + gather_block_statement(block, symbol_table, fqn_context, diagnostics) + } + VariableDeclarationStatement(variable_declaration) => { + gather_variable_declaration(variable_declaration, symbol_table, diagnostics) + } + AssignStatement(assign_statement) => { + gather_assign_statement(assign_statement, symbol_table, fqn_context, diagnostics) + } + _ => todo!(), + } +} + +fn gather_variable_declaration( + variable_declaration: &mut VariableDeclarationStatement, + symbol_table: &mut SymbolTable, + diagnostics: &mut DiagnosticsContainer, +) { + let variable_name = variable_declaration.identifier.name().to_string(); + + let insert_result = symbol_table.insert( + variable_name.clone(), + Symbol::Variable(VariableSymbol { + name: variable_name, + is_mutable: variable_declaration.is_mutable, + }), + ); + + if let Err(msg) = insert_result { + diagnostics.add_error_at_identifier(&msg, &variable_declaration.identifier); + } + + variable_declaration + .identifier + .set_scope_id(symbol_table.current_scope_id()); +} + +fn gather_assign_statement( + assign_statement: &mut AssignStatement, + symbol_table: &mut SymbolTable, + fqn_context: &mut FqnContext, + diagnostics: &mut DiagnosticsContainer, +) { + gather_expression(&mut assign_statement.lhs, symbol_table); + gather_expression(&mut assign_statement.rhs, symbol_table); +} + +fn gather_expression(expression: &mut Expression, symbol_table: &mut SymbolTable) { + use crate::ast::Expression::*; + match expression { + FullyQualifiedName(fully_qualified_name) => { + gather_fully_qualified_name(fully_qualified_name, symbol_table); + } + _ => {} + } +} + +fn gather_fully_qualified_name( + fully_qualified_name: &mut FullyQualifiedName, + symbol_table: &mut SymbolTable, +) { + fully_qualified_name.set_scope_id(symbol_table.current_scope_id()); +} diff --git a/src/name_analysis/mod.rs b/src/name_analysis/mod.rs new file mode 100644 index 0000000..beac2bf --- /dev/null +++ b/src/name_analysis/mod.rs @@ -0,0 +1,76 @@ +use crate::ast::named::Named; +use crate::ast::{CompilationUnit, Identifier}; +use crate::diagnostic::DmDiagnostic; +use crate::name_analysis::fqn_context::FqnContext; +use crate::name_analysis::gather::gather_module_level_declaration; +use crate::name_analysis::resolve::resolve_module_level_declaration; +use crate::name_analysis::symbol_table::SymbolTable; +use codespan_reporting::diagnostic::{Diagnostic, Label}; + +mod fqn_context; +mod gather; +mod resolve; +pub mod symbol; +pub mod symbol_table; + +pub(self) struct DiagnosticsContainer { + file_id: usize, + diagnostics: Vec, +} + +impl DiagnosticsContainer { + pub fn new(file_id: usize) -> DiagnosticsContainer { + DiagnosticsContainer { + file_id, + diagnostics: vec![], + } + } + + pub fn add(&mut self, f: impl FnOnce(usize) -> DmDiagnostic) { + self.diagnostics.push(f(self.file_id)); + } + + pub fn add_error_at_identifier(&mut self, msg: &str, identifier: &Identifier) { + self.diagnostics.push( + Diagnostic::error() + .with_message(msg) + .with_label(Label::primary(self.file_id, identifier.range)), + ); + } +} + +impl Into> for DiagnosticsContainer { + fn into(self) -> Vec { + self.diagnostics + } +} + +pub fn analyze_names( + file_id: usize, + compilation_unit: &mut CompilationUnit, + symbol_table: &mut SymbolTable, +) -> Vec { + let mut diagnostics = DiagnosticsContainer::new(file_id); + + let mut fqn_context = FqnContext::new(); + if let Some(namespace) = &compilation_unit.namespace { + fqn_context.push(namespace.name().to_string()); + } + + for declaration in &mut compilation_unit.declarations { + gather_module_level_declaration( + declaration, + symbol_table, + &mut fqn_context, + &mut diagnostics, + ); + } + + assert_eq!(symbol_table.current_scope_id(), 0); + + for declaration in &mut compilation_unit.declarations { + resolve_module_level_declaration(declaration, symbol_table, &mut diagnostics); + } + + diagnostics.into() +} diff --git a/src/name_analysis/resolve.rs b/src/name_analysis/resolve.rs new file mode 100644 index 0000000..1925da4 --- /dev/null +++ b/src/name_analysis/resolve.rs @@ -0,0 +1,137 @@ +use crate::ast::named::Named; +use crate::ast::*; +use crate::name_analysis::symbol_table::SymbolTable; +use crate::name_analysis::DiagnosticsContainer; +use codespan_reporting::diagnostic::{Diagnostic, Label}; + +pub(super) fn resolve_module_level_declaration( + declaration: &mut ModuleLevelDeclaration, + symbol_table: &mut SymbolTable, + diagnostics: &mut DiagnosticsContainer, +) { + use crate::ast::ModuleLevelDeclaration::*; + match declaration { + Function(function_definition) => { + resolve_function_definition(function_definition, symbol_table, diagnostics) + } + _ => todo!(), + } +} + +fn resolve_function_definition( + function_definition: &mut FunctionDefinition, + symbol_table: &mut SymbolTable, + diagnostics: &mut DiagnosticsContainer, +) { + resolve_function_body(&mut function_definition.body, symbol_table, diagnostics); +} + +fn resolve_function_body( + function_body: &mut FunctionBody, + symbol_table: &mut SymbolTable, + diagnostics: &mut DiagnosticsContainer, +) { + use crate::ast::FunctionBody::*; + match function_body { + Block(block) => resolve_block(block, symbol_table, diagnostics), + _ => todo!(), + } +} + +fn resolve_block( + block_statement: &mut BlockStatement, + symbol_table: &mut SymbolTable, + diagnostics: &mut DiagnosticsContainer, +) { + for statement in block_statement.statements.iter_mut() { + resolve_statement(statement, symbol_table, diagnostics); + } + if let Some(expression) = block_statement.expression.as_mut() { + resolve_expression(expression, symbol_table, diagnostics); + } +} + +fn resolve_statement( + statement: &mut Statement, + symbol_table: &mut SymbolTable, + diagnostics: &mut DiagnosticsContainer, +) { + use crate::ast::Statement::*; + match statement { + BlockStatement(block) => resolve_block(block, symbol_table, diagnostics), + VariableDeclarationStatement(variable_declaration) => { + resolve_variable_declaration(variable_declaration, symbol_table, diagnostics) + } + AssignStatement(assign_statement) => { + resolve_assign_statement(assign_statement, symbol_table, diagnostics) + } + CallStatement(call_statement) => { + resolve_call_statement(call_statement, symbol_table, diagnostics) + } + _ => todo!(), + } +} + +fn resolve_variable_declaration( + variable_declaration: &mut VariableDeclarationStatement, + symbol_table: &mut SymbolTable, + diagnostics: &mut DiagnosticsContainer, +) { + if let Some(initializer) = &mut variable_declaration.initializer { + resolve_expression(initializer, symbol_table, diagnostics) + } +} + +fn resolve_assign_statement( + assign_statement: &mut AssignStatement, + symbol_table: &mut SymbolTable, + diagnostics: &mut DiagnosticsContainer, +) { + resolve_expression(&mut assign_statement.lhs, symbol_table, diagnostics); + resolve_expression(&mut assign_statement.rhs, symbol_table, diagnostics); +} + +fn resolve_call_statement( + call_statement: &mut CallStatement, + symbol_table: &mut SymbolTable, + diagnostics: &mut DiagnosticsContainer, +) { + resolve_expression(&mut call_statement.0, symbol_table, diagnostics) +} + +fn resolve_expression( + expression: &mut Expression, + symbol_table: &mut SymbolTable, + diagnostics: &mut DiagnosticsContainer, +) { + use crate::ast::Expression::*; + match expression { + FullyQualifiedName(fqn) => resolve_fully_qualified_name(fqn, symbol_table, diagnostics), + Literal(_) => {} + _ => todo!(), + } +} + +fn resolve_fully_qualified_name( + fully_qualified_name: &mut FullyQualifiedName, + symbol_table: &mut SymbolTable, + diagnostics: &mut DiagnosticsContainer, +) { + let lookup_result = symbol_table.lookup( + fully_qualified_name.name().as_ref(), + fully_qualified_name.scope_id().expect(&format!( + "FullyQualifiedName has no scope_id set: {:?}", + fully_qualified_name + )), + ); + match lookup_result { + Ok(symbol) => { + fully_qualified_name.set_symbol(symbol.clone()); + } + Err(e) => diagnostics.add(|file_id| { + Diagnostic::error() + .with_message(e) + .with_label(Label::primary(file_id, fully_qualified_name.range)) + }), + } +} diff --git a/src/name_analysis/symbol.rs b/src/name_analysis/symbol.rs new file mode 100644 index 0000000..14d1620 --- /dev/null +++ b/src/name_analysis/symbol.rs @@ -0,0 +1,59 @@ +use std::fmt::Display; + +#[derive(Debug, Clone)] +pub enum Symbol { + Function(FunctionSymbol), + Variable(VariableSymbol), +} + +impl Symbol { + pub fn name(&self) -> &str { + match self { + Symbol::Function(s) => s.declared_name.as_str(), + Symbol::Variable(s) => s.name.as_str(), + } + } +} + +impl Display for Symbol { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use Symbol::*; + match self { + Function(function_symbol) => write!(f, "{}", function_symbol), + Variable(variable_symbol) => write!(f, "{}", variable_symbol), + } + } +} + +#[derive(Debug, Clone)] +pub struct FunctionSymbol { + pub fqn: String, + pub declared_name: String, + pub is_public: bool, +} + +impl Display for FunctionSymbol { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "FunctionSymbol(fqn = {}, declared_name = {}, is_public = {})", + self.fqn, self.declared_name, self.is_public + ) + } +} + +#[derive(Debug, Clone)] +pub struct VariableSymbol { + pub name: String, + pub is_mutable: bool, +} + +impl Display for VariableSymbol { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "VariableSymbol(name = {}, is_mutable = {})", + self.name, self.is_mutable + ) + } +} \ No newline at end of file diff --git a/src/name_analysis/symbol_table.rs b/src/name_analysis/symbol_table.rs new file mode 100644 index 0000000..c280704 --- /dev/null +++ b/src/name_analysis/symbol_table.rs @@ -0,0 +1,90 @@ +use crate::name_analysis::symbol::Symbol; +use std::collections::HashMap; +use std::fmt::Display; + +pub struct Scope { + parent: Option, + symbols: HashMap, + debug_name: String, +} + +#[derive(Default)] +pub struct SymbolTable { + scopes: Vec, + current_scope_id: usize, +} + +/// Contains a vec of scopes, like a flattened tree +impl SymbolTable { + pub fn new() -> Self { + let mut t = SymbolTable::default(); + t.scopes.push(Scope { + parent: None, + symbols: HashMap::new(), + debug_name: String::from("Global Scope"), + }); + t.current_scope_id = 0; + t + } + + pub fn current_scope_id(&self) -> usize { + self.current_scope_id + } + + pub fn push_scope(&mut self, debug_name: &str) { + let id = self.scopes.len(); + self.scopes.push(Scope { + parent: Some(self.current_scope_id), + symbols: HashMap::new(), + debug_name: debug_name.to_string(), + }); + self.current_scope_id = id; + } + + pub fn pop_scope(&mut self) { + if let Some(parent_id) = self.scopes[self.current_scope_id].parent { + self.current_scope_id = parent_id; + } + } + + pub fn insert(&mut self, name: String, symbol: Symbol) -> Result<(), String> { + if let Some(current_symbol) = self.scopes[self.current_scope_id].symbols.get(&name) { + Err(format!( + "Symbol '{}' already defined in current scope.", + current_symbol.name() + )) + } else { + self.scopes[self.current_scope_id] + .symbols + .insert(name, symbol); + Ok(()) + } + } + + pub fn lookup(&self, name: &str, scope_id: usize) -> Result<&Symbol, String> { + let mut scope_opt = Some(&self.scopes[scope_id]); + while let Some(scope) = scope_opt { + if let Some(symbol) = scope.symbols.get(name) { + return Ok(symbol); + } + scope_opt = if let Some(parent_id) = scope.parent { + Some(&self.scopes[parent_id]) + } else { + None + }; + } + Err(format!("Symbol '{}' not found in current scope.", name)) + } +} + +impl Display for SymbolTable { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for (i, scope) in self.scopes.iter().enumerate() { + writeln!(f, "Scope {} {}", i, scope.debug_name)?; + for (name, symbol) in &scope.symbols { + writeln!(f, " {}({})", name, symbol)?; + } + } + Ok(()) + } +}