Skip to main content

etdl_compiler/
lib.rs

1//! Compiler pipeline for the Event Tree Definition Language (ETDL).
2//!
3//! Validates `.etdl` documents, resolves fault tree top-event probabilities
4//! ([IEC 61025:2006](https://github.com/usamassem/etdl-specification)), and
5//! generates service-local code. Reliability — event trees (IEC 62502),
6//! fault trees (IEC 61025), retry policies, SLAs — becomes a build-time,
7//! machine-checked artifact instead of scattered runtime guesses.
8//!
9//! # Pipeline
10//!
11//! 1. [`validate::validate_document`] — structural and semantic diagnostics (E-1xx,
12//!    V-1xx..V-5xx, W-4xx)
13//! 2. [`fault_tree::resolve_fault_trees`] — exact top-event probability evaluation
14//!    (AND/OR/NOT/XOR/VOTING gates, exponential failure model)
15//! 3. [`typeck`] — ECEL condition type-checking against AsyncAPI schemas
16//! 4. [`codegen::CodeGenerator`] — backend trait; [`codegen::RustCodeGenerator`]
17//!    emits async handlers with embedded probabilities, retry policies, and
18//!    `etdl-core` instrumentation
19//!
20//! # Example
21//!
22//! ```no_run
23//! use etdl_compiler::Compiler;
24//! use etdl_parser::{parse_document_from_file, load_asyncapi_imports};
25//! use std::path::Path;
26//!
27//! let base = Path::new(".");
28//! let doc = parse_document_from_file(&base.join("order-fulfillment.etdl"))?;
29//! let registry = load_asyncapi_imports(&doc, base)?;
30//! let result = Compiler::new().compile(&doc, &registry);
31//! assert!(result.diagnostics.iter().all(|d| !d.is_error()));
32//! assert!(result.rust_output.is_some());
33//! # Ok::<(), String>(())
34//! ```
35
36use etdl_parser::ast::EtlDocument;
37use etdl_parser::asyncapi::AsyncApiRegistry;
38
39pub mod codegen;
40pub mod fault_tree;
41mod typeck;
42pub mod validate;
43
44pub use codegen::{CodeGenerator, RustCodeGenerator};
45pub use validate::Diagnostic;
46
47pub struct Compiler {
48    pub rust_codegen: RustCodeGenerator,
49}
50
51#[derive(Debug, Clone)]
52pub struct CompilationResult {
53    pub diagnostics: Vec<Diagnostic>,
54    pub rust_output: Option<String>,
55}
56
57impl Compiler {
58    pub fn new() -> Self {
59        Compiler {
60            rust_codegen: RustCodeGenerator::new(),
61        }
62    }
63
64    /// Run the full validation pipeline (semantic checks, fault-tree
65    /// resolution, probability validation, ECEL type checking) without
66    /// generating any code.
67    pub fn validate(
68        &self,
69        doc: &EtlDocument,
70        asyncapi_registry: &AsyncApiRegistry,
71    ) -> Vec<Diagnostic> {
72        let mut diagnostics = Vec::new();
73
74        validate::validate_document(doc, asyncapi_registry, &mut diagnostics);
75
76        if diagnostics.iter().any(|d| d.is_error()) {
77            return diagnostics;
78        }
79
80        let fault_tree_probs = fault_tree::resolve_fault_trees(doc, &mut diagnostics);
81        let resolved_probabilities =
82            validate::resolve_probability_links(doc, &fault_tree_probs, &mut diagnostics);
83
84        validate::validate_probability_sums(doc, &resolved_probabilities, &mut diagnostics);
85
86        if diagnostics.iter().any(|d| d.is_error()) {
87            return diagnostics;
88        }
89
90        typeck::type_check_conditions(doc, asyncapi_registry, &mut diagnostics);
91
92        diagnostics
93    }
94
95    pub fn compile(
96        &self,
97        doc: &EtlDocument,
98        asyncapi_registry: &AsyncApiRegistry,
99    ) -> CompilationResult {
100        let mut diagnostics = self.validate(doc, asyncapi_registry);
101
102        let has_errors = diagnostics.iter().any(|d| d.is_error());
103        if has_errors {
104            return CompilationResult {
105                diagnostics,
106                rust_output: None,
107            };
108        }
109
110        let fault_tree_probs = fault_tree::resolve_fault_trees(doc, &mut Vec::new());
111
112        let mut rust_output = String::new();
113        let gen_result = self
114            .rust_codegen
115            .generate_all(doc, &fault_tree_probs, &asyncapi_registry, &mut diagnostics);
116
117        if gen_result.is_ok() {
118            rust_output = gen_result.unwrap();
119        }
120
121        CompilationResult {
122            diagnostics,
123            rust_output: if rust_output.is_empty() {
124                None
125            } else {
126                Some(rust_output)
127            },
128        }
129    }
130}
131
132impl Default for Compiler {
133    fn default() -> Self {
134        Self::new()
135    }
136}