Skip to main content

automapper_validation/eval/
expr_eval.rs

1//! Evaluates `ConditionExpr` trees using a `ConditionEvaluator`.
2
3use super::context::EvaluationContext;
4use super::evaluator::{ConditionEvaluator, ConditionResult};
5use crate::expr::ConditionExpr;
6
7/// Evaluates a `ConditionExpr` AST against an evaluation context.
8///
9/// Uses three-valued short-circuit logic:
10/// - AND: False short-circuits to False; all True -> True; else Unknown
11/// - OR: True short-circuits to True; all False -> False; else Unknown
12/// - XOR: requires both operands known; Unknown if either is Unknown
13/// - NOT: inverts True/False; preserves Unknown
14pub struct ConditionExprEvaluator<'a, E: ConditionEvaluator> {
15    evaluator: &'a E,
16}
17
18impl<'a, E: ConditionEvaluator> ConditionExprEvaluator<'a, E> {
19    /// Create a new expression evaluator wrapping a condition evaluator.
20    pub fn new(evaluator: &'a E) -> Self {
21        Self { evaluator }
22    }
23
24    /// The wrapped condition evaluator.
25    pub fn evaluator(&self) -> &'a E {
26        self.evaluator
27    }
28
29    /// Evaluate a condition expression tree.
30    pub fn evaluate(&self, expr: &ConditionExpr, ctx: &EvaluationContext) -> ConditionResult {
31        match expr {
32            ConditionExpr::Ref(id) => self.evaluator.evaluate(*id, ctx),
33
34            ConditionExpr::And(exprs) => self.evaluate_and(exprs, ctx),
35
36            ConditionExpr::Or(exprs) => self.evaluate_or(exprs, ctx),
37
38            ConditionExpr::Xor(left, right) => {
39                let l = self.evaluate(left, ctx);
40                let r = self.evaluate(right, ctx);
41                self.evaluate_xor(l, r)
42            }
43
44            ConditionExpr::Not(inner) => {
45                let result = self.evaluate(inner, ctx);
46                self.evaluate_not(result)
47            }
48
49            ConditionExpr::Package { .. } => {
50                // Package cardinality constraints are evaluated separately;
51                // in a boolean context they are trivially true.
52                ConditionResult::True
53            }
54        }
55    }
56
57    /// AND with short-circuit: any False -> False, all True -> True, else Unknown.
58    fn evaluate_and(&self, exprs: &[ConditionExpr], ctx: &EvaluationContext) -> ConditionResult {
59        let mut has_unknown = false;
60
61        for expr in exprs {
62            match self.evaluate(expr, ctx) {
63                ConditionResult::False => return ConditionResult::False,
64                ConditionResult::Unknown => has_unknown = true,
65                ConditionResult::True => {}
66            }
67        }
68
69        if has_unknown {
70            ConditionResult::Unknown
71        } else {
72            ConditionResult::True
73        }
74    }
75
76    /// OR with short-circuit: any True -> True, all False -> False, else Unknown.
77    fn evaluate_or(&self, exprs: &[ConditionExpr], ctx: &EvaluationContext) -> ConditionResult {
78        let mut has_unknown = false;
79
80        for expr in exprs {
81            match self.evaluate(expr, ctx) {
82                ConditionResult::True => return ConditionResult::True,
83                ConditionResult::Unknown => has_unknown = true,
84                ConditionResult::False => {}
85            }
86        }
87
88        if has_unknown {
89            ConditionResult::Unknown
90        } else {
91            ConditionResult::False
92        }
93    }
94
95    /// XOR: both must be known. True XOR False = True, same values = False, Unknown if either Unknown.
96    fn evaluate_xor(&self, left: ConditionResult, right: ConditionResult) -> ConditionResult {
97        match (left, right) {
98            (ConditionResult::True, ConditionResult::False)
99            | (ConditionResult::False, ConditionResult::True) => ConditionResult::True,
100            (ConditionResult::True, ConditionResult::True)
101            | (ConditionResult::False, ConditionResult::False) => ConditionResult::False,
102            _ => ConditionResult::Unknown,
103        }
104    }
105
106    /// NOT: inverts True/False, preserves Unknown.
107    fn evaluate_not(&self, result: ConditionResult) -> ConditionResult {
108        match result {
109            ConditionResult::True => ConditionResult::False,
110            ConditionResult::False => ConditionResult::True,
111            ConditionResult::Unknown => ConditionResult::Unknown,
112        }
113    }
114
115    /// Parse an AHB status string, evaluate it, and return the result.
116    ///
117    /// Returns `ConditionResult::True` if there are no conditions (unconditionally required).
118    pub fn evaluate_status(&self, ahb_status: &str, ctx: &EvaluationContext) -> ConditionResult {
119        use crate::expr::ConditionParser;
120
121        match ConditionParser::parse(ahb_status) {
122            Ok(Some(expr)) => self.evaluate(&expr, ctx),
123            Ok(None) => ConditionResult::True, // No conditions = unconditionally true
124            Err(_) => ConditionResult::Unknown, // Parse error = treat as unknown
125        }
126    }
127
128    /// Like [`evaluate_status`](Self::evaluate_status), but expands UB condition
129    /// references inline during parsing.
130    pub fn evaluate_status_with_ub(
131        &self,
132        ahb_status: &str,
133        ctx: &EvaluationContext,
134        ub_definitions: &std::collections::BTreeMap<String, crate::expr::ConditionExpr>,
135    ) -> ConditionResult {
136        use crate::expr::ConditionParser;
137
138        match ConditionParser::parse_with_ub(ahb_status, ub_definitions) {
139            Ok(Some(expr)) => self.evaluate(&expr, ctx),
140            Ok(None) => ConditionResult::True,
141            Err(_) => ConditionResult::Unknown,
142        }
143    }
144
145    /// Like [`evaluate_status`](Self::evaluate_status), but also returns the
146    /// IDs of conditions that evaluated to `Unknown`.
147    pub fn evaluate_status_detailed(
148        &self,
149        ahb_status: &str,
150        ctx: &EvaluationContext,
151    ) -> (ConditionResult, Vec<u32>) {
152        use crate::expr::ConditionParser;
153
154        match ConditionParser::parse(ahb_status) {
155            Ok(Some(expr)) => {
156                let result = self.evaluate(&expr, ctx);
157                if result.is_unknown() {
158                    let unknown_ids = self.collect_unknown_ids(&expr, ctx);
159                    (result, unknown_ids)
160                } else {
161                    (result, Vec::new())
162                }
163            }
164            Ok(None) => (ConditionResult::True, Vec::new()),
165            Err(_) => (ConditionResult::Unknown, Vec::new()),
166        }
167    }
168
169    /// Like [`evaluate_status_detailed`](Self::evaluate_status_detailed), but
170    /// expands UB condition references inline during parsing.
171    pub fn evaluate_status_detailed_with_ub(
172        &self,
173        ahb_status: &str,
174        ctx: &EvaluationContext,
175        ub_definitions: &std::collections::BTreeMap<String, crate::expr::ConditionExpr>,
176    ) -> (ConditionResult, Vec<u32>) {
177        use crate::expr::ConditionParser;
178
179        match ConditionParser::parse_with_ub(ahb_status, ub_definitions) {
180            Ok(Some(expr)) => {
181                let result = self.evaluate(&expr, ctx);
182                if result.is_unknown() {
183                    let unknown_ids = self.collect_unknown_ids(&expr, ctx);
184                    (result, unknown_ids)
185                } else {
186                    (result, Vec::new())
187                }
188            }
189            Ok(None) => (ConditionResult::True, Vec::new()),
190            Err(_) => (ConditionResult::Unknown, Vec::new()),
191        }
192    }
193
194    /// Collect condition IDs that evaluate to `Unknown` within an expression.
195    fn collect_unknown_ids(&self, expr: &ConditionExpr, ctx: &EvaluationContext) -> Vec<u32> {
196        let mut ids = Vec::new();
197        self.collect_unknown_ids_inner(expr, ctx, &mut ids);
198        ids
199    }
200
201    fn collect_unknown_ids_inner(
202        &self,
203        expr: &ConditionExpr,
204        ctx: &EvaluationContext,
205        ids: &mut Vec<u32>,
206    ) {
207        match expr {
208            ConditionExpr::Ref(id) => {
209                if self.evaluator.evaluate(*id, ctx).is_unknown() {
210                    ids.push(*id);
211                }
212            }
213            ConditionExpr::And(exprs) | ConditionExpr::Or(exprs) => {
214                for e in exprs {
215                    self.collect_unknown_ids_inner(e, ctx, ids);
216                }
217            }
218            ConditionExpr::Xor(left, right) => {
219                self.collect_unknown_ids_inner(left, ctx, ids);
220                self.collect_unknown_ids_inner(right, ctx, ids);
221            }
222            ConditionExpr::Not(inner) => {
223                self.collect_unknown_ids_inner(inner, ctx, ids);
224            }
225            ConditionExpr::Package { .. } => {
226                // Package constraints have no condition IDs to collect
227            }
228        }
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::super::evaluator::{ConditionResult as CR, NoOpExternalProvider};
235    use super::*;
236    use mig_types::segment::OwnedSegment;
237    use std::collections::HashMap;
238
239    /// A mock condition evaluator for testing.
240    struct MockEvaluator {
241        results: HashMap<u32, ConditionResult>,
242        external_ids: Vec<u32>,
243    }
244
245    impl MockEvaluator {
246        fn new() -> Self {
247            Self {
248                results: HashMap::new(),
249                external_ids: Vec::new(),
250            }
251        }
252
253        fn with_condition(mut self, id: u32, result: ConditionResult) -> Self {
254            self.results.insert(id, result);
255            self
256        }
257    }
258
259    impl ConditionEvaluator for MockEvaluator {
260        fn evaluate(&self, condition: u32, _ctx: &EvaluationContext) -> ConditionResult {
261            self.results
262                .get(&condition)
263                .copied()
264                .unwrap_or(ConditionResult::Unknown)
265        }
266
267        fn is_external(&self, condition: u32) -> bool {
268            self.external_ids.contains(&condition)
269        }
270
271        fn message_type(&self) -> &str {
272            "TEST"
273        }
274
275        fn format_version(&self) -> &str {
276            "FV_TEST"
277        }
278    }
279
280    fn empty_context() -> (NoOpExternalProvider, Vec<OwnedSegment>) {
281        (NoOpExternalProvider, Vec::new())
282    }
283
284    fn make_ctx<'a>(
285        external: &'a NoOpExternalProvider,
286        segments: &'a [OwnedSegment],
287    ) -> EvaluationContext<'a> {
288        EvaluationContext::new("11001", external, segments)
289    }
290
291    // === Single condition ===
292
293    #[test]
294    fn test_eval_single_true() {
295        let eval = MockEvaluator::new().with_condition(1, CR::True);
296        let (ext, segs) = empty_context();
297        let ctx = make_ctx(&ext, &segs);
298        let expr_eval = ConditionExprEvaluator::new(&eval);
299
300        assert_eq!(expr_eval.evaluate(&ConditionExpr::Ref(1), &ctx), CR::True);
301    }
302
303    #[test]
304    fn test_eval_single_false() {
305        let eval = MockEvaluator::new().with_condition(1, CR::False);
306        let (ext, segs) = empty_context();
307        let ctx = make_ctx(&ext, &segs);
308        let expr_eval = ConditionExprEvaluator::new(&eval);
309
310        assert_eq!(expr_eval.evaluate(&ConditionExpr::Ref(1), &ctx), CR::False);
311    }
312
313    #[test]
314    fn test_eval_single_unknown() {
315        let eval = MockEvaluator::new(); // No condition registered -> Unknown
316        let (ext, segs) = empty_context();
317        let ctx = make_ctx(&ext, &segs);
318        let expr_eval = ConditionExprEvaluator::new(&eval);
319
320        assert_eq!(
321            expr_eval.evaluate(&ConditionExpr::Ref(999), &ctx),
322            CR::Unknown
323        );
324    }
325
326    // === AND ===
327
328    #[test]
329    fn test_eval_and_both_true() {
330        let eval = MockEvaluator::new()
331            .with_condition(1, CR::True)
332            .with_condition(2, CR::True);
333        let (ext, segs) = empty_context();
334        let ctx = make_ctx(&ext, &segs);
335        let expr_eval = ConditionExprEvaluator::new(&eval);
336
337        let expr = ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
338        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::True);
339    }
340
341    #[test]
342    fn test_eval_and_one_false_short_circuits() {
343        let eval = MockEvaluator::new()
344            .with_condition(1, CR::False)
345            .with_condition(2, CR::True);
346        let (ext, segs) = empty_context();
347        let ctx = make_ctx(&ext, &segs);
348        let expr_eval = ConditionExprEvaluator::new(&eval);
349
350        let expr = ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
351        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::False);
352    }
353
354    #[test]
355    fn test_eval_and_one_unknown_true_gives_unknown() {
356        let eval = MockEvaluator::new()
357            .with_condition(1, CR::True)
358            .with_condition(2, CR::Unknown);
359        let (ext, segs) = empty_context();
360        let ctx = make_ctx(&ext, &segs);
361        let expr_eval = ConditionExprEvaluator::new(&eval);
362
363        let expr = ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
364        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::Unknown);
365    }
366
367    #[test]
368    fn test_eval_and_false_beats_unknown() {
369        // AND with False and Unknown should be False (short-circuit)
370        let eval = MockEvaluator::new()
371            .with_condition(1, CR::False)
372            .with_condition(2, CR::Unknown);
373        let (ext, segs) = empty_context();
374        let ctx = make_ctx(&ext, &segs);
375        let expr_eval = ConditionExprEvaluator::new(&eval);
376
377        let expr = ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
378        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::False);
379    }
380
381    #[test]
382    fn test_eval_and_three_way() {
383        let eval = MockEvaluator::new()
384            .with_condition(182, CR::True)
385            .with_condition(6, CR::True)
386            .with_condition(570, CR::True);
387        let (ext, segs) = empty_context();
388        let ctx = make_ctx(&ext, &segs);
389        let expr_eval = ConditionExprEvaluator::new(&eval);
390
391        let expr = ConditionExpr::And(vec![
392            ConditionExpr::Ref(182),
393            ConditionExpr::Ref(6),
394            ConditionExpr::Ref(570),
395        ]);
396        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::True);
397    }
398
399    #[test]
400    fn test_eval_and_three_way_one_false() {
401        let eval = MockEvaluator::new()
402            .with_condition(182, CR::True)
403            .with_condition(6, CR::True)
404            .with_condition(570, CR::False);
405        let (ext, segs) = empty_context();
406        let ctx = make_ctx(&ext, &segs);
407        let expr_eval = ConditionExprEvaluator::new(&eval);
408
409        let expr = ConditionExpr::And(vec![
410            ConditionExpr::Ref(182),
411            ConditionExpr::Ref(6),
412            ConditionExpr::Ref(570),
413        ]);
414        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::False);
415    }
416
417    // === OR ===
418
419    #[test]
420    fn test_eval_or_both_false() {
421        let eval = MockEvaluator::new()
422            .with_condition(1, CR::False)
423            .with_condition(2, CR::False);
424        let (ext, segs) = empty_context();
425        let ctx = make_ctx(&ext, &segs);
426        let expr_eval = ConditionExprEvaluator::new(&eval);
427
428        let expr = ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
429        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::False);
430    }
431
432    #[test]
433    fn test_eval_or_one_true_short_circuits() {
434        let eval = MockEvaluator::new()
435            .with_condition(1, CR::False)
436            .with_condition(2, CR::True);
437        let (ext, segs) = empty_context();
438        let ctx = make_ctx(&ext, &segs);
439        let expr_eval = ConditionExprEvaluator::new(&eval);
440
441        let expr = ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
442        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::True);
443    }
444
445    #[test]
446    fn test_eval_or_true_beats_unknown() {
447        let eval = MockEvaluator::new()
448            .with_condition(1, CR::Unknown)
449            .with_condition(2, CR::True);
450        let (ext, segs) = empty_context();
451        let ctx = make_ctx(&ext, &segs);
452        let expr_eval = ConditionExprEvaluator::new(&eval);
453
454        let expr = ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
455        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::True);
456    }
457
458    #[test]
459    fn test_eval_or_false_and_unknown_gives_unknown() {
460        let eval = MockEvaluator::new()
461            .with_condition(1, CR::False)
462            .with_condition(2, CR::Unknown);
463        let (ext, segs) = empty_context();
464        let ctx = make_ctx(&ext, &segs);
465        let expr_eval = ConditionExprEvaluator::new(&eval);
466
467        let expr = ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
468        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::Unknown);
469    }
470
471    // === XOR ===
472
473    #[test]
474    fn test_eval_xor_true_false() {
475        let eval = MockEvaluator::new()
476            .with_condition(1, CR::True)
477            .with_condition(2, CR::False);
478        let (ext, segs) = empty_context();
479        let ctx = make_ctx(&ext, &segs);
480        let expr_eval = ConditionExprEvaluator::new(&eval);
481
482        let expr = ConditionExpr::Xor(
483            Box::new(ConditionExpr::Ref(1)),
484            Box::new(ConditionExpr::Ref(2)),
485        );
486        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::True);
487    }
488
489    #[test]
490    fn test_eval_xor_both_true() {
491        let eval = MockEvaluator::new()
492            .with_condition(1, CR::True)
493            .with_condition(2, CR::True);
494        let (ext, segs) = empty_context();
495        let ctx = make_ctx(&ext, &segs);
496        let expr_eval = ConditionExprEvaluator::new(&eval);
497
498        let expr = ConditionExpr::Xor(
499            Box::new(ConditionExpr::Ref(1)),
500            Box::new(ConditionExpr::Ref(2)),
501        );
502        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::False);
503    }
504
505    #[test]
506    fn test_eval_xor_both_false() {
507        let eval = MockEvaluator::new()
508            .with_condition(1, CR::False)
509            .with_condition(2, CR::False);
510        let (ext, segs) = empty_context();
511        let ctx = make_ctx(&ext, &segs);
512        let expr_eval = ConditionExprEvaluator::new(&eval);
513
514        let expr = ConditionExpr::Xor(
515            Box::new(ConditionExpr::Ref(1)),
516            Box::new(ConditionExpr::Ref(2)),
517        );
518        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::False);
519    }
520
521    #[test]
522    fn test_eval_xor_unknown_propagates() {
523        let eval = MockEvaluator::new()
524            .with_condition(1, CR::True)
525            .with_condition(2, CR::Unknown);
526        let (ext, segs) = empty_context();
527        let ctx = make_ctx(&ext, &segs);
528        let expr_eval = ConditionExprEvaluator::new(&eval);
529
530        let expr = ConditionExpr::Xor(
531            Box::new(ConditionExpr::Ref(1)),
532            Box::new(ConditionExpr::Ref(2)),
533        );
534        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::Unknown);
535    }
536
537    // === NOT ===
538
539    #[test]
540    fn test_eval_not_true() {
541        let eval = MockEvaluator::new().with_condition(1, CR::True);
542        let (ext, segs) = empty_context();
543        let ctx = make_ctx(&ext, &segs);
544        let expr_eval = ConditionExprEvaluator::new(&eval);
545
546        let expr = ConditionExpr::Not(Box::new(ConditionExpr::Ref(1)));
547        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::False);
548    }
549
550    #[test]
551    fn test_eval_not_false() {
552        let eval = MockEvaluator::new().with_condition(1, CR::False);
553        let (ext, segs) = empty_context();
554        let ctx = make_ctx(&ext, &segs);
555        let expr_eval = ConditionExprEvaluator::new(&eval);
556
557        let expr = ConditionExpr::Not(Box::new(ConditionExpr::Ref(1)));
558        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::True);
559    }
560
561    #[test]
562    fn test_eval_not_unknown() {
563        let eval = MockEvaluator::new(); // 1 -> Unknown
564        let (ext, segs) = empty_context();
565        let ctx = make_ctx(&ext, &segs);
566        let expr_eval = ConditionExprEvaluator::new(&eval);
567
568        let expr = ConditionExpr::Not(Box::new(ConditionExpr::Ref(1)));
569        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::Unknown);
570    }
571
572    // === Complex expressions ===
573
574    #[test]
575    fn test_eval_complex_nested() {
576        // (([1] ∧ [2]) ∨ ([3] ∧ [4])) ∧ [5]
577        // [1]=T, [2]=F, [3]=T, [4]=T, [5]=T
578        // ([1]∧[2])=F, ([3]∧[4])=T, F∨T=T, T∧[5]=T
579        let eval = MockEvaluator::new()
580            .with_condition(1, CR::True)
581            .with_condition(2, CR::False)
582            .with_condition(3, CR::True)
583            .with_condition(4, CR::True)
584            .with_condition(5, CR::True);
585        let (ext, segs) = empty_context();
586        let ctx = make_ctx(&ext, &segs);
587        let expr_eval = ConditionExprEvaluator::new(&eval);
588
589        let expr = ConditionExpr::And(vec![
590            ConditionExpr::Or(vec![
591                ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]),
592                ConditionExpr::And(vec![ConditionExpr::Ref(3), ConditionExpr::Ref(4)]),
593            ]),
594            ConditionExpr::Ref(5),
595        ]);
596        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::True);
597    }
598
599    #[test]
600    fn test_eval_xor_with_nested_and() {
601        // ([102] ∧ [2006]) ⊻ ([103] ∧ [2005])
602        // [102]=T, [2006]=T, [103]=F, [2005]=F
603        // T∧T=T, F∧F=F, T⊻F=T
604        let eval = MockEvaluator::new()
605            .with_condition(102, CR::True)
606            .with_condition(2006, CR::True)
607            .with_condition(103, CR::False)
608            .with_condition(2005, CR::False);
609        let (ext, segs) = empty_context();
610        let ctx = make_ctx(&ext, &segs);
611        let expr_eval = ConditionExprEvaluator::new(&eval);
612
613        let expr = ConditionExpr::Xor(
614            Box::new(ConditionExpr::And(vec![
615                ConditionExpr::Ref(102),
616                ConditionExpr::Ref(2006),
617            ])),
618            Box::new(ConditionExpr::And(vec![
619                ConditionExpr::Ref(103),
620                ConditionExpr::Ref(2005),
621            ])),
622        );
623        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::True);
624    }
625
626    // === evaluate_status ===
627
628    #[test]
629    fn test_evaluate_status_with_conditions() {
630        let eval = MockEvaluator::new()
631            .with_condition(182, CR::True)
632            .with_condition(152, CR::True);
633        let (ext, segs) = empty_context();
634        let ctx = make_ctx(&ext, &segs);
635        let expr_eval = ConditionExprEvaluator::new(&eval);
636
637        assert_eq!(
638            expr_eval.evaluate_status("Muss [182] ∧ [152]", &ctx),
639            CR::True
640        );
641    }
642
643    #[test]
644    fn test_evaluate_status_no_conditions() {
645        let eval = MockEvaluator::new();
646        let (ext, segs) = empty_context();
647        let ctx = make_ctx(&ext, &segs);
648        let expr_eval = ConditionExprEvaluator::new(&eval);
649
650        assert_eq!(expr_eval.evaluate_status("Muss", &ctx), CR::True);
651    }
652
653    #[test]
654    fn test_evaluate_status_empty() {
655        let eval = MockEvaluator::new();
656        let (ext, segs) = empty_context();
657        let ctx = make_ctx(&ext, &segs);
658        let expr_eval = ConditionExprEvaluator::new(&eval);
659
660        assert_eq!(expr_eval.evaluate_status("", &ctx), CR::True);
661    }
662
663    // === Unknown propagation comprehensive ===
664
665    #[test]
666    fn test_unknown_propagation_and_or_mix() {
667        // [1](Unknown) ∨ ([2](True) ∧ [3](Unknown))
668        // [2]∧[3] = Unknown (True ∧ Unknown)
669        // Unknown ∨ Unknown = Unknown
670        let eval = MockEvaluator::new().with_condition(2, CR::True);
671        // 1 and 3 default to Unknown
672        let (ext, segs) = empty_context();
673        let ctx = make_ctx(&ext, &segs);
674        let expr_eval = ConditionExprEvaluator::new(&eval);
675
676        let expr = ConditionExpr::Or(vec![
677            ConditionExpr::Ref(1),
678            ConditionExpr::And(vec![ConditionExpr::Ref(2), ConditionExpr::Ref(3)]),
679        ]);
680        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::Unknown);
681    }
682
683    #[test]
684    fn test_or_short_circuits_past_unknown() {
685        // [1](Unknown) ∨ [2](True) -> True (True short-circuits)
686        let eval = MockEvaluator::new().with_condition(2, CR::True);
687        let (ext, segs) = empty_context();
688        let ctx = make_ctx(&ext, &segs);
689        let expr_eval = ConditionExprEvaluator::new(&eval);
690
691        let expr = ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
692        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::True);
693    }
694
695    #[test]
696    fn test_and_short_circuits_past_unknown() {
697        // [1](Unknown) ∧ [2](False) -> False (False short-circuits)
698        let eval = MockEvaluator::new().with_condition(2, CR::False);
699        let (ext, segs) = empty_context();
700        let ctx = make_ctx(&ext, &segs);
701        let expr_eval = ConditionExprEvaluator::new(&eval);
702
703        let expr = ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
704        assert_eq!(expr_eval.evaluate(&expr, &ctx), CR::False);
705    }
706
707    // === evaluate_status_detailed ===
708
709    #[test]
710    fn test_detailed_returns_unknown_ids() {
711        // [182]=True, [8]=Unknown → AND = Unknown, unknown_ids = [8]
712        let eval = MockEvaluator::new().with_condition(182, CR::True);
713        let (ext, segs) = empty_context();
714        let ctx = make_ctx(&ext, &segs);
715        let expr_eval = ConditionExprEvaluator::new(&eval);
716
717        let (result, unknown_ids) = expr_eval.evaluate_status_detailed("Muss [182] ∧ [8]", &ctx);
718        assert_eq!(result, CR::Unknown);
719        assert_eq!(unknown_ids, vec![8]);
720    }
721
722    #[test]
723    fn test_detailed_multiple_unknown_ids() {
724        // [1]=Unknown, [2]=True, [3]=Unknown → AND = Unknown, unknown_ids = [1, 3]
725        let eval = MockEvaluator::new().with_condition(2, CR::True);
726        let (ext, segs) = empty_context();
727        let ctx = make_ctx(&ext, &segs);
728        let expr_eval = ConditionExprEvaluator::new(&eval);
729
730        let (result, unknown_ids) =
731            expr_eval.evaluate_status_detailed("Muss [1] ∧ [2] ∧ [3]", &ctx);
732        assert_eq!(result, CR::Unknown);
733        assert_eq!(unknown_ids, vec![1, 3]);
734    }
735
736    #[test]
737    fn test_detailed_no_unknown_when_true() {
738        let eval = MockEvaluator::new()
739            .with_condition(182, CR::True)
740            .with_condition(152, CR::True);
741        let (ext, segs) = empty_context();
742        let ctx = make_ctx(&ext, &segs);
743        let expr_eval = ConditionExprEvaluator::new(&eval);
744
745        let (result, unknown_ids) = expr_eval.evaluate_status_detailed("Muss [182] ∧ [152]", &ctx);
746        assert_eq!(result, CR::True);
747        assert!(unknown_ids.is_empty());
748    }
749
750    #[test]
751    fn test_detailed_no_unknown_when_false() {
752        let eval = MockEvaluator::new().with_condition(182, CR::False);
753        let (ext, segs) = empty_context();
754        let ctx = make_ctx(&ext, &segs);
755        let expr_eval = ConditionExprEvaluator::new(&eval);
756
757        let (result, unknown_ids) = expr_eval.evaluate_status_detailed("Muss [182] ∧ [8]", &ctx);
758        assert_eq!(result, CR::False);
759        assert!(unknown_ids.is_empty());
760    }
761}