pub mod context;
use crate::parsing::registry::LanguageId;
use crate::types::{CompactString, FileId, Range, SymbolId, SymbolKind, compact_string};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Visibility {
Public,
Crate,
Module,
Private,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum ScopeContext {
Local {
hoisted: bool,
parent_name: Option<CompactString>,
parent_kind: Option<SymbolKind>,
},
Parameter,
ClassMember {
class_name: Option<CompactString>,
},
#[default]
Module,
Package,
Global,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Symbol {
pub id: SymbolId,
pub name: CompactString,
pub kind: SymbolKind,
pub file_id: FileId,
pub range: Range,
pub file_path: Box<str>,
pub signature: Option<Box<str>>,
pub doc_comment: Option<Box<str>>,
pub module_path: Option<Box<str>>,
pub visibility: Visibility,
pub scope_context: Option<ScopeContext>,
pub language_id: Option<LanguageId>,
}
#[repr(C, align(32))]
#[derive(Debug, Clone, Copy)]
pub struct CompactSymbol {
pub name_offset: u32,
pub kind: u8,
pub flags: u8,
pub file_id: u16,
pub start_line: u32,
pub start_col: u16,
pub end_line: u32,
pub end_col: u16,
pub symbol_id: u32,
_padding: [u8; 2],
}
impl Symbol {
pub fn new(
id: SymbolId,
name: impl Into<CompactString>,
kind: SymbolKind,
file_id: FileId,
range: Range,
) -> Self {
Self {
id,
name: name.into(),
kind,
file_id,
range,
file_path: "<unknown>".into(),
signature: None,
doc_comment: None,
module_path: None,
visibility: Visibility::Private,
scope_context: None, language_id: None, }
}
pub fn new_with_scope(
id: SymbolId,
name: impl Into<CompactString>,
kind: SymbolKind,
file_id: FileId,
range: Range,
scope: ScopeContext,
) -> Self {
let mut symbol = Self::new(id, name, kind, file_id, range);
symbol.scope_context = Some(scope);
symbol
}
pub fn with_file_path(mut self, file_path: impl Into<Box<str>>) -> Self {
self.file_path = file_path.into();
self
}
pub fn with_signature(mut self, signature: impl Into<Box<str>>) -> Self {
self.signature = Some(signature.into());
self
}
pub fn with_doc(mut self, doc: impl Into<Box<str>>) -> Self {
self.doc_comment = Some(doc.into());
self
}
pub fn with_module_path(mut self, path: impl Into<Box<str>>) -> Self {
self.module_path = Some(path.into());
self
}
pub fn with_visibility(mut self, visibility: Visibility) -> Self {
self.visibility = visibility;
self
}
pub fn with_scope(mut self, scope: ScopeContext) -> Self {
self.scope_context = Some(scope);
self
}
pub fn with_language_id(mut self, language_id: LanguageId) -> Self {
self.language_id = Some(language_id);
self
}
pub fn as_name(&self) -> &str {
&self.name
}
pub fn into_name(self) -> CompactString {
self.name
}
pub fn as_signature(&self) -> Option<&str> {
self.signature.as_deref()
}
pub fn as_doc_comment(&self) -> Option<&str> {
self.doc_comment.as_deref()
}
pub fn as_module_path(&self) -> Option<&str> {
self.module_path.as_deref()
}
pub fn to_compact(&self, string_table: &mut StringTable) -> CompactSymbol {
let name_offset = string_table.intern(&self.name);
CompactSymbol {
name_offset,
kind: self.kind as u8,
flags: 0,
file_id: self.file_id.value() as u16,
start_line: self.range.start_line,
start_col: self.range.start_column,
end_line: self.range.end_line,
end_col: self.range.end_column,
symbol_id: self.id.value(),
_padding: [0; 2],
}
}
}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)?;
if let Some(sig) = &self.signature {
write!(f, "\n Signature: {sig}")?;
}
write!(f, "\n Kind: {:?}", self.kind)?;
write!(f, "\n Visibility: {:?}", self.visibility)?;
write!(
f,
"\n Location: file#{} {}:{}-{}:{}",
self.file_id.value(),
self.range.start_line,
self.range.start_column,
self.range.end_line,
self.range.end_column
)?;
if let Some(module) = &self.module_path {
write!(f, "\n Module: {module}")?;
}
if let Some(doc) = &self.doc_comment {
let truncated = if doc.len() > 100 {
format!("{}...", &doc[..100])
} else {
doc.to_string()
};
write!(f, "\n Doc: {truncated}")?;
}
Ok(())
}
}
pub struct StringTable {
data: Vec<u8>,
offsets: std::collections::HashMap<String, u32>,
}
impl Default for StringTable {
fn default() -> Self {
Self {
data: vec![0], offsets: std::collections::HashMap::new(),
}
}
}
impl StringTable {
pub fn new() -> Self {
Self::default()
}
pub fn intern(&mut self, s: &str) -> u32 {
if let Some(&offset) = self.offsets.get(s) {
return offset;
}
let offset = self.data.len() as u32;
self.data.extend_from_slice(s.as_bytes());
self.data.push(0); self.offsets.insert(s.to_string(), offset);
offset
}
pub fn get(&self, offset: u32) -> Option<&str> {
let start = offset as usize;
if start >= self.data.len() {
return None;
}
let end = self.data[start..]
.iter()
.position(|&b| b == 0)
.map(|pos| start + pos)?;
std::str::from_utf8(&self.data[start..end]).ok()
}
}
impl CompactSymbol {
pub fn from_symbol(symbol: &Symbol, string_table: &StringTable) -> Option<Self> {
let name_offset = string_table.offsets.get(symbol.name.as_ref())?;
Some(CompactSymbol {
name_offset: *name_offset,
kind: symbol.kind as u8,
flags: 0,
file_id: symbol.file_id.value() as u16,
start_line: symbol.range.start_line,
start_col: symbol.range.start_column,
end_line: symbol.range.end_line,
end_col: symbol.range.end_column,
symbol_id: symbol.id.value(),
_padding: [0; 2],
})
}
pub fn to_symbol(&self, string_table: &StringTable) -> Option<Symbol> {
let name = string_table.get(self.name_offset)?;
let kind = match self.kind {
0 => SymbolKind::Function,
1 => SymbolKind::Method,
2 => SymbolKind::Struct,
3 => SymbolKind::Enum,
4 => SymbolKind::Trait,
5 => SymbolKind::Interface,
6 => SymbolKind::Class,
7 => SymbolKind::Module,
8 => SymbolKind::Variable,
9 => SymbolKind::Constant,
10 => SymbolKind::Field,
11 => SymbolKind::Parameter,
12 => SymbolKind::TypeAlias,
13 => SymbolKind::Macro,
_ => return None,
};
Some(Symbol {
id: SymbolId::new(self.symbol_id)?,
name: compact_string(name),
kind,
file_id: FileId::new(self.file_id as u32)?,
range: Range::new(self.start_line, self.start_col, self.end_line, self.end_col),
file_path: "<unknown>".into(),
signature: None,
doc_comment: None,
module_path: None,
visibility: Visibility::Private,
scope_context: None, language_id: None, })
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem;
#[test]
fn test_symbol_creation() {
let id = SymbolId::new(1).unwrap();
let file_id = FileId::new(10).unwrap();
let range = Range::new(5, 10, 5, 20);
let symbol = Symbol::new(id, "test_function", SymbolKind::Function, file_id, range);
assert_eq!(symbol.id, id);
assert_eq!(symbol.name.as_ref(), "test_function");
assert_eq!(symbol.kind, SymbolKind::Function);
assert_eq!(symbol.file_id, file_id);
assert_eq!(symbol.range, range);
assert!(symbol.signature.is_none());
}
#[test]
fn test_symbol_with_signature() {
let symbol = Symbol::new(
SymbolId::new(1).unwrap(),
"add",
SymbolKind::Function,
FileId::new(1).unwrap(),
Range::new(1, 0, 3, 1),
)
.with_signature("fn add(a: i32, b: i32) -> i32");
assert_eq!(
symbol.signature.as_deref(),
Some("fn add(a: i32, b: i32) -> i32")
);
}
#[test]
fn test_compact_symbol_size() {
assert_eq!(mem::size_of::<CompactSymbol>(), 32);
assert_eq!(mem::align_of::<CompactSymbol>(), 32);
}
#[test]
fn test_string_table() {
let mut table = StringTable::new();
let offset1 = table.intern("hello");
let offset2 = table.intern("world");
let offset3 = table.intern("hello");
assert_eq!(offset1, 1);
assert_ne!(offset1, offset2);
assert_eq!(offset1, offset3);
assert_eq!(table.get(offset1), Some("hello"));
assert_eq!(table.get(offset2), Some("world"));
assert_eq!(table.get(999), None);
}
#[test]
fn test_symbol_to_compact_and_back() {
let mut string_table = StringTable::new();
let original = Symbol::new(
SymbolId::new(42).unwrap(),
"test_method",
SymbolKind::Method,
FileId::new(7).unwrap(),
Range::new(10, 5, 15, 20),
);
let compact = original.to_compact(&mut string_table);
let restored = compact.to_symbol(&string_table).unwrap();
assert_eq!(original.id, restored.id);
assert_eq!(original.name, restored.name);
assert_eq!(original.kind, restored.kind);
assert_eq!(original.file_id, restored.file_id);
assert_eq!(original.range, restored.range);
}
#[test]
fn test_all_symbol_kinds_conversion() {
let kinds = [
SymbolKind::Function,
SymbolKind::Method,
SymbolKind::Struct,
SymbolKind::Enum,
SymbolKind::Trait,
SymbolKind::Interface,
SymbolKind::Class,
SymbolKind::Module,
SymbolKind::Variable,
SymbolKind::Constant,
SymbolKind::Field,
SymbolKind::Parameter,
SymbolKind::TypeAlias,
SymbolKind::Macro,
];
let mut string_table = StringTable::new();
for (i, kind) in kinds.iter().enumerate() {
let symbol = Symbol::new(
SymbolId::new((i + 1) as u32).unwrap(),
format!("test_{i}"),
*kind,
FileId::new(1).unwrap(),
Range::new(1, 0, 1, 10),
);
let compact = symbol.to_compact(&mut string_table);
let restored = compact.to_symbol(&string_table).unwrap();
assert_eq!(symbol.kind, restored.kind);
}
}
}