use crate::backend::emit_fragments; use crate::building::plan::{BuildStep, BuildTarget}; use crate::common::diagnostics::DiagnosticBundle; use crate::common::files::FileManager; use crate::common::spans::{FileId, Span}; use crate::deps::resolver::ProjectKey; use prometeu_analysis::ids::ProjectId; use crate::frontends::pbs::ast::ParsedAst; use crate::frontends::pbs::collector::SymbolCollector; use crate::frontends::pbs::lowering::Lowerer; use crate::frontends::pbs::parser::Parser; use crate::frontends::pbs::resolver::{ModuleProvider, Resolver}; use crate::frontends::pbs::symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, Visibility}; use crate::frontends::pbs::typecheck::TypeChecker; use crate::frontends::pbs::types::PbsType; use crate::lowering::core_to_vm; use crate::semantics::export_surface::ExportSurfaceKind; use prometeu_bytecode::{ConstantPoolEntry, DebugInfo, FunctionMeta}; use prometeu_analysis::NameInterner; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct ExportKey { pub module_path: String, pub symbol_name: String, pub kind: ExportSurfaceKind, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExportMetadata { pub func_idx: Option, pub is_host: bool, pub ty: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct ImportKey { pub dep_alias: String, pub module_path: String, pub symbol_name: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ImportMetadata { pub key: ImportKey, pub relocation_pcs: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CompiledModule { pub project_id: ProjectId, pub project_key: ProjectKey, pub target: BuildTarget, pub exports: BTreeMap, pub imports: Vec, pub const_pool: Vec, pub code: Vec, pub function_metas: Vec, pub debug_info: Option, pub symbols: Vec, } #[derive(Debug)] pub enum CompileError { Frontend(crate::common::diagnostics::DiagnosticBundle), DuplicateExport { symbol: String, first_dep: String, second_dep: String, }, Io(std::io::Error), Internal(String), } impl std::fmt::Display for CompileError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { CompileError::Frontend(d) => write!(f, "Frontend error: {:?}", d), CompileError::DuplicateExport { symbol, first_dep, second_dep } => { write!(f, "duplicate export: symbol `{}`\n first defined in dependency `{}`\n again defined in dependency `{}`", symbol, first_dep, second_dep) } CompileError::Io(e) => write!(f, "IO error: {}", e), CompileError::Internal(s) => write!(f, "Internal error: {}", s), } } } impl std::error::Error for CompileError {} impl From for CompileError { fn from(e: std::io::Error) -> Self { CompileError::Io(e) } } impl From for CompileError { fn from(d: crate::common::diagnostics::DiagnosticBundle) -> Self { CompileError::Frontend(d) } } struct ProjectModuleProvider { modules: HashMap, } impl ModuleProvider for ProjectModuleProvider { fn get_module_symbols(&self, from_path: &str) -> Option<&ModuleSymbols> { self.modules.get(from_path) } } pub fn compile_project( step: BuildStep, dep_modules: &HashMap, file_manager: &mut FileManager, ) -> Result { let mut interner = NameInterner::new(); // 1. Parse all files and group symbols by module let mut module_symbols_map: HashMap = HashMap::new(); let mut parsed_files: Vec<(String, ParsedAst)> = Vec::new(); // (module_path, ast) for source_rel in &step.sources { let source_abs = step.project_dir.join(source_rel); let source_code = std::fs::read_to_string(&source_abs)?; let file_id = file_manager.add(source_abs.clone(), source_code.clone()); let mut parser = Parser::new(&source_code, FileId(file_id as u32), &mut interner); let parsed = parser.parse_file()?; let mut collector = SymbolCollector::new(&interner); let (ts, vs) = collector.collect(&parsed.arena, parsed.root)?; let full_path = source_rel.to_string_lossy().replace('\\', "/"); let logical_module_path = if let Some(stripped) = full_path.strip_prefix("src/main/modules/") { stripped } else if let Some(stripped) = full_path.strip_prefix("src/test/modules/") { stripped } else { &full_path }; let module_path = std::path::Path::new(logical_module_path) .parent() .map(|p| p.to_string_lossy().replace('\\', "/")) .unwrap_or_else(|| "".to_string()); let ms = module_symbols_map.entry(module_path.clone()).or_insert_with(ModuleSymbols::new); // Merge symbols for sym in ts.symbols.into_values() { if let Some(existing) = ms.type_symbols.get(sym.name) { return Err(DiagnosticBundle::error( "E_RESOLVE_DUPLICATE_SYMBOL", format!( "Duplicate type symbol '{}' in module '{}'", interner.resolve(existing.name), module_path ), existing.span.clone(), ).into()); } let _ = ms.type_symbols.insert(sym); } for sym in vs.symbols.into_values() { if let Some(existing) = ms.value_symbols.get(sym.name) { return Err(DiagnosticBundle::error( "E_RESOLVE_DUPLICATE_SYMBOL", format!( "Duplicate value symbol '{}' in module '{}'", interner.resolve(existing.name), module_path ), existing.span.clone(), ).into()); } let _ = ms.value_symbols.insert(sym); } parsed_files.push((module_path, parsed)); } // 2. Synthesize ModuleSymbols for dependencies let mut all_visible_modules = module_symbols_map.clone(); for (alias, project_id) in &step.deps { if let Some(compiled) = dep_modules.get(project_id) { for (key, meta) in &compiled.exports { // Support syntax: "alias/module" and "@alias:module" let key_module_path = &key.module_path; let synthetic_paths = [ format!("{}/{}", alias, key_module_path), format!("@{}:{}", alias, key_module_path), ]; for synthetic_module_path in synthetic_paths { let ms = all_visible_modules.entry(synthetic_module_path.clone()).or_insert_with(ModuleSymbols::new); let sym = Symbol { name: interner.intern(&key.symbol_name), kind: match key.kind { ExportSurfaceKind::Service => SymbolKind::Service, ExportSurfaceKind::DeclareType => { match &meta.ty { Some(PbsType::Contract(_)) => SymbolKind::Contract, Some(PbsType::ErrorType(_)) => SymbolKind::ErrorType, _ => SymbolKind::Struct, } } ExportSurfaceKind::Function => SymbolKind::Function, }, namespace: key.kind.namespace(), visibility: Visibility::Pub, ty: meta.ty.clone(), is_host: meta.is_host, span: Span::new(FileId::INVALID, 0, 0), origin: Some(synthetic_module_path.clone()), }; if sym.namespace == Namespace::Type { if let Some(existing) = ms.type_symbols.get(sym.name) { return Err(CompileError::DuplicateExport { symbol: interner.resolve(sym.name).to_string(), first_dep: existing.origin.clone().unwrap_or_else(|| "unknown".to_string()), second_dep: sym.origin.unwrap_or_else(|| "unknown".to_string()), }); } let _ = ms.type_symbols.insert(sym.clone()); } else { if let Some(existing) = ms.value_symbols.get(sym.name) { return Err(CompileError::DuplicateExport { symbol: interner.resolve(sym.name).to_string(), first_dep: existing.origin.clone().unwrap_or_else(|| "unknown".to_string()), second_dep: sym.origin.unwrap_or_else(|| "unknown".to_string()), }); } let _ = ms.value_symbols.insert(sym.clone()); } } } } } // 3. Resolve and TypeCheck each file let module_provider = ProjectModuleProvider { modules: all_visible_modules, }; // We need to collect imported symbols for Lowerer let mut file_imported_symbols: HashMap = HashMap::new(); // keyed by module_path for (module_path, parsed) in &parsed_files { let ms = module_symbols_map.get(module_path).unwrap(); // Ensure primitive names are interned before creating resolver/bootstrap { let primitives = ["int", "bool", "float", "string", "bounded", "void"]; for p in primitives { interner.intern(p); } } let mut resolver = Resolver::new(ms, &module_provider, &interner); resolver.bootstrap_types(&interner); resolver.resolve(&parsed.arena, parsed.root)?; // Capture imported symbols file_imported_symbols.insert(module_path.clone(), resolver.imported_symbols.clone()); // TypeChecker also needs &mut ModuleSymbols let mut ms_mut = module_symbols_map.get_mut(module_path).unwrap(); let imported = file_imported_symbols.get(module_path).unwrap(); let mut typechecker = TypeChecker::new(&mut ms_mut, imported, &module_provider, &interner); typechecker.check(&parsed.arena, parsed.root)?; } // 4. Lower ALL modules to VM and emit a single combined bytecode image for this project // Rationale: services and functions can live in multiple modules; exports must refer to // correct function indices within this CompiledModule. We aggregate all VM functions // into a single ir_vm::Module and assemble once using the public API `emit_fragments`. // Combined VM module (we will merge const pools and remap ConstIds accordingly) let mut combined_vm = crate::ir_vm::Module::new(step.project_key.name.clone()); combined_vm.const_pool = crate::ir_core::ConstPool::new(); // Track origin module_path for each function we append to combined_vm let mut combined_func_origins: Vec = Vec::new(); // Helper to insert a constant value into combined pool and return its new id let mut insert_const = |pool: &mut crate::ir_core::ConstPool, val: &crate::ir_core::ConstantValue| -> crate::ir_vm::types::ConstId { let new_id = pool.insert(val.clone()); crate::ir_vm::types::ConstId(new_id.0) }; // Accumulate VM functions from each source file, remapping ConstIds as we go for (module_path, parsed) in &parsed_files { let ms = module_symbols_map.get(module_path).unwrap(); let imported = file_imported_symbols.get(module_path).unwrap(); let lowerer = Lowerer::new(&parsed.arena, ms, imported, &interner); let program = lowerer.lower_file(parsed.root, module_path)?; let vm_module = core_to_vm::lower_program(&program) .map_err(|e| CompileError::Internal(format!("Lowering error ({}): {}", module_path, e)))?; // Build remap for this module's const ids let mut const_map: Vec = Vec::with_capacity(vm_module.const_pool.constants.len()); for c in &vm_module.const_pool.constants { let new_id = insert_const(&mut combined_vm.const_pool, c); const_map.push(new_id); } // Clone functions and remap any PushConst const ids for mut f in vm_module.functions.into_iter() { for instr in &mut f.body { if let crate::ir_vm::instr::InstrKind::PushConst(old_id) = instr.kind { let mapped = const_map.get(old_id.0 as usize).cloned().unwrap_or(old_id); instr.kind = crate::ir_vm::instr::InstrKind::PushConst(mapped); } } combined_func_origins.push(module_path.clone()); combined_vm.functions.push(f); } } // Assemble once for the whole project using the public API let fragments = emit_fragments(&combined_vm) .map_err(|e| CompileError::Internal(format!("Emission error: {}", e)))?; // Post-fix FunctionMeta slots from VM IR (some emitters may default to 0) let mut fixed_function_metas = fragments.functions.clone(); for (i, fm) in fixed_function_metas.iter_mut().enumerate() { if let Some(vm_func) = combined_vm.functions.get(i) { fm.param_slots = vm_func.param_slots; fm.local_slots = vm_func.local_slots; fm.return_slots = vm_func.return_slots; } } // Note: Entry point validation for the root project is now performed at the orchestrator level, // after compilation and before linking, using enriched debug info. We skip it here to avoid // double validation and mismatches with name annotations. // 5. Collect exports let mut exports = BTreeMap::new(); for (module_path, ms) in &module_symbols_map { for sym in ms.type_symbols.symbols.values() { if sym.visibility == Visibility::Pub { if let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) { exports.insert(ExportKey { module_path: module_path.clone(), symbol_name: interner.resolve(sym.name).to_string(), kind: surface_kind, }, ExportMetadata { func_idx: None, is_host: sym.is_host, ty: sym.ty.clone(), }); } } } // Build a set of public function names declared in this module (value namespace) let mut pub_fn_names: std::collections::HashSet = std::collections::HashSet::new(); for sym in ms.value_symbols.symbols.values() { if sym.visibility == Visibility::Pub { if let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) { if matches!(surface_kind, ExportSurfaceKind::Function) { pub_fn_names.insert(interner.resolve(sym.name).to_string()); } } } } for sym in ms.value_symbols.symbols.values() { if sym.visibility == Visibility::Pub { if let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) { // Encontrar TODAS as funções no módulo VM combinado originadas deste module_path // cujo nome seja igual ao do símbolo público; para cada uma, exportar // tanto o nome simples quanto o alias com aridade. let name_simple = interner.resolve(sym.name).to_string(); let mut any_found = false; for (i, f) in combined_vm.functions.iter().enumerate() { if combined_func_origins.get(i).map(|s| s.as_str()) != Some(module_path.as_str()) { continue; } if f.name != name_simple { continue; } any_found = true; let meta = ExportMetadata { func_idx: Some(i as u32), is_host: sym.is_host, ty: sym.ty.clone(), }; // Simple name exports.insert(ExportKey { module_path: module_path.clone(), symbol_name: name_simple.clone(), kind: surface_kind, }, meta.clone()); // name/arity let arity = f.params.len(); let export_name_arity = format!("{}/{}", name_simple, arity); exports.insert(ExportKey { module_path: module_path.clone(), symbol_name: export_name_arity, kind: surface_kind, }, meta); } // Caso nada tenha sido encontrado no VM (ex.: métodos ainda não materializados), // publique ao menos o nome simples sem func_idx (mantém compatibilidade de surface) if !any_found { exports.insert(ExportKey { module_path: module_path.clone(), symbol_name: name_simple.clone(), kind: surface_kind, }, ExportMetadata { func_idx: None, is_host: sym.is_host, ty: sym.ty.clone() }); } } } } } // 6. Collect symbols let project_symbols = crate::common::symbols::collect_symbols( &step.project_key.name, &module_symbols_map, file_manager, &interner, ); // 6.b) Enriquecer debug_info com metadados de função (offset/len) para análise externa let mut dbg = fragments.debug_info.clone().unwrap_or_default(); // Adiciona pares (func_idx, (code_offset, code_len)) ao campo function_names como anotações extras // Sem quebrar o formato, usamos o name como "name@offset+len" para tooling/analysis.json let mut enriched_function_names = Vec::new(); for (i, (fid, name)) in fragments .debug_info .as_ref() .map(|d| d.function_names.clone()) .unwrap_or_default() .into_iter() .enumerate() { let meta = &fixed_function_metas[i]; let annotated = format!("{}@{}+{}", name, meta.code_offset, meta.code_len); enriched_function_names.push((fid, annotated)); } if !enriched_function_names.is_empty() { dbg.function_names = enriched_function_names; } // 7. Collect imports from unresolved labels let mut imports = Vec::new(); for (label, pcs) in fragments.unresolved_labels { if label.starts_with('@') { // Format: @dep_alias::module_path:symbol_name let parts: Vec<&str> = label[1..].splitn(2, "::").collect(); if parts.len() == 2 { let dep_alias = parts[0].to_string(); let rest = parts[1]; let sub_parts: Vec<&str> = rest.rsplitn(2, ':').collect(); if sub_parts.len() == 2 { let symbol_name = sub_parts[0].to_string(); let module_path = sub_parts[1].to_string(); imports.push(ImportMetadata { key: ImportKey { dep_alias, module_path, symbol_name, }, relocation_pcs: pcs, }); } } } } Ok(CompiledModule { project_id: step.project_id, project_key: step.project_key, target: step.target, exports, imports, const_pool: fragments.const_pool, code: fragments.code, function_metas: fixed_function_metas, debug_info: Some(dbg), symbols: project_symbols, }) } #[cfg(test)] mod tests { use super::*; use std::fs; use std::path::PathBuf; use tempfile::tempdir; #[test] fn test_compile_root_only_project() { let dir = tempdir().unwrap(); let project_dir = dir.path().to_path_buf(); fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); let main_code = r#" pub declare struct Vec2(x: int, y: int) fn add(a: int, b: int): int { return a + b; } mod fn frame(): void { let x = add(1, 2); } "#; fs::write(project_dir.join("src/main/modules/main.pbs"), main_code).unwrap(); let project_key = ProjectKey { name: "root".to_string(), version: "0.1.0".to_string() }; let project_id = ProjectId(0); let step = BuildStep { project_id, project_key: project_key.clone(), project_dir: project_dir.clone(), target: BuildTarget::Main, sources: vec![PathBuf::from("src/main/modules/main.pbs")], deps: BTreeMap::new(), }; let mut file_manager = FileManager::new(); let compiled = compile_project(step, &HashMap::new(), &mut file_manager).expect("Failed to compile project"); assert_eq!(compiled.project_id, project_id); assert_eq!(compiled.target, BuildTarget::Main); // Vec2 should be exported let vec2_key = ExportKey { module_path: "".to_string(), symbol_name: "Vec2".to_string(), kind: ExportSurfaceKind::DeclareType, }; assert!(compiled.exports.contains_key(&vec2_key)); // frame is NOT exported (top-level fn cannot be pub in v0) // Wait, I put "pub fn frame" in the test code. SymbolCollector should have ignored pub. // Actually, SymbolCollector might error on pub fn. } }