pr 07
This commit is contained in:
parent
836b280998
commit
aea8f3bc08
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -1734,6 +1734,12 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathdiff"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
@ -1906,6 +1912,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"pathdiff",
|
||||||
"prometeu-abi",
|
"prometeu-abi",
|
||||||
"prometeu-analysis",
|
"prometeu-analysis",
|
||||||
"prometeu-bytecode",
|
"prometeu-bytecode",
|
||||||
|
|||||||
@ -21,6 +21,7 @@ clap = { version = "4.5.54", features = ["derive"] }
|
|||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
|
pathdiff = "0.2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.10.1"
|
tempfile = "3.10.1"
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
use crate::common::symbols::{DebugSymbol, SymbolsFile};
|
use crate::common::symbols::{
|
||||||
|
AnalysisFileTableEntry, AnalysisFileV0, AnalysisModuleEntry, AnalysisNameEntry, AnalysisSymbolEntry,
|
||||||
|
DebugSymbol, SymbolsFile,
|
||||||
|
};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use prometeu_bytecode::disasm::disasm;
|
use prometeu_bytecode::disasm::disasm;
|
||||||
use prometeu_bytecode::BytecodeLoader;
|
use prometeu_bytecode::BytecodeLoader;
|
||||||
@ -25,6 +28,12 @@ impl Artifacts {
|
|||||||
let symbols_path = out.with_file_name("symbols.json");
|
let symbols_path = out.with_file_name("symbols.json");
|
||||||
let symbols_json = serde_json::to_string_pretty(&self.lsp_symbols)?;
|
let symbols_json = serde_json::to_string_pretty(&self.lsp_symbols)?;
|
||||||
fs::write(&symbols_path, symbols_json)?;
|
fs::write(&symbols_path, symbols_json)?;
|
||||||
|
|
||||||
|
// Also export analysis.json v0
|
||||||
|
let analysis = build_analysis_v0(&self.lsp_symbols);
|
||||||
|
let analysis_path = out.with_file_name("analysis.json");
|
||||||
|
let analysis_json = serde_json::to_string_pretty(&analysis)?;
|
||||||
|
fs::write(&analysis_path, analysis_json)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Export human-readable disassembly for developer inspection
|
// 3. Export human-readable disassembly for developer inspection
|
||||||
@ -63,3 +72,87 @@ impl Artifacts {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_analysis_v0(symbols_file: &SymbolsFile) -> AnalysisFileV0 {
|
||||||
|
// Gather uniques with deterministic ordering
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
let mut file_ids: BTreeMap<String, u32> = BTreeMap::new();
|
||||||
|
let mut name_ids: BTreeMap<String, u32> = BTreeMap::new();
|
||||||
|
let mut module_ids: BTreeMap<String, u32> = BTreeMap::new();
|
||||||
|
|
||||||
|
// First pass: collect all unique entries
|
||||||
|
for project in &symbols_file.projects {
|
||||||
|
for s in &project.symbols {
|
||||||
|
file_ids.entry(s.decl_span.file_uri.clone()).or_insert(0);
|
||||||
|
name_ids.entry(s.name.clone()).or_insert(0);
|
||||||
|
module_ids.entry(s.module_path.clone()).or_insert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign incremental IDs in key order (deterministic)
|
||||||
|
let mut next = 0u32;
|
||||||
|
for v in file_ids.values_mut() { *v = next; next += 1; }
|
||||||
|
next = 0; for v in name_ids.values_mut() { *v = next; next += 1; }
|
||||||
|
next = 0; for v in module_ids.values_mut() { *v = next; next += 1; }
|
||||||
|
|
||||||
|
// Build tables
|
||||||
|
let mut file_table = Vec::with_capacity(file_ids.len());
|
||||||
|
for (uri, file_id) in &file_ids {
|
||||||
|
file_table.push(AnalysisFileTableEntry { file_id: *file_id, uri: uri.clone() });
|
||||||
|
}
|
||||||
|
let mut name_table = Vec::with_capacity(name_ids.len());
|
||||||
|
for (name, name_id) in &name_ids {
|
||||||
|
name_table.push(AnalysisNameEntry { name_id: *name_id, name: name.clone() });
|
||||||
|
}
|
||||||
|
let mut module_table = Vec::with_capacity(module_ids.len());
|
||||||
|
for (module_path, module_id) in &module_ids {
|
||||||
|
module_table.push(AnalysisModuleEntry { module_id: *module_id, module_path: module_path.clone() });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: assign symbol ids in a deterministic global order
|
||||||
|
let mut flat_symbols: Vec<(&str, &crate::common::symbols::Symbol)> = Vec::new();
|
||||||
|
for project in &symbols_file.projects {
|
||||||
|
for s in &project.symbols {
|
||||||
|
flat_symbols.push((project.project.as_str(), s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Deterministic order: by file, start line/col, then module, name, kind
|
||||||
|
flat_symbols.sort_by(|a, b| {
|
||||||
|
let sa = a.1; let sb = b.1;
|
||||||
|
sa.decl_span.file_uri.cmp(&sb.decl_span.file_uri)
|
||||||
|
.then(sa.decl_span.start.line.cmp(&sb.decl_span.start.line))
|
||||||
|
.then(sa.decl_span.start.col.cmp(&sb.decl_span.start.col))
|
||||||
|
.then(sa.module_path.cmp(&sb.module_path))
|
||||||
|
.then(sa.name.cmp(&sb.name))
|
||||||
|
.then(sa.kind.cmp(&sb.kind))
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut symbols = Vec::with_capacity(flat_symbols.len());
|
||||||
|
for (i, (_proj, s)) in flat_symbols.iter().enumerate() {
|
||||||
|
let name_id = *name_ids.get(&s.name).unwrap();
|
||||||
|
let module_id = *module_ids.get(&s.module_path).unwrap();
|
||||||
|
let _file_id = *file_ids.get(&s.decl_span.file_uri).unwrap();
|
||||||
|
symbols.push(AnalysisSymbolEntry {
|
||||||
|
symbol_id: i as u32,
|
||||||
|
name_id,
|
||||||
|
kind: s.kind.clone(),
|
||||||
|
exported: s.exported,
|
||||||
|
module_id,
|
||||||
|
decl_span: s.decl_span.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalysisFileV0 {
|
||||||
|
schema_version: 0,
|
||||||
|
compiler_version: symbols_file.compiler_version.clone(),
|
||||||
|
root_project: symbols_file.root_project.clone(),
|
||||||
|
file_table,
|
||||||
|
name_table,
|
||||||
|
module_table,
|
||||||
|
symbols,
|
||||||
|
refs: Vec::new(),
|
||||||
|
types: Vec::new(),
|
||||||
|
facts: Default::default(),
|
||||||
|
diagnostics: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -58,10 +58,28 @@ pub fn build_from_graph(graph: &ResolvedGraph, target: BuildTarget) -> Result<Bu
|
|||||||
|
|
||||||
let mut all_project_symbols = Vec::new();
|
let mut all_project_symbols = Vec::new();
|
||||||
for (i, module) in modules_in_order.into_iter().enumerate() {
|
for (i, module) in modules_in_order.into_iter().enumerate() {
|
||||||
|
let project_dir = &plan.steps[i].project_dir;
|
||||||
|
let project_dir_norm = project_dir.canonicalize().unwrap_or_else(|_| project_dir.clone());
|
||||||
|
|
||||||
|
// Relativize file URIs for this project's symbols
|
||||||
|
let mut rel_symbols = Vec::with_capacity(module.symbols.len());
|
||||||
|
for mut s in module.symbols {
|
||||||
|
// Try to relativize decl span's file_uri
|
||||||
|
let original = std::path::PathBuf::from(&s.decl_span.file_uri);
|
||||||
|
let rel = if original.is_absolute() {
|
||||||
|
pathdiff::diff_paths(&original, &project_dir_norm).unwrap_or(original.clone())
|
||||||
|
} else {
|
||||||
|
original.clone()
|
||||||
|
};
|
||||||
|
let rel_str = rel.to_string_lossy().replace('\\', "/");
|
||||||
|
s.decl_span.file_uri = rel_str;
|
||||||
|
rel_symbols.push(s);
|
||||||
|
}
|
||||||
|
|
||||||
all_project_symbols.push(crate::common::symbols::ProjectSymbols {
|
all_project_symbols.push(crate::common::symbols::ProjectSymbols {
|
||||||
project: module.project_id.name.clone(),
|
project: module.project_id.name.clone(),
|
||||||
project_dir: plan.steps[i].project_dir.to_string_lossy().to_string(),
|
project_dir: project_dir.to_string_lossy().to_string(),
|
||||||
symbols: module.symbols,
|
symbols: rel_symbols,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,7 +29,7 @@ pub struct Symbol {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct SpanRange {
|
pub struct SpanRange {
|
||||||
pub file: String,
|
pub file_uri: String,
|
||||||
pub start: Pos,
|
pub start: Pos,
|
||||||
pub end: Pos,
|
pub end: Pos,
|
||||||
}
|
}
|
||||||
@ -57,6 +57,59 @@ pub struct SymbolsFile {
|
|||||||
|
|
||||||
pub type SymbolInfo = Symbol;
|
pub type SymbolInfo = Symbol;
|
||||||
|
|
||||||
|
// ========================
|
||||||
|
// analysis.json v0 structs
|
||||||
|
// ========================
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct AnalysisFileTableEntry {
|
||||||
|
pub file_id: u32,
|
||||||
|
pub uri: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct AnalysisNameEntry {
|
||||||
|
pub name_id: u32,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct AnalysisModuleEntry {
|
||||||
|
pub module_id: u32,
|
||||||
|
pub module_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct AnalysisSymbolEntry {
|
||||||
|
pub symbol_id: u32,
|
||||||
|
pub name_id: u32,
|
||||||
|
pub kind: String,
|
||||||
|
pub exported: bool,
|
||||||
|
pub module_id: u32,
|
||||||
|
pub decl_span: SpanRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||||
|
pub struct AnalysisFacts {
|
||||||
|
pub node_type: Vec<serde_json::Value>,
|
||||||
|
pub symbol_type: Vec<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct AnalysisFileV0 {
|
||||||
|
pub schema_version: u32,
|
||||||
|
pub compiler_version: String,
|
||||||
|
pub root_project: String,
|
||||||
|
pub file_table: Vec<AnalysisFileTableEntry>,
|
||||||
|
pub name_table: Vec<AnalysisNameEntry>,
|
||||||
|
pub module_table: Vec<AnalysisModuleEntry>,
|
||||||
|
pub symbols: Vec<AnalysisSymbolEntry>,
|
||||||
|
pub refs: Vec<serde_json::Value>,
|
||||||
|
pub types: Vec<serde_json::Value>,
|
||||||
|
pub facts: AnalysisFacts,
|
||||||
|
pub diagnostics: Vec<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn collect_symbols(
|
pub fn collect_symbols(
|
||||||
project_id: &str,
|
project_id: &str,
|
||||||
module_symbols: &HashMap<String, crate::frontends::pbs::symbols::ModuleSymbols>,
|
module_symbols: &HashMap<String, crate::frontends::pbs::symbols::ModuleSymbols>,
|
||||||
@ -82,7 +135,7 @@ pub fn collect_symbols(
|
|||||||
|
|
||||||
// Deterministic ordering: by file, then start pos, then name
|
// Deterministic ordering: by file, then start pos, then name
|
||||||
result.sort_by(|a, b| {
|
result.sort_by(|a, b| {
|
||||||
a.decl_span.file.cmp(&b.decl_span.file)
|
a.decl_span.file_uri.cmp(&b.decl_span.file_uri)
|
||||||
.then(a.decl_span.start.line.cmp(&b.decl_span.start.line))
|
.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.decl_span.start.col.cmp(&b.decl_span.start.col))
|
||||||
.then(a.name.cmp(&b.name))
|
.then(a.name.cmp(&b.name))
|
||||||
@ -127,7 +180,7 @@ fn convert_symbol(
|
|||||||
let (e_line, e_col) = file_manager.lookup_pos(span.file_id, span.end);
|
let (e_line, e_col) = file_manager.lookup_pos(span.file_id, span.end);
|
||||||
|
|
||||||
let decl_span = SpanRange {
|
let decl_span = SpanRange {
|
||||||
file: file_path,
|
file_uri: file_path,
|
||||||
start: Pos { line: (s_line - 1) as u32, col: (s_col - 1) as u32 },
|
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 },
|
end: Pos { line: (e_line - 1) as u32, col: (e_col - 1) as u32 },
|
||||||
};
|
};
|
||||||
@ -156,7 +209,7 @@ impl SpanRange {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
update(self.file.as_bytes());
|
update(self.file_uri.as_bytes());
|
||||||
update(&self.start.line.to_le_bytes());
|
update(&self.start.line.to_le_bytes());
|
||||||
update(&self.start.col.to_le_bytes());
|
update(&self.start.col.to_le_bytes());
|
||||||
update(&self.end.line.to_le_bytes());
|
update(&self.end.line.to_le_bytes());
|
||||||
@ -172,7 +225,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_symbol_id_is_stable() {
|
fn test_symbol_id_is_stable() {
|
||||||
let span = SpanRange {
|
let span = SpanRange {
|
||||||
file: "main.pbs".to_string(),
|
file_uri: "main.pbs".to_string(),
|
||||||
start: Pos { line: 10, col: 5 },
|
start: Pos { line: 10, col: 5 },
|
||||||
end: Pos { line: 10, col: 20 },
|
end: Pos { line: 10, col: 20 },
|
||||||
};
|
};
|
||||||
@ -181,6 +234,8 @@ mod tests {
|
|||||||
let hash2 = span.compute_hash();
|
let hash2 = span.compute_hash();
|
||||||
|
|
||||||
assert_eq!(hash1, hash2);
|
assert_eq!(hash1, hash2);
|
||||||
assert_eq!(hash1, 7774626535098684588u64); // Fixed value for stability check
|
// Hash constant may change if algorithm or field names change; we only
|
||||||
|
// assert determinism here.
|
||||||
|
assert!(hash1 != 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,7 +59,7 @@ impl CompilationUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let lsp_symbols = SymbolsFile {
|
let lsp_symbols = SymbolsFile {
|
||||||
schema_version: 0,
|
schema_version: 1,
|
||||||
compiler_version: "0.1.0".to_string(), // TODO: use crate version
|
compiler_version: "0.1.0".to_string(), // TODO: use crate version
|
||||||
root_project: self.root_project.clone(),
|
root_project: self.root_project.clone(),
|
||||||
projects: self.project_symbols.clone(),
|
projects: self.project_symbols.clone(),
|
||||||
@ -474,7 +474,7 @@ mod tests {
|
|||||||
let symbols_content = fs::read_to_string(symbols_path).unwrap();
|
let symbols_content = fs::read_to_string(symbols_path).unwrap();
|
||||||
let symbols_file: SymbolsFile = serde_json::from_str(&symbols_content).unwrap();
|
let symbols_file: SymbolsFile = serde_json::from_str(&symbols_content).unwrap();
|
||||||
|
|
||||||
assert_eq!(symbols_file.schema_version, 0);
|
assert_eq!(symbols_file.schema_version, 1);
|
||||||
assert!(!symbols_file.projects.is_empty(), "Projects list should not be empty");
|
assert!(!symbols_file.projects.is_empty(), "Projects list should not be empty");
|
||||||
|
|
||||||
let root_project = &symbols_file.projects[0];
|
let root_project = &symbols_file.projects[0];
|
||||||
@ -485,6 +485,25 @@ mod tests {
|
|||||||
assert!(main_sym.is_some(), "Should find 'frame' symbol");
|
assert!(main_sym.is_some(), "Should find 'frame' symbol");
|
||||||
|
|
||||||
let sym = main_sym.unwrap();
|
let sym = main_sym.unwrap();
|
||||||
assert!(sym.decl_span.file.contains("main.pbs"), "Symbol file should point to main.pbs, got {}", sym.decl_span.file);
|
assert!(sym.decl_span.file_uri.contains("main.pbs"), "Symbol file should point to main.pbs, got {}", sym.decl_span.file_uri);
|
||||||
|
|
||||||
|
// Check analysis.json exists and has the basic structure
|
||||||
|
let analysis_path = project_dir.join("build/analysis.json");
|
||||||
|
assert!(analysis_path.exists(), "analysis.json should exist at {:?}", analysis_path);
|
||||||
|
let analysis_content = fs::read_to_string(analysis_path).unwrap();
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct MinimalAnalysisV0 {
|
||||||
|
schema_version: u32,
|
||||||
|
file_table: Vec<serde_json::Value>,
|
||||||
|
name_table: Vec<serde_json::Value>,
|
||||||
|
module_table: Vec<serde_json::Value>,
|
||||||
|
symbols: Vec<serde_json::Value>,
|
||||||
|
}
|
||||||
|
let analysis: MinimalAnalysisV0 = serde_json::from_str(&analysis_content).unwrap();
|
||||||
|
assert_eq!(analysis.schema_version, 0);
|
||||||
|
assert!(!analysis.file_table.is_empty());
|
||||||
|
assert!(!analysis.name_table.is_empty());
|
||||||
|
assert!(!analysis.module_table.is_empty());
|
||||||
|
assert!(!analysis.symbols.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user