//! # Compiler Orchestration //! //! This module provides the high-level API for triggering the compilation process. //! It handles the transition between different compiler phases: Frontend -> IR -> Backend. use crate::backend; use crate::common::config::ProjectConfig; use crate::common::files::FileManager; use crate::common::symbols::Symbol; use crate::frontends::Frontend; use crate::ir; use anyhow::Result; use std::path::Path; /// The result of a successful compilation process. /// It contains the final binary and the metadata needed for debugging. #[derive(Debug)] pub struct CompilationUnit { /// The raw binary data formatted as Prometeu ByteCode (PBC). /// This is what gets written to a `.pbc` file. pub rom: Vec, /// The list of debug symbols discovered during compilation. /// These are used to map bytecode offsets back to source code locations. pub symbols: Vec, } impl CompilationUnit { /// Writes the compilation results (PBC binary, disassembly, and symbols) to the disk. /// /// # Arguments /// * `out` - The base path for the output `.pbc` file. /// * `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 artifacts = backend::artifacts::Artifacts::new(self.rom.clone(), self.symbols.clone()); artifacts.export(out, emit_disasm, emit_symbols) } } /// Orchestrates the compilation of a Prometeu project starting from an entry file. /// /// This function executes the full compiler pipeline: /// 1. **Frontend**: Loads and parses the entry file (and its dependencies). /// Currently, it uses the `TypescriptFrontend`. /// 2. **IR Generation**: The frontend produces a high-level Intermediate Representation (IR). /// 3. **Validation**: Checks the IR for consistency and VM compatibility. /// 4. **Backend**: Lowers the IR into final Prometeu ByteCode. /// /// # Errors /// Returns an error if parsing fails, validation finds issues, or code generation fails. /// /// # Example /// ```no_run /// use std::path::Path; /// let project_dir = Path::new("."); /// let unit = prometeu_compiler::compiler::compile(project_dir).expect("Failed to compile"); /// unit.export(Path::new("build/program.pbc"), true, true).unwrap(); /// ``` pub fn compile(project_dir: &Path) -> Result { let config = ProjectConfig::load(project_dir)?; // 1. Select Frontend // The _frontend is responsible for parsing source code and producing the IR. let _frontend: Box = match config.script_fe.as_str() { "pbs" => Box::new(crate::frontends::pbs::PbsFrontend), _ => anyhow::bail!("Invalid frontend: {}", config.script_fe), }; #[allow(unreachable_code, unused_variables, unused_mut)] { let entry = project_dir.join(&config.entry); let mut file_manager = FileManager::new(); // 2. Compile to IR (Intermediate Representation) // This step abstracts away source-specific syntax (like TypeScript) into a // generic set of instructions that the backend can understand. let ir_module = _frontend.compile_to_ir(&entry, &mut file_manager) .map_err(|bundle| { if let Some(diag) = bundle.diagnostics.first() { anyhow::anyhow!("{}", diag.message) } else { anyhow::anyhow!("Compilation failed with {} errors", bundle.diagnostics.len()) } })?; // 3. IR Validation // Ensures the generated IR is sound and doesn't violate any VM constraints // before we spend time generating bytecode. ir::validate::validate_module(&ir_module) .map_err(|bundle| anyhow::anyhow!("IR Validation failed: {:?}", bundle))?; // 4. Emit Bytecode // The backend takes the validated IR and produces the final binary executable. let result = backend::emit_module(&ir_module, &file_manager)?; Ok(CompilationUnit { rom: result.rom, symbols: result.symbols, }) } } #[cfg(test)] mod tests { use super::*; use std::fs; use tempfile::tempdir; #[test] fn test_invalid_frontend() { let dir = tempdir().unwrap(); let config_path = dir.path().join("prometeu.json"); fs::write( config_path, r#"{ "script_fe": "invalid", "entry": "main.pbs" }"#, ) .unwrap(); let result = compile(dir.path()); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("Invalid frontend: invalid")); } }