impl SymbolTable {
#[must_use]
pub fn new() -> Self {
Self {
symbols: DashMap::new(),
span_index: DashMap::new(),
}
}
pub fn insert(&self, qualified_name: QualifiedName, location: Location) {
debug!("Inserting symbol: {} at {:?}", qualified_name, location);
self.symbols
.insert(qualified_name.clone(), location.clone());
let mut entry = self
.span_index
.entry(location.file_path.clone())
.or_default();
entry.push((location.span.start, qualified_name));
entry.sort_by_key(|(pos, _)| *pos);
}
#[must_use]
pub fn resolve_relative(&self, rel: &RelativeLocation, file: &Path) -> Option<Location> {
match rel {
RelativeLocation::Function { name, module } => {
let qname = self.build_qualified_name(file, module.as_deref(), name)?;
self.symbols.get(&qname).map(|entry| entry.clone())
}
RelativeLocation::Span { start, end } => Some(Location {
file_path: file.to_owned(),
span: Span {
start: BytePos(*start),
end: BytePos(*end),
},
}),
RelativeLocation::Symbol { qualified_name } => {
let qname: QualifiedName = qualified_name.parse().ok()?;
self.symbols.get(&qname).map(|entry| entry.clone())
}
}
}
#[must_use]
pub fn symbol_at_location(&self, location: &Location) -> Option<QualifiedName> {
if let Some(spans) = self.span_index.get(&location.file_path) {
let pos = location.span.start;
match spans.binary_search_by_key(&pos, |(start_pos, _)| *start_pos) {
Ok(index) => Some(spans[index].1.clone()),
Err(index) => {
if index > 0 {
Some(spans[index - 1].1.clone())
} else {
None
}
}
}
} else {
None
}
}
#[must_use]
pub fn symbols_in_span(&self, location: &Location) -> Vec<QualifiedName> {
if let Some(spans) = self.span_index.get(&location.file_path) {
let start_idx = match spans.binary_search_by_key(&location.span.start, |(pos, _)| *pos)
{
Ok(idx) => idx,
Err(idx) => idx.saturating_sub(1), };
let mut result = Vec::new();
for i in start_idx..spans.len() {
let (pos, qname) = &spans[i];
if *pos > location.span.end {
break; }
if location.span.contains(*pos) {
result.push(qname.clone());
}
}
result
} else {
Vec::new()
}
}
#[must_use]
pub fn get_location(&self, qualified_name: &QualifiedName) -> Option<Location> {
self.symbols.get(qualified_name).map(|entry| entry.clone())
}
#[must_use]
pub fn all_symbols(&self) -> Vec<(QualifiedName, Location)> {
self.symbols
.iter()
.map(|entry| (entry.key().clone(), entry.value().clone()))
.collect()
}
pub fn clear(&self) {
self.symbols.clear();
self.span_index.clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.symbols.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.symbols.is_empty()
}
fn build_qualified_name(
&self,
file: &Path,
module: Option<&str>,
name: &str,
) -> Option<QualifiedName> {
let module_path = match module {
Some(explicit_module) => self.parse_explicit_module(explicit_module),
None => self.infer_module_from_file_path(file),
};
Some(QualifiedName::new(module_path, name.to_string()))
}
fn parse_explicit_module(&self, module: &str) -> Vec<String> {
module
.split("::")
.map(std::string::ToString::to_string)
.collect()
}
fn infer_module_from_file_path(&self, file: &Path) -> Vec<String> {
let mut module_path = Vec::new();
if let Some(stem_str) = self.extract_significant_file_stem(file) {
module_path.push(stem_str);
}
self.add_parent_directories_to_module_path(file, &mut module_path);
module_path
}
fn extract_significant_file_stem(&self, file: &Path) -> Option<String> {
file.file_stem()
.and_then(|stem| stem.to_str())
.filter(|&stem_str| !matches!(stem_str, "mod" | "lib" | "main"))
.map(std::string::ToString::to_string)
}
fn add_parent_directories_to_module_path(&self, file: &Path, module_path: &mut Vec<String>) {
let mut current = file.parent();
while let Some(parent) = current {
if let Some(dir_name) = self.extract_directory_name(parent) {
if dir_name == "src" {
break;
}
module_path.insert(0, dir_name);
}
current = parent.parent();
}
}
fn extract_directory_name(&self, path: &Path) -> Option<String> {
path.file_name()
.and_then(|name| name.to_str())
.map(std::string::ToString::to_string)
}
}