Add semantic analysis module and first steps.
This commit is contained in:
parent
0b270c186b
commit
d5bfe9ad28
@ -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);
|
||||
|
||||
@ -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<str> {
|
||||
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);
|
||||
|
||||
|
||||
@ -69,6 +69,22 @@ impl Function {
|
||||
&self.declared_name
|
||||
}
|
||||
|
||||
pub fn declared_name_owned(&self) -> Rc<str> {
|
||||
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()
|
||||
}
|
||||
|
||||
@ -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<str> {
|
||||
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
|
||||
}
|
||||
|
||||
@ -40,6 +40,14 @@ impl Parameter {
|
||||
&self.declared_name
|
||||
}
|
||||
|
||||
pub fn declared_name_owned(&self) -> Rc<str> {
|
||||
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()
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
207
dmc-lib/src/semantic_analysis/collect_scopes.rs
Normal file
207
dmc-lib/src/semantic_analysis/collect_scopes.rs
Normal file
@ -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<Scope>,
|
||||
current_scope_id: Option<ScopeId>,
|
||||
nodes_to_scopes: HashMap<NodeId, ScopeId>,
|
||||
}
|
||||
|
||||
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<ScopeId> {
|
||||
self.current_scope_id
|
||||
}
|
||||
|
||||
pub fn nodes_to_scopes(&self) -> &HashMap<NodeId, ScopeId> {
|
||||
&self.nodes_to_scopes
|
||||
}
|
||||
|
||||
pub fn nodes_to_scopes_mut(&mut self) -> &mut HashMap<NodeId, ScopeId> {
|
||||
&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);
|
||||
}
|
||||
122
dmc-lib/src/semantic_analysis/collect_symbols.rs
Normal file
122
dmc-lib/src/semantic_analysis/collect_symbols.rs
Normal file
@ -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<Scope>,
|
||||
nodes_to_scopes: HashMap<NodeId, ScopeId>,
|
||||
symbols: Vec<Symbol>,
|
||||
diagnostics: Diagnostics,
|
||||
}
|
||||
|
||||
impl SymbolCollectionContext {
|
||||
pub fn new(scopes: Vec<Scope>, nodes_to_scopes: HashMap<NodeId, ScopeId>) -> 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());
|
||||
}
|
||||
30
dmc-lib/src/semantic_analysis/diagnostic_helpers.rs
Normal file
30
dmc-lib/src/semantic_analysis/diagnostic_helpers.rs
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
7
dmc-lib/src/semantic_analysis/mod.rs
Normal file
7
dmc-lib/src/semantic_analysis/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod collect_scopes;
|
||||
mod collect_symbols;
|
||||
mod diagnostic_helpers;
|
||||
mod resolve_names;
|
||||
mod scope;
|
||||
mod semantic_context;
|
||||
mod symbol;
|
||||
266
dmc-lib/src/semantic_analysis/resolve_names.rs
Normal file
266
dmc-lib/src/semantic_analysis/resolve_names.rs
Normal file
@ -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<Scope>,
|
||||
symbols: Vec<Symbol>,
|
||||
nodes_to_scopes: HashMap<NodeId, ScopeId>,
|
||||
nodes_to_symbols: HashMap<NodeId, SymbolId>,
|
||||
diagnostics: Diagnostics,
|
||||
}
|
||||
|
||||
impl NameResolutionContext {
|
||||
pub fn new(
|
||||
scopes: Vec<Scope>,
|
||||
symbols: Vec<Symbol>,
|
||||
nodes_to_scopes: HashMap<NodeId, ScopeId>,
|
||||
) -> 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<Scope> {
|
||||
&mut self.scopes
|
||||
}
|
||||
|
||||
pub fn symbols(&self) -> &[Symbol] {
|
||||
&self.symbols
|
||||
}
|
||||
|
||||
pub fn symbols_mut(&mut self) -> &mut Vec<Symbol> {
|
||||
&mut self.symbols
|
||||
}
|
||||
|
||||
pub fn nodes_to_scopes(&self) -> &HashMap<NodeId, ScopeId> {
|
||||
&self.nodes_to_scopes
|
||||
}
|
||||
|
||||
pub fn nodes_to_symbols(&self) -> &HashMap<NodeId, SymbolId> {
|
||||
&self.nodes_to_symbols
|
||||
}
|
||||
|
||||
pub fn nodes_to_symbols_mut(&mut self) -> &mut HashMap<NodeId, SymbolId> {
|
||||
&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<SymbolId> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
dmc-lib/src/semantic_analysis/scope.rs
Normal file
31
dmc-lib/src/semantic_analysis/scope.rs
Normal file
@ -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<ScopeId>,
|
||||
symbols: HashMap<Rc<str>, SymbolId>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(parent_id: Option<ScopeId>) -> Self {
|
||||
Self {
|
||||
parent_id,
|
||||
symbols: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent_id(&self) -> Option<ScopeId> {
|
||||
self.parent_id
|
||||
}
|
||||
|
||||
pub fn symbols(&self) -> &HashMap<Rc<str>, SymbolId> {
|
||||
&self.symbols
|
||||
}
|
||||
|
||||
pub fn symbols_mut(&mut self) -> &mut HashMap<Rc<str>, SymbolId> {
|
||||
&mut self.symbols
|
||||
}
|
||||
}
|
||||
10
dmc-lib/src/semantic_analysis/semantic_context.rs
Normal file
10
dmc-lib/src/semantic_analysis/semantic_context.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use crate::ast::NodeId;
|
||||
use crate::semantic_analysis::scope::{Scope, ScopeId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct SemanticContext {
|
||||
scopes: Vec<Scope>,
|
||||
node_scopes: HashMap<NodeId, ScopeId>,
|
||||
}
|
||||
|
||||
impl SemanticContext {}
|
||||
110
dmc-lib/src/semantic_analysis/symbol.rs
Normal file
110
dmc-lib/src/semantic_analysis/symbol.rs
Normal file
@ -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<str> {
|
||||
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<str>,
|
||||
source_range: Option<SourceRange>,
|
||||
is_extern: bool,
|
||||
}
|
||||
|
||||
impl FunctionSymbol {
|
||||
pub fn new(name: Rc<str>, source_range: Option<SourceRange>, 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<str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn source_range(&self) -> Option<&SourceRange> {
|
||||
self.source_range.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ParameterSymbol {
|
||||
name: Rc<str>,
|
||||
source_range: Option<SourceRange>,
|
||||
}
|
||||
|
||||
impl ParameterSymbol {
|
||||
pub fn new(name: Rc<str>, source_range: Option<SourceRange>) -> Self {
|
||||
Self { name, source_range }
|
||||
}
|
||||
|
||||
pub fn declared_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn declared_name_owned(&self) -> Rc<str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn source_range(&self) -> Option<&SourceRange> {
|
||||
self.source_range.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VariableSymbol {
|
||||
name: Rc<str>,
|
||||
source_range: Option<SourceRange>,
|
||||
}
|
||||
|
||||
impl VariableSymbol {
|
||||
pub fn new(name: Rc<str>, source_range: Option<SourceRange>) -> Self {
|
||||
Self { name, source_range }
|
||||
}
|
||||
|
||||
pub fn declared_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn declared_name_owned(&self) -> Rc<str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn source_range(&self) -> Option<&SourceRange> {
|
||||
self.source_range.as_ref()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user