Nilton Constantino 3509eada8b
pr 06
2026-01-27 13:56:57 +00:00

129 lines
4.8 KiB
Rust

//! # 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<u8>,
/// The list of debug symbols discovered during compilation.
/// These are used to map bytecode offsets back to source code locations.
pub symbols: Vec<Symbol>,
}
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<CompilationUnit> {
let config = ProjectConfig::load(project_dir)?;
// 1. Select Frontend
// The _frontend is responsible for parsing source code and producing the IR.
let _frontend: Box<dyn Frontend> = 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"));
}
}