use std::collections::HashMap;
use crate::core::Span;
use crate::core::events::EventEmitter;
use crate::core::operation::{EventBus, OperationResult};
use crate::semantic::SymbolTableEvent;
use crate::semantic::types::normalize_path;
use super::scope::{Import, Scope};
use super::symbol::Symbol;
use super::symbol::SymbolId;
pub struct SymbolTable {
pub(super) arena: Vec<Symbol>,
pub(super) scopes: Vec<Scope>,
pub(super) current_scope: usize,
current_file: Option<String>,
pub events: EventEmitter<SymbolTableEvent, SymbolTable>,
pub(super) symbols_by_file: HashMap<String, Vec<SymbolId>>,
pub(super) imports_by_file: HashMap<String, Vec<Import>>,
pub(super) symbols_by_qname: HashMap<String, SymbolId>,
}
impl SymbolTable {
pub fn new() -> Self {
Self {
arena: Vec::new(),
scopes: vec![Scope::new(None)],
current_scope: 0,
current_file: None,
events: EventEmitter::new(),
symbols_by_file: HashMap::new(),
imports_by_file: HashMap::new(),
symbols_by_qname: HashMap::new(),
}
}
pub fn set_current_file(&mut self, file_path: Option<String>) {
let _ = {
self.current_file = file_path.clone();
let event = file_path.map(|path| SymbolTableEvent::FileChanged { file_path: path });
OperationResult::<(), String, SymbolTableEvent>::success((), event)
}
.publish(self);
}
pub fn current_file(&self) -> Option<&str> {
self.current_file.as_deref()
}
pub fn get_current_scope(&self) -> usize {
self.current_scope
}
pub fn enter_scope(&mut self) -> usize {
let parent = self.current_scope;
let scope_id = self.scopes.len();
self.scopes.push(Scope::new(Some(parent)));
self.scopes[parent].children.push(scope_id);
self.current_scope = scope_id;
scope_id
}
pub fn exit_scope(&mut self) {
if let Some(parent) = self.scopes[self.current_scope].parent {
self.current_scope = parent;
}
}
pub fn insert(&mut self, name: String, symbol: Symbol) -> Result<(), String> {
{
let qualified_name = symbol.qualified_name().to_string();
let source_file = symbol.source_file().map(normalize_path);
let scope = &self.scopes[self.current_scope];
if scope.symbols.contains_key(&name) {
return OperationResult::failure(format!(
"Symbol '{name}' already defined in this scope"
))
.publish(self);
}
let symbol_id = SymbolId::new(self.arena.len());
self.arena.push(symbol);
self.scopes[self.current_scope]
.symbols
.insert(name, symbol_id);
self.symbols_by_qname
.insert(qualified_name.clone(), symbol_id);
if let Some(file_path) = source_file {
self.symbols_by_file
.entry(file_path)
.or_default()
.push(symbol_id);
}
let event = SymbolTableEvent::SymbolInserted {
qualified_name,
symbol_id: symbol_id.index(),
};
OperationResult::success((), Some(event))
}
.publish(self)
}
pub fn add_import(
&mut self,
path: String,
is_recursive: bool,
is_public: bool,
span: Option<crate::core::Span>,
file: Option<String>,
) {
let _ = {
let is_namespace = path.ends_with("::*") || path.ends_with("::**");
let import = Import {
path: path.clone(),
is_recursive,
is_namespace,
is_public,
span,
file: file.clone(),
};
self.scopes[self.current_scope].imports.push(import.clone());
if let Some(file_path) = file {
let normalized = normalize_path(&file_path);
self.imports_by_file
.entry(normalized)
.or_default()
.push(import);
}
let event = SymbolTableEvent::ImportAdded { import_path: path };
OperationResult::<(), String, SymbolTableEvent>::success((), Some(event))
}
.publish(self);
}
pub fn current_scope_id(&self) -> usize {
self.current_scope
}
pub fn scope_count(&self) -> usize {
self.scopes.len()
}
pub fn scopes(&self) -> &[Scope] {
&self.scopes
}
pub fn get_symbol_in_scope(&self, scope_id: usize, name: &str) -> Option<&Symbol> {
let id = self.scopes.get(scope_id)?.symbols.get(name)?;
self.get_symbol(*id)
}
pub fn get_symbol_id_in_scope(&self, scope_id: usize, name: &str) -> Option<SymbolId> {
self.scopes.get(scope_id)?.symbols.get(name).copied()
}
pub fn get_scope_parent(&self, scope_id: usize) -> Option<usize> {
self.scopes.get(scope_id)?.parent
}
pub fn get_scope_for_file(&self, file_path: &str) -> Option<usize> {
for (scope_id, scope) in self.scopes.iter().enumerate() {
if scope
.imports
.iter()
.any(|import| import.file.as_deref() == Some(file_path))
{
return Some(scope_id);
}
}
self.get_symbols_for_file(file_path)
.first()
.map(|symbol| symbol.scope_id())
}
pub fn get_scope_imports(&self, scope_id: usize) -> Vec<super::scope::Import> {
self.scopes
.get(scope_id)
.map(|scope| scope.imports.clone())
.unwrap_or_default()
}
pub fn get_file_imports(&self, file_path: &str) -> Vec<(String, Span)> {
let normalized = normalize_path(file_path);
self.imports_by_file
.get(&normalized)
.into_iter()
.flatten()
.filter_map(|import| import.span.map(|span| (import.path.clone(), span)))
.collect()
}
pub fn get_symbols_for_file(&self, file_path: &str) -> Vec<&Symbol> {
let normalized = normalize_path(file_path);
self.symbols_by_file
.get(&normalized)
.into_iter()
.flatten()
.filter_map(|id| self.get_symbol(*id))
.collect()
}
pub fn get_symbol(&self, id: SymbolId) -> Option<&Symbol> {
self.arena.get(id.index())
}
pub fn get_symbol_mut(&mut self, id: SymbolId) -> Option<&mut Symbol> {
self.arena.get_mut(id.index())
}
pub fn find_by_qualified_name(&self, qualified_name: &str) -> Option<&Symbol> {
let id = self.symbols_by_qname.get(qualified_name)?;
self.get_symbol(*id)
}
pub fn find_id_by_qualified_name(&self, qualified_name: &str) -> Option<SymbolId> {
self.symbols_by_qname.get(qualified_name).copied()
}
pub fn get_qualified_names_for_file(&self, file_path: &str) -> Vec<String> {
let normalized = normalize_path(file_path);
self.symbols_by_file
.get(&normalized)
.into_iter()
.flatten()
.filter_map(|id| self.get_symbol(*id).map(|s| s.qualified_name().to_string()))
.collect()
}
}
impl Default for SymbolTable {
fn default() -> Self {
Self::new()
}
}
impl EventBus<SymbolTableEvent> for SymbolTable {
fn publish(&mut self, event: &SymbolTableEvent) {
let emitter = std::mem::take(&mut self.events);
self.events = emitter.emit(event.clone(), self);
}
}