Much work to produce better errors during name analysis.

This commit is contained in:
Jesse Brault 2025-05-16 15:58:42 -05:00
parent dda86f75e7
commit f5a82c414c
13 changed files with 668 additions and 275 deletions

7
Cargo.lock generated
View File

@ -148,6 +148,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"codespan-reporting", "codespan-reporting",
"log",
"pest", "pest",
"pest_derive", "pest_derive",
] ]
@ -190,6 +191,12 @@ version = "0.2.167"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"

View File

@ -16,3 +16,4 @@ pest = "2.7.14"
clap = { version = "4.5.23", features = ["derive"] } clap = { version = "4.5.23", features = ["derive"] }
pest_derive = "2.7.14" pest_derive = "2.7.14"
codespan-reporting = "0.12.0" codespan-reporting = "0.12.0"
log = "0.4.27"

View File

@ -1,13 +1,13 @@
ns greeter ns greeter
fn main() { fn main(x: String) {
let x = 'Hello'; let x = 'Hello';
let y = 'World'; let y = 'World';
{ {
let x = 'Hello'; let test = 'test';
let test = 'Test'; let x = 'x';
let test = 'Again'; let y = 'y';
let z = 'z';
let test = 'oops.';
}; };
x = y;
let x = 'Hello!';
} }

File diff suppressed because it is too large Load Diff

View File

@ -55,15 +55,17 @@ pub enum SuffixUnaryOperator {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Identifier { pub struct Identifier {
pub name: String, pub name: String,
pub file_id: usize,
pub range: Range<usize>, pub range: Range<usize>,
scope_id: Option<usize>, scope_id: Option<usize>,
symbol: Option<Symbol>, symbol: Option<Symbol>,
} }
impl Identifier { impl Identifier {
pub fn new(name: &str, range: Range<usize>) -> Self { pub fn new(name: &str, file_id: usize, range: Range<usize>) -> Self {
Identifier { Identifier {
name: name.to_string(), name: name.to_string(),
file_id,
range, range,
scope_id: None, scope_id: None,
symbol: None, symbol: None,
@ -90,16 +92,18 @@ impl Identifier {
#[derive(Debug)] #[derive(Debug)]
pub struct FullyQualifiedName { pub struct FullyQualifiedName {
pub identifiers: Vec<Identifier>, pub identifiers: Vec<Identifier>,
pub file_id: usize,
pub range: Range<usize>, pub range: Range<usize>,
scope_id: Option<usize>, scope_id: Option<usize>,
symbol: Option<Symbol>, symbol: Option<Symbol>,
} }
impl FullyQualifiedName { impl FullyQualifiedName {
pub fn new(identifiers: Vec<Identifier>, range: Range<usize>) -> Self { pub fn new(identifiers: Vec<Identifier>, file_id: usize, range: Range<usize>) -> Self {
FullyQualifiedName { FullyQualifiedName {
identifiers, identifiers,
range, range,
file_id,
scope_id: None, scope_id: None,
symbol: None, symbol: None,
} }

View File

@ -17,7 +17,7 @@ pub fn name_analysis(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
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(compilation_unit_pair); let mut compilation_unit = build_ast(file_id, compilation_unit_pair);
let mut symbol_table = SymbolTable::new(); let mut symbol_table = SymbolTable::new();
let diagnostics = analyze_names(file_id, &mut compilation_unit, &mut symbol_table); let diagnostics = analyze_names(file_id, &mut compilation_unit, &mut symbol_table);
if diagnostics.is_empty() { if diagnostics.is_empty() {

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(compilation_unit_pair); let compilation_unit = build_ast(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(compilation_unit_pair); let compilation_unit = build_ast(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,10 +1,45 @@
use crate::ast::named::Named; use crate::ast::named::Named;
use crate::ast::*; use crate::ast::*;
use crate::name_analysis::fqn_context::FqnContext; use crate::name_analysis::fqn_context::FqnContext;
use crate::name_analysis::symbol::{FunctionSymbol, Symbol, VariableSymbol}; use crate::name_analysis::symbol::{FunctionSymbol, ModuleSymbol, SourceDefinition, Symbol, VariableSymbol};
use crate::name_analysis::symbol_table::SymbolTable; use crate::name_analysis::symbol_table::{SymbolInsertError, SymbolTable};
use crate::name_analysis::DiagnosticsContainer; use crate::name_analysis::DiagnosticsContainer;
use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::diagnostic::{Diagnostic, Label};
use std::range::Range;
fn handle_insert_error(
err: SymbolInsertError,
error_symbol_name: &str,
error_file_id: usize,
error_range: Range<usize>,
symbol_types: &str,
diagnostics: &mut DiagnosticsContainer,
) {
match err {
SymbolInsertError::SymbolAlreadyDefined(s) => {
let already_defined_definition = s.definition();
diagnostics.add(
Diagnostic::error()
.with_message(format!(
"{} symbol '{}' already defined in the current scope.",
symbol_types, error_symbol_name,
))
.with_label(
Label::primary(error_file_id, error_range)
.with_message("Symbol duplicated here."),
)
.with_label(
Label::secondary(
already_defined_definition.file_id(),
already_defined_definition.range(),
)
.with_message("Symbol defined here."),
),
);
}
}
}
pub(super) fn gather_module_level_declaration( pub(super) fn gather_module_level_declaration(
declaration: &mut ModuleLevelDeclaration, declaration: &mut ModuleLevelDeclaration,
@ -14,6 +49,9 @@ pub(super) fn gather_module_level_declaration(
) { ) {
use ModuleLevelDeclaration::*; use ModuleLevelDeclaration::*;
match declaration { match declaration {
Module(module_declaration) => {
gather_module_declaration(module_declaration, symbol_table, fqn_context, diagnostics);
}
Function(function_definition) => { Function(function_definition) => {
gather_function_definition(function_definition, symbol_table, fqn_context, diagnostics) gather_function_definition(function_definition, symbol_table, fqn_context, diagnostics)
} }
@ -21,28 +59,78 @@ pub(super) fn gather_module_level_declaration(
} }
} }
fn gather_module_declaration(
declaration: &mut ModuleDeclaration,
symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext,
diagnostics: &mut DiagnosticsContainer,
) {
// 1. Add mod identifier symbol
// 2. Update fqn context
// 3. Push scope
// 4. Process declarations
// 5. Pop scope
let module_name = declaration.identifier.name();
let insert_result = symbol_table.insert(
&module_name,
Symbol::Module(ModuleSymbol::new(
&fqn_context.resolve(&module_name),
&module_name,
declaration.is_public,
&declaration.identifier,
)),
);
if let Err(err) = insert_result {
handle_insert_error(
err,
&module_name,
declaration.identifier.file_id,
declaration.identifier.range,
"module/type",
diagnostics,
)
}
fqn_context.push(module_name.to_string());
symbol_table.push_scope(&format!("Module '{}' Scope", module_name));
for inner_declaration in &mut declaration.declarations {
gather_module_level_declaration(inner_declaration, symbol_table, fqn_context, diagnostics);
}
symbol_table.pop_scope()
}
fn gather_function_definition( fn gather_function_definition(
function: &mut FunctionDefinition, function: &mut FunctionDefinition,
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext, fqn_context: &mut FqnContext,
diagnostics: &mut DiagnosticsContainer, diagnostics: &mut DiagnosticsContainer,
) { ) {
let declared_name = function.identifier.name().to_string(); let declared_name = function.identifier.name();
let resolved_name = fqn_context.resolve(&declared_name); let resolved_name = fqn_context.resolve(&declared_name);
let insert_result = symbol_table.insert( let insert_result = symbol_table.insert(
declared_name.clone(), &declared_name,
Symbol::Function(FunctionSymbol { Symbol::Function(FunctionSymbol::new(
fqn: resolved_name.clone(), &resolved_name,
declared_name, &declared_name,
is_public: function.is_public, function.is_public,
}), &function.identifier,
)),
); );
if let Err(msg) = insert_result { if let Err(err) = insert_result {
diagnostics.add(|file_id| { handle_insert_error(
Diagnostic::error().with_label(Label::primary(file_id, function.identifier.range)) err,
}); &declared_name,
function.identifier.file_id,
function.identifier.range,
"function/variable",
diagnostics,
)
} }
function function
@ -80,18 +168,28 @@ fn gather_parameter(
let parameter_name = parameter.identifier.name(); let parameter_name = parameter.identifier.name();
let insert_result = symbol_table.insert( let insert_result = symbol_table.insert(
parameter_name.to_string(), &parameter_name,
Symbol::Variable(VariableSymbol { Symbol::Variable(VariableSymbol::new(
name: parameter_name.to_string(), &parameter_name,
is_mutable: false, false,
}), SourceDefinition::from_identifier(&parameter.identifier),
)),
); );
if let Err(msg) = insert_result { if let Err(err) = insert_result {
diagnostics.add_error_at_identifier(&msg, &parameter.identifier); handle_insert_error(
err,
&parameter_name,
parameter.identifier.file_id,
parameter.identifier.range,
"function/variable",
diagnostics,
)
} }
parameter.identifier.set_scope_id(symbol_table.current_scope_id()); parameter
.identifier
.set_scope_id(symbol_table.current_scope_id());
} }
fn gather_function_body( fn gather_function_body(
@ -114,7 +212,7 @@ fn gather_block_statement(
diagnostics: &mut DiagnosticsContainer, diagnostics: &mut DiagnosticsContainer,
) { ) {
symbol_table.push_scope("BlockStatementScope"); symbol_table.push_scope("BlockStatementScope");
gather_block_statement(block, symbol_table, fqn_context, diagnostics); gather_block_statement_inner(block, symbol_table, fqn_context, diagnostics);
symbol_table.pop_scope(); symbol_table.pop_scope();
} }
@ -158,24 +256,32 @@ fn gather_variable_declaration(
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
diagnostics: &mut DiagnosticsContainer, diagnostics: &mut DiagnosticsContainer,
) { ) {
let variable_name = variable_declaration.identifier.name().to_string(); let variable_name = variable_declaration.identifier.name();
let insert_result = symbol_table.insert( let insert_result = symbol_table.insert(
variable_name.clone(), &variable_name,
Symbol::Variable(VariableSymbol { Symbol::Variable(VariableSymbol::new(
name: variable_name, &variable_name,
is_mutable: variable_declaration.is_mutable, variable_declaration.is_mutable,
}), SourceDefinition::from_identifier(&variable_declaration.identifier),
)),
); );
if let Err(msg) = insert_result { if let Err(err) = insert_result {
diagnostics.add_error_at_identifier(&msg, &variable_declaration.identifier); handle_insert_error(
err,
&variable_name,
variable_declaration.identifier.file_id,
variable_declaration.identifier.range,
"function/variable",
diagnostics,
)
} }
variable_declaration variable_declaration
.identifier .identifier
.set_scope_id(symbol_table.current_scope_id()); .set_scope_id(symbol_table.current_scope_id());
if let Some(initializer) = &mut variable_declaration.initializer { if let Some(initializer) = &mut variable_declaration.initializer {
gather_expression(initializer, symbol_table, diagnostics); gather_expression(initializer, symbol_table, diagnostics);
} }

View File

@ -6,6 +6,7 @@ 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 codespan_reporting::diagnostic::{Diagnostic, Label};
use log::debug;
mod fqn_context; mod fqn_context;
mod gather; mod gather;
@ -26,8 +27,8 @@ impl DiagnosticsContainer {
} }
} }
pub fn add(&mut self, f: impl FnOnce(usize) -> DmDiagnostic) { pub fn add(&mut self, diagnostic: DmDiagnostic) {
self.diagnostics.push(f(self.file_id)); self.diagnostics.push(diagnostic);
} }
pub fn add_error_at_identifier(&mut self, msg: &str, identifier: &Identifier) { pub fn add_error_at_identifier(&mut self, msg: &str, identifier: &Identifier) {
@ -86,15 +87,16 @@ mod tests {
use pest::Parser; use pest::Parser;
fn assert_no_diagnostics(src: &str) { fn assert_no_diagnostics(src: &str) {
let mut files = SimpleFiles::new();
let test_file_id = files.add("test.dm", src);
let parse_result = DeimosParser::parse(Rule::CompilationUnit, src); let parse_result = DeimosParser::parse(Rule::CompilationUnit, src);
if let Err(err) = &parse_result { if let Err(err) = &parse_result {
panic!("{:?}", err); 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 compilation_unit_pair = parse_result.unwrap().next().unwrap();
let test_file_id = files.add("test.dm", src); let mut ast = build_ast(test_file_id, compilation_unit_pair);
let mut symbol_table = SymbolTable::new(); let mut symbol_table = SymbolTable::new();

View File

@ -128,10 +128,16 @@ fn resolve_fully_qualified_name(
Ok(symbol) => { Ok(symbol) => {
fully_qualified_name.set_symbol(symbol.clone()); fully_qualified_name.set_symbol(symbol.clone());
} }
Err(e) => diagnostics.add(|file_id| { Err(e) => diagnostics.add(
Diagnostic::error() Diagnostic::error()
.with_message(e) .with_message(format!(
.with_label(Label::primary(file_id, fully_qualified_name.range)) "No symbol with name '{}' found in current scope.",
}), fully_qualified_name.name()
))
.with_label(Label::primary(
fully_qualified_name.file_id,
fully_qualified_name.range,
)),
),
} }
} }

View File

@ -1,9 +1,35 @@
use crate::ast::Identifier;
use std::fmt::Display; use std::fmt::Display;
use std::range::Range;
#[derive(Clone, Debug)]
pub struct SourceDefinition {
file_id: usize,
range: Range<usize>,
}
impl SourceDefinition {
pub fn from_identifier(identifier: &Identifier) -> Self {
SourceDefinition {
file_id: identifier.file_id,
range: identifier.range,
}
}
pub fn file_id(&self) -> usize {
self.file_id
}
pub fn range(&self) -> Range<usize> {
self.range.clone()
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Symbol { pub enum Symbol {
Function(FunctionSymbol), Function(FunctionSymbol),
Variable(VariableSymbol), Variable(VariableSymbol),
Module(ModuleSymbol),
} }
impl Symbol { impl Symbol {
@ -11,6 +37,15 @@ impl Symbol {
match self { match self {
Symbol::Function(s) => s.declared_name.as_str(), Symbol::Function(s) => s.declared_name.as_str(),
Symbol::Variable(s) => s.name.as_str(), Symbol::Variable(s) => s.name.as_str(),
Symbol::Module(s) => s.declared_name.as_str(),
}
}
pub fn definition(&self) -> &SourceDefinition {
match self {
Symbol::Function(s) => s.definition(),
Symbol::Module(s) => s.definition(),
Symbol::Variable(s) => s.definition(),
} }
} }
} }
@ -21,6 +56,7 @@ impl Display for Symbol {
match self { match self {
Function(function_symbol) => write!(f, "{}", function_symbol), Function(function_symbol) => write!(f, "{}", function_symbol),
Variable(variable_symbol) => write!(f, "{}", variable_symbol), Variable(variable_symbol) => write!(f, "{}", variable_symbol),
Module(module_symbol) => write!(f, "{}", module_symbol),
} }
} }
} }
@ -30,6 +66,27 @@ pub struct FunctionSymbol {
pub fqn: String, pub fqn: String,
pub declared_name: String, pub declared_name: String,
pub is_public: bool, pub is_public: bool,
definition: SourceDefinition,
}
impl FunctionSymbol {
pub fn new(
fqn: &str,
declared_name: &str,
is_public: bool,
identifier: &Identifier,
) -> FunctionSymbol {
FunctionSymbol {
fqn: fqn.to_string(),
declared_name: declared_name.to_string(),
is_public,
definition: SourceDefinition::from_identifier(identifier),
}
}
fn definition(&self) -> &SourceDefinition {
&self.definition
}
} }
impl Display for FunctionSymbol { impl Display for FunctionSymbol {
@ -46,6 +103,21 @@ impl Display for FunctionSymbol {
pub struct VariableSymbol { pub struct VariableSymbol {
pub name: String, pub name: String,
pub is_mutable: bool, pub is_mutable: bool,
definition: SourceDefinition,
}
impl VariableSymbol {
pub fn new(name: &str, is_mutable: bool, definition: SourceDefinition) -> Self {
VariableSymbol {
name: name.to_string(),
is_mutable,
definition,
}
}
pub fn definition(&self) -> &SourceDefinition {
&self.definition
}
} }
impl Display for VariableSymbol { impl Display for VariableSymbol {
@ -56,4 +128,42 @@ impl Display for VariableSymbol {
self.name, self.is_mutable self.name, self.is_mutable
) )
} }
} }
#[derive(Debug, Clone)]
pub struct ModuleSymbol {
pub fqn: String,
pub declared_name: String,
pub is_public: bool,
definition: SourceDefinition,
}
impl ModuleSymbol {
pub fn new(
fqn: &str,
declared_name: &str,
is_public: bool,
identifier: &Identifier,
) -> ModuleSymbol {
ModuleSymbol {
fqn: fqn.to_string(),
declared_name: declared_name.to_string(),
is_public,
definition: SourceDefinition::from_identifier(identifier),
}
}
pub fn definition(&self) -> &SourceDefinition {
&self.definition
}
}
impl Display for ModuleSymbol {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"ModuleSymbol(name = {}, is_public = {})",
self.fqn, self.is_public
)
}
}

View File

@ -1,14 +1,35 @@
use crate::name_analysis::symbol::Symbol; use crate::name_analysis::symbol::Symbol;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use crate::name_analysis::symbol_table::SymbolInsertError::SymbolAlreadyDefined;
use crate::name_analysis::symbol_table::SymbolLookupError::NoDefinition;
pub struct Scope { pub struct Scope {
parent: Option<usize>, parent: Option<usize>,
symbols: HashMap<String, Symbol>, function_and_variable_symbols: HashMap<String, Symbol>,
type_and_module_symbols: HashMap<String, Symbol>,
debug_name: String, debug_name: String,
} }
#[derive(Default)] impl Scope {
pub fn new(parent: Option<usize>, debug_name: String) -> Scope {
Scope {
parent,
function_and_variable_symbols: HashMap::new(),
type_and_module_symbols: HashMap::new(),
debug_name,
}
}
}
pub enum SymbolInsertError {
SymbolAlreadyDefined(Symbol),
}
pub enum SymbolLookupError {
NoDefinition
}
pub struct SymbolTable { pub struct SymbolTable {
scopes: Vec<Scope>, scopes: Vec<Scope>,
current_scope_id: usize, current_scope_id: usize,
@ -17,13 +38,10 @@ pub struct SymbolTable {
/// Contains a vec of scopes, like a flattened tree /// Contains a vec of scopes, like a flattened tree
impl SymbolTable { impl SymbolTable {
pub fn new() -> Self { pub fn new() -> Self {
let mut t = SymbolTable::default(); let mut t = SymbolTable {
t.scopes.push(Scope { scopes: vec![Scope::new(None, String::from("GlobalScope"))],
parent: None, current_scope_id: 0,
symbols: HashMap::new(), };
debug_name: String::from("Global Scope"),
});
t.current_scope_id = 0;
t t
} }
@ -33,11 +51,8 @@ impl SymbolTable {
pub fn push_scope(&mut self, debug_name: &str) { pub fn push_scope(&mut self, debug_name: &str) {
let id = self.scopes.len(); let id = self.scopes.len();
self.scopes.push(Scope { self.scopes
parent: Some(self.current_scope_id), .push(Scope::new(Some(self.current_scope_id), debug_name.to_string()));
symbols: HashMap::new(),
debug_name: debug_name.to_string(),
});
self.current_scope_id = id; self.current_scope_id = id;
} }
@ -47,24 +62,47 @@ impl SymbolTable {
} }
} }
pub fn insert(&mut self, name: String, symbol: Symbol) -> Result<(), String> { pub fn insert(&mut self, name: &str, symbol: Symbol) -> Result<(), SymbolInsertError> {
if let Some(current_symbol) = self.scopes[self.current_scope_id].symbols.get(&name) { match symbol {
Err(format!( Symbol::Function(_) | Symbol::Variable(_) => {
"Symbol '{}' already defined in current scope.", self.insert_function_or_variable(name, symbol)
current_symbol.name() }
)) Symbol::Module(_) => self.insert_module_or_type(name, symbol),
}
}
fn insert_function_or_variable(&mut self, name: &str, symbol: Symbol) -> Result<(), SymbolInsertError> {
if let Some(defined_symbol) = self.scopes[self.current_scope_id]
.function_and_variable_symbols
.get(name)
{
Err(SymbolAlreadyDefined(defined_symbol.clone()))
} else { } else {
self.scopes[self.current_scope_id] self.scopes[self.current_scope_id]
.symbols .function_and_variable_symbols
.insert(name, symbol); .insert(name.to_string(), symbol);
Ok(()) Ok(())
} }
} }
pub fn lookup(&self, name: &str, scope_id: usize) -> Result<&Symbol, String> { fn insert_module_or_type(&mut self, name: &str, symbol: Symbol) -> Result<(), SymbolInsertError> {
if let Some(defined_symbol) = self.scopes[self.current_scope_id]
.type_and_module_symbols
.get(name)
{
Err(SymbolAlreadyDefined(defined_symbol.clone()))
} else {
self.scopes[self.current_scope_id]
.type_and_module_symbols
.insert(name.to_string(), symbol);
Ok(())
}
}
pub fn lookup(&self, name: &str, scope_id: usize) -> Result<&Symbol, SymbolLookupError> {
let mut scope_opt = Some(&self.scopes[scope_id]); let mut scope_opt = Some(&self.scopes[scope_id]);
while let Some(scope) = scope_opt { while let Some(scope) = scope_opt {
if let Some(symbol) = scope.symbols.get(name) { if let Some(symbol) = scope.function_and_variable_symbols.get(name) {
return Ok(symbol); return Ok(symbol);
} }
scope_opt = if let Some(parent_id) = scope.parent { scope_opt = if let Some(parent_id) = scope.parent {
@ -73,15 +111,16 @@ impl SymbolTable {
None None
}; };
} }
Err(format!("Symbol '{}' not found in current scope.", name)) Err(NoDefinition)
} }
} }
impl Display for SymbolTable { impl Display for SymbolTable {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "SymbolTable(current_scope = {})", self.current_scope_id)?;
for (i, scope) in self.scopes.iter().enumerate() { for (i, scope) in self.scopes.iter().enumerate() {
writeln!(f, "Scope {} {}", i, scope.debug_name)?; writeln!(f, "Scope {} {}", i, scope.debug_name)?;
for (name, symbol) in &scope.symbols { for (name, symbol) in &scope.function_and_variable_symbols {
writeln!(f, " {}({})", name, symbol)?; writeln!(f, " {}({})", name, symbol)?;
} }
} }