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;
41pub mod reliability;
42mod typeck;
43pub mod validate;
44
45pub use codegen::{CodeGenerator, RustCodeGenerator};
46pub use validate::Diagnostic;
47
48pub struct Compiler {
49    pub rust_codegen: RustCodeGenerator,
50}
51
52/// A complete compilation result including optional reliability provenance.
53#[derive(Debug, Clone)]
54pub struct CompilationResult {
55    pub diagnostics: Vec<Diagnostic>,
56    pub rust_output: Option<String>,
57    /// Reliability build manifest, present when the document declares the
58    /// reliability supplement and external sources were resolved.
59    pub build_manifest: Option<serde_json::Value>,
60}
61
62impl Compiler {
63    pub fn new() -> Self {
64        Compiler {
65            rust_codegen: RustCodeGenerator::new(),
66        }
67    }
68
69    /// Run the full validation pipeline (semantic checks, fault-tree
70    /// resolution, probability validation, ECEL type checking) without
71    /// generating any code.
72    pub fn validate(
73        &self,
74        doc: &EtlDocument,
75        asyncapi_registry: &AsyncApiRegistry,
76    ) -> Vec<Diagnostic> {
77        self.validate_with_base(doc, asyncapi_registry, std::path::Path::new("."))
78    }
79
80    /// Validate, resolving external reliability sources relative to `base_dir`.
81    pub fn validate_with_base(
82        &self,
83        doc: &EtlDocument,
84        asyncapi_registry: &AsyncApiRegistry,
85        base_dir: &std::path::Path,
86    ) -> Vec<Diagnostic> {
87        let mut diagnostics = Vec::new();
88
89        validate::validate_document(doc, asyncapi_registry, &mut diagnostics);
90
91        if diagnostics.iter().any(|d| d.is_error()) {
92            return diagnostics;
93        }
94
95        // Resolve external reliability sources so that basic events whose
96        // probability comes from an artifact can be evaluated.
97        let (resolved_events, _manifest) =
98            reliability::resolve_reliability(doc, base_dir, &mut diagnostics);
99        let overrides: fault_tree::BasicEventOverrides = resolved_events
100            .iter()
101            .map(|r| (r.basic_event.clone(), r.resolved.value))
102            .collect();
103
104        let fault_tree_probs =
105            fault_tree::resolve_fault_trees_with_overrides(doc, &overrides, &mut diagnostics);
106        let resolved_probabilities =
107            validate::resolve_probability_links(doc, &fault_tree_probs, &mut diagnostics);
108
109        validate::validate_probability_sums(doc, &resolved_probabilities, &mut diagnostics);
110
111        if diagnostics.iter().any(|d| d.is_error()) {
112            return diagnostics;
113        }
114
115        typeck::type_check_conditions(doc, asyncapi_registry, &mut diagnostics);
116
117        diagnostics
118    }
119
120    pub fn compile(
121        &self,
122        doc: &EtlDocument,
123        asyncapi_registry: &AsyncApiRegistry,
124    ) -> CompilationResult {
125        self.compile_with_base(doc, asyncapi_registry, std::path::Path::new("."))
126    }
127
128    /// Compile with a base directory for resolving relative reliability
129    /// artifact paths.
130    pub fn compile_with_base(
131        &self,
132        doc: &EtlDocument,
133        asyncapi_registry: &AsyncApiRegistry,
134        base_dir: &std::path::Path,
135    ) -> CompilationResult {
136        let mut diagnostics = self.validate_with_base(doc, asyncapi_registry, base_dir);
137
138        let has_errors = diagnostics.iter().any(|d| d.is_error());
139        if has_errors {
140            return CompilationResult {
141                diagnostics,
142                rust_output: None,
143                build_manifest: None,
144            };
145        }
146
147        // Reliability: resolve external probability sources to deterministic
148        // scalars BEFORE fault-tree evaluation. Preserves the build-time
149        // resolution model; nothing is resolved at runtime.
150        let (resolved_events, manifest) =
151            reliability::resolve_reliability(doc, base_dir, &mut diagnostics);
152
153        let overrides: fault_tree::BasicEventOverrides = resolved_events
154            .iter()
155            .map(|r| (r.basic_event.clone(), r.resolved.value))
156            .collect();
157
158        let build_manifest = manifest.as_ref().and_then(|m| serde_json::to_value(m).ok());
159
160        let fault_tree_probs =
161            fault_tree::resolve_fault_trees_with_overrides(doc, &overrides, &mut diagnostics);
162
163        let mut rust_output = String::new();
164        let gen_result = self.rust_codegen.generate_all(
165            doc,
166            &fault_tree_probs,
167            asyncapi_registry,
168            &mut diagnostics,
169        );
170
171        if let Ok(gen_code) = gen_result {
172            rust_output = gen_code;
173        }
174
175        CompilationResult {
176            diagnostics,
177            rust_output: if rust_output.is_empty() {
178                None
179            } else {
180                Some(rust_output)
181            },
182            build_manifest,
183        }
184    }
185}
186
187impl Default for Compiler {
188    fn default() -> Self {
189        Self::new()
190    }
191}