Refactor name-analysis for multiple files; set up (failing) test case for multiple files.

This commit is contained in:
Jesse Brault 2025-05-18 08:00:32 -05:00
parent 3026d22750
commit 0c18b976d7
7 changed files with 122 additions and 82 deletions

View File

@ -15,8 +15,8 @@ fn expect_and_use<T>(
f(file_id, pair) f(file_id, pair)
} }
pub fn build_ast(file_id: usize, compilation_unit_pair: Pair<Rule>) -> CompilationUnit { pub fn build_ast(file_name: &str, file_id: usize, compilation_unit_pair: Pair<Rule>) -> CompilationUnit {
build_compilation_unit(file_id, compilation_unit_pair) build_compilation_unit(file_name, file_id, compilation_unit_pair)
} }
fn build_identifier(file_id: usize, identifier_pair: Pair<Rule>) -> Identifier { fn build_identifier(file_id: usize, identifier_pair: Pair<Rule>) -> Identifier {
@ -293,7 +293,7 @@ fn build_references(file_id: usize, ref_list_pair: Pair<Rule>) -> References {
) )
} }
fn build_compilation_unit(file_id: usize, compilation_unit_pair: Pair<Rule>) -> CompilationUnit { fn build_compilation_unit(file_name: &str, file_id: usize, compilation_unit_pair: Pair<Rule>) -> CompilationUnit {
let mut namespace = None; let mut namespace = None;
let mut declarations = vec![]; let mut declarations = vec![];
@ -311,6 +311,8 @@ fn build_compilation_unit(file_id: usize, compilation_unit_pair: Pair<Rule>) ->
} }
CompilationUnit { CompilationUnit {
file_name: file_name.to_string(),
file_id,
namespace, namespace,
declarations, declarations,
} }
@ -1558,7 +1560,7 @@ mod tests {
if pairs.as_str().trim() != src.trim() { if pairs.as_str().trim() != src.trim() {
panic!("Parsing did not consume entire input."); panic!("Parsing did not consume entire input.");
} }
let ast = build_ast(0, pairs.next().unwrap()); let ast = build_ast("test.dm", 0, pairs.next().unwrap());
} }
} }

View File

@ -287,6 +287,8 @@ impl Default for References {
#[derive(Debug)] #[derive(Debug)]
pub struct CompilationUnit { pub struct CompilationUnit {
pub file_name: String,
pub file_id: usize,
pub namespace: Option<FullyQualifiedName>, pub namespace: Option<FullyQualifiedName>,
pub declarations: Vec<ModuleLevelDeclaration>, pub declarations: Vec<ModuleLevelDeclaration>,
} }

View File

@ -45,11 +45,9 @@ fn main() {
} }
} }
Commands::NameAnalysis { paths } => { Commands::NameAnalysis { paths } => {
for path in paths { let result = name_analysis(&paths);
let result = name_analysis(&path); if let Err(e) = result {
if let Err(e) = result { eprintln!("{}", e)
eprintln!("{}", e);
}
} }
} }
} }

View File

@ -6,33 +6,41 @@ use deimos::name_analysis::analyze_names;
use deimos::name_analysis::symbol_table::SymbolTable; use deimos::name_analysis::symbol_table::SymbolTable;
use deimos::parser::{DeimosParser, Rule}; use deimos::parser::{DeimosParser, Rule};
use pest::Parser; use pest::Parser;
use std::path::Path; use std::path::PathBuf;
pub fn name_analysis(path: &Path) -> Result<(), Box<dyn std::error::Error>> { pub fn name_analysis(paths: &Vec<PathBuf>) -> Result<(), Box<dyn std::error::Error>> {
let src = std::fs::read_to_string(path).unwrap(); let mut compilation_units = vec![];
let parse_result = DeimosParser::parse(Rule::CompilationUnit, &src);
let mut files = SimpleFiles::new(); let mut files = SimpleFiles::new();
let file_id = files.add(path.display().to_string(), &src);
for path in paths {
let src = std::fs::read_to_string(path).unwrap();
let parse_result = DeimosParser::parse(Rule::CompilationUnit, &src);
let file_id = files.add(path.display().to_string(), src.clone()); // I don't love this clone
match parse_result { match parse_result {
Ok(mut pairs) => { Ok(mut pairs) => {
let compilation_unit_pair = pairs.next().unwrap(); let compilation_unit_pair = pairs.next().unwrap();
let mut compilation_unit = build_ast(file_id, compilation_unit_pair); let compilation_unit = build_ast(&path.display().to_string(), file_id, compilation_unit_pair);
let mut symbol_table = SymbolTable::new(); compilation_units.push(compilation_unit);
let diagnostics = analyze_names(file_id, &mut compilation_unit, &mut symbol_table); Ok::<(), Box<dyn std::error::Error>>(())
if diagnostics.is_empty() {
println!("Name analysis complete.");
println!("Symbol table\n-------\n{}", symbol_table);
Ok(())
} else {
let writer = StandardStream::stderr(ColorChoice::Always);
let config = term::Config::default();
for diagnostic in diagnostics {
term::emit(&mut writer.lock(), &config, &files, &diagnostic)?;
}
Ok(())
} }
} Err(e) => Err(e.into()),
Err(e) => Err(e.into()), }?;
} }
let mut symbol_table = SymbolTable::new();
let diagnostics = analyze_names(&mut compilation_units, &mut symbol_table);
if diagnostics.is_empty() {
println!("Name analysis complete.");
println!("Symbol table\n-------\n{}", symbol_table);
} else {
let writer = StandardStream::stderr(ColorChoice::Always);
let config = term::Config::default();
for diagnostic in diagnostics {
term::emit(&mut writer.lock(), &config, &files, &diagnostic)?;
}
}
Ok(())
} }

View File

@ -11,7 +11,7 @@ pub fn pretty_print_parse(path: &PathBuf) {
match parse_result { match parse_result {
Ok(mut pairs) => { Ok(mut pairs) => {
let compilation_unit_pair = pairs.next().unwrap(); let compilation_unit_pair = pairs.next().unwrap();
let compilation_unit = build_ast(0, compilation_unit_pair); let compilation_unit = build_ast(&path.display().to_string(), 0, compilation_unit_pair);
let mut indent_writer = IndentWriter::new(0, " ", Box::new(std::io::stdout())); let mut indent_writer = IndentWriter::new(0, " ", Box::new(std::io::stdout()));
compilation_unit compilation_unit
.pretty_print(&mut indent_writer) .pretty_print(&mut indent_writer)

View File

@ -11,7 +11,7 @@ pub fn unparse(path: &PathBuf) {
match parse_result { match parse_result {
Ok(mut pairs) => { Ok(mut pairs) => {
let compilation_unit_pair = pairs.next().unwrap(); let compilation_unit_pair = pairs.next().unwrap();
let compilation_unit = build_ast(0, compilation_unit_pair); let compilation_unit = build_ast(&path.display().to_string(), 0, compilation_unit_pair);
let mut writer = IndentWriter::new(0, " ", Box::new(std::io::stdout())); let mut writer = IndentWriter::new(0, " ", Box::new(std::io::stdout()));
compilation_unit compilation_unit
.unparse(&mut writer) .unparse(&mut writer)

View File

@ -1,12 +1,10 @@
use crate::ast::named::Named; use crate::ast::named::Named;
use crate::ast::{CompilationUnit, Identifier}; use crate::ast::CompilationUnit;
use crate::diagnostic::DmDiagnostic; use crate::diagnostic::DmDiagnostic;
use crate::name_analysis::fqn_context::FqnContext; use crate::name_analysis::fqn_context::FqnContext;
use crate::name_analysis::gather::gather_module_level_declaration; use crate::name_analysis::gather::gather_module_level_declaration;
use crate::name_analysis::resolve::resolve_module_level_declaration; use crate::name_analysis::resolve::resolve_module_level_declaration;
use crate::name_analysis::symbol_table::SymbolTable; use crate::name_analysis::symbol_table::SymbolTable;
use codespan_reporting::diagnostic::{Diagnostic, Label};
use log::debug;
mod fqn_context; mod fqn_context;
mod gather; mod gather;
@ -15,14 +13,12 @@ pub mod symbol;
pub mod symbol_table; pub mod symbol_table;
pub(self) struct DiagnosticsContainer { pub(self) struct DiagnosticsContainer {
file_id: usize,
diagnostics: Vec<DmDiagnostic>, diagnostics: Vec<DmDiagnostic>,
} }
impl DiagnosticsContainer { impl DiagnosticsContainer {
pub fn new(file_id: usize) -> DiagnosticsContainer { pub fn new() -> DiagnosticsContainer {
DiagnosticsContainer { DiagnosticsContainer {
file_id,
diagnostics: vec![], diagnostics: vec![],
} }
} }
@ -30,14 +26,6 @@ impl DiagnosticsContainer {
pub fn add(&mut self, diagnostic: DmDiagnostic) { pub fn add(&mut self, diagnostic: DmDiagnostic) {
self.diagnostics.push(diagnostic); self.diagnostics.push(diagnostic);
} }
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 { impl Into<Vec<DmDiagnostic>> for DiagnosticsContainer {
@ -47,30 +35,36 @@ impl Into<Vec<DmDiagnostic>> for DiagnosticsContainer {
} }
pub fn analyze_names( pub fn analyze_names(
file_id: usize, compilation_units: &mut Vec<CompilationUnit>,
compilation_unit: &mut CompilationUnit,
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
) -> Vec<DmDiagnostic> { ) -> Vec<DmDiagnostic> {
let mut diagnostics = DiagnosticsContainer::new(file_id); let mut diagnostics = DiagnosticsContainer::new();
let mut fqn_context = FqnContext::new(); // gather symbols
if let Some(namespace) = &compilation_unit.namespace { for compilation_unit in compilation_units.iter_mut() {
fqn_context.push(namespace.name().to_string()); let mut fqn_context = FqnContext::new();
if let Some(namespace) = &compilation_unit.namespace {
fqn_context.push(namespace.name().to_string());
}
symbol_table.push_scope(&format!("FileScope({})", compilation_unit.file_name));
for declaration in &mut compilation_unit.declarations {
gather_module_level_declaration(
declaration,
symbol_table,
&mut fqn_context,
&mut diagnostics,
);
}
symbol_table.pop_scope();
assert_eq!(symbol_table.current_scope_id(), 0);
} }
for declaration in &mut compilation_unit.declarations { // resolve symbols
gather_module_level_declaration( for compilation_unit in compilation_units.iter_mut() {
declaration, for declaration in &mut compilation_unit.declarations {
symbol_table, resolve_module_level_declaration(declaration, symbol_table, &mut diagnostics);
&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() diagnostics.into()
@ -84,23 +78,31 @@ mod tests {
use codespan_reporting::files::SimpleFiles; use codespan_reporting::files::SimpleFiles;
use codespan_reporting::term; use codespan_reporting::term;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use indoc::indoc;
use pest::Parser; use pest::Parser;
use std::collections::HashMap;
fn assert_no_diagnostics(src: &str) { fn assert_no_diagnostics(sources: HashMap<&str, &str>) {
let mut files = SimpleFiles::new(); let mut files = SimpleFiles::new();
let test_file_id = files.add("test.dm", src); let mut compilation_units = vec![];
let parse_result = DeimosParser::parse(Rule::CompilationUnit, src); for (file_name, source) in sources {
if let Err(err) = &parse_result { let file_id = files.add(file_name, source);
panic!("{:?}", err); let parse_result = DeimosParser::parse(Rule::CompilationUnit, source);
if let Err(err) = &parse_result {
panic!("{:?}", err);
}
let mut pairs = parse_result.unwrap();
if pairs.as_str().trim() != source.trim() {
panic!("Parsing did not consume entire input.");
}
compilation_units.push(build_ast(file_name, file_id, pairs.next().unwrap()))
} }
let compilation_unit_pair = parse_result.unwrap().next().unwrap();
let mut ast = build_ast(test_file_id, compilation_unit_pair);
let mut symbol_table = SymbolTable::new(); let mut symbol_table = SymbolTable::new();
let diagnostics = analyze_names(test_file_id, &mut ast, &mut symbol_table); let diagnostics = analyze_names(&mut compilation_units, &mut symbol_table);
if !diagnostics.is_empty() { if !diagnostics.is_empty() {
let writer = StandardStream::stderr(ColorChoice::Always); let writer = StandardStream::stderr(ColorChoice::Always);
@ -117,12 +119,40 @@ mod tests {
#[test] #[test]
fn params_seen() { fn params_seen() {
assert_no_diagnostics( let sources: HashMap<&str, &str> = HashMap::from([(
r#" "main.dm",
indoc! {"
fn main(args: Array<String>) { fn main(args: Array<String>) {
let x = args; let x = args;
} }"},
"#, )]);
)
assert_no_diagnostics(sources)
}
#[test]
fn two_files() {
let sources: HashMap<&str, &str> = HashMap::from([
(
"main.dm",
indoc! {"
use std::core::println;
fn main(args: Array<String>) {
println(\"Hello, World!\");
}
"},
),
(
"print.dm",
indoc! {"
ns std::core;
declare platform fn println(msg: String) -> Void
"},
),
]);
assert_no_diagnostics(sources);
} }
} }