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)
}
pub fn build_ast(file_id: usize, compilation_unit_pair: Pair<Rule>) -> CompilationUnit {
build_compilation_unit(file_id, compilation_unit_pair)
pub fn build_ast(file_name: &str, file_id: usize, compilation_unit_pair: Pair<Rule>) -> CompilationUnit {
build_compilation_unit(file_name, file_id, compilation_unit_pair)
}
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 declarations = vec![];
@ -311,6 +311,8 @@ fn build_compilation_unit(file_id: usize, compilation_unit_pair: Pair<Rule>) ->
}
CompilationUnit {
file_name: file_name.to_string(),
file_id,
namespace,
declarations,
}
@ -1558,7 +1560,7 @@ mod tests {
if pairs.as_str().trim() != src.trim() {
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)]
pub struct CompilationUnit {
pub file_name: String,
pub file_id: usize,
pub namespace: Option<FullyQualifiedName>,
pub declarations: Vec<ModuleLevelDeclaration>,
}

View File

@ -45,11 +45,9 @@ fn main() {
}
}
Commands::NameAnalysis { paths } => {
for path in paths {
let result = name_analysis(&path);
let result = name_analysis(&paths);
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::parser::{DeimosParser, Rule};
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 mut compilation_units = vec![];
let mut files = SimpleFiles::new();
for path in paths {
let src = std::fs::read_to_string(path).unwrap();
let parse_result = DeimosParser::parse(Rule::CompilationUnit, &src);
let mut files = SimpleFiles::new();
let file_id = files.add(path.display().to_string(), &src);
let file_id = files.add(path.display().to_string(), src.clone()); // I don't love this clone
match parse_result {
Ok(mut pairs) => {
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);
compilation_units.push(compilation_unit);
Ok::<(), Box<dyn std::error::Error>>(())
}
Err(e) => Err(e.into()),
}?;
}
let mut symbol_table = SymbolTable::new();
let diagnostics = analyze_names(file_id, &mut compilation_unit, &mut symbol_table);
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);
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()),
}
}

View File

@ -11,7 +11,7 @@ pub fn pretty_print_parse(path: &PathBuf) {
match parse_result {
Ok(mut pairs) => {
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()));
compilation_unit
.pretty_print(&mut indent_writer)

View File

@ -11,7 +11,7 @@ pub fn unparse(path: &PathBuf) {
match parse_result {
Ok(mut pairs) => {
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()));
compilation_unit
.unparse(&mut writer)

View File

@ -1,12 +1,10 @@
use crate::ast::named::Named;
use crate::ast::{CompilationUnit, Identifier};
use crate::ast::CompilationUnit;
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};
use log::debug;
mod fqn_context;
mod gather;
@ -15,14 +13,12 @@ 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 {
pub fn new() -> DiagnosticsContainer {
DiagnosticsContainer {
file_id,
diagnostics: vec![],
}
}
@ -30,14 +26,6 @@ impl DiagnosticsContainer {
pub fn add(&mut self, diagnostic: DmDiagnostic) {
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 {
@ -47,17 +35,19 @@ impl Into<Vec<DmDiagnostic>> for DiagnosticsContainer {
}
pub fn analyze_names(
file_id: usize,
compilation_unit: &mut CompilationUnit,
compilation_units: &mut Vec<CompilationUnit>,
symbol_table: &mut SymbolTable,
) -> Vec<DmDiagnostic> {
let mut diagnostics = DiagnosticsContainer::new(file_id);
let mut diagnostics = DiagnosticsContainer::new();
// gather symbols
for compilation_unit in compilation_units.iter_mut() {
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,
@ -66,12 +56,16 @@ pub fn analyze_names(
&mut diagnostics,
);
}
symbol_table.pop_scope();
assert_eq!(symbol_table.current_scope_id(), 0);
}
// resolve symbols
for compilation_unit in compilation_units.iter_mut() {
for declaration in &mut compilation_unit.declarations {
resolve_module_level_declaration(declaration, symbol_table, &mut diagnostics);
}
}
diagnostics.into()
}
@ -84,23 +78,31 @@ mod tests {
use codespan_reporting::files::SimpleFiles;
use codespan_reporting::term;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use indoc::indoc;
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 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 {
let file_id = files.add(file_name, source);
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.");
}
let compilation_unit_pair = parse_result.unwrap().next().unwrap();
let mut ast = build_ast(test_file_id, compilation_unit_pair);
compilation_units.push(build_ast(file_name, file_id, pairs.next().unwrap()))
}
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() {
let writer = StandardStream::stderr(ColorChoice::Always);
@ -117,12 +119,40 @@ mod tests {
#[test]
fn params_seen() {
assert_no_diagnostics(
r#"
let sources: HashMap<&str, &str> = HashMap::from([(
"main.dm",
indoc! {"
fn main(args: Array<String>) {
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);
}
}