1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5use crate::guard_algebra::minimize_disjunction_by;
6use crate::{ConditionalGuard, Guard, Predicate};
7
8#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct GuardDnf(BTreeSet<BTreeSet<Predicate>>);
14
15impl Default for GuardDnf {
16 fn default() -> Self {
17 Self::unconditional()
18 }
19}
20
21impl GuardDnf {
22 #[must_use]
24 pub fn unconditional() -> Self {
25 Self(BTreeSet::from([BTreeSet::new()]))
26 }
27
28 #[must_use]
30 pub fn never() -> Self {
31 Self(BTreeSet::new())
32 }
33
34 #[must_use]
36 pub fn from_conjunction(predicates: impl IntoIterator<Item = Predicate>) -> Self {
37 Self::from_disjunction([predicates])
38 }
39
40 #[must_use]
42 pub fn from_guards(guards: impl IntoIterator<Item = Guard>) -> Self {
43 Self::from_conjunction(guards.into_iter().map(Predicate::from))
44 }
45
46 #[must_use]
48 pub fn from_guard_disjunction(
49 conjunctions: impl IntoIterator<Item = impl IntoIterator<Item = Guard>>,
50 ) -> Self {
51 Self::from_disjunction(
52 conjunctions
53 .into_iter()
54 .map(|guards| guards.into_iter().map(Predicate::from)),
55 )
56 }
57
58 #[must_use]
60 pub fn normalize_conditional_guard_disjunction(
61 conjunctions: impl IntoIterator<Item = impl IntoIterator<Item = ConditionalGuard>>,
62 ) -> Vec<Vec<ConditionalGuard>> {
63 let keys = conjunctions
64 .into_iter()
65 .map(|conjunction| {
66 let mut key = conjunction.into_iter().collect::<Vec<_>>();
67 key.sort();
68 key.dedup();
69 key
70 })
71 .collect();
72 minimize_disjunction_by(keys, crate::guard_algebra::guards_are_complementary)
73 }
74
75 #[must_use]
77 pub fn from_disjunction(
78 conjunctions: impl IntoIterator<Item = impl IntoIterator<Item = Predicate>>,
79 ) -> Self {
80 let keys = conjunctions
81 .into_iter()
82 .filter_map(normalize_conjunction)
83 .collect::<Vec<_>>();
84 let keys = minimize_disjunction_by(keys, predicates_are_complementary);
85 Self(
86 keys.into_iter()
87 .map(|key| key.into_iter().collect())
88 .collect(),
89 )
90 }
91
92 #[must_use]
94 pub fn is_unconditional(&self) -> bool {
95 self.0.contains(&BTreeSet::new())
96 }
97
98 #[must_use]
100 pub fn is_never(&self) -> bool {
101 self.0.is_empty()
102 }
103
104 #[must_use]
106 pub fn disjuncts(&self) -> &BTreeSet<BTreeSet<Predicate>> {
107 &self.0
108 }
109
110 #[must_use]
112 pub fn guard_conjunctions(&self) -> Vec<Vec<Guard>> {
113 let mut seen = BTreeSet::new();
114 let mut projected = Vec::new();
115 for conjunction in &self.0 {
116 let mut guards =
121 Predicate::contract_guard_stack(&conjunction.iter().cloned().collect::<Vec<_>>());
122 Guard::canonicalize_all(&mut guards);
123 if seen.insert(guards.clone()) {
124 projected.push(guards);
125 }
126 }
127 projected
128 }
129
130 #[must_use]
132 pub fn single_guard_conjunction(&self) -> Option<Vec<Guard>> {
133 let [guards] = self.guard_conjunctions().try_into().ok()?;
134 Some(guards)
135 }
136
137 #[must_use]
139 pub fn conjoined(&self, other: &Self) -> Self {
140 Self::from_disjunction(self.0.iter().flat_map(|left| {
141 other.0.iter().map(|right| {
142 left.iter()
143 .chain(right)
144 .cloned()
145 .collect::<Vec<Predicate>>()
146 })
147 }))
148 }
149
150 #[must_use]
152 pub fn conjoined_with_guards(&self, guards: impl IntoIterator<Item = Guard>) -> Self {
153 self.conjoined(&Self::from_guards(guards))
154 }
155
156 pub fn union_absorbing(&mut self, other: Self) {
159 *self = Self::from_disjunction(std::mem::take(&mut self.0).into_iter().chain(other.0));
160 }
161
162 pub fn map_value_paths<F>(&mut self, map: &mut F)
164 where
165 F: FnMut(crate::ValuesPath) -> crate::ValuesPath,
166 {
167 *self =
168 Self::from_disjunction(std::mem::take(&mut self.0).into_iter().map(|conjunction| {
169 conjunction
170 .into_iter()
171 .map(|predicate| predicate.map_value_paths(map))
172 .collect::<Vec<_>>()
173 }));
174 }
175}
176
177impl Serialize for GuardDnf {
178 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
179 where
180 S: Serializer,
181 {
182 self.guard_conjunctions().serialize(serializer)
183 }
184}
185
186impl<'de> Deserialize<'de> for GuardDnf {
187 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
190 where
191 D: Deserializer<'de>,
192 {
193 let conjunctions = Vec::<Vec<Guard>>::deserialize(deserializer)?;
194 Ok(Self::from_guard_disjunction(conjunctions))
195 }
196}
197
198fn normalize_conjunction(
199 predicates: impl IntoIterator<Item = Predicate>,
200) -> Option<Vec<Predicate>> {
201 fn push(predicate: Predicate, normalized: &mut BTreeSet<Predicate>) -> bool {
202 match predicate {
203 Predicate::True => true,
204 Predicate::False => false,
205 Predicate::And(predicates) => predicates
206 .into_iter()
207 .all(|predicate| push(predicate, normalized)),
208 Predicate::Or(predicates) if disjunction_is_tautology(&predicates) => true,
209 predicate => {
210 if normalized
211 .iter()
212 .any(|other| predicates_are_contradictory(&predicate, other))
213 {
214 return false;
215 }
216 normalized.insert(predicate);
217 true
218 }
219 }
220 }
221
222 let mut normalized = BTreeSet::new();
223 for predicate in predicates {
224 if !push(predicate, &mut normalized) {
225 return None;
226 }
227 }
228 let absorbed_disjunctions = normalized
229 .iter()
230 .filter(|predicate| match predicate {
231 Predicate::Or(alternatives) => alternatives
232 .iter()
233 .any(|alternative| normalized.contains(alternative)),
234 _ => false,
235 })
236 .cloned()
237 .collect::<Vec<_>>();
238 for predicate in absorbed_disjunctions {
239 normalized.remove(&predicate);
240 }
241 let exact = Predicate::all(
242 normalized
243 .iter()
244 .filter(|predicate| !predicate.contains_approximation())
245 .cloned()
246 .collect(),
247 );
248 normalized.retain(|predicate| {
249 let Predicate::Approximate {
250 sound_subset: Some(sound_subset),
251 ..
252 } = predicate
253 else {
254 return true;
255 };
256 !crate::predicate_bdd::exact_implies(&exact, sound_subset)
257 });
258 Some(normalized.into_iter().collect())
259}
260
261fn disjunction_is_tautology(predicates: &[Predicate]) -> bool {
262 predicates.iter().any(|predicate| {
263 predicates
264 .iter()
265 .any(|other| predicates_are_complementary(predicate, other))
266 })
267}
268
269fn predicates_are_contradictory(left: &Predicate, right: &Predicate) -> bool {
270 if predicates_are_complementary(left, right) {
271 return true;
272 }
273
274 if matches!(
275 (left, right),
276 (
277 Predicate::Guard(Guard::Eq {
278 path: left_path,
279 value: left_value,
280 }),
281 Predicate::Guard(Guard::NotEq {
282 path: right_path,
283 value: right_value,
284 })
285 ) | (
286 Predicate::Guard(Guard::NotEq {
287 path: left_path,
288 value: left_value,
289 }),
290 Predicate::Guard(Guard::Eq {
291 path: right_path,
292 value: right_value,
293 })
294 ) if left_path == right_path && left_value == right_value
295 ) {
296 return true;
297 }
298
299 match (left, right) {
300 (
301 Predicate::Guard(Guard::Eq {
302 path: left_path,
303 value: left_value,
304 }),
305 Predicate::Guard(Guard::Eq {
306 path: right_path,
307 value: right_value,
308 }),
309 ) => left_path == right_path && left_value != right_value,
310 (
311 Predicate::Guard(Guard::MatchesPattern {
312 path: pattern_path, ..
313 }),
314 Predicate::Guard(Guard::Eq {
315 path: value_path,
316 value,
317 }),
318 )
319 | (
320 Predicate::Guard(Guard::Eq {
321 path: value_path,
322 value,
323 }),
324 Predicate::Guard(Guard::MatchesPattern {
325 path: pattern_path, ..
326 }),
327 ) => pattern_path == value_path && !matches!(value, crate::GuardValue::String(_)),
328 (
329 Predicate::Guard(Guard::TypeIs {
330 path: type_path,
331 schema_type,
332 }),
333 Predicate::Guard(Guard::Eq {
334 path: value_path,
335 value,
336 }),
337 )
338 | (
339 Predicate::Guard(Guard::Eq {
340 path: value_path,
341 value,
342 }),
343 Predicate::Guard(Guard::TypeIs {
344 path: type_path,
345 schema_type,
346 }),
347 ) => type_path == value_path && !guard_value_has_schema_type(value, schema_type),
348 _ => false,
349 }
350}
351
352fn guard_value_has_schema_type(value: &crate::GuardValue, schema_type: &str) -> bool {
353 match value {
354 crate::GuardValue::String(_) => schema_type == "string",
355 crate::GuardValue::Bool(_) => schema_type == "boolean",
356 crate::GuardValue::Int(_) => matches!(schema_type, "integer" | "number"),
357 crate::GuardValue::Float(_) => schema_type == "number",
358 crate::GuardValue::Null => schema_type == "null",
359 }
360}
361
362fn predicates_are_complementary(left: &Predicate, right: &Predicate) -> bool {
363 match (left, right) {
364 (predicate, Predicate::Not(negated)) | (Predicate::Not(negated), predicate) => {
365 predicate == negated.as_ref()
366 }
367 _ => false,
368 }
369}
370
371#[cfg(test)]
372#[path = "tests/guard_dnf.rs"]
373mod tests;