use crate::codegen::{self, CodegenError, ExecutionResult, MachineCode}; use crate::emit::{self, EmitError}; use crate::l0_ir::L0Program; use crate::l1_ir::L1Program; use crate::parser::{self, ParseError}; use crate::translate_validate::{self, VerificationReport}; use crate::verify_l0; use crate::verify_l1::{self, VerifyError}; use std::fmt; /// Errors from any stage of the pipeline. #[derive(Debug)] pub enum PipelineError { Parse(ParseError), VerifyL1(VerifyError), Emit(EmitError), VerifyL0(Vec), Codegen(CodegenError), TranslationValidation(String), } impl fmt::Display for PipelineError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { PipelineError::Parse(e) => write!(f, "parse: {}", e), PipelineError::VerifyL1(e) => write!(f, "L1 verify: {}", e), PipelineError::Emit(e) => write!(f, "emit: {}", e), PipelineError::VerifyL0(errs) => { write!(f, "L0 verify:")?; for e in errs { write!(f, " {}", e)?; } Ok(()) } PipelineError::Codegen(e) => write!(f, "codegen: {}", e), PipelineError::TranslationValidation(msg) => write!(f, "translation validation: {}", msg), } } } impl std::error::Error for PipelineError {} /// All intermediate representations captured during pipeline execution. pub struct PipelineIR { pub l1: L1Program, pub l0: L0Program, pub machine_code: MachineCode, } /// Run the full pipeline: DSL text -> parse -> L1 IR -> verify -> emit L0 IR /// -> verify -> codegen -> execute. /// /// Returns both the execution result and captured intermediate representations. pub fn run(source: &str) -> Result<(ExecutionResult, PipelineIR), PipelineError> { // Parse let l1 = parser::parse(source).map_err(PipelineError::Parse)?; // Verify L1 verify_l1::verify(&l1).map_err(PipelineError::VerifyL1)?; // Emit L0 IR let l0 = emit::emit(&l1).map_err(PipelineError::Emit)?; // Verify L0 verify_l0::verify(&l0).map_err(PipelineError::VerifyL0)?; // Codegen let mc = codegen::codegen(&l0).map_err(PipelineError::Codegen)?; // Execute let result = codegen::execute(&mc).map_err(PipelineError::Codegen)?; let ir = PipelineIR { l1, l0, machine_code: mc, }; Ok((result, ir)) } /// Dump all intermediate representations for a given DSL input. /// Returns a human-readable string showing L1 IR, L0 IR, and x86-64 hex. pub fn dump_ir(source: &str) -> Result { let l1 = parser::parse(source).map_err(PipelineError::Parse)?; verify_l1::verify(&l1).map_err(PipelineError::VerifyL1)?; let l0 = emit::emit(&l1).map_err(PipelineError::Emit)?; verify_l0::verify(&l0).map_err(PipelineError::VerifyL0)?; let mc = codegen::codegen(&l0).map_err(PipelineError::Codegen)?; let mut out = String::new(); out.push_str("========== L1 IR ==========\n"); out.push_str(&format!("{}", l1)); out.push('\n'); out.push_str("========== L0 IR ==========\n"); out.push_str(&format!("{}", l0)); out.push('\n'); out.push_str("========== x86-64 Machine Code ==========\n"); out.push_str(&mc.hex_dump()); out.push('\n'); Ok(out) } /// Run the full verification pipeline: parse → L1 verify → emit → L0 verify → translate validate → report. /// /// Returns the verification report containing results from all three validation tools. pub fn verify_translation(source: &str) -> Result { // Parse let l1 = parser::parse(source).map_err(PipelineError::Parse)?; // Verify L1 verify_l1::verify(&l1).map_err(PipelineError::VerifyL1)?; // Emit L0 IR let l0 = emit::emit(&l1).map_err(PipelineError::Emit)?; // Verify L0 verify_l0::verify(&l0).map_err(PipelineError::VerifyL0)?; // Translation validation let report = translate_validate::validate(&l1, &l0); if !report.all_passed() { return Err(PipelineError::TranslationValidation(format!("{}", report))); } Ok(report) } #[cfg(test)] mod tests { use super::*; #[test] fn counter_end_to_end() { let source = include_str!("../examples/counter.l1"); let (result, _ir) = run(source).expect("counter pipeline should succeed"); let count = result.read_u64("counter_state", 0).expect("should read counter_state"); assert_eq!(count, 5, "counter should be 5 after 5 steps"); } #[test] fn window_end_to_end() { let source = include_str!("../examples/window.l1"); let (result, _ir) = run(source).expect("window pipeline should succeed"); let total = result.read_u64("accumulator_state", 0).expect("should read accumulator_state"); assert_eq!(total, 30, "total should be 30 after 3 steps of Add(10)"); let observed = result.read_output_values(); assert_eq!(observed, vec![10, 20, 30], "observed values should be [10, 20, 30]"); } #[test] fn counter_ir_dump() { let source = include_str!("../examples/counter.l1"); let dump = dump_ir(source).expect("dump should succeed"); assert!(dump.contains("========== L1 IR ==========")); assert!(dump.contains("========== L0 IR ==========")); assert!(dump.contains("========== x86-64 Machine Code ==========")); assert!(dump.contains("actor counter")); assert!(dump.contains("=== Regions ===")); assert!(dump.contains("counter_state")); } #[test] fn window_ir_dump() { let source = include_str!("../examples/window.l1"); let dump = dump_ir(source).expect("dump should succeed"); assert!(dump.contains("========== L1 IR ==========")); assert!(dump.contains("accumulator")); assert!(dump.contains("output")); } #[test] fn counter_test_vector_full_pipeline() { // Full test vector: DSL source -> L1 IR -> L0 IR -> x86-64 -> result let source = include_str!("../examples/counter.l1"); let (result, ir) = run(source).expect("pipeline should succeed"); // 1. DSL source is the counter.l1 file (inlined via include_str!) assert!(source.contains("actor counter")); assert!(source.contains("on Increment(amount: u64)")); assert!(source.contains("steps: 5")); // 2. Expected L1 IR text let l1_text = format!("{}", ir.l1); assert!(l1_text.contains("actor counter {")); assert!(l1_text.contains("count: u64 = 0")); assert!(l1_text.contains("window count_view : (count) readers(display)")); assert!(l1_text.contains("on Increment(amount: u64) { count = (count + amount) }")); assert!(l1_text.contains("leaf ticker {")); assert!(l1_text.contains("forward(counter, Increment(1))")); assert!(l1_text.contains("leaf display {")); assert!(l1_text.contains("pipeline main { ticker -> counter -> display }")); assert!(l1_text.contains("steps: 5")); // 3. Expected L0 IR text let l0_text = format!("{}", ir.l0); assert!(l0_text.contains("=== Regions ===")); assert!(l0_text.contains("region counter_state : 8 bytes, rw, state")); assert!(l0_text.contains("queue")); assert!(l0_text.contains("region step_counter : 8 bytes, rw, control")); assert!(l0_text.contains("=== Blocks ===")); assert!(l0_text.contains("entry:")); assert!(l0_text.contains("loop_check:")); assert!(l0_text.contains("step:")); assert!(l0_text.contains("exit:")); assert!(l0_text.contains("load.64")); assert!(l0_text.contains("store.64")); assert!(l0_text.contains("cmp.lt")); assert!(l0_text.contains("branch")); assert!(l0_text.contains("queue_push")); assert!(l0_text.contains("queue_pop")); assert!(l0_text.contains("terminate")); // 4. Expected x86-64 bytes (non-empty hex string) let hex = ir.machine_code.hex_dump(); assert!(!hex.is_empty(), "machine code should not be empty"); // Verify it ends with ret (c3) preceded by the epilogue pops assert!(hex.contains("c3"), "machine code should contain ret instruction"); // 5. Expected execution result let count = result.read_u64("counter_state", 0).unwrap(); assert_eq!(count, 5, "counter should equal step count (5)"); } #[test] fn window_test_vector_full_pipeline() { // Full test vector: DSL source -> L1 IR -> L0 IR -> x86-64 -> result let source = include_str!("../examples/window.l1"); let (result, ir) = run(source).expect("pipeline should succeed"); // 1. DSL source assert!(source.contains("actor accumulator")); assert!(source.contains("on Add(value: u64)")); assert!(source.contains("steps: 3")); // 2. Expected L1 IR text let l1_text = format!("{}", ir.l1); assert!(l1_text.contains("actor accumulator {")); assert!(l1_text.contains("total: u64 = 0")); assert!(l1_text.contains("window total_view : (total) readers(observe)")); assert!(l1_text.contains("on Add(value: u64) { total = (total + value) }")); assert!(l1_text.contains("leaf source {")); assert!(l1_text.contains("forward(accumulator, Add(10))")); assert!(l1_text.contains("leaf observe {")); assert!(l1_text.contains("emit(total)")); assert!(l1_text.contains("pipeline main { source -> accumulator -> observe }")); assert!(l1_text.contains("steps: 3")); // 3. Expected L0 IR text let l0_text = format!("{}", ir.l0); assert!(l0_text.contains("region accumulator_state : 8 bytes, rw, state")); assert!(l0_text.contains("region output :")); assert!(l0_text.contains("output")); assert!(l0_text.contains("entry:")); assert!(l0_text.contains("queue_push output")); // 4. x86-64 bytes let hex = ir.machine_code.hex_dump(); assert!(!hex.is_empty()); assert!(hex.contains("c3")); // 5. Expected execution result let total = result.read_u64("accumulator_state", 0).unwrap(); assert_eq!(total, 30, "total should be 30"); let observed = result.read_output_values(); assert_eq!(observed, vec![10, 20, 30], "observed values should be [10, 20, 30]"); } #[test] fn pipeline_error_on_invalid_input() { let result = run("invalid garbage input"); assert!(result.is_err()); } #[test] fn verify_translation_counter() { let source = include_str!("../examples/counter.l1"); let report = verify_translation(source).expect("counter verification should pass"); assert!(report.all_passed()); assert_eq!(report.results.len(), 3); } #[test] fn verify_translation_window() { let source = include_str!("../examples/window.l1"); let report = verify_translation(source).expect("window verification should pass"); assert!(report.all_passed()); } #[test] fn verify_translation_product() { let source = include_str!("../examples/product.l1"); let report = verify_translation(source).expect("product verification should pass"); assert!(report.all_passed()); } #[test] fn verify_translation_invalid_input() { let result = verify_translation("invalid garbage"); assert!(result.is_err()); } }