From d5bfe9ad28229e3b9bf0ce588c44fd0d19385b8a Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Sat, 23 May 2026 21:21:31 -0500 Subject: [PATCH] Add semantic analysis module and first steps. --- dmc-lib/src/ast/assign_statement.rs | 4 + dmc-lib/src/ast/extern_function.rs | 20 ++ dmc-lib/src/ast/function.rs | 16 ++ dmc-lib/src/ast/let_statement.rs | 12 + dmc-lib/src/ast/parameter.rs | 8 + dmc-lib/src/ast/type_use.rs | 4 + dmc-lib/src/lib.rs | 1 + .../src/semantic_analysis/collect_scopes.rs | 207 ++++++++++++++ .../src/semantic_analysis/collect_symbols.rs | 122 ++++++++ .../semantic_analysis/diagnostic_helpers.rs | 30 ++ dmc-lib/src/semantic_analysis/mod.rs | 7 + .../src/semantic_analysis/resolve_names.rs | 266 ++++++++++++++++++ dmc-lib/src/semantic_analysis/scope.rs | 31 ++ .../src/semantic_analysis/semantic_context.rs | 10 + dmc-lib/src/semantic_analysis/symbol.rs | 110 ++++++++ 15 files changed, 848 insertions(+) create mode 100644 dmc-lib/src/semantic_analysis/collect_scopes.rs create mode 100644 dmc-lib/src/semantic_analysis/collect_symbols.rs create mode 100644 dmc-lib/src/semantic_analysis/diagnostic_helpers.rs create mode 100644 dmc-lib/src/semantic_analysis/mod.rs create mode 100644 dmc-lib/src/semantic_analysis/resolve_names.rs create mode 100644 dmc-lib/src/semantic_analysis/scope.rs create mode 100644 dmc-lib/src/semantic_analysis/semantic_context.rs create mode 100644 dmc-lib/src/semantic_analysis/symbol.rs diff --git a/dmc-lib/src/ast/assign_statement.rs b/dmc-lib/src/ast/assign_statement.rs index 54dd62e..d76997c 100644 --- a/dmc-lib/src/ast/assign_statement.rs +++ b/dmc-lib/src/ast/assign_statement.rs @@ -37,6 +37,10 @@ impl AssignStatement { &self.destination } + pub fn value(&self) -> &Expression { + &self.value + } + pub fn init_scopes(&mut self, symbol_table: &mut SymbolTable, container_scope: usize) { self.destination.init_scopes(symbol_table, container_scope); self.value.init_scopes(symbol_table, container_scope); diff --git a/dmc-lib/src/ast/extern_function.rs b/dmc-lib/src/ast/extern_function.rs index b89bdde..6d02aa5 100644 --- a/dmc-lib/src/ast/extern_function.rs +++ b/dmc-lib/src/ast/extern_function.rs @@ -42,10 +42,30 @@ impl ExternFunction { } } + pub fn node_id(&self) -> NodeId { + self.node_id + } + pub fn declared_name(&self) -> &str { &self.declared_name } + pub fn declared_name_owned(&self) -> Rc { + self.declared_name.clone() + } + + pub fn declared_name_source_range(&self) -> SourceRange { + self.declared_name_source_range.clone() + } + + pub fn parameters(&self) -> &[Parameter] { + &self.parameters + } + + pub fn return_type(&self) -> &TypeUse { + &self.return_type + } + pub fn init_scopes(&mut self, symbol_table: &mut SymbolTable, container_scope: usize) { self.scope_id = Some(container_scope); diff --git a/dmc-lib/src/ast/function.rs b/dmc-lib/src/ast/function.rs index f63d167..85fc90b 100644 --- a/dmc-lib/src/ast/function.rs +++ b/dmc-lib/src/ast/function.rs @@ -69,6 +69,22 @@ impl Function { &self.declared_name } + pub fn declared_name_owned(&self) -> Rc { + self.declared_name.clone() + } + + pub fn declared_name_source_range(&self) -> SourceRange { + self.declared_name_source_range.clone() + } + + pub fn parameters(&self) -> &[Parameter] { + &self.parameters + } + + pub fn return_type(&self) -> Option<&TypeUse> { + self.return_type.as_ref() + } + pub fn statements(&self) -> Vec<&Statement> { self.statements.iter().collect() } diff --git a/dmc-lib/src/ast/let_statement.rs b/dmc-lib/src/ast/let_statement.rs index 98a757e..221beb5 100644 --- a/dmc-lib/src/ast/let_statement.rs +++ b/dmc-lib/src/ast/let_statement.rs @@ -44,10 +44,22 @@ impl LetStatement { } } + pub fn node_id(&self) -> NodeId { + self.node_id + } + pub fn declared_name(&self) -> &str { &self.declared_name } + pub fn declared_name_owned(&self) -> Rc { + self.declared_name.clone() + } + + pub fn declared_name_source_range(&self) -> SourceRange { + self.declared_name_source_range.clone() + } + pub fn initializer(&self) -> &Expression { &self.initializer } diff --git a/dmc-lib/src/ast/parameter.rs b/dmc-lib/src/ast/parameter.rs index 87d1e4a..4a8e135 100644 --- a/dmc-lib/src/ast/parameter.rs +++ b/dmc-lib/src/ast/parameter.rs @@ -40,6 +40,14 @@ impl Parameter { &self.declared_name } + pub fn declared_name_owned(&self) -> Rc { + self.declared_name.clone() + } + + pub fn declared_name_source_range(&self) -> SourceRange { + self.declared_name_source_range.clone() + } + pub fn scope_id(&self) -> usize { self.scope_id.unwrap() } diff --git a/dmc-lib/src/ast/type_use.rs b/dmc-lib/src/ast/type_use.rs index 1698a84..e124525 100644 --- a/dmc-lib/src/ast/type_use.rs +++ b/dmc-lib/src/ast/type_use.rs @@ -39,6 +39,10 @@ impl TypeUse { &self.declared_name } + pub fn node_id(&self) -> NodeId { + self.node_id + } + pub fn init_scopes(&mut self, symbol_table: &mut SymbolTable, container_scope: usize) { self.scope_id = Some(container_scope); for type_use in &mut self.generic_arguments { diff --git a/dmc-lib/src/lib.rs b/dmc-lib/src/lib.rs index e3f8012..542bc2a 100644 --- a/dmc-lib/src/lib.rs +++ b/dmc-lib/src/lib.rs @@ -10,6 +10,7 @@ pub mod lexer; pub mod offset_counter; pub mod parser; pub mod scope; +pub mod semantic_analysis; pub mod source_range; pub mod symbol; pub mod symbol_table; diff --git a/dmc-lib/src/semantic_analysis/collect_scopes.rs b/dmc-lib/src/semantic_analysis/collect_scopes.rs new file mode 100644 index 0000000..57f2048 --- /dev/null +++ b/dmc-lib/src/semantic_analysis/collect_scopes.rs @@ -0,0 +1,207 @@ +use crate::ast::NodeId; +use crate::ast::assign_statement::AssignStatement; +use crate::ast::binary_expression::BinaryExpression; +use crate::ast::call::Call; +use crate::ast::compilation_unit::CompilationUnit; +use crate::ast::expression::Expression; +use crate::ast::expression_statement::ExpressionStatement; +use crate::ast::extern_function::ExternFunction; +use crate::ast::function::Function; +use crate::ast::identifier::Identifier; +use crate::ast::let_statement::LetStatement; +use crate::ast::negative_expression::NegativeExpression; +use crate::ast::statement::Statement; +use crate::semantic_analysis::scope::{Scope, ScopeId}; +use std::collections::HashMap; + +pub struct ScopeCollectionContext { + scopes: Vec, + current_scope_id: Option, + nodes_to_scopes: HashMap, +} + +impl ScopeCollectionContext { + pub fn new() -> Self { + Self { + scopes: Vec::new(), + current_scope_id: None, + nodes_to_scopes: HashMap::new(), + } + } + + pub fn push_scope(&mut self, scope: Scope) -> ScopeId { + self.scopes.push(scope); + self.current_scope_id = Some(self.scopes.len() - 1); + self.current_scope_id.unwrap() // guaranteed because we just set it + } + + pub fn pop_scope(&mut self) { + let popped_scope = self.scopes.pop().unwrap(); + self.current_scope_id = popped_scope.parent_id(); + } + + pub fn current_scope_id(&self) -> Option { + self.current_scope_id + } + + pub fn nodes_to_scopes(&self) -> &HashMap { + &self.nodes_to_scopes + } + + pub fn nodes_to_scopes_mut(&mut self) -> &mut HashMap { + &mut self.nodes_to_scopes + } +} + +pub fn collect_scopes(compilation_unit: &CompilationUnit) -> ScopeCollectionContext { + let mut ctx = ScopeCollectionContext::new(); + ctx.push_scope(Scope::new(None)); + for function in compilation_unit.functions() { + collect_scopes_function(function, &mut ctx); + } + for extern_function in compilation_unit.extern_functions() { + collect_scopes_extern_function(extern_function, &mut ctx); + } + ctx.pop_scope(); + ctx +} + +fn collect_scopes_function(function: &Function, ctx: &mut ScopeCollectionContext) { + // get containing scope id + let containing_scope_id = ctx.current_scope_id().unwrap(); // guaranteed because functions are in modules + + // save function's containing scope id + ctx.nodes_to_scopes_mut() + .insert(function.node_id(), containing_scope_id); + + // push function scope + let function_scope = Scope::new(Some(containing_scope_id)); + let function_scope_id = ctx.push_scope(function_scope); + + // save return-type and parameters' scope id + for parameter in function.parameters() { + ctx.nodes_to_scopes_mut() + .insert(parameter.node_id(), function_scope_id); + } + match function.return_type() { + None => { + // no-op + } + Some(type_use) => { + ctx.nodes_to_scopes_mut() + .insert(type_use.node_id(), function_scope_id); + } + } + + // push block scope for body + let block_scope = Scope::new(Some(function_scope_id)); + ctx.push_scope(block_scope); + + for statement in function.statements() { + collect_scopes_statement(statement, ctx); + } + + ctx.pop_scope(); // block + ctx.pop_scope(); // function +} + +fn collect_scopes_extern_function( + extern_function: &ExternFunction, + ctx: &mut ScopeCollectionContext, +) { + let containing_scope_id = ctx.current_scope_id().unwrap(); + + ctx.nodes_to_scopes_mut() + .insert(extern_function.node_id(), containing_scope_id); + + let function_scope = Scope::new(Some(containing_scope_id)); + let function_scope_id = ctx.push_scope(function_scope); + + for parameter in extern_function.parameters() { + ctx.nodes_to_scopes_mut() + .insert(parameter.node_id(), function_scope_id); + } + ctx.nodes_to_scopes_mut() + .insert(extern_function.return_type().node_id(), function_scope_id); + + ctx.pop_scope(); +} + +fn collect_scopes_statement(statement: &Statement, ctx: &mut ScopeCollectionContext) { + match statement { + Statement::Let(let_statement) => collect_scopes_let_statement(let_statement, ctx), + Statement::Expression(expression_statement) => { + collect_scopes_expression_statement(expression_statement, ctx) + } + Statement::Assign(assign_statement) => { + collect_scopes_assign_statement(assign_statement, ctx) + } + } +} + +fn collect_scopes_let_statement(let_statement: &LetStatement, ctx: &mut ScopeCollectionContext) { + collect_scopes_expression(let_statement.initializer(), ctx); +} + +fn collect_scopes_expression_statement( + expression_statement: &ExpressionStatement, + ctx: &mut ScopeCollectionContext, +) { + collect_scopes_expression(expression_statement.expression(), ctx); +} + +fn collect_scopes_assign_statement( + assign_statement: &AssignStatement, + ctx: &mut ScopeCollectionContext, +) { + collect_scopes_expression(assign_statement.value(), ctx); + collect_scopes_expression(assign_statement.destination(), ctx); +} + +fn collect_scopes_expression(expression: &Expression, ctx: &mut ScopeCollectionContext) { + match expression { + Expression::Binary(binary_expression) => { + collect_scopes_binary_expression(binary_expression, ctx); + } + Expression::Negative(negative_expression) => { + collect_scopes_negative_expression(negative_expression, ctx); + } + Expression::Call(call) => { + collect_scopes_call(call, ctx); + } + Expression::Identifier(identifier) => { + collect_scopes_identifier(identifier, ctx); + } + Expression::Integer(_) => {} + Expression::Double(_) => {} + Expression::String(_) => {} + } +} + +fn collect_scopes_binary_expression( + binary_expression: &BinaryExpression, + ctx: &mut ScopeCollectionContext, +) { + collect_scopes_expression(binary_expression.lhs(), ctx); + collect_scopes_expression(binary_expression.rhs(), ctx); +} + +fn collect_scopes_negative_expression( + negative_expression: &NegativeExpression, + ctx: &mut ScopeCollectionContext, +) { + collect_scopes_expression(negative_expression.operand(), ctx); +} + +fn collect_scopes_call(call: &Call, ctx: &mut ScopeCollectionContext) { + for argument in call.arguments() { + collect_scopes_expression(argument, ctx); + } + collect_scopes_expression(call.callee(), ctx); +} + +fn collect_scopes_identifier(identifier: &Identifier, ctx: &mut ScopeCollectionContext) { + let current_scope_id = ctx.current_scope_id().unwrap(); // if this fails, we tried to do this without any context + ctx.nodes_to_scopes_mut() + .insert(identifier.node_id(), current_scope_id); +} diff --git a/dmc-lib/src/semantic_analysis/collect_symbols.rs b/dmc-lib/src/semantic_analysis/collect_symbols.rs new file mode 100644 index 0000000..2531bef --- /dev/null +++ b/dmc-lib/src/semantic_analysis/collect_symbols.rs @@ -0,0 +1,122 @@ +use crate::ast::NodeId; +use crate::ast::compilation_unit::CompilationUnit; +use crate::ast::extern_function::ExternFunction; +use crate::ast::function::Function; +use crate::ast::parameter::Parameter; +use crate::diagnostic::Diagnostics; +use crate::semantic_analysis::diagnostic_helpers::symbol_already_declared; +use crate::semantic_analysis::scope::{Scope, ScopeId}; +use crate::semantic_analysis::symbol::{FunctionSymbol, ParameterSymbol, Symbol}; +use std::collections::HashMap; + +pub struct SymbolCollectionContext { + scopes: Vec, + nodes_to_scopes: HashMap, + symbols: Vec, + diagnostics: Diagnostics, +} + +impl SymbolCollectionContext { + pub fn new(scopes: Vec, nodes_to_scopes: HashMap) -> Self { + Self { + scopes, + nodes_to_scopes, + symbols: Vec::new(), + diagnostics: Diagnostics::new(), + } + } + + pub fn find_symbol_in_scope_for(&self, name: &str, node_id: NodeId) -> Option<&Symbol> { + let scope_id = self.nodes_to_scopes[&node_id]; + let scope = &self.scopes[scope_id]; + if let Some(symbol_id) = scope.symbols().get(name) { + Some(&self.symbols[*symbol_id]) + } else { + None + } + } + + pub fn insert_symbol(&mut self, symbol: Symbol, node_id: NodeId) { + let declared_name = symbol.declared_name_owned(); + self.symbols.push(symbol); + let symbol_id = self.symbols.len() - 1; + let scope_id = self.nodes_to_scopes[&node_id]; + let scope = &mut self.scopes[scope_id]; + scope.symbols_mut().insert(declared_name, symbol_id); + } + + pub fn diagnostics_mut(&mut self) -> &mut Diagnostics { + &mut self.diagnostics + } +} + +pub fn collect_symbols(compilation_unit: &CompilationUnit, ctx: &mut SymbolCollectionContext) { + for function in compilation_unit.functions() { + collect_symbols_function(function, ctx); + } + + for extern_function in compilation_unit.extern_functions() { + collect_symbols_extern_function(extern_function, ctx); + } +} + +fn collect_symbols_function(function: &Function, ctx: &mut SymbolCollectionContext) { + // function itself + let function_symbol = Symbol::Function(FunctionSymbol::new( + function.declared_name_owned(), + Some(function.declared_name_source_range()), + false, + )); + + // insert + if let Some(already_declared) = + ctx.find_symbol_in_scope_for(function.declared_name(), function.node_id()) + { + let diagnostic = symbol_already_declared(already_declared, &function_symbol); + ctx.diagnostics_mut().push(diagnostic); + } else { + ctx.insert_symbol(function_symbol, function.node_id()); + } + + // parameters + for parameter in function.parameters() { + collect_symbols_parameter(parameter, ctx); + } + + // n.b. do not do statements yet, because variables are declared and resolved in the resolution pass +} + +fn collect_symbols_extern_function( + extern_function: &ExternFunction, + ctx: &mut SymbolCollectionContext, +) { + // function itself + let function_symbol = Symbol::Function(FunctionSymbol::new( + extern_function.declared_name_owned(), + Some(extern_function.declared_name_source_range()), + true, + )); + + // insert function symbol + if let Some(already_declared) = + ctx.find_symbol_in_scope_for(extern_function.declared_name(), extern_function.node_id()) + { + let diagnostic = symbol_already_declared(already_declared, &function_symbol); + ctx.diagnostics_mut().push(diagnostic); + } else { + ctx.insert_symbol(function_symbol, extern_function.node_id()); + } + + // parameters + for parameter in extern_function.parameters() { + collect_symbols_parameter(parameter, ctx); + } +} + +fn collect_symbols_parameter(parameter: &Parameter, ctx: &mut SymbolCollectionContext) { + let parameter_symbol = ParameterSymbol::new( + parameter.declared_name_owned(), + Some(parameter.declared_name_source_range()), + ); + ctx.insert_symbol(Symbol::Parameter(parameter_symbol), parameter.node_id()); +} diff --git a/dmc-lib/src/semantic_analysis/diagnostic_helpers.rs b/dmc-lib/src/semantic_analysis/diagnostic_helpers.rs new file mode 100644 index 0000000..25e9e43 --- /dev/null +++ b/dmc-lib/src/semantic_analysis/diagnostic_helpers.rs @@ -0,0 +1,30 @@ +use crate::diagnostic::{Diagnostic, SecondaryLabel}; +use crate::semantic_analysis::symbol::Symbol; + +pub fn symbol_already_declared(already_declared: &Symbol, would_insert: &Symbol) -> Diagnostic { + let secondary_label = if let Some(source_range) = already_declared.source_range() { + Some(SecondaryLabel::new( + source_range.start(), + source_range.start(), + Some("Symbol already declared here".to_string()), + )) + } else { + None + }; + + let would_insert_source_range = would_insert.source_range().unwrap(); + let diagnostic = Diagnostic::new( + &format!( + "Symbol {} already declared in current scope.", + would_insert.declared_name() + ), + would_insert_source_range.start(), + would_insert_source_range.end(), + ); + + if let Some(secondary_label) = secondary_label { + diagnostic.with_secondary_labels(&[secondary_label]) + } else { + diagnostic + } +} diff --git a/dmc-lib/src/semantic_analysis/mod.rs b/dmc-lib/src/semantic_analysis/mod.rs new file mode 100644 index 0000000..0e178a2 --- /dev/null +++ b/dmc-lib/src/semantic_analysis/mod.rs @@ -0,0 +1,7 @@ +mod collect_scopes; +mod collect_symbols; +mod diagnostic_helpers; +mod resolve_names; +mod scope; +mod semantic_context; +mod symbol; diff --git a/dmc-lib/src/semantic_analysis/resolve_names.rs b/dmc-lib/src/semantic_analysis/resolve_names.rs new file mode 100644 index 0000000..816982b --- /dev/null +++ b/dmc-lib/src/semantic_analysis/resolve_names.rs @@ -0,0 +1,266 @@ +use crate::ast::NodeId; +use crate::ast::assign_statement::AssignStatement; +use crate::ast::binary_expression::BinaryExpression; +use crate::ast::call::Call; +use crate::ast::compilation_unit::CompilationUnit; +use crate::ast::expression::Expression; +use crate::ast::expression_statement::ExpressionStatement; +use crate::ast::function::Function; +use crate::ast::identifier::Identifier; +use crate::ast::let_statement::LetStatement; +use crate::ast::negative_expression::NegativeExpression; +use crate::ast::statement::Statement; +use crate::diagnostic::Diagnostics; +use crate::diagnostic_factories::symbol_not_found; +use crate::semantic_analysis::scope::{Scope, ScopeId}; +use crate::semantic_analysis::symbol::{Symbol, SymbolId, VariableSymbol}; +use std::collections::HashMap; + +pub struct NameResolutionContext { + scopes: Vec, + symbols: Vec, + nodes_to_scopes: HashMap, + nodes_to_symbols: HashMap, + diagnostics: Diagnostics, +} + +impl NameResolutionContext { + pub fn new( + scopes: Vec, + symbols: Vec, + nodes_to_scopes: HashMap, + ) -> Self { + Self { + scopes, + symbols, + nodes_to_scopes, + nodes_to_symbols: HashMap::new(), + diagnostics: Diagnostics::new(), + } + } + + pub fn scopes(&self) -> &[Scope] { + &self.scopes + } + + pub fn scopes_mut(&mut self) -> &mut Vec { + &mut self.scopes + } + + pub fn symbols(&self) -> &[Symbol] { + &self.symbols + } + + pub fn symbols_mut(&mut self) -> &mut Vec { + &mut self.symbols + } + + pub fn nodes_to_scopes(&self) -> &HashMap { + &self.nodes_to_scopes + } + + pub fn nodes_to_symbols(&self) -> &HashMap { + &self.nodes_to_symbols + } + + pub fn nodes_to_symbols_mut(&mut self) -> &mut HashMap { + &mut self.nodes_to_symbols + } + + pub fn diagnostics_mut(&mut self) -> &mut Diagnostics { + &mut self.diagnostics + } +} + +enum StatementResolutionPhase { + Static, +} + +#[derive(Copy, Clone)] +enum ExpressionResolutionPhase { + Static, +} + +pub fn resolve_names(compilation_unit: &CompilationUnit, ctx: &mut NameResolutionContext) { + for function in compilation_unit.functions() { + resolve_names_function(function, ctx); + } +} + +fn resolve_names_function(function: &Function, ctx: &mut NameResolutionContext) { + for statement in function.statements() { + resolve_names_statement(statement, ctx, StatementResolutionPhase::Static); + } +} + +fn resolve_names_statement( + statement: &Statement, + ctx: &mut NameResolutionContext, + phase: StatementResolutionPhase, +) { + match statement { + Statement::Let(let_statement) => { + resolve_names_let_statement(let_statement, ctx, phase); + } + Statement::Expression(expression_statement) => { + resolve_names_expression_statement(expression_statement, ctx); + } + Statement::Assign(assign_statement) => { + resolve_names_assign_statement(assign_statement, ctx); + } + } +} + +fn resolve_names_let_statement( + let_statement: &LetStatement, + ctx: &mut NameResolutionContext, + phase: StatementResolutionPhase, +) { + match phase { + StatementResolutionPhase::Static => { + // first, resolve the initializer expression + resolve_names_expression( + let_statement.initializer(), + ctx, + ExpressionResolutionPhase::Static, + ); + // now add the declared name to the ctx so later usages can access it + let scope_id = ctx.nodes_to_scopes()[&let_statement.node_id()]; + let symbol = Symbol::Variable(VariableSymbol::new( + let_statement.declared_name_owned(), + Some(let_statement.declared_name_source_range()), + )); + // the following could be a method, but this is probably the only place in this phase + // where we are pushing symbols still + ctx.symbols_mut().push(symbol); + let symbol_id = ctx.symbols().len() - 1; + let scope = &mut ctx.scopes_mut()[scope_id]; + scope + .symbols_mut() + .insert(let_statement.declared_name_owned(), symbol_id); + } + } +} + +fn resolve_names_expression_statement( + expression_statement: &ExpressionStatement, + ctx: &mut NameResolutionContext, +) { + resolve_names_expression( + expression_statement.expression(), + ctx, + ExpressionResolutionPhase::Static, + ); +} + +fn resolve_names_assign_statement( + assign_statement: &AssignStatement, + ctx: &mut NameResolutionContext, +) { + resolve_names_expression( + assign_statement.value(), + ctx, + ExpressionResolutionPhase::Static, + ); + resolve_names_expression( + assign_statement.destination(), + ctx, + ExpressionResolutionPhase::Static, + ); +} + +fn resolve_names_expression( + expression: &Expression, + ctx: &mut NameResolutionContext, + phase: ExpressionResolutionPhase, +) { + match expression { + Expression::Binary(binary_expression) => { + resolve_names_binary_expression(binary_expression, ctx, phase); + } + Expression::Negative(negative_expression) => { + resolve_names_negative_expression(negative_expression, ctx, phase); + } + Expression::Call(call) => { + resolve_names_call(call, ctx, phase); + } + Expression::Identifier(identifier) => { + resolve_names_identifier(identifier, ctx, phase); + } + Expression::Integer(_) => {} + Expression::Double(_) => {} + Expression::String(_) => {} + } +} + +fn resolve_names_binary_expression( + binary_expression: &BinaryExpression, + ctx: &mut NameResolutionContext, + phase: ExpressionResolutionPhase, +) { + resolve_names_expression(binary_expression.lhs(), ctx, phase); + resolve_names_expression(binary_expression.rhs(), ctx, phase); +} + +fn resolve_names_negative_expression( + negative_expression: &NegativeExpression, + ctx: &mut NameResolutionContext, + phase: ExpressionResolutionPhase, +) { + resolve_names_expression(negative_expression.operand(), ctx, phase); +} + +fn resolve_names_call( + call: &Call, + ctx: &mut NameResolutionContext, + phase: ExpressionResolutionPhase, +) { + for argument in call.arguments() { + resolve_names_expression(argument, ctx, phase); + } + resolve_names_expression(call.callee(), ctx, phase); +} + +fn resolve_names_identifier( + identifier: &Identifier, + ctx: &mut NameResolutionContext, + phase: ExpressionResolutionPhase, +) { + match phase { + ExpressionResolutionPhase::Static => { + let scope_id = ctx.nodes_to_scopes()[&identifier.scope_id()]; + let mut maybe_scope = Some(&ctx.scopes()[scope_id]); + let mut found_symbol_id: Option = None; + while let Some(scope) = maybe_scope { + let maybe_symbol_id = scope.symbols().get(identifier.name()).cloned(); // cloned because ctx cannot be borrowed both immutably and mutably + match maybe_symbol_id { + None => { + maybe_scope = match scope.parent_id() { + None => None, + Some(parent_id) => Some(&ctx.scopes()[parent_id]), + } + } + Some(symbol_id) => { + found_symbol_id = Some(symbol_id); + break; + } + } + } + match found_symbol_id { + None => { + ctx.diagnostics_mut().push(symbol_not_found( + identifier.name(), + identifier.source_range(), + )); + } + Some(symbol_id) => { + // In the future, we may want to differentiate between different types of + // symbols, but for now, let's just assume that we always refer to the + // nearest symbol in the ancestor scope chain. + ctx.nodes_to_symbols_mut() + .insert(identifier.node_id(), symbol_id); + } + } + } + } +} diff --git a/dmc-lib/src/semantic_analysis/scope.rs b/dmc-lib/src/semantic_analysis/scope.rs new file mode 100644 index 0000000..fecc544 --- /dev/null +++ b/dmc-lib/src/semantic_analysis/scope.rs @@ -0,0 +1,31 @@ +use crate::semantic_analysis::symbol::SymbolId; +use std::collections::HashMap; +use std::rc::Rc; + +pub type ScopeId = usize; + +pub struct Scope { + parent_id: Option, + symbols: HashMap, SymbolId>, +} + +impl Scope { + pub fn new(parent_id: Option) -> Self { + Self { + parent_id, + symbols: HashMap::new(), + } + } + + pub fn parent_id(&self) -> Option { + self.parent_id + } + + pub fn symbols(&self) -> &HashMap, SymbolId> { + &self.symbols + } + + pub fn symbols_mut(&mut self) -> &mut HashMap, SymbolId> { + &mut self.symbols + } +} diff --git a/dmc-lib/src/semantic_analysis/semantic_context.rs b/dmc-lib/src/semantic_analysis/semantic_context.rs new file mode 100644 index 0000000..5122b32 --- /dev/null +++ b/dmc-lib/src/semantic_analysis/semantic_context.rs @@ -0,0 +1,10 @@ +use crate::ast::NodeId; +use crate::semantic_analysis::scope::{Scope, ScopeId}; +use std::collections::HashMap; + +pub struct SemanticContext { + scopes: Vec, + node_scopes: HashMap, +} + +impl SemanticContext {} diff --git a/dmc-lib/src/semantic_analysis/symbol.rs b/dmc-lib/src/semantic_analysis/symbol.rs new file mode 100644 index 0000000..a99d978 --- /dev/null +++ b/dmc-lib/src/semantic_analysis/symbol.rs @@ -0,0 +1,110 @@ +use crate::source_range::SourceRange; +use std::rc::Rc; + +pub type SymbolId = usize; + +pub enum Symbol { + Function(FunctionSymbol), + Parameter(ParameterSymbol), + Variable(VariableSymbol), +} + +impl Symbol { + pub fn declared_name(&self) -> &str { + match self { + Symbol::Function(function_symbol) => function_symbol.declared_name(), + Symbol::Parameter(parameter_symbol) => parameter_symbol.declared_name(), + Symbol::Variable(variable_symbol) => variable_symbol.declared_name(), + } + } + + pub fn declared_name_owned(&self) -> Rc { + match self { + Symbol::Function(function_symbol) => function_symbol.declared_name_owned(), + Symbol::Parameter(parameter_symbol) => parameter_symbol.declared_name_owned(), + Symbol::Variable(variable_symbol) => variable_symbol.declared_name_owned(), + } + } + + pub fn source_range(&self) -> Option<&SourceRange> { + match self { + Symbol::Function(function_symbol) => function_symbol.source_range(), + Symbol::Parameter(parameter_symbol) => parameter_symbol.source_range(), + Symbol::Variable(variable_symbol) => variable_symbol.source_range(), + } + } +} + +pub struct FunctionSymbol { + name: Rc, + source_range: Option, + is_extern: bool, +} + +impl FunctionSymbol { + pub fn new(name: Rc, source_range: Option, is_extern: bool) -> Self { + Self { + name, + source_range, + is_extern, + } + } + + pub fn declared_name(&self) -> &str { + &self.name + } + + pub fn declared_name_owned(&self) -> Rc { + self.name.clone() + } + + pub fn source_range(&self) -> Option<&SourceRange> { + self.source_range.as_ref() + } +} + +pub struct ParameterSymbol { + name: Rc, + source_range: Option, +} + +impl ParameterSymbol { + pub fn new(name: Rc, source_range: Option) -> Self { + Self { name, source_range } + } + + pub fn declared_name(&self) -> &str { + &self.name + } + + pub fn declared_name_owned(&self) -> Rc { + self.name.clone() + } + + pub fn source_range(&self) -> Option<&SourceRange> { + self.source_range.as_ref() + } +} + +pub struct VariableSymbol { + name: Rc, + source_range: Option, +} + +impl VariableSymbol { + pub fn new(name: Rc, source_range: Option) -> Self { + Self { name, source_range } + } + + pub fn declared_name(&self) -> &str { + &self.name + } + + pub fn declared_name_owned(&self) -> Rc { + self.name.clone() + } + + pub fn source_range(&self) -> Option<&SourceRange> { + self.source_range.as_ref() + } +}