1use bool_logic::ast::{All, Any, Not, Var, any, expr};
2use bool_logic::cfg::ast::{Expr, Pred, flag, target_family, target_os};
3use bool_logic::visit_mut::{VisitMut, walk_mut_expr, walk_mut_expr_list};
4
5use bool_logic::transforms::dedup_list::DedupList;
6use bool_logic::transforms::eval_const::EvalConst;
7use bool_logic::transforms::flatten_nested_list::FlattenNestedList;
8use bool_logic::transforms::flatten_single::FlattenSingle;
9use bool_logic::transforms::merge_all_of_any::MergeAllOfAny;
10use bool_logic::transforms::merge_all_of_not_any::MergeAllOfNotAny;
11use bool_logic::transforms::simplify_all_not_any::SimplifyAllNotAny;
12use bool_logic::transforms::simplify_by_short_circuit::SimplifyByShortCircuit;
13use bool_logic::transforms::simplify_nested_list::SimplifyNestedList;
14
15use std::cmp::Ordering;
16use std::cmp::Ordering::{Equal, Greater, Less};
17use std::mem;
18
19use stdx::iter::filter_map_collect_vec;
20use stdx::iter::map_collect_vec;
21use stdx::vec::VecExt;
22
23use log::debug;
24use log::trace;
25
26pub fn simplified_expr(x: impl Into<Expr>) -> Expr {
27 let mut x = x.into();
28
29 debug!("input: {x}");
30
31 ExpandTargetVendor.visit_mut_expr(&mut x);
32 trace!("after ExpandTargetVendor: {x}");
33
34 UnifyTargetFamily.visit_mut_expr(&mut x);
35 trace!("after UnifyTargetFamily: {x}");
36
37 for _ in 0..3 {
38 FlattenSingle.visit_mut_expr(&mut x);
39 trace!("after FlattenSingle: {x}");
40
41 FlattenNestedList.visit_mut_expr(&mut x);
42 trace!("after FlattenNestedList: {x}");
43
44 DedupList.visit_mut_expr(&mut x);
45 trace!("after DedupList: {x}");
46
47 EvalConst.visit_mut_expr(&mut x);
48 trace!("after EvalConst: {x}");
49
50 SimplifyNestedList.visit_mut_expr(&mut x);
51 trace!("after SimplifyNestedList: {x}");
52
53 MergeAllOfNotAny.visit_mut_expr(&mut x);
54 trace!("after MergeAllOfNotAny: {x}");
55
56 SimplifyAllNotAny.visit_mut_expr(&mut x);
57 trace!("after SimplifyAllNotAny: {x}");
58
59 MergeAllOfAny.visit_mut_expr(&mut x);
60 trace!("after MergeAllOfAny: {x}");
61
62 ImplyByKey.visit_mut_expr(&mut x);
63 trace!("after ImplyByKey: {x}");
64
65 SuppressTargetFamily.visit_mut_expr(&mut x);
66 trace!("after SuppressTargetFamily: {x}");
67
68 EvalConst.visit_mut_expr(&mut x);
69 trace!("after EvalConst: {x}");
70
71 MergePattern.visit_mut_expr(&mut x);
72 trace!("after MergePattern: {x}");
73
74 EvalConst.visit_mut_expr(&mut x);
75 trace!("after EvalConst: {x}");
76
77 SimplifyByShortCircuit.visit_mut_expr(&mut x);
78 trace!("after SimplifyByShortCircuit: {x}");
79
80 EvalConst.visit_mut_expr(&mut x);
81 trace!("after EvalConst: {x}");
82 }
83
84 SimplifyTargetFamily.visit_mut_expr(&mut x);
85 trace!("after SimplifyTargetFamily: {x}");
86
87 SortByPriority.visit_mut_expr(&mut x);
88 trace!("after SortByPriority: {x}");
89
90 SortByValue.visit_mut_expr(&mut x);
91 trace!("after SortByValue: {x}");
92
93 debug!("output: {x}");
94
95 x
96}
97
98struct SortByPriority;
99
100impl SortByPriority {
101 fn get_priority(x: &Expr) -> u32 {
102 match x {
103 Expr::Not(_) => 103,
104 Expr::Any(_) => 101,
105 Expr::All(_) => 102,
106 Expr::Var(Var(pred)) => match pred.key.as_str() {
107 "target_family" => 1,
108 "target_arch" => 2,
109 "target_vendor" => 3,
110 "target_os" => 4,
111 "target_env" => 5,
112 "target_pointer_width" => 6,
113 _ => 0,
114 },
115 Expr::Const(_) => panic!(),
116 }
117 }
118}
119
120impl VisitMut<Pred> for SortByPriority {
121 fn visit_mut_expr(&mut self, expr: &mut Expr) {
122 if let Some(list) = expr.as_mut_expr_list() {
123 list.sort_by(|lhs, rhs| {
124 let lhs = Self::get_priority(lhs);
125 let rhs = Self::get_priority(rhs);
126 lhs.cmp(&rhs)
127 });
128 }
129
130 walk_mut_expr(self, expr);
131 }
132}
133
134struct SortByValue;
135
136impl SortByValue {
137 fn cmp_var(lhs: &Expr, rhs: &Expr) -> Ordering {
138 let Expr::Var(Var(lhs)) = lhs else {
139 return Equal;
140 };
141 let Expr::Var(Var(rhs)) = rhs else {
142 return Equal;
143 };
144
145 let ok = Ord::cmp(lhs.key.as_str(), rhs.key.as_str());
146
147 match (lhs.value.as_deref(), rhs.value.as_deref()) {
148 (None, None) => ok,
149 (Some(lv), Some(rv)) => ok.then_with(|| Ord::cmp(lv, rv)),
150 (None, Some(_)) => Less,
151 (Some(_), None) => Greater,
152 }
153 }
154
155 fn cmp_not(lhs: &Expr, rhs: &Expr) -> Ordering {
156 let Expr::Not(Not(lhs)) = lhs else {
157 return Equal;
158 };
159 let Expr::Not(Not(rhs)) = rhs else {
160 return Equal;
161 };
162
163 Self::cmp_var(lhs, rhs)
164 }
165}
166
167impl VisitMut<Pred> for SortByValue {
168 fn visit_mut_expr(&mut self, expr: &mut Expr) {
169 if let Some(list) = expr.as_mut_expr_list() {
170 list.sort_by(Self::cmp_var);
171 list.sort_by(Self::cmp_not);
172 }
173
174 walk_mut_expr(self, expr);
175 }
176}
177
178struct ExpandTargetVendor;
181
182impl ExpandTargetVendor {
183 const APPLE_OSES: &'static [&'static str] = &["ios", "macos", "tvos", "visionos", "watchos"];
184}
185
186impl VisitMut<Pred> for ExpandTargetVendor {
187 fn visit_mut_expr(&mut self, x: &mut Expr) {
188 walk_mut_expr(self, x);
189
190 let expand = matches!(
191 x,
192 Expr::Var(Var(pred))
193 if pred.key == "target_vendor" && pred.value.as_deref() == Some("apple")
194 );
195
196 if expand {
197 let oses = map_collect_vec(Self::APPLE_OSES, |os| expr(target_os(*os)));
198 *x = Expr::Any(Any(oses));
199 }
200 }
201}
202
203struct UnifyTargetFamily;
204
205impl VisitMut<Pred> for UnifyTargetFamily {
206 fn visit_mut_var(&mut self, Var(pred): &mut Var<Pred>) {
207 if pred.value.is_none() && matches!(pred.key.as_str(), "unix" | "windows" | "wasm") {
208 *pred = target_family(pred.key.clone());
209 }
210 }
211}
212
213struct SimplifyTargetFamily;
214
215impl VisitMut<Pred> for SimplifyTargetFamily {
216 fn visit_mut_var(&mut self, Var(pred): &mut Var<Pred>) {
217 if pred.key == "target_family" {
218 if let Some(value) = pred.value.as_deref() {
219 if matches!(value, "unix" | "windows" | "wasm") {
220 *pred = flag(value);
221 }
222 }
223 }
224 }
225}
226
227struct ImplyByKey;
228
229impl ImplyByKey {
230 const UNIQUE_VALUED_KEYS: &'static [&'static str] = &[
231 "target_family",
232 "target_arch",
233 "target_vendor",
234 "target_os",
235 "target_env",
236 "target_pointer_width",
237 ];
238
239 fn is_expr_any_pred(any: &[Expr], key: &str) -> bool {
240 any.iter()
241 .all(|x| x.as_var().is_some_and(|Var(var)| var.key == key))
242 }
243
244 fn fix(pos_key: &str, pos_any_values: &[&str], expr: &mut Expr) {
245 match expr {
246 Expr::Any(Any(any)) => {
247 for x in any.iter_mut() {
248 Self::fix(pos_key, pos_any_values, x);
249 }
250 }
251 Expr::All(All(all)) => {
252 for x in all.iter_mut() {
253 Self::fix(pos_key, pos_any_values, x);
254 }
255 }
256 Expr::Not(Not(not)) => {
257 Self::fix(pos_key, pos_any_values, not);
258 }
259 Expr::Var(Var(var)) => {
260 if var.key == pos_key {
261 let var_value = var.value.as_deref().unwrap();
262 if pos_any_values.contains(&var_value) {
263 if pos_any_values.len() == 1 {
264 *expr = Expr::Const(true);
265 }
266 } else {
267 *expr = Expr::Const(false);
268 }
269 }
270 }
271 Expr::Const(_) => {}
272 }
273 }
274}
275
276impl VisitMut<Pred> for ImplyByKey {
277 fn visit_mut_all(&mut self, All(all): &mut All<Pred>) {
278 walk_mut_expr_list(self, all);
279
280 let mut i = 0;
281 while i < all.len() {
282 match &all[i] {
283 Expr::Var(Var(pos)) if Self::UNIQUE_VALUED_KEYS.contains(&pos.key.as_str()) => {
284 assert!(pos.value.is_some());
285
286 let pos = pos.clone();
287 let pos_key = pos.key.as_str();
288 let pos_any_values = &[pos.value.as_deref().unwrap()];
289
290 for (_, x) in all.iter_mut().enumerate().filter(|&(j, _)| j != i) {
291 Self::fix(pos_key, pos_any_values, x);
292 }
293 }
294 Expr::Any(Any(any)) => {
295 if let Some(pos_key) = Self::UNIQUE_VALUED_KEYS
296 .iter()
297 .find(|k| Self::is_expr_any_pred(any, k))
298 {
299 let any = any.clone();
300 let pos_any_values = map_collect_vec(&any, |x| {
301 x.as_var().unwrap().0.value.as_deref().unwrap()
302 });
303
304 for (_, x) in all.iter_mut().enumerate().filter(|&(j, _)| j != i) {
305 Self::fix(pos_key, &pos_any_values, x);
306 }
307 }
308 }
309 _ => {}
310 }
311 i += 1;
312 }
313 }
314}
315
316struct SuppressTargetFamily;
317
318impl SuppressTargetFamily {
319 fn is_target_os_pred(x: &Expr) -> bool {
320 match x {
321 Expr::Var(Var(var)) => var.key == "target_os",
322 _ => false,
323 }
324 }
325
326 fn has_specified_target_os(x: &Expr) -> bool {
327 if Self::is_target_os_pred(x) {
328 return true;
329 }
330
331 if let Expr::Any(Any(any)) = x {
332 return any.iter().all(Self::is_target_os_pred);
333 }
334
335 false
336 }
337
338 #[allow(clippy::match_like_matches_macro)]
339 fn is_suppressed_target_family(pred: &Pred) -> bool {
340 match (pred.key.as_str(), pred.value.as_deref()) {
341 ("target_family", Some("unix")) => true,
342 ("target_family", Some("windows")) => true,
343 _ => false,
344 }
345 }
346}
347
348impl VisitMut<Pred> for SuppressTargetFamily {
349 fn visit_mut_all(&mut self, All(all): &mut All<Pred>) {
350 if all.iter().any(Self::has_specified_target_os) {
351 all.remove_if(|x| match x {
352 Expr::Var(Var(pred)) => Self::is_suppressed_target_family(pred),
353 Expr::Not(Not(not)) => match &**not {
354 Expr::Var(Var(pred)) => Self::is_suppressed_target_family(pred),
355 _ => false,
356 },
357 _ => false,
358 });
359 }
360
361 walk_mut_expr_list(self, all);
362 }
363}
364
365struct MergePattern;
366
367impl MergePattern {
368 fn merge(any_list: &mut [Expr]) {
369 let mut pattern_list = filter_map_collect_vec(any_list, |x| {
370 if let Expr::All(All(all)) = x {
371 if let [first, second] = all.as_mut_slice() {
372 if first.is_any() || first.is_var() {
373 return Some((first, second));
374 }
375 }
376 }
377 None
378 });
379
380 if let [head, rest @ ..] = pattern_list.as_mut_slice() {
381 let agg = match head.0 {
382 Expr::Any(Any(any)) => any,
383 Expr::Var(var) => {
384 *head.0 = expr(any((var.clone(),)));
385 head.0.as_mut_any().map(|x| &mut x.0).unwrap()
386 }
387 _ => panic!(),
388 };
389
390 for x in rest {
391 let to_agg = if x.1 == head.1 {
392 &mut *x.0
393 } else if x.0 == head.1 {
394 &mut *x.1
395 } else {
396 continue;
397 };
398
399 match mem::replace(to_agg, Expr::Const(false)) {
400 Expr::Any(Any(any)) => agg.extend(any),
401 Expr::Var(var) => agg.push(expr(var.clone())),
402 other => *to_agg = other,
403 }
404 }
405
406 if agg.len() == 1 {
407 *head.0 = agg.pop().unwrap();
408 }
409 }
410 }
411}
412
413impl VisitMut<Pred> for MergePattern {
414 fn visit_mut_any(&mut self, Any(any_list): &mut Any<Pred>) {
415 Self::merge(any_list);
416 Self::merge(&mut any_list[1..]);
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use bool_logic::ast::all;
423 use bool_logic::ast::not;
424 use bool_logic::cfg::ast::target_os;
425 use bool_logic::cfg::ast::target_vendor;
426
427 use super::*;
428
429 #[test]
430 fn sort() {
431 let mut expr = expr(all((not(flag("unix")), flag("unix"))));
432 SortByPriority.visit_mut_expr(&mut expr);
433 assert_eq!(expr.to_string(), "all(unix, not(unix))");
434 }
435
436 #[test]
437 fn expand_target_vendor() {
438 let expr = simplified_expr(target_vendor("apple"));
439 assert_eq!(
440 expr.to_string(),
441 concat!(
442 r#"any(target_os = "ios", target_os = "macos", target_os = "tvos", "#,
443 r#"target_os = "visionos", target_os = "watchos")"#,
444 )
445 );
446
447 let expr = simplified_expr(all((target_vendor("apple"), target_os("linux"))));
449 assert_eq!(expr.to_string(), "false");
450 }
451
452 #[test]
453 fn suppress_target_family() {
454 let expr = simplified_expr(all((target_os("linux"), flag("unix"))));
456 assert_eq!(expr.to_string(), r#"target_os = "linux""#);
457
458 let expr = simplified_expr(all((target_vendor("apple"), flag("unix"))));
460 assert_eq!(
461 expr.to_string(),
462 concat!(
463 r#"any(target_os = "ios", target_os = "macos", target_os = "tvos", "#,
464 r#"target_os = "visionos", target_os = "watchos")"#,
465 )
466 );
467
468 let expr = simplified_expr(all((target_vendor("unknown"), flag("unix"))));
470 assert_eq!(expr.to_string(), r#"all(unix, target_vendor = "unknown")"#);
471 }
472
473 #[test]
474 fn imply() {
475 {
476 let mut expr = expr(all((target_os("linux"), not(target_os("emscripten")))));
477 ImplyByKey.visit_mut_expr(&mut expr);
478 assert_eq!(expr.to_string(), r#"all(target_os = "linux", not(false))"#);
479 }
480 {
481 let mut expr = expr(all((
482 any((target_os("ios"), target_os("macos"))), any((target_os("linux"), target_os("android"))), )));
485 ImplyByKey.visit_mut_expr(&mut expr);
486 assert_eq!(
487 expr.to_string(),
488 r#"all(any(target_os = "ios", target_os = "macos"), any(false, false))"#
489 );
490 }
491 }
492}