Refactor name-analysis code into separate module/files.
This commit is contained in:
parent
6ab9efa8fd
commit
4224055860
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -1 +0,0 @@
|
||||
pub mod name_analysis;
|
@ -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<usize>,
|
||||
symbols: HashMap<String, Symbol>,
|
||||
debug_name: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SymbolTable {
|
||||
scopes: Vec<Scope>,
|
||||
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<String>,
|
||||
}
|
||||
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
resolve_expression(file_id, &mut call_statement.0, symbol_table)
|
||||
}
|
||||
|
||||
fn resolve_expression(
|
||||
file_id: usize,
|
||||
expression: &mut Expression,
|
||||
symbol_table: &mut SymbolTable,
|
||||
) -> Vec<Diagnostic<usize>> {
|
||||
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<Diagnostic<usize>> {
|
||||
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))
|
||||
],
|
||||
}
|
||||
}
|
3
src/diagnostic/mod.rs
Normal file
3
src/diagnostic/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
use codespan_reporting::diagnostic::Diagnostic;
|
||||
|
||||
pub type DmDiagnostic = Diagnostic<usize>;
|
@ -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;
|
||||
|
38
src/name_analysis/fqn_context.rs
Normal file
38
src/name_analysis/fqn_context.rs
Normal file
@ -0,0 +1,38 @@
|
||||
pub(super) struct FqnContext {
|
||||
stack: Vec<String>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
154
src/name_analysis/gather.rs
Normal file
154
src/name_analysis/gather.rs
Normal file
@ -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());
|
||||
}
|
76
src/name_analysis/mod.rs
Normal file
76
src/name_analysis/mod.rs
Normal file
@ -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<DmDiagnostic>,
|
||||
}
|
||||
|
||||
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<Vec<DmDiagnostic>> for DiagnosticsContainer {
|
||||
fn into(self) -> Vec<DmDiagnostic> {
|
||||
self.diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
pub fn analyze_names(
|
||||
file_id: usize,
|
||||
compilation_unit: &mut CompilationUnit,
|
||||
symbol_table: &mut SymbolTable,
|
||||
) -> Vec<DmDiagnostic> {
|
||||
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()
|
||||
}
|
137
src/name_analysis/resolve.rs
Normal file
137
src/name_analysis/resolve.rs
Normal file
@ -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))
|
||||
}),
|
||||
}
|
||||
}
|
59
src/name_analysis/symbol.rs
Normal file
59
src/name_analysis/symbol.rs
Normal file
@ -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
|
||||
)
|
||||
}
|
||||
}
|
90
src/name_analysis/symbol_table.rs
Normal file
90
src/name_analysis/symbol_table.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use crate::name_analysis::symbol::Symbol;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
|
||||
pub struct Scope {
|
||||
parent: Option<usize>,
|
||||
symbols: HashMap<String, Symbol>,
|
||||
debug_name: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SymbolTable {
|
||||
scopes: Vec<Scope>,
|
||||
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(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user