1 //! Disjunctive Normal Form construction.
3 //! Algorithm from <https://www.cs.drexel.edu/~jjohnson/2015-16/fall/CS270/Lectures/3/dnf.pdf>,
4 //! which would have been much easier to read if it used pattern matching. It's also missing the
5 //! entire "distribute ANDs over ORs" part, which is not trivial. Oh well.
7 //! This is currently both messy and inefficient. Feel free to improve, there are unit tests.
11 use rustc_hash::FxHashSet;
13 use crate::{CfgAtom, CfgDiff, CfgExpr, CfgOptions, InactiveReason};
15 /// A `#[cfg]` directive in Disjunctive Normal Form (DNF).
17 conjunctions: Vec<Conjunction>,
21 literals: Vec<Literal>,
26 var: Option<CfgAtom>, // None = Invalid
30 pub fn new(expr: CfgExpr) -> Self {
31 let builder = Builder { expr: DnfExpr { conjunctions: Vec::new() } };
36 /// Computes a list of present or absent atoms in `opts` that cause this expression to evaluate
39 /// Note that flipping a subset of these atoms might be sufficient to make the whole expression
40 /// evaluate to `true`. For that, see `compute_enable_hints`.
42 /// Returns `None` when `self` is already true, or contains errors.
43 pub fn why_inactive(&self, opts: &CfgOptions) -> Option<InactiveReason> {
44 let mut res = InactiveReason { enabled: Vec::new(), disabled: Vec::new() };
46 for conj in &self.conjunctions {
47 let mut conj_is_true = true;
48 for lit in &conj.literals {
49 let atom = lit.var.as_ref()?;
50 let enabled = opts.enabled.contains(atom);
51 if lit.negate == enabled {
52 // Literal is false, but needs to be true for this conjunction.
56 res.enabled.push(atom.clone());
58 res.disabled.push(atom.clone());
64 // This expression is not actually inactive.
69 res.enabled.sort_unstable();
71 res.disabled.sort_unstable();
76 /// Returns `CfgDiff` objects that would enable this directive if applied to `opts`.
77 pub fn compute_enable_hints<'a>(
80 ) -> impl Iterator<Item = CfgDiff> + 'a {
81 // A cfg is enabled if any of `self.conjunctions` evaluate to `true`.
83 self.conjunctions.iter().filter_map(move |conj| {
84 let mut enable = FxHashSet::default();
85 let mut disable = FxHashSet::default();
86 for lit in &conj.literals {
87 let atom = lit.var.as_ref()?;
88 let enabled = opts.enabled.contains(atom);
89 if lit.negate && enabled {
90 disable.insert(atom.clone());
92 if !lit.negate && !enabled {
93 enable.insert(atom.clone());
97 // Check that this actually makes `conj` true.
98 for lit in &conj.literals {
99 let atom = lit.var.as_ref()?;
100 let enabled = enable.contains(atom)
101 || (opts.enabled.contains(atom) && !disable.contains(atom));
102 if enabled == lit.negate {
107 if enable.is_empty() && disable.is_empty() {
111 let mut diff = CfgDiff {
112 enable: enable.into_iter().collect(),
113 disable: disable.into_iter().collect(),
116 // Undo the FxHashMap randomization for consistent output.
117 diff.enable.sort_unstable();
118 diff.disable.sort_unstable();
125 impl fmt::Display for DnfExpr {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 if self.conjunctions.len() != 1 {
130 for (i, conj) in self.conjunctions.iter().enumerate() {
135 write!(f, "{}", conj)?;
137 if self.conjunctions.len() != 1 {
146 fn new(parts: Vec<CfgExpr>) -> Self {
147 let mut literals = Vec::new();
150 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
151 literals.push(Literal::new(part));
153 CfgExpr::All(conj) => {
155 literals.extend(Conjunction::new(conj).literals);
157 CfgExpr::Any(_) => unreachable!("disjunction in conjunction"),
165 impl fmt::Display for Conjunction {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 if self.literals.len() != 1 {
170 for (i, lit) in self.literals.iter().enumerate() {
175 write!(f, "{}", lit)?;
177 if self.literals.len() != 1 {
186 fn new(expr: CfgExpr) -> Self {
188 CfgExpr::Invalid => Self { negate: false, var: None },
189 CfgExpr::Atom(atom) => Self { negate: false, var: Some(atom) },
190 CfgExpr::Not(expr) => match *expr {
191 CfgExpr::Invalid => Self { negate: true, var: None },
192 CfgExpr::Atom(atom) => Self { negate: true, var: Some(atom) },
193 _ => unreachable!("non-atom {:?}", expr),
195 CfgExpr::Any(_) | CfgExpr::All(_) => unreachable!("non-literal {:?}", expr),
200 impl fmt::Display for Literal {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 Some(var) => write!(f, "{}", var)?,
208 None => f.write_str("<invalid>")?,
224 fn lower(mut self, expr: CfgExpr) -> DnfExpr {
225 let expr = make_nnf(expr);
226 let expr = make_dnf(expr);
229 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => {
230 self.expr.conjunctions.push(Conjunction::new(vec![expr]));
232 CfgExpr::All(conj) => {
233 self.expr.conjunctions.push(Conjunction::new(conj));
235 CfgExpr::Any(mut disj) => {
237 while let Some(conj) = disj.pop() {
239 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::All(_) | CfgExpr::Not(_) => {
240 self.expr.conjunctions.push(Conjunction::new(vec![conj]));
242 CfgExpr::Any(inner_disj) => {
244 disj.extend(inner_disj.into_iter().rev());
255 fn make_dnf(expr: CfgExpr) -> CfgExpr {
257 CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => expr,
258 CfgExpr::Any(e) => flatten(CfgExpr::Any(e.into_iter().map(make_dnf).collect())),
260 let e = e.into_iter().map(make_dnf).collect::<Vec<_>>();
262 flatten(CfgExpr::Any(distribute_conj(&e)))
267 /// Turns a conjunction of expressions into a disjunction of expressions.
268 fn distribute_conj(conj: &[CfgExpr]) -> Vec<CfgExpr> {
269 fn go(out: &mut Vec<CfgExpr>, with: &mut Vec<CfgExpr>, rest: &[CfgExpr]) {
271 [head, tail @ ..] => match head {
272 CfgExpr::Any(disj) => {
274 with.push(part.clone());
280 with.push(head.clone());
286 // Turn accumulated parts into a new conjunction.
287 out.push(CfgExpr::All(with.clone()));
292 let mut out = Vec::new(); // contains only `all()`
293 let mut with = Vec::new();
295 go(&mut out, &mut with, conj);
300 fn make_nnf(expr: CfgExpr) -> CfgExpr {
302 CfgExpr::Invalid | CfgExpr::Atom(_) => expr,
303 CfgExpr::Any(expr) => CfgExpr::Any(expr.into_iter().map(make_nnf).collect()),
304 CfgExpr::All(expr) => CfgExpr::All(expr.into_iter().map(make_nnf).collect()),
305 CfgExpr::Not(operand) => match *operand {
306 CfgExpr::Invalid | CfgExpr::Atom(_) => CfgExpr::Not(operand.clone()), // Original negated expr
307 CfgExpr::Not(expr) => {
308 // Remove double negation.
311 // Convert negated conjunction/disjunction using DeMorgan's Law.
312 CfgExpr::Any(inner) => CfgExpr::All(
313 inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
315 CfgExpr::All(inner) => CfgExpr::Any(
316 inner.into_iter().map(|expr| make_nnf(CfgExpr::Not(Box::new(expr)))).collect(),
322 /// Collapses nested `any()` and `all()` predicates.
323 fn flatten(expr: CfgExpr) -> CfgExpr {
325 CfgExpr::All(inner) => CfgExpr::All(
328 .flat_map(|e| match e {
329 CfgExpr::All(inner) => inner,
334 CfgExpr::Any(inner) => CfgExpr::Any(
337 .flat_map(|e| match e {
338 CfgExpr::Any(inner) => inner,