use crate::common::symbols::{DebugSymbol, SymbolsFile}; use anyhow::{Context, Result}; use prometeu_bytecode::disasm::disasm; use prometeu_bytecode::BytecodeLoader; use std::fs; use std::path::Path; pub struct Artifacts { pub rom: Vec, pub debug_symbols: Vec, pub lsp_symbols: SymbolsFile, } impl Artifacts { 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 LSP if emit_symbols { let symbols_path = out.with_file_name("symbols.json"); let symbols_json = serde_json::to_string_pretty(&self.lsp_symbols)?; fs::write(&symbols_path, symbols_json)?; } // 3. Export human-readable disassembly for developer inspection if emit_disasm { let disasm_path = out.with_extension("disasm.txt"); // Extract the actual bytecode (stripping the industrial PBS\0 header) let rom_to_disasm = if let Ok(module) = BytecodeLoader::load(&self.rom) { module.code } else { self.rom.clone() }; let instructions = disasm(&rom_to_disasm).map_err(|e| anyhow::anyhow!("Disassembly failed: {}", e))?; 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.debug_symbols.iter().find(|s| s.pc == instr.pc); let comment = if let Some(s) = symbol { format!(" ; {}:{}", s.file, s.line) } else { "".to_string() }; let operands_str = instr.operands.iter() .map(|o| format!("{:?}", o)) .collect::>() .join(" "); disasm_text.push_str(&format!("{:08X} {:?} {}{}\n", instr.pc, instr.opcode, operands_str, comment)); } fs::write(disasm_path, disasm_text)?; } Ok(()) } }