deimos-lang/src/name_analysis/mod.rs
2025-05-16 11:21:44 -05:00

127 lines
3.6 KiB
Rust

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()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::build::build_ast;
use crate::parser::{DeimosParser, Rule};
use codespan_reporting::files::SimpleFiles;
use codespan_reporting::term;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use pest::Parser;
fn assert_no_diagnostics(src: &str) {
let parse_result = DeimosParser::parse(Rule::CompilationUnit, src);
if let Err(err) = &parse_result {
panic!("{:?}", err);
}
let compilation_unit_pair = parse_result.unwrap().next().unwrap();
let mut ast = build_ast(compilation_unit_pair);
let mut files = SimpleFiles::new();
let test_file_id = files.add("test.dm", src);
let mut symbol_table = SymbolTable::new();
let diagnostics = analyze_names(test_file_id, &mut ast, &mut symbol_table);
if !diagnostics.is_empty() {
let writer = StandardStream::stderr(ColorChoice::Always);
let config = term::Config::default();
for diagnostic in &diagnostics {
term::emit(&mut writer.lock(), &config, &files, &diagnostic).unwrap();
}
eprintln!("{}", symbol_table);
panic!("Diagnostics was not empty!");
}
}
#[test]
fn params_seen() {
assert_no_diagnostics(
r#"
fn main(args: Array<String>) {
let x = args;
}
"#,
)
}
}