//! Top-level compiler struct — orchestrates the full pipeline. use std::path::PathBuf; use el_lexer::tokenize; use el_parser::parse; use el_seal::{seal, SealConfig, SealedArtifact}; use el_types::TypeChecker; use crate::bytecode::{deserialize_bytecode, serialize_bytecode}; use crate::codegen::Codegen; use crate::error::{CompileError, CompileResult}; /// Which compilation target to produce. #[derive(Debug, Clone, PartialEq)] pub enum Target { /// Full debug info: source maps, stack traces, no optimization. Debug, /// Optimized, stripped, no debug info. Release, /// Quantum-sealed: encrypted bytecode, cannot be decompiled. Prod, } /// Compiler configuration. #[derive(Debug, Clone)] pub struct CompilerOptions { pub target: Target, pub output_path: PathBuf, pub source_path: PathBuf, /// Path to an Engram database for `activate` type resolution. /// `None` disables semantic type compatibility (falls back to structural). pub engram_db_path: Option, /// Seal configuration for the `prod` target. pub seal_config: SealConfig, } impl Default for CompilerOptions { fn default() -> Self { Self { target: Target::Debug, output_path: PathBuf::from("out.elc"), source_path: PathBuf::from("main.el"), engram_db_path: None, seal_config: SealConfig::default(), } } } /// The output of a compilation. #[derive(Debug)] pub struct CompileOutput { /// The compiled artifact bytes. Format depends on target: /// - Debug/Release: JSON-serialized `Vec` /// - Prod: `SealedArtifact` wire format (`ENGRAM01` + JSON body) pub artifact: Vec, pub target: Target, /// Whether the artifact is quantum-sealed. pub sealed: bool, /// JSON source map (debug target only). pub source_map: Option, /// Type-check and compilation diagnostics. pub diagnostics: Vec, } /// The Engram language compiler. pub struct Compiler; impl Compiler { /// Compile `source` with the given options. pub fn compile(source: &str, opts: CompilerOptions) -> CompileResult { // ── Step 1: Lex ─────────────────────────────────────────────────────── let tokens = tokenize(source)?; // ── Step 2: Parse ───────────────────────────────────────────────────── let program = parse(tokens, source.to_string())?; // ── Step 3: Type-check ──────────────────────────────────────────────── let mut checker = TypeChecker::with_builtins(); let diags = checker.check(&program); let diagnostics: Vec = diags.iter().map(|d| d.message.clone()).collect(); // We continue compiling even with type errors in debug/release mode. // In prod mode, type errors are fatal. if opts.target == Target::Prod && !checker.ok() { return Err(CompileError::Type( diagnostics.join("; ") )); } // ── Step 4: Code generation ─────────────────────────────────────────── let emit_sm = matches!(opts.target, Target::Debug); let cg = Codegen::new(emit_sm); let (bytecode, source_map) = cg.generate(&program) .map_err(|e| CompileError::Codegen(e.to_string()))?; let bytecode_bytes = serialize_bytecode(&bytecode) .map_err(|e| CompileError::Codegen(e.to_string()))?; // ── Step 5: Target-specific post-processing ─────────────────────────── match opts.target { Target::Debug => { let sm_json = source_map.to_json() .map_err(|e| CompileError::Serialization(e.to_string()))?; Ok(CompileOutput { artifact: bytecode_bytes, target: Target::Debug, sealed: false, source_map: Some(sm_json), diagnostics, }) } Target::Release => { Ok(CompileOutput { artifact: bytecode_bytes, target: Target::Release, sealed: false, source_map: None, diagnostics, }) } Target::Prod => { let artifact = seal(&bytecode_bytes, &opts.seal_config)?; let artifact_bytes = artifact.to_bytes() .map_err(|e| CompileError::Serialization(e.to_string()))?; Ok(CompileOutput { artifact: artifact_bytes, target: Target::Prod, sealed: true, source_map: None, diagnostics, }) } } } /// Convenience: compile and unseal, returning the bytecode instructions. pub fn compile_and_unseal( source: &str, opts: CompilerOptions, binding_key: &[u8], ) -> CompileResult> { let output = Self::compile(source, opts)?; let sealed_artifact = SealedArtifact::from_bytes(&output.artifact) .map_err(CompileError::Seal)?; let bytecode_bytes = el_seal::unseal(&sealed_artifact, binding_key) .map_err(CompileError::Seal)?; let instructions = deserialize_bytecode(&bytecode_bytes) .map_err(|e| CompileError::Codegen(e.to_string()))?; Ok(instructions) } } #[cfg(test)] mod tests { use el_seal::{DeploymentBinding, SealAlgorithm}; use super::*; fn debug_opts() -> CompilerOptions { CompilerOptions { target: Target::Debug, ..Default::default() } } fn release_opts() -> CompilerOptions { CompilerOptions { target: Target::Release, ..Default::default() } } fn prod_opts() -> CompilerOptions { CompilerOptions { target: Target::Prod, seal_config: SealConfig { algorithm: SealAlgorithm::Aes256Gcm, deployment_binding: DeploymentBinding::None, }, ..Default::default() } } #[test] fn test_compile_hello_world_debug() { let src = r#"let msg: String = "Hello, World!""#; let out = Compiler::compile(src, debug_opts()).unwrap(); assert!(!out.artifact.is_empty()); assert!(!out.sealed); assert!(out.source_map.is_some()); } #[test] fn test_compile_release_no_source_map() { let src = "let x: Int = 42"; let out = Compiler::compile(src, release_opts()).unwrap(); assert!(out.source_map.is_none()); assert!(!out.sealed); } #[test] fn test_compile_prod_is_sealed() { let src = "let x: Int = 1"; let out = Compiler::compile(src, prod_opts()).unwrap(); assert!(out.sealed); // Artifact must start with ENGRAM01 magic assert_eq!(&out.artifact[..8], b"ENGRAM01"); } #[test] fn test_prod_roundtrip() { let src = "let answer: Int = 42"; let opts = prod_opts(); let out = Compiler::compile(src, opts).unwrap(); let sealed = SealedArtifact::from_bytes(&out.artifact).unwrap(); let bytecode_bytes = el_seal::unseal(&sealed, &[]).unwrap(); let instructions = deserialize_bytecode(&bytecode_bytes).unwrap(); // Should have a PUSH 42, STORE answer, and HALT at minimum assert!(instructions.iter().any(|b| matches!(b, crate::bytecode::Bytecode::Push(crate::bytecode::Value::Int(42))))); } #[test] fn test_compile_fn_def() { let src = r#" fn add(a: Int, b: Int) -> Int { return a + b } "#; let out = Compiler::compile(src, debug_opts()).unwrap(); assert!(!out.artifact.is_empty()); } #[test] fn test_compile_type_mismatch_warning_debug() { // In debug mode, type errors are warnings (not fatal) let src = r#"let x: Int = "not an int""#; let out = Compiler::compile(src, debug_opts()).unwrap(); assert!(!out.diagnostics.is_empty()); } #[test] fn test_source_map_has_entries() { let src = "let x = 1\nlet y = 2"; let out = Compiler::compile(src, debug_opts()).unwrap(); let sm_json = out.source_map.unwrap(); let sm: crate::source_map::SourceMap = serde_json::from_str(&sm_json).unwrap(); assert!(!sm.entries.is_empty()); } }