187 lines
5.3 KiB
Rust
187 lines
5.3 KiB
Rust
use crate::common::spans::Span;
|
|
use prometeu_analysis::NameInterner;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RawSymbol {
|
|
pub pc: u32,
|
|
pub span: Span,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct DebugSymbol {
|
|
pub pc: u32,
|
|
pub file: String,
|
|
pub line: usize,
|
|
pub col: usize,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct Symbol {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub kind: String,
|
|
pub exported: bool,
|
|
pub module_path: String,
|
|
pub decl_span: SpanRange,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct SpanRange {
|
|
pub file: String,
|
|
pub start: Pos,
|
|
pub end: Pos,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct Pos {
|
|
pub line: u32,
|
|
pub col: u32,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct ProjectSymbols {
|
|
pub project: String,
|
|
pub project_dir: String,
|
|
pub symbols: Vec<Symbol>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct SymbolsFile {
|
|
pub schema_version: u32,
|
|
pub compiler_version: String,
|
|
pub root_project: String,
|
|
pub projects: Vec<ProjectSymbols>,
|
|
}
|
|
|
|
pub type SymbolInfo = Symbol;
|
|
|
|
pub fn collect_symbols(
|
|
project_id: &str,
|
|
module_symbols: &HashMap<String, crate::frontends::pbs::symbols::ModuleSymbols>,
|
|
file_manager: &crate::common::files::FileManager,
|
|
interner: &NameInterner,
|
|
) -> Vec<Symbol> {
|
|
let mut result = Vec::new();
|
|
|
|
for (module_path, ms) in module_symbols {
|
|
// Collect from type_symbols
|
|
for sym in ms.type_symbols.symbols.values() {
|
|
if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager, interner) {
|
|
result.push(s);
|
|
}
|
|
}
|
|
// Collect from value_symbols
|
|
for sym in ms.value_symbols.symbols.values() {
|
|
if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager, interner) {
|
|
result.push(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deterministic ordering: by file, then start pos, then name
|
|
result.sort_by(|a, b| {
|
|
a.decl_span.file.cmp(&b.decl_span.file)
|
|
.then(a.decl_span.start.line.cmp(&b.decl_span.start.line))
|
|
.then(a.decl_span.start.col.cmp(&b.decl_span.start.col))
|
|
.then(a.name.cmp(&b.name))
|
|
});
|
|
|
|
result
|
|
}
|
|
|
|
fn convert_symbol(
|
|
project_id: &str,
|
|
module_path: &str,
|
|
sym: &crate::frontends::pbs::symbols::Symbol,
|
|
file_manager: &crate::common::files::FileManager,
|
|
interner: &NameInterner,
|
|
) -> Option<Symbol> {
|
|
use crate::frontends::pbs::symbols::{SymbolKind, Visibility};
|
|
|
|
let kind = match sym.kind {
|
|
SymbolKind::Service => "service",
|
|
SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => "type",
|
|
SymbolKind::Function => "function",
|
|
SymbolKind::Local => return None, // Ignore locals for v0
|
|
};
|
|
|
|
let exported = sym.visibility == Visibility::Pub;
|
|
|
|
// According to v0 policy, only service and declare are exported.
|
|
// Functions are NOT exportable yet.
|
|
if exported && sym.kind == SymbolKind::Function {
|
|
// This should have been caught by semantic analysis, but we enforce it here too
|
|
// for the symbols.json output.
|
|
// Actually, we'll just mark it exported=false if it's a function.
|
|
}
|
|
|
|
let span = sym.span;
|
|
let file_path = file_manager.get_path(span.file_id)
|
|
.map(|p| p.to_string_lossy().to_string())
|
|
.unwrap_or_else(|| format!("unknown_file_{}", span.file_id));
|
|
|
|
// Convert 1-based to 0-based
|
|
let (s_line, s_col) = file_manager.lookup_pos(span.file_id, span.start);
|
|
let (e_line, e_col) = file_manager.lookup_pos(span.file_id, span.end);
|
|
|
|
let decl_span = SpanRange {
|
|
file: file_path,
|
|
start: Pos { line: (s_line - 1) as u32, col: (s_col - 1) as u32 },
|
|
end: Pos { line: (e_line - 1) as u32, col: (e_col - 1) as u32 },
|
|
};
|
|
|
|
let hash = decl_span.compute_hash();
|
|
let name = interner.resolve(sym.name).to_string();
|
|
let id = format!("{}:{}:{}:{}:{:016x}", project_id, kind, module_path, name, hash);
|
|
|
|
Some(Symbol {
|
|
id,
|
|
name,
|
|
kind: kind.to_string(),
|
|
exported,
|
|
module_path: module_path.to_string(),
|
|
decl_span,
|
|
})
|
|
}
|
|
|
|
impl SpanRange {
|
|
pub fn compute_hash(&self) -> u64 {
|
|
let mut h = 0xcbf29ce484222325u64;
|
|
let mut update = |bytes: &[u8]| {
|
|
for b in bytes {
|
|
h ^= *b as u64;
|
|
h = h.wrapping_mul(0x100000001b3u64);
|
|
}
|
|
};
|
|
|
|
update(self.file.as_bytes());
|
|
update(&self.start.line.to_le_bytes());
|
|
update(&self.start.col.to_le_bytes());
|
|
update(&self.end.line.to_le_bytes());
|
|
update(&self.end.col.to_le_bytes());
|
|
h
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_symbol_id_is_stable() {
|
|
let span = SpanRange {
|
|
file: "main.pbs".to_string(),
|
|
start: Pos { line: 10, col: 5 },
|
|
end: Pos { line: 10, col: 20 },
|
|
};
|
|
|
|
let hash1 = span.compute_hash();
|
|
let hash2 = span.compute_hash();
|
|
|
|
assert_eq!(hash1, hash2);
|
|
assert_eq!(hash1, 7774626535098684588u64); // Fixed value for stability check
|
|
}
|
|
}
|