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