diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 127d54f..043d2f9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -79,9 +79,9 @@ pub mod build { include!(concat!(env!("OUT_DIR"), "/src/ast/build.rs")); - pub fn build_ast(file_id: usize, parsed_pairs: &mut Pairs) -> Box { + pub fn build_ast(file_id: usize, parsed_pairs: &mut Pairs) -> CompilationUnit { let compilation_unit_pair = parsed_pairs.next().unwrap(); - Box::new(build_compilation_unit(file_id, compilation_unit_pair)) + build_compilation_unit(file_id, compilation_unit_pair) } #[cfg(test)] diff --git a/src/bin/dmc/name_analysis.rs b/src/bin/dmc/name_analysis.rs index 2b6f75e..ddfd75c 100644 --- a/src/bin/dmc/name_analysis.rs +++ b/src/bin/dmc/name_analysis.rs @@ -3,40 +3,69 @@ use codespan_reporting::term; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; use deimos::ast::build::build_ast; use deimos::name_analysis::analyze_names; -use deimos::name_analysis::symbol_table::symbol_tree::SymbolTree; use deimos::name_analysis::symbol_table::SymbolTable; use deimos::parser::{DeimosParser, Rule}; use deimos::std_core::add_std_core_symbols; use pest::Parser; +use std::collections::HashMap; +use std::fmt::{Debug, Display, Formatter}; use std::path::PathBuf; -pub fn name_analysis(paths: &Vec) -> Result<(), Box> { - let mut compilation_units = vec![]; - let mut files: SimpleFiles = SimpleFiles::new(); +struct ParseErrors { + errors: Vec>, +} +impl Debug for ParseErrors { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + +impl Display for ParseErrors { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "There were errors during parsing.")?; + for parse_error in &self.errors { + writeln!(f, "{}", parse_error)?; + } + Ok(()) + } +} + +impl std::error::Error for ParseErrors {} + +pub fn name_analysis(paths: &[PathBuf]) -> Result<(), Box> { + let mut paths_and_sources: HashMap = HashMap::new(); 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 + paths_and_sources.insert(path.display().to_string(), src); + } + let mut compilation_units = vec![]; + let mut files: SimpleFiles<&str, &str> = SimpleFiles::new(); + let mut parse_errors = vec![]; + + for (path, source) in &paths_and_sources { + let parse_result = DeimosParser::parse(Rule::CompilationUnit, source); match parse_result { Ok(mut pairs) => { + let file_id = files.add(path, source); let compilation_unit = build_ast(file_id, &mut pairs); compilation_units.push(compilation_unit); - Ok::<(), Box>(()) } - Err(e) => Err(e.into()), - }?; + Err(error) => { + parse_errors.push(error); + } + } + } + + if !parse_errors.is_empty() { + return Err(Box::new(ParseErrors { errors: parse_errors })); } let mut symbol_table = SymbolTable::new(); add_std_core_symbols(&mut symbol_table).expect("Failed to add std::core symbols."); - let diagnostics = analyze_names( - &mut compilation_units, - &files, - &mut symbol_table, - ); + let diagnostics = analyze_names(&mut compilation_units, &files, &mut symbol_table); if diagnostics.is_empty() { println!("Name analysis complete."); println!("{}", symbol_table); diff --git a/src/name_analysis/first_pass.rs b/src/name_analysis/first_pass.rs index c107e5b..2d23941 100644 --- a/src/name_analysis/first_pass.rs +++ b/src/name_analysis/first_pass.rs @@ -1,4 +1,9 @@ -use crate::ast::node::{CompilationUnit, ConcreteUseStatement, ConcreteUseStatementSuffix, Function, FunctionBody, GenericParameters, Identifier, IdentifierOrFqn, Module, ModuleLevelDeclaration, Parameters, PrimitiveType, ReturnType, StarUseStatement, TypeUse, TypedArray, UseStatement, UseStatementIdentifier, UseStatementPrefix}; +use crate::ast::node::{ + CompilationUnit, ConcreteUseStatement, ConcreteUseStatementSuffix, Function, FunctionBody, + GenericParameters, Identifier, IdentifierOrFqn, Module, ModuleLevelDeclaration, Parameters, + PrimitiveType, ReturnType, StarUseStatement, TypeUse, TypedArray, UseStatement, + UseStatementIdentifier, UseStatementPrefix, +}; use crate::diagnostic::DmDiagnostic; use crate::name_analysis::symbol::function_symbol::FunctionSymbol; use crate::name_analysis::symbol::generic_type_symbol::GenericTypeSymbol; @@ -9,8 +14,10 @@ use crate::name_analysis::symbol::primitive_type_symbol::PrimitiveTypeSymbol; use crate::name_analysis::symbol::source_definition::SourceDefinition; use crate::name_analysis::symbol::type_symbol::TypeSymbol; use crate::name_analysis::symbol::use_symbol::{ConcreteUseSymbol, StarUseSymbol}; -use crate::name_analysis::symbol_table::{SymbolInsertError, SymbolTable}; -use crate::name_analysis::util::{format_fqn, handle_insert_error, handle_lookup_error, join_fqn_parts}; +use crate::name_analysis::symbol_table::SymbolTable; +use crate::name_analysis::util::{ + format_fqn, handle_insert_error, handle_lookup_error, join_fqn_parts, +}; use std::cell::RefCell; use std::rc::Rc; @@ -140,10 +147,12 @@ fn na_p1_star_use_statement( .map(Identifier::name) .map(|name| Rc::from(name)) .collect::>>(); - + let to_insert = StarUseSymbol::new( &fqn_parts, - Some(SourceDefinition::from_star_use_statement(star_use_statement)) + Some(SourceDefinition::from_star_use_statement( + star_use_statement, + )), ); match symbol_table.insert_star_use_symbol(to_insert) { Ok(star_use_symbol) => { diff --git a/src/name_analysis/mod.rs b/src/name_analysis/mod.rs index a3b99d0..3e00c8e 100644 --- a/src/name_analysis/mod.rs +++ b/src/name_analysis/mod.rs @@ -36,8 +36,11 @@ pub mod symbol; pub mod symbol_table; mod util; -pub fn analyze_names<'a, F: Files<'a, FileId = usize, Name = String>>( - compilation_units: &mut Vec>, +pub fn analyze_names< + 'a, + F: Files<'a, FileId = usize, Name = &'a str, Source = &'a str> + ?Sized, +>( + compilation_units: &mut Vec, files: &'a F, symbol_table: &mut SymbolTable, ) -> Vec { @@ -46,7 +49,7 @@ pub fn analyze_names<'a, F: Files<'a, FileId = usize, Name = String>>( // gather symbols for compilation_unit in compilation_units.iter_mut() { let file_name = files.name(compilation_unit.file_id()).unwrap(); - na_p1_compilation_unit(&file_name, compilation_unit, symbol_table, &mut diagnostics); + na_p1_compilation_unit(file_name, compilation_unit, symbol_table, &mut diagnostics); } // resolve symbols @@ -57,218 +60,133 @@ pub fn analyze_names<'a, F: Files<'a, FileId = usize, Name = String>>( diagnostics.into() } -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::ast::build::build_ast; -// use crate::ast::children::NodeRef; -// use crate::ast::walk::walk_depth_first; -// use crate::parser::{DeimosParser, Rule}; -// use crate::std_core::add_std_core_symbols; -// 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_number_of_diagnostics( -// sources: HashMap<&str, &str>, -// symbol_table: &mut SymbolTable, -// n_diagnostics: usize, -// ) -> Vec { -// let mut files = SimpleFiles::new(); -// let mut compilation_units = vec![]; -// -// 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."); -// } -// -// compilation_units.push(build_ast(file_name, file_id, pairs.next().unwrap())); -// } -// -// let diagnostics = analyze_names(&mut compilation_units, symbol_table); -// -// if diagnostics.len() != n_diagnostics { -// 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); -// } -// -// assert_eq!(n_diagnostics, diagnostics.len()); -// -// compilation_units -// } -// -// fn assert_no_diagnostics( -// sources: HashMap<&str, &str>, -// symbol_table: &mut SymbolTable, -// ) -> Vec { -// assert_number_of_diagnostics(sources, symbol_table, 0) -// } -// -// fn assert_saved_symbols(compilation_unit: &CompilationUnit) { -// walk_depth_first(compilation_unit, &mut |node_ref| match node_ref { -// NodeRef::Identifier(identifier) => { -// if identifier.saved_symbol().is_none() { -// panic!("{:?} does not have a saved symbol.", identifier) -// } -// } -// NodeRef::FullyQualifiedName(fqn) => { -// if fqn.saved_symbol().is_none() { -// panic!("{:?} does not have a saved symbol.", fqn) -// } -// } -// NodeRef::UseStatement(use_statement) => match use_statement { -// _ => todo!(), -// }, -// _ => {} -// }) -// } -// -// fn assert_resolved_symbols(compilation_unit: &CompilationUnit) { -// walk_depth_first(compilation_unit, &mut |node_ref| match node_ref { -// NodeRef::UseStatement(use_statement) => match use_statement { -// _ => todo!(), -// }, -// _ => {} -// }) -// } -// -// #[test] -// fn params_seen() { -// let sources: HashMap<&str, &str> = HashMap::from([( -// "main.dm", -// indoc! {" -// fn main(args: Array) { -// let x = args; -// }"}, -// )]); -// -// let cus = assert_no_diagnostics(sources, &mut SymbolTable::new()); -// for ref cu in cus { -// assert_saved_symbols(cu); -// } -// } -// -// #[test] -// fn two_files() { -// let sources: HashMap<&str, &str> = HashMap::from([ -// ( -// "main.dm", -// indoc! {" -// use test::Greeter; -// "}, -// ), -// ( -// "deps.dm", -// indoc! {" -// ns test; -// -// pub class Greeter {} -// "}, -// ), -// ]); -// -// let cus = assert_no_diagnostics(sources, &mut SymbolTable::new()); -// for ref cu in cus { -// assert_saved_symbols(cu); -// assert_resolved_symbols(cu); -// } -// } -// -// #[test] -// fn sees_std_core_println() { -// let sources: HashMap<&str, &str> = HashMap::from([( -// "main.dm", -// indoc! {" -// fn main(args: Array) { -// println(args) -// } -// "}, -// )]); -// -// let mut symbol_table = SymbolTable::new(); -// add_std_core_symbols(&mut symbol_table).expect("Failed to add std::core symbols."); -// let cus = assert_no_diagnostics(sources, &mut symbol_table); -// for ref cu in cus { -// assert_saved_symbols(cu); -// assert_resolved_symbols(cu); -// } -// } -// -// #[test] -// fn sees_duplicate_fn() { -// let sources: HashMap<&str, &str> = HashMap::from([( -// "main.dm", -// indoc! {" -// fn main(args: Array) {} -// fn main(args: Array) {} -// "}, -// )]); -// assert_number_of_diagnostics(sources, &mut SymbolTable::new(), 1); -// } -// -// #[test] -// fn use_class_from_other_file() { -// let sources: HashMap<&str, &str> = HashMap::from([ -// ( -// "main.dm", -// indoc! {" -// use greeter::Greeter; -// -// fn test(greeter: Greeter) {} -// "}, -// ), -// ( -// "greeter.dm", -// indoc! {" -// ns greeter; -// -// class Greeter {} -// "}, -// ), -// ]); -// let mut symbol_table = SymbolTable::new(); -// let cus = assert_no_diagnostics(sources, &mut symbol_table); -// for ref cu in cus { -// assert_saved_symbols(cu); -// assert_resolved_symbols(cu); -// } -// } -// -// #[test] -// fn shadow_import() { -// let sources: HashMap<&str, &str> = HashMap::from([ -// ( -// "main.dm", -// indoc! {" -// use greeter::Greeter; -// -// class Greeter {} -// "}, -// ), -// ( -// "greeter.dm", -// indoc! {" -// ns greeter; -// -// class Greeter {} -// "}, -// ), -// ]); -// assert_number_of_diagnostics(sources, &mut SymbolTable::new(), 1); -// } -// } +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::build::build_ast; + use crate::parser::{DeimosParser, Rule}; + use crate::std_core::add_std_core_symbols; + use codespan_reporting::files::SimpleFiles; + use codespan_reporting::term; + use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; + use pest::Parser; + use std::collections::HashMap; + + fn parse_compilation_units<'a>( + files: &mut SimpleFiles<&'a str, &'a str>, + sources: HashMap<&'a str, &'a str>, + ) -> Vec { + let mut compilation_units: Vec = vec![]; + + for (file_name, source) in sources { + 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 file_id = files.add(file_name, source); + compilation_units.push(build_ast(file_id, &mut pairs)); + } + + compilation_units + } + + fn assert_number_of_diagnostics<'a>( + sources: HashMap<&'a str, &'a str>, + symbol_table: &mut SymbolTable, + number_of_diagnostics: usize, + ) -> Vec { + let mut files = SimpleFiles::<&'a str, &'a str>::new(); + let mut compilation_units = parse_compilation_units(&mut files, sources); + + let diagnostics = analyze_names(&mut compilation_units, &files, symbol_table); + + if diagnostics.len() != number_of_diagnostics { + 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); + } + + assert_eq!(number_of_diagnostics, diagnostics.len()); + + compilation_units + } + + fn assert_no_diagnostics( + sources: HashMap<&str, &str>, + symbol_table: &mut SymbolTable, + ) -> Vec { + assert_number_of_diagnostics(sources, symbol_table, 0) + } + + #[test] + fn params_seen() { + let sources = HashMap::from([( + "main.dm", + " + fn main(args: Array) + let x = args + end + ", + )]); + + assert_no_diagnostics(sources, &mut SymbolTable::new()); + } + + #[test] + fn two_files() { + let sources = HashMap::from([ + ( + "main.dm", + " + use test::Greeter + ", + ), + ( + "deps.dm", + " + ns test + + pub class Greeter end + ", + ), + ]); + assert_no_diagnostics(sources, &mut SymbolTable::new()); + } + + #[test] + fn sees_std_core_println() { + let sources: HashMap<&str, &str> = HashMap::from([( + "main.dm", + " + fn main(args: Array) + std::core::println args + end + ", + )]); + + let mut symbol_table = SymbolTable::new(); + add_std_core_symbols(&mut symbol_table).expect("Failed to add std::core symbols."); + assert_no_diagnostics(sources, &mut symbol_table); + } + + #[test] + fn sees_duplicate_fn() { + let sources: HashMap<&str, &str> = HashMap::from([( + "main.dm", + " + fn main(args: Array) end + fn main(args: Array) end + ", + )]); + assert_number_of_diagnostics(sources, &mut SymbolTable::new(), 1); + } +}