From 741b18fa01fbc32be53aff693d76b695abed93cc Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Tue, 3 Feb 2026 08:43:09 +0000 Subject: [PATCH] pr 67 --- .../src/backend/artifacts.rs | 15 +- .../src/backend/emit_bytecode.rs | 6 +- .../prometeu-compiler/src/building/linker.rs | 8 +- .../src/building/orchestrator.rs | 14 +- .../prometeu-compiler/src/building/output.rs | 7 +- .../prometeu-compiler/src/common/symbols.rs | 168 +++++++++++++++++- crates/prometeu-compiler/src/compiler.rs | 43 +++-- .../src/frontends/pbs/collector.rs | 2 +- .../src/frontends/pbs/resolver.rs | 2 +- .../tests/export_conflicts.rs | 3 + 10 files changed, 239 insertions(+), 29 deletions(-) diff --git a/crates/prometeu-compiler/src/backend/artifacts.rs b/crates/prometeu-compiler/src/backend/artifacts.rs index c3688ddb..0da1817e 100644 --- a/crates/prometeu-compiler/src/backend/artifacts.rs +++ b/crates/prometeu-compiler/src/backend/artifacts.rs @@ -1,4 +1,4 @@ -use crate::common::symbols::Symbol; +use crate::common::symbols::{DebugSymbol, SymbolsFile}; use anyhow::{Context, Result}; use prometeu_bytecode::disasm::disasm; use prometeu_bytecode::BytecodeLoader; @@ -7,22 +7,23 @@ use std::path::Path; pub struct Artifacts { pub rom: Vec, - pub symbols: Vec, + pub debug_symbols: Vec, + pub lsp_symbols: SymbolsFile, } impl Artifacts { - pub fn new(rom: Vec, symbols: Vec) -> Self { - Self { rom, symbols } + pub fn new(rom: Vec, debug_symbols: Vec, lsp_symbols: SymbolsFile) -> Self { + Self { rom, debug_symbols, lsp_symbols } } pub fn export(&self, out: &Path, emit_disasm: bool, emit_symbols: bool) -> Result<()> { // 1. Save the main binary fs::write(out, &self.rom).with_context(|| format!("Failed to write PBC to {:?}", out))?; - // 2. Export symbols for the HostDebugger + // 2. Export symbols for LSP if emit_symbols { let symbols_path = out.with_file_name("symbols.json"); - let symbols_json = serde_json::to_string_pretty(&self.symbols)?; + let symbols_json = serde_json::to_string_pretty(&self.lsp_symbols)?; fs::write(&symbols_path, symbols_json)?; } @@ -42,7 +43,7 @@ impl Artifacts { let mut disasm_text = String::new(); for instr in instructions { // Find a matching symbol to show which source line generated this instruction - let symbol = self.symbols.iter().find(|s| s.pc == instr.pc); + let symbol = self.debug_symbols.iter().find(|s| s.pc == instr.pc); let comment = if let Some(s) = symbol { format!(" ; {}:{}", s.file, s.line) } else { diff --git a/crates/prometeu-compiler/src/backend/emit_bytecode.rs b/crates/prometeu-compiler/src/backend/emit_bytecode.rs index cc278258..4d240ffb 100644 --- a/crates/prometeu-compiler/src/backend/emit_bytecode.rs +++ b/crates/prometeu-compiler/src/backend/emit_bytecode.rs @@ -5,9 +5,8 @@ //! //! It performs two main tasks: //! 1. **Instruction Lowering**: Translates `ir_vm::Instruction` into `prometeu_bytecode::asm::Asm` ops. -//! 2. **Symbol Mapping**: Associates bytecode offsets (Program Counter) with source code locations. +//! 2. **DebugSymbol Mapping**: Associates bytecode offsets (Program Counter) with source code locations. -use crate::common::symbols::Symbol; use crate::ir_core::ConstantValue; use crate::ir_vm; use crate::ir_vm::instr::InstrKind; @@ -21,8 +20,6 @@ use prometeu_bytecode::{BytecodeModule, ConstantPoolEntry, DebugInfo, FunctionMe pub struct EmitResult { /// The serialized binary data of the PBC file. pub rom: Vec, - /// Metadata mapping bytecode offsets to source code positions. - pub symbols: Vec, } pub struct EmitFragments { @@ -55,7 +52,6 @@ pub fn emit_module(module: &ir_vm::Module) -> Result { Ok(EmitResult { rom: bytecode_module.serialize(), - symbols: vec![], // Symbols are currently not used in the new pipeline }) } diff --git a/crates/prometeu-compiler/src/building/linker.rs b/crates/prometeu-compiler/src/building/linker.rs index 6248227d..0dba4886 100644 --- a/crates/prometeu-compiler/src/building/linker.rs +++ b/crates/prometeu-compiler/src/building/linker.rs @@ -75,7 +75,7 @@ impl Linker { let mut combined_pc_to_span = Vec::new(); let mut combined_function_names = Vec::new(); - // 1. Symbol resolution map: (ProjectId, module_path, symbol_name) -> func_idx in combined_functions + // 1. DebugSymbol resolution map: (ProjectId, module_path, symbol_name) -> func_idx in combined_functions let mut global_symbols = HashMap::new(); let mut module_code_offsets = Vec::with_capacity(modules.len()); @@ -159,7 +159,7 @@ impl Linker { let symbol_id = (dep_project_id.clone(), import.key.module_path.clone(), import.key.symbol_name.clone()); let &target_func_idx = global_symbols.get(&symbol_id) - .ok_or_else(|| LinkError::UnresolvedSymbol(format!("Symbol '{}:{}' not found in project {:?}", symbol_id.1, symbol_id.2, symbol_id.0)))?; + .ok_or_else(|| LinkError::UnresolvedSymbol(format!("DebugSymbol '{}:{}' not found in project {:?}", symbol_id.1, symbol_id.2, symbol_id.0)))?; for &reloc_pc in &import.relocation_pcs { let absolute_pc = code_offset + reloc_pc as usize; @@ -315,6 +315,7 @@ mod tests { ..Default::default() }], debug_info: None, + symbols: vec![], }; // Root module: calls 'lib::math:add' @@ -351,6 +352,7 @@ mod tests { ..Default::default() }], debug_info: None, + symbols: vec![], }; let lib_step = BuildStep { @@ -401,6 +403,7 @@ mod tests { code: vec![], function_metas: vec![], debug_info: None, + symbols: vec![], }; let m2 = CompiledModule { @@ -412,6 +415,7 @@ mod tests { code: vec![], function_metas: vec![], debug_info: None, + symbols: vec![], }; let result = Linker::link(vec![m1, m2], vec![step.clone(), step]).unwrap(); diff --git a/crates/prometeu-compiler/src/building/orchestrator.rs b/crates/prometeu-compiler/src/building/orchestrator.rs index 473bc772..449e983b 100644 --- a/crates/prometeu-compiler/src/building/orchestrator.rs +++ b/crates/prometeu-compiler/src/building/orchestrator.rs @@ -16,6 +16,7 @@ pub enum BuildError { pub struct BuildResult { pub image: ProgramImage, pub file_manager: FileManager, + pub symbols: Vec, } impl std::fmt::Display for BuildError { @@ -53,9 +54,20 @@ pub fn build_from_graph(graph: &ResolvedGraph, target: BuildTarget) -> Result, pub function_metas: Vec, pub debug_info: Option, + pub symbols: Vec, } #[derive(Debug)] @@ -310,7 +311,10 @@ pub fn compile_project( } } - // 6. Collect imports from unresolved labels + // 6. Collect symbols + let project_symbols = crate::common::symbols::collect_symbols(&step.project_id.name, &module_symbols_map, file_manager); + + // 7. Collect imports from unresolved labels let mut imports = Vec::new(); for (label, pcs) in fragments.unresolved_labels { if label.starts_with('@') { @@ -346,6 +350,7 @@ pub fn compile_project( code: fragments.code, function_metas: fragments.functions, debug_info: fragments.debug_info, + symbols: project_symbols, }) } diff --git a/crates/prometeu-compiler/src/common/symbols.rs b/crates/prometeu-compiler/src/common/symbols.rs index 1b024b66..f810f0ce 100644 --- a/crates/prometeu-compiler/src/common/symbols.rs +++ b/crates/prometeu-compiler/src/common/symbols.rs @@ -1,5 +1,6 @@ use serde::{Serialize, Deserialize}; use crate::common::spans::Span; +use std::collections::HashMap; #[derive(Debug, Clone)] pub struct RawSymbol { @@ -8,9 +9,174 @@ pub struct RawSymbol { } #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Symbol { +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, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SymbolsFile { + pub schema_version: u32, + pub compiler_version: String, + pub root_project: String, + pub projects: Vec, +} + +pub type SymbolInfo = Symbol; + +pub fn collect_symbols( + project_id: &str, + module_symbols: &HashMap, + file_manager: &crate::common::files::FileManager, +) -> Vec { + 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) { + 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) { + 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, +) -> Option { + 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 id = format!("{}:{}:{}:{}:{:016x}", project_id, kind, module_path, sym.name, hash); + + Some(Symbol { + id, + name: sym.name.clone(), + 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 + } +} diff --git a/crates/prometeu-compiler/src/compiler.rs b/crates/prometeu-compiler/src/compiler.rs index 4d2196d8..49ac1dbe 100644 --- a/crates/prometeu-compiler/src/compiler.rs +++ b/crates/prometeu-compiler/src/compiler.rs @@ -5,7 +5,7 @@ use crate::backend; use crate::common::config::ProjectConfig; -use crate::common::symbols::{Symbol, RawSymbol}; +use crate::common::symbols::{DebugSymbol, RawSymbol, SymbolsFile, ProjectSymbols}; use crate::common::files::FileManager; use crate::common::spans::Span; use anyhow::Result; @@ -26,6 +26,12 @@ pub struct CompilationUnit { /// The file manager containing all source files used during compilation. pub file_manager: FileManager, + + /// The high-level project symbols for LSP and other tools. + pub project_symbols: Vec, + + /// The name of the root project. + pub root_project: String, } impl CompilationUnit { @@ -36,7 +42,7 @@ impl CompilationUnit { /// * `emit_disasm` - If true, a `.disasm` file will be created next to the output. /// * `emit_symbols` - If true, a `.json` symbols file will be created next to the output. pub fn export(&self, out: &Path, emit_disasm: bool, emit_symbols: bool) -> Result<()> { - let mut symbols = Vec::new(); + let mut debug_symbols = Vec::new(); for raw in &self.raw_symbols { let path = self.file_manager.get_path(raw.span.file_id) .map(|p| p.to_string_lossy().to_string()) @@ -44,7 +50,7 @@ impl CompilationUnit { let (line, col) = self.file_manager.lookup_pos(raw.span.file_id, raw.span.start); - symbols.push(Symbol { + debug_symbols.push(DebugSymbol { pc: raw.pc, file: path, line, @@ -52,7 +58,18 @@ impl CompilationUnit { }); } - let artifacts = backend::artifacts::Artifacts::new(self.rom.clone(), symbols); + let lsp_symbols = SymbolsFile { + schema_version: 0, + compiler_version: "0.1.0".to_string(), // TODO: use crate version + root_project: self.root_project.clone(), + projects: self.project_symbols.clone(), + }; + + let artifacts = backend::artifacts::Artifacts::new( + self.rom.clone(), + debug_symbols, + lsp_symbols, + ); artifacts.export(out, emit_disasm, emit_symbols) } } @@ -112,6 +129,8 @@ pub fn compile_ext(project_dir: &Path, explain_deps: bool) -> Result = serde_json::from_str(&symbols_content).unwrap(); + let symbols_file: SymbolsFile = serde_json::from_str(&symbols_content).unwrap(); - assert!(!symbols.is_empty(), "Symbols list should not be empty"); + assert_eq!(symbols_file.schema_version, 0); + assert!(!symbols_file.projects.is_empty(), "Projects list should not be empty"); - // Check for non-zero line/col - let main_sym = symbols.iter().find(|s| s.line > 0 && s.col > 0); - assert!(main_sym.is_some(), "Should find at least one symbol with real location"); + let root_project = &symbols_file.projects[0]; + assert!(!root_project.symbols.is_empty(), "Symbols list should not be empty"); + + // Check for a symbol (v0 schema uses 0-based lines) + let main_sym = root_project.symbols.iter().find(|s| s.name == "frame"); + assert!(main_sym.is_some(), "Should find 'frame' symbol"); let sym = main_sym.unwrap(); - assert!(sym.file.contains("main.pbs"), "Symbol file should point to main.pbs, got {}", sym.file); + assert!(sym.decl_span.file.contains("main.pbs"), "Symbol file should point to main.pbs, got {}", sym.decl_span.file); } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/collector.rs b/crates/prometeu-compiler/src/frontends/pbs/collector.rs index f3ef16c8..d2b4dab9 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/collector.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/collector.rs @@ -151,7 +151,7 @@ impl SymbolCollector { level: DiagnosticLevel::Error, code: Some("E_RESOLVE_NAMESPACE_COLLISION".to_string()), message: format!( - "Symbol '{}' collides with another symbol in the {:?} namespace defined at {:?}", + "DebugSymbol '{}' collides with another symbol in the {:?} namespace defined at {:?}", symbol.name, existing.namespace, existing.span ), span: Some(symbol.span), diff --git a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs index a1d863cb..96356269 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs @@ -436,7 +436,7 @@ impl<'a> Resolver<'a> { self.diagnostics.push(Diagnostic { level: DiagnosticLevel::Error, code: Some("E_RESOLVE_VISIBILITY".to_string()), - message: format!("Symbol '{}' is not visible here", sym.name), + message: format!("DebugSymbol '{}' is not visible here", sym.name), span: Some(span), }); } diff --git a/crates/prometeu-compiler/tests/export_conflicts.rs b/crates/prometeu-compiler/tests/export_conflicts.rs index 7ecd2f6b..7fd513d7 100644 --- a/crates/prometeu-compiler/tests/export_conflicts.rs +++ b/crates/prometeu-compiler/tests/export_conflicts.rs @@ -37,6 +37,7 @@ fn test_local_vs_dependency_conflict() { code: vec![], function_metas: vec![], debug_info: None, + symbols: vec![], }; let mut dep_modules = HashMap::new(); @@ -96,6 +97,7 @@ fn test_aliased_dependency_conflict() { code: vec![], function_metas: vec![], debug_info: None, + symbols: vec![], }; // Dependency 2: exports "c:Vector" @@ -119,6 +121,7 @@ fn test_aliased_dependency_conflict() { code: vec![], function_metas: vec![], debug_info: None, + symbols: vec![], }; let mut dep_modules = HashMap::new();