use crate::error_suggestions;
use crate::{Module, ModuleError, ModuleResult};
use std::collections::HashMap;
pub struct ModuleResolver {
symbol_tables: HashMap<String, SymbolTable>,
}
#[derive(Debug, Clone)]
pub struct SymbolTable {
pub module_name: String,
pub types: HashMap<String, SymbolInfo>,
pub constants: HashMap<String, SymbolInfo>,
pub variables: HashMap<String, SymbolInfo>,
pub procedures: HashMap<String, SymbolInfo>,
pub functions: HashMap<String, SymbolInfo>,
pub classes: HashMap<String, SymbolInfo>,
}
#[derive(Debug, Clone)]
pub struct SymbolInfo {
pub name: String,
pub module: String,
pub is_public: bool,
pub kind: SymbolKind,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SymbolKind {
Type,
Constant,
Variable,
Procedure,
Function,
Class,
Interface,
}
impl ModuleResolver {
pub fn new() -> Self {
Self {
symbol_tables: HashMap::new(),
}
}
pub fn build_symbol_table(&mut self, module: &Module) -> ModuleResult<()> {
let mut table = SymbolTable {
module_name: module.name.clone(),
types: HashMap::new(),
constants: HashMap::new(),
variables: HashMap::new(),
procedures: HashMap::new(),
functions: HashMap::new(),
classes: HashMap::new(),
};
for type_decl in &module.unit.interface.types {
table.types.insert(
type_decl.name.clone(),
SymbolInfo {
name: type_decl.name.clone(),
module: module.name.clone(),
is_public: true,
kind: SymbolKind::Type,
},
);
}
for const_decl in &module.unit.interface.constants {
table.constants.insert(
const_decl.name.clone(),
SymbolInfo {
name: const_decl.name.clone(),
module: module.name.clone(),
is_public: true,
kind: SymbolKind::Constant,
},
);
}
for var_decl in &module.unit.interface.variables {
table.variables.insert(
var_decl.name.clone(),
SymbolInfo {
name: var_decl.name.clone(),
module: module.name.clone(),
is_public: true,
kind: SymbolKind::Variable,
},
);
}
for proc_decl in &module.unit.interface.procedures {
table.procedures.insert(
proc_decl.name.clone(),
SymbolInfo {
name: proc_decl.name.clone(),
module: module.name.clone(),
is_public: true,
kind: SymbolKind::Procedure,
},
);
}
for func_decl in &module.unit.interface.functions {
table.functions.insert(
func_decl.name.clone(),
SymbolInfo {
name: func_decl.name.clone(),
module: module.name.clone(),
is_public: true,
kind: SymbolKind::Function,
},
);
}
for class_decl in &module.unit.interface.classes {
table.classes.insert(
class_decl.name.clone(),
SymbolInfo {
name: class_decl.name.clone(),
module: module.name.clone(),
is_public: true,
kind: SymbolKind::Class,
},
);
}
self.symbol_tables.insert(module.name.clone(), table);
Ok(())
}
pub fn resolve_symbol(
&self,
symbol_name: &str,
current_module: &str,
used_modules: &[String],
) -> ModuleResult<SymbolInfo> {
if let Some(table) = self.symbol_tables.get(current_module) {
if let Some(info) = self.find_in_table(table, symbol_name) {
return Ok(info.clone());
}
}
for module_name in used_modules {
if let Some(table) = self.symbol_tables.get(module_name) {
if let Some(info) = self.find_in_table(table, symbol_name) {
if info.is_public {
return Ok(info.clone());
}
}
}
}
let mut msg = format!(
"Symbol '{}' not found in module '{}' or its dependencies",
symbol_name, current_module
);
if let Some(table) = self.symbol_tables.get(current_module) {
let candidates: Vec<String> = table
.types
.keys()
.chain(table.constants.keys())
.chain(table.variables.keys())
.chain(table.procedures.keys())
.chain(table.functions.keys())
.chain(table.classes.keys())
.cloned()
.collect();
if let Some(sugg) = error_suggestions::suggest_identifier(symbol_name, &candidates) {
msg.push_str(&format!("\n {}", error_suggestions::did_you_mean(symbol_name, &sugg)));
}
}
Err(ModuleError::ModuleNotFound(msg))
}
fn find_in_table<'a>(&self, table: &'a SymbolTable, name: &str) -> Option<&'a SymbolInfo> {
table
.types
.get(name)
.or_else(|| table.constants.get(name))
.or_else(|| table.variables.get(name))
.or_else(|| table.procedures.get(name))
.or_else(|| table.functions.get(name))
.or_else(|| table.classes.get(name))
}
pub fn get_exported_symbols(&self, module_name: &str) -> Vec<SymbolInfo> {
if let Some(table) = self.symbol_tables.get(module_name) {
let mut symbols = Vec::new();
symbols.extend(table.types.values().cloned());
symbols.extend(table.constants.values().cloned());
symbols.extend(table.variables.values().cloned());
symbols.extend(table.procedures.values().cloned());
symbols.extend(table.functions.values().cloned());
symbols.extend(table.classes.values().cloned());
symbols
} else {
Vec::new()
}
}
pub fn is_symbol_exported(&self, module_name: &str, symbol_name: &str) -> bool {
if let Some(table) = self.symbol_tables.get(module_name) {
self.find_in_table(table, symbol_name)
.map(|info| info.is_public)
.unwrap_or(false)
} else {
false
}
}
}
impl Default for ModuleResolver {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolver_creation() {
let resolver = ModuleResolver::new();
assert_eq!(resolver.symbol_tables.len(), 0);
}
#[test]
fn test_symbol_table_creation() {
let table = SymbolTable {
module_name: "Test".to_string(),
types: HashMap::new(),
constants: HashMap::new(),
variables: HashMap::new(),
procedures: HashMap::new(),
functions: HashMap::new(),
classes: HashMap::new(),
};
assert_eq!(table.module_name, "Test");
assert_eq!(table.types.len(), 0);
}
#[test]
fn test_symbol_info() {
let info = SymbolInfo {
name: "MyType".to_string(),
module: "TestModule".to_string(),
is_public: true,
kind: SymbolKind::Type,
};
assert_eq!(info.name, "MyType");
assert_eq!(info.module, "TestModule");
assert!(info.is_public);
assert_eq!(info.kind, SymbolKind::Type);
}
}