etdl-compiler 0.1.3

ETDL compiler: IEC 61025 fault tree resolution, MOCUS cut sets, ECEL type-checking, semantic validation, and code generation for event-driven microservices
Documentation
//! Compiler pipeline for the Event Tree Definition Language (ETDL).
//!
//! Validates `.etdl` documents, resolves fault tree top-event probabilities
//! ([IEC 61025:2006](https://github.com/usamassem/etdl-specification)), and
//! generates service-local code. Reliability — event trees (IEC 62502),
//! fault trees (IEC 61025), retry policies, SLAs — becomes a build-time,
//! machine-checked artifact instead of scattered runtime guesses.
//!
//! # Pipeline
//!
//! 1. [`validate::validate_document`] — structural and semantic diagnostics (E-1xx,
//!    V-1xx..V-5xx, W-4xx)
//! 2. [`fault_tree::resolve_fault_trees`] — exact top-event probability evaluation
//!    (AND/OR/NOT/XOR/VOTING gates, exponential failure model)
//! 3. [`typeck`] — ECEL condition type-checking against AsyncAPI schemas
//! 4. [`codegen::CodeGenerator`] — backend trait; [`codegen::RustCodeGenerator`]
//!    emits async handlers with embedded probabilities, retry policies, and
//!    `etdl-core` instrumentation
//!
//! # Example
//!
//! ```no_run
//! use etdl_compiler::Compiler;
//! use etdl_parser::{parse_document_from_file, load_asyncapi_imports};
//! use std::path::Path;
//!
//! let base = Path::new(".");
//! let doc = parse_document_from_file(&base.join("order-fulfillment.etdl"))?;
//! let registry = load_asyncapi_imports(&doc, base)?;
//! let result = Compiler::new().compile(&doc, &registry);
//! assert!(result.diagnostics.iter().all(|d| !d.is_error()));
//! assert!(result.rust_output.is_some());
//! # Ok::<(), String>(())
//! ```

use etdl_parser::ast::EtlDocument;
use etdl_parser::asyncapi::AsyncApiRegistry;

pub mod codegen;
pub mod fault_tree;
mod typeck;
pub mod validate;

pub use codegen::{CodeGenerator, RustCodeGenerator};
pub use validate::Diagnostic;

pub struct Compiler {
    pub rust_codegen: RustCodeGenerator,
}

#[derive(Debug, Clone)]
pub struct CompilationResult {
    pub diagnostics: Vec<Diagnostic>,
    pub rust_output: Option<String>,
}

impl Compiler {
    pub fn new() -> Self {
        Compiler {
            rust_codegen: RustCodeGenerator::new(),
        }
    }

    /// Run the full validation pipeline (semantic checks, fault-tree
    /// resolution, probability validation, ECEL type checking) without
    /// generating any code.
    pub fn validate(
        &self,
        doc: &EtlDocument,
        asyncapi_registry: &AsyncApiRegistry,
    ) -> Vec<Diagnostic> {
        let mut diagnostics = Vec::new();

        validate::validate_document(doc, asyncapi_registry, &mut diagnostics);

        if diagnostics.iter().any(|d| d.is_error()) {
            return diagnostics;
        }

        let fault_tree_probs = fault_tree::resolve_fault_trees(doc, &mut diagnostics);
        let resolved_probabilities =
            validate::resolve_probability_links(doc, &fault_tree_probs, &mut diagnostics);

        validate::validate_probability_sums(doc, &resolved_probabilities, &mut diagnostics);

        if diagnostics.iter().any(|d| d.is_error()) {
            return diagnostics;
        }

        typeck::type_check_conditions(doc, asyncapi_registry, &mut diagnostics);

        diagnostics
    }

    pub fn compile(
        &self,
        doc: &EtlDocument,
        asyncapi_registry: &AsyncApiRegistry,
    ) -> CompilationResult {
        let mut diagnostics = self.validate(doc, asyncapi_registry);

        let has_errors = diagnostics.iter().any(|d| d.is_error());
        if has_errors {
            return CompilationResult {
                diagnostics,
                rust_output: None,
            };
        }

        let fault_tree_probs = fault_tree::resolve_fault_trees(doc, &mut Vec::new());

        let mut rust_output = String::new();
        let gen_result = self
            .rust_codegen
            .generate_all(doc, &fault_tree_probs, &asyncapi_registry, &mut diagnostics);

        if gen_result.is_ok() {
            rust_output = gen_result.unwrap();
        }

        CompilationResult {
            diagnostics,
            rust_output: if rust_output.is_empty() {
                None
            } else {
                Some(rust_output)
            },
        }
    }
}

impl Default for Compiler {
    fn default() -> Self {
        Self::new()
    }
}