diff --git a/Cargo.lock b/Cargo.lock index e9bda67..3ae2be0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,17 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -136,6 +147,7 @@ name = "deimos" version = "0.1.0" dependencies = [ "clap", + "codespan-reporting", "pest", "pest_derive", ] @@ -253,6 +265,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.10.8" @@ -281,6 +313,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -319,6 +360,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "utf8parse" version = "0.2.2" @@ -331,6 +378,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index c0e667e..a1d1c76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ path = "src/bin/dmc/main.rs" pest = "2.7.14" clap = { version = "4.5.23", features = ["derive"] } pest_derive = "2.7.14" +codespan-reporting = "0.12.0" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/sketching/may_2025/name_one.dm b/sketching/may_2025/name_one.dm index 562c5b5..c94a645 100644 --- a/sketching/may_2025/name_one.dm +++ b/sketching/may_2025/name_one.dm @@ -4,7 +4,10 @@ fn main() { let x = 'Hello'; let y = 'World'; { + let x = 'Hello'; let test = 'Test'; + let test = 'Again'; }; x = y; + let x = 'Hello!'; } \ No newline at end of file diff --git a/src/ast/build.rs b/src/ast/build.rs index e2edddc..a4acfc7 100644 --- a/src/ast/build.rs +++ b/src/ast/build.rs @@ -14,10 +14,18 @@ pub fn build_ast(compilation_unit_pair: Pair) -> CompilationUnit { } fn build_identifier(identifier_pair: Pair) -> Identifier { - Identifier::new(identifier_pair.as_span().as_str()) + let as_span = identifier_pair.as_span(); + Identifier::new( + as_span.as_str(), + Range { + start: as_span.start(), + end: as_span.end(), + }, + ) } fn build_fqn(fqn_pair: Pair) -> FullyQualifiedName { + let as_span = fqn_pair.as_span(); FullyQualifiedName::new( fqn_pair .into_inner() @@ -25,6 +33,10 @@ fn build_fqn(fqn_pair: Pair) -> FullyQualifiedName { expect_and_use(identifier_pair, Rule::Identifier, build_identifier) }) .collect(), + Range { + start: as_span.start(), + end: as_span.end(), + }, ) } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 8300d16..9548221 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1,5 +1,6 @@ use crate::compile::name_analysis::Symbol; use pest::Parser; +use std::range::Range; pub mod build; pub mod named; @@ -54,14 +55,16 @@ pub enum SuffixUnaryOperator { #[derive(Debug, Clone)] pub struct Identifier { pub name: String, + pub range: Range, scope_id: Option, symbol: Option, } impl Identifier { - pub fn new(name: &str) -> Self { + pub fn new(name: &str, range: Range) -> Self { Identifier { name: name.to_string(), + range, scope_id: None, symbol: None, } @@ -87,14 +90,16 @@ impl Identifier { #[derive(Debug)] pub struct FullyQualifiedName { pub identifiers: Vec, + pub range: Range, scope_id: Option, symbol: Option, } impl FullyQualifiedName { - pub fn new(identifiers: Vec) -> Self { + pub fn new(identifiers: Vec, range: Range) -> Self { FullyQualifiedName { identifiers, + range, scope_id: None, symbol: None, } diff --git a/src/bin/dmc/main.rs b/src/bin/dmc/main.rs index bc3948a..ea350bf 100644 --- a/src/bin/dmc/main.rs +++ b/src/bin/dmc/main.rs @@ -46,7 +46,10 @@ fn main() { } Commands::NameAnalysis { paths } => { for path in paths { - name_analysis(&path) + let result = name_analysis(&path); + if let Err(e) = result { + eprintln!("{}", e); + } } } } diff --git a/src/bin/dmc/name_analysis.rs b/src/bin/dmc/name_analysis.rs index 3a506b5..4aad6b3 100644 --- a/src/bin/dmc/name_analysis.rs +++ b/src/bin/dmc/name_analysis.rs @@ -1,36 +1,37 @@ +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::parser::{DeimosParser, Rule}; use pest::Parser; use std::path::Path; -pub fn name_analysis(path: &Path) { +pub fn name_analysis(path: &Path) -> Result<(), Box> { let src = std::fs::read_to_string(path).unwrap(); - let parse_result = DeimosParser::parse( - Rule::CompilationUnit, - &src - ); + let parse_result = DeimosParser::parse(Rule::CompilationUnit, &src); + let mut files = SimpleFiles::new(); + let file_id = files.add(path.display().to_string(), &src); + match parse_result { Ok(mut pairs) => { let compilation_unit_pair = pairs.next().unwrap(); let mut compilation_unit = build_ast(compilation_unit_pair); let mut symbol_table = SymbolTable::new(); - let name_analysis_result = analyze_names( - &mut compilation_unit, - &mut symbol_table, - ); - match name_analysis_result { - Err(e) => { - eprintln!("{}", e); - } - Ok(_) => { - println!("name_analysis complete"); - println!("{}", symbol_table); + let diagnostics = analyze_names(file_id, &mut compilation_unit, &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) => { - eprintln!("{}", e); - } + Err(e) => Err(e.into()), } -} \ No newline at end of file +} diff --git a/src/compile/name_analysis.rs b/src/compile/name_analysis.rs index 39e6bb1..bf774e5 100644 --- a/src/compile/name_analysis.rs +++ b/src/compile/name_analysis.rs @@ -1,5 +1,6 @@ use crate::ast::named::Named; use crate::ast::*; +use codespan_reporting::diagnostic::{Diagnostic, Label}; use std::collections::HashMap; use std::fmt::Display; @@ -9,6 +10,15 @@ pub enum Symbol { 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::*; @@ -96,7 +106,10 @@ impl SymbolTable { 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", current_symbol)) + Err(format!( + "Symbol '{}' already defined in current scope.", + current_symbol.name() + )) } else { self.scopes[self.current_scope_id] .symbols @@ -117,7 +130,7 @@ impl SymbolTable { None }; } - Err(format!("Symbol '{}' not found", name)) + Err(format!("Symbol '{}' not found in current scope.", name)) } } @@ -173,101 +186,138 @@ impl FqnContext { } pub fn analyze_names( + file_id: usize, compilation_unit: &mut CompilationUnit, symbol_table: &mut SymbolTable, -) -> Result<(), String> { +) -> Vec> { + 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 { - gather_module_level_declaration(declaration, symbol_table, &mut fqn_context)?; + 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 { - resolve_module_level_declaration(declaration, symbol_table)?; + diagnostics.extend(resolve_module_level_declaration(file_id, declaration, symbol_table)); } - Ok(()) + diagnostics } fn gather_module_level_declaration( + file_id: usize, declaration: &mut ModuleLevelDeclaration, symbol_table: &mut SymbolTable, fqn_context: &mut FqnContext, -) -> Result<(), String> { +) -> Vec> { use ModuleLevelDeclaration::*; match declaration { Function(function_definition) => { - gather_function_definition(function_definition, symbol_table, fqn_context) + 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, -) -> Result<(), String> { +) -> Vec> { + let mut diagnostics = vec![]; + let declared_name = function.identifier.name().to_string(); let resolved_name = fqn_context.resolve(&declared_name); - symbol_table.insert( + + 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 - gather_function_body(&mut function.body, symbol_table, fqn_context)?; + diagnostics.extend(gather_function_body( + file_id, + &mut function.body, + symbol_table, + fqn_context, + )); symbol_table.pop_scope(); - Ok(()) + + diagnostics } fn gather_function_body( + file_id: usize, function_body: &mut FunctionBody, symbol_table: &mut SymbolTable, fqn_context: &mut FqnContext, -) -> Result<(), String> { +) -> Vec> { use FunctionBody::*; match function_body { - Block(block) => gather_block_statement(block, symbol_table, fqn_context), + 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, -) -> Result<(), String> { +) -> Vec> { + let mut diagnostics = vec![]; symbol_table.push_scope("BlockStatementScope"); for statement in &mut block.statements { - gather_statement(statement, symbol_table, fqn_context)?; + diagnostics.extend(gather_statement( + file_id, + statement, + symbol_table, + fqn_context, + )); } symbol_table.pop_scope(); - Ok(()) + diagnostics } fn gather_statement( + file_id: usize, statement: &mut Statement, symbol_table: &mut SymbolTable, fqn_context: &mut FqnContext, -) -> Result<(), String> { +) -> Vec> { use Statement::*; match statement { - BlockStatement(block) => gather_block_statement(block, symbol_table, fqn_context), + BlockStatement(block) => gather_block_statement(file_id, block, symbol_table, fqn_context), VariableDeclarationStatement(variable_declaration) => { - gather_variable_declaration(variable_declaration, symbol_table, fqn_context) + gather_variable_declaration(file_id, variable_declaration, symbol_table) } AssignStatement(assign_statement) => { gather_assign_statement(assign_statement, symbol_table, fqn_context) @@ -277,162 +327,198 @@ fn gather_statement( } fn gather_variable_declaration( + file_id: usize, variable_declaration: &mut VariableDeclarationStatement, symbol_table: &mut SymbolTable, - fqn_context: &mut FqnContext, -) -> Result<(), String> { +) -> Vec> { + let mut diagnostics = vec![]; let variable_name = variable_declaration.identifier.name().to_string(); - symbol_table.insert( + + 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()); - Ok(()) + + diagnostics } fn gather_assign_statement( assign_statement: &mut AssignStatement, symbol_table: &mut SymbolTable, fqn_context: &mut FqnContext, -) -> Result<(), String> { - gather_expression(&mut assign_statement.lhs, symbol_table, fqn_context)?; - gather_expression(&mut assign_statement.rhs, symbol_table, fqn_context)?; - Ok(()) +) -> Vec> { + 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, -) -> Result<(), String> { +) -> Vec> { use Expression::*; match expression { FullyQualifiedName(fully_qualified_name) => { - gather_fully_qualified_name(fully_qualified_name, symbol_table, fqn_context)?; + gather_fully_qualified_name(fully_qualified_name, symbol_table) + } + _ => { + vec![] } - _ => {} } - Ok(()) } fn gather_fully_qualified_name( fully_qualified_name: &mut FullyQualifiedName, symbol_table: &mut SymbolTable, - fqn_context: &mut FqnContext, -) -> Result<(), String> { +) -> Vec> { fully_qualified_name.set_scope_id(symbol_table.current_scope_id()); - Ok(()) + vec![] } /* Resolve */ fn resolve_module_level_declaration( + file_id: usize, declaration: &mut ModuleLevelDeclaration, symbol_table: &mut SymbolTable, -) -> Result<(), String> { +) -> Vec> { use ModuleLevelDeclaration::*; match declaration { Function(function_definition) => { - resolve_function_definition(function_definition, symbol_table) + 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, -) -> Result<(), String> { - resolve_function_body(&mut function_definition.body, symbol_table) +) -> Vec> { + 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, -) -> Result<(), String> { +) -> Vec> { use FunctionBody::*; match function_body { - Block(block) => resolve_block(block, symbol_table), + Block(block) => resolve_block(file_id, block, symbol_table), _ => todo!(), } } fn resolve_block( + file_id: usize, block_statement: &mut BlockStatement, symbol_table: &mut SymbolTable, -) -> Result<(), String> { - for statement in &mut block_statement.statements { - resolve_statement(statement, symbol_table)?; - } - Ok(()) +) -> Vec> { + 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, -) -> Result<(), String> { +) -> Vec> { use Statement::*; match statement { - BlockStatement(block) => resolve_block(block, symbol_table), + BlockStatement(block) => resolve_block(file_id, block, symbol_table), VariableDeclarationStatement(variable_declaration) => { - resolve_variable_declaration(variable_declaration, symbol_table) + resolve_variable_declaration(file_id, variable_declaration, symbol_table) } AssignStatement(assign_statement) => { - resolve_assign_statement(assign_statement, symbol_table) + resolve_assign_statement(file_id, assign_statement, symbol_table) } - CallStatement(call_statement) => resolve_call_statement(call_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, -) -> Result<(), String> { +) -> Vec> { if let Some(initializer) = &mut variable_declaration.initializer { - resolve_expression(initializer, symbol_table) + resolve_expression(file_id, initializer, symbol_table) } else { - Ok(()) + vec![] } } fn resolve_assign_statement( + file_id: usize, assign_statement: &mut AssignStatement, symbol_table: &mut SymbolTable, -) -> Result<(), String> { - resolve_expression(&mut assign_statement.lhs, symbol_table)?; - resolve_expression(&mut assign_statement.rhs, symbol_table)?; - Ok(()) +) -> Vec> { + 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, -) -> Result<(), String> { - resolve_expression(&mut call_statement.0, symbol_table) +) -> Vec> { + resolve_expression(file_id, &mut call_statement.0, symbol_table) } fn resolve_expression( + file_id: usize, expression: &mut Expression, symbol_table: &mut SymbolTable, -) -> Result<(), String> { +) -> Vec> { use Expression::*; match expression { - FullyQualifiedName(fqn) => resolve_fully_qualified_name(fqn, symbol_table), - Literal(_) => Ok(()), + 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, -) -> Result<(), String> { +) -> Vec> { let lookup_result = symbol_table.lookup( fully_qualified_name.name().as_ref(), fully_qualified_name.scope_id().expect(&format!( @@ -443,8 +529,12 @@ fn resolve_fully_qualified_name( match lookup_result { Ok(symbol) => { fully_qualified_name.set_symbol(symbol.clone()); - Ok(()) + vec![] } - Err(e) => Err(e), + Err(e) => vec![ + Diagnostic::error() + .with_message(e) + .with_label(Label::primary(file_id, fully_qualified_name.range)) + ], } } diff --git a/src/lib.rs b/src/lib.rs index c0a8b56..44f09d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(new_range_api)] #![allow(warnings)] pub mod ast; pub mod compile;