Skip to main content

automapper_validation/expr/
ast.rs

1//! Condition expression AST types.
2
3use std::collections::BTreeSet;
4
5/// A parsed AHB condition expression tree.
6///
7/// Represents boolean combinations of condition references like `[1] ∧ [2]` or
8/// `([3] ∨ [4]) ⊻ [5]`.
9///
10/// # Examples
11///
12/// A single condition reference:
13/// ```
14/// use automapper_validation::expr::ConditionExpr;
15/// let expr = ConditionExpr::Ref(931);
16/// assert_eq!(expr.condition_ids(), [931].into());
17/// ```
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum ConditionExpr {
20    /// A leaf reference to a single condition by number, e.g., `[931]`.
21    Ref(u32),
22
23    /// Boolean AND of one or more expressions. All must be true.
24    /// Invariant: `exprs.len() >= 2`.
25    And(Vec<ConditionExpr>),
26
27    /// Boolean OR of one or more expressions. At least one must be true.
28    /// Invariant: `exprs.len() >= 2`.
29    Or(Vec<ConditionExpr>),
30
31    /// Boolean XOR of exactly two expressions. Exactly one must be true.
32    Xor(Box<ConditionExpr>, Box<ConditionExpr>),
33
34    /// Boolean NOT of an expression.
35    Not(Box<ConditionExpr>),
36
37    /// Package cardinality constraint: [NP_min..max]
38    Package { id: u32, min: u32, max: u32 },
39}
40
41impl ConditionExpr {
42    /// Extracts all condition IDs referenced in this expression tree.
43    pub fn condition_ids(&self) -> BTreeSet<u32> {
44        let mut ids = BTreeSet::new();
45        self.collect_ids(&mut ids);
46        ids
47    }
48
49    fn collect_ids(&self, ids: &mut BTreeSet<u32>) {
50        match self {
51            ConditionExpr::Ref(id) => {
52                ids.insert(*id);
53            }
54            ConditionExpr::And(exprs) | ConditionExpr::Or(exprs) => {
55                for expr in exprs {
56                    expr.collect_ids(ids);
57                }
58            }
59            ConditionExpr::Xor(left, right) => {
60                left.collect_ids(ids);
61                right.collect_ids(ids);
62            }
63            ConditionExpr::Not(inner) => {
64                inner.collect_ids(ids);
65            }
66            ConditionExpr::Package { .. } => {
67                // Package constraints are structural, not condition references
68            }
69        }
70    }
71}
72
73impl std::fmt::Display for ConditionExpr {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        match self {
76            ConditionExpr::Ref(id) => write!(f, "[{id}]"),
77            ConditionExpr::And(exprs) => {
78                let parts: Vec<String> = exprs.iter().map(|e| format!("{e}")).collect();
79                write!(f, "({})", parts.join(" ∧ "))
80            }
81            ConditionExpr::Or(exprs) => {
82                let parts: Vec<String> = exprs.iter().map(|e| format!("{e}")).collect();
83                write!(f, "({})", parts.join(" ∨ "))
84            }
85            ConditionExpr::Xor(left, right) => write!(f, "({left} ⊻ {right})"),
86            ConditionExpr::Not(inner) => write!(f, "NOT {inner}"),
87            ConditionExpr::Package { id, min, max } => write!(f, "[{id}P{min}..{max}]"),
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_ref_condition_ids() {
98        let expr = ConditionExpr::Ref(931);
99        assert_eq!(expr.condition_ids(), [931].into());
100    }
101
102    #[test]
103    fn test_and_condition_ids() {
104        let expr = ConditionExpr::And(vec![
105            ConditionExpr::Ref(1),
106            ConditionExpr::Ref(2),
107            ConditionExpr::Ref(3),
108        ]);
109        assert_eq!(expr.condition_ids(), [1, 2, 3].into());
110    }
111
112    #[test]
113    fn test_nested_condition_ids() {
114        // (([1] ∧ [2]) ∨ ([3] ∧ [4])) ⊻ [5]
115        let expr = ConditionExpr::Xor(
116            Box::new(ConditionExpr::Or(vec![
117                ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]),
118                ConditionExpr::And(vec![ConditionExpr::Ref(3), ConditionExpr::Ref(4)]),
119            ])),
120            Box::new(ConditionExpr::Ref(5)),
121        );
122        assert_eq!(expr.condition_ids(), [1, 2, 3, 4, 5].into());
123    }
124
125    #[test]
126    fn test_not_condition_ids() {
127        let expr = ConditionExpr::Not(Box::new(ConditionExpr::Ref(42)));
128        assert_eq!(expr.condition_ids(), [42].into());
129    }
130
131    #[test]
132    fn test_display_ref() {
133        let expr = ConditionExpr::Ref(931);
134        assert_eq!(format!("{expr}"), "[931]");
135    }
136
137    #[test]
138    fn test_display_and() {
139        let expr = ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
140        assert_eq!(format!("{expr}"), "([1] ∧ [2])");
141    }
142
143    #[test]
144    fn test_display_complex() {
145        let expr = ConditionExpr::Xor(
146            Box::new(ConditionExpr::And(vec![
147                ConditionExpr::Ref(102),
148                ConditionExpr::Ref(2006),
149            ])),
150            Box::new(ConditionExpr::And(vec![
151                ConditionExpr::Ref(103),
152                ConditionExpr::Ref(2005),
153            ])),
154        );
155        assert_eq!(format!("{expr}"), "(([102] ∧ [2006]) ⊻ ([103] ∧ [2005]))");
156    }
157
158    #[test]
159    fn test_display_not() {
160        let expr = ConditionExpr::Not(Box::new(ConditionExpr::Ref(1)));
161        assert_eq!(format!("{expr}"), "NOT [1]");
162    }
163
164    #[test]
165    fn test_equality() {
166        let a = ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
167        let b = ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
168        assert_eq!(a, b);
169    }
170
171    #[test]
172    fn test_inequality() {
173        let a = ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
174        let b = ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]);
175        assert_ne!(a, b);
176    }
177
178    #[test]
179    fn test_package_condition_ids() {
180        let expr = ConditionExpr::Package {
181            id: 4,
182            min: 0,
183            max: 1,
184        };
185        assert!(
186            expr.condition_ids().is_empty(),
187            "Package nodes have no condition IDs"
188        );
189    }
190
191    #[test]
192    fn test_package_display() {
193        let expr = ConditionExpr::Package {
194            id: 4,
195            min: 0,
196            max: 1,
197        };
198        assert_eq!(format!("{expr}"), "[4P0..1]");
199    }
200
201    #[test]
202    fn test_clone() {
203        let expr = ConditionExpr::Xor(
204            Box::new(ConditionExpr::Ref(1)),
205            Box::new(ConditionExpr::Ref(2)),
206        );
207        let cloned = expr.clone();
208        assert_eq!(expr, cloned);
209    }
210}