espresso_logic/expression/
mod.rs

1//! Boolean expression types with operator overloading and parsing support
2//!
3//! This module provides a boolean expression representation that can be constructed
4//! programmatically using operator overloading or parsed from strings. Expressions
5//! can be minimized using the Espresso algorithm by implementing the Cover trait.
6
7use std::collections::BTreeSet;
8use std::fmt;
9use std::ops::{Add, Mul, Not};
10use std::sync::Arc;
11
12mod cover;
13pub use cover::ExprCover;
14
15// Lalrpop-generated parser module (generated in OUT_DIR at build time)
16#[allow(clippy::all)]
17mod parser {
18    #![allow(clippy::all)]
19    #![allow(dead_code)]
20    #![allow(unused_variables)]
21    #![allow(unused_imports)]
22    #![allow(non_snake_case)]
23    #![allow(non_camel_case_types)]
24    #![allow(non_upper_case_globals)]
25    include!(concat!(env!("OUT_DIR"), "/expression/bool_expr.rs"));
26}
27
28/// Inner representation of a boolean expression
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub(crate) enum BoolExprInner {
31    /// A named variable
32    Variable(Arc<str>),
33    /// Logical AND of two expressions
34    And(BoolExpr, BoolExpr),
35    /// Logical OR of two expressions
36    Or(BoolExpr, BoolExpr),
37    /// Logical NOT of an expression
38    Not(BoolExpr),
39    /// A constant value (true or false)
40    Constant(bool),
41}
42
43/// A boolean expression that can be manipulated programmatically
44///
45/// Uses `Arc` internally for efficient cloning. Provides a fluent method-based API
46/// and an `expr!` macro for clean syntax.
47///
48/// # Examples
49///
50/// # Examples
51///
52/// ## Method-based API
53/// ```
54/// use espresso_logic::BoolExpr;
55///
56/// let a = BoolExpr::variable("a");
57/// let b = BoolExpr::variable("b");
58/// let expr = a.and(&b).or(&a.not().and(&b.not()));
59/// ```
60///
61/// ## Using operator overloading (requires explicit &)
62/// ```  
63/// use espresso_logic::BoolExpr;
64///
65/// let a = BoolExpr::variable("a");
66/// let b = BoolExpr::variable("b");
67/// let expr = &a * &b + &(&a).not() * &(&b).not();
68/// ```
69#[derive(Clone, PartialEq, Eq)]
70pub struct BoolExpr {
71    inner: Arc<BoolExprInner>,
72}
73
74impl BoolExpr {
75    /// Create a variable expression with the given name
76    pub fn variable(name: &str) -> Self {
77        BoolExpr {
78            inner: Arc::new(BoolExprInner::Variable(Arc::from(name))),
79        }
80    }
81
82    /// Create a constant expression (true or false)
83    pub fn constant(value: bool) -> Self {
84        BoolExpr {
85            inner: Arc::new(BoolExprInner::Constant(value)),
86        }
87    }
88
89    /// Parse a boolean expression from a string
90    ///
91    /// Supports standard boolean operators:
92    /// - `+` for OR
93    /// - `*` for AND  
94    /// - `~` or `!` for NOT
95    /// - Parentheses for grouping
96    /// - Constants: `0`, `1`, `true`, `false`
97    pub fn parse(input: &str) -> Result<Self, String> {
98        parser::ExprParser::new()
99            .parse(input)
100            .map_err(|e| format!("Parse error: {}", e))
101    }
102
103    /// Collect all variables used in this expression in alphabetical order
104    ///
105    /// Returns a `BTreeSet` which maintains variables in sorted order.
106    /// This ordering is used when converting to covers for minimization.
107    pub fn collect_variables(&self) -> BTreeSet<Arc<str>> {
108        let mut vars = BTreeSet::new();
109        self.collect_variables_impl(&mut vars);
110        vars
111    }
112
113    fn collect_variables_impl(&self, vars: &mut BTreeSet<Arc<str>>) {
114        match self.inner.as_ref() {
115            BoolExprInner::Variable(name) => {
116                vars.insert(Arc::clone(name));
117            }
118            BoolExprInner::And(left, right) | BoolExprInner::Or(left, right) => {
119                left.collect_variables_impl(vars);
120                right.collect_variables_impl(vars);
121            }
122            BoolExprInner::Not(expr) => {
123                expr.collect_variables_impl(vars);
124            }
125            BoolExprInner::Constant(_) => {}
126        }
127    }
128
129    /// Logical AND: create a new expression that is the conjunction of this and another
130    pub fn and(&self, other: &BoolExpr) -> BoolExpr {
131        BoolExpr {
132            inner: Arc::new(BoolExprInner::And(self.clone(), other.clone())),
133        }
134    }
135
136    /// Logical OR: create a new expression that is the disjunction of this and another
137    pub fn or(&self, other: &BoolExpr) -> BoolExpr {
138        BoolExpr {
139            inner: Arc::new(BoolExprInner::Or(self.clone(), other.clone())),
140        }
141    }
142
143    /// Logical NOT: create a new expression that is the negation of this one
144    pub fn not(&self) -> BoolExpr {
145        BoolExpr {
146            inner: Arc::new(BoolExprInner::Not(self.clone())),
147        }
148    }
149
150    /// Get a reference to the inner expression (internal use)
151    pub(crate) fn inner(&self) -> &BoolExprInner {
152        &self.inner
153    }
154
155    /// Minimize this boolean expression using Espresso
156    ///
157    /// This is a convenience method that creates an `ExprCover`, minimizes it,
158    /// and returns the minimized expression.
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// use espresso_logic::{BoolExpr, expr};
164    ///
165    /// # fn main() -> std::io::Result<()> {
166    /// let a = BoolExpr::variable("a");
167    /// let b = BoolExpr::variable("b");
168    /// let c = BoolExpr::variable("c");
169    ///
170    /// // Redundant expression
171    /// let expr = expr!(a * b + a * b * c);
172    ///
173    /// // Minimize it
174    /// let minimized = expr.minimize()?;
175    ///
176    /// // minimized should be simpler (just a * b)
177    /// # Ok(())
178    /// # }
179    /// ```
180    pub fn minimize(self) -> std::io::Result<BoolExpr> {
181        use crate::Cover;
182        let mut cover = ExprCover::from_expr(self);
183        cover.minimize()?;
184        Ok(cover.to_expr())
185    }
186}
187
188/// Macro for building boolean expressions with clean syntax
189///
190/// Automatically inserts borrows so you can write `expr!(a * b + !a * !b)`
191/// instead of `&a * &b + &(!&a) * &(!&b)`.
192///
193/// Supports:
194/// - `*` for AND
195/// - `+` for OR
196/// - `!` for NOT  
197/// - Parentheses for grouping
198///
199/// # Examples
200///
201/// ```
202/// use espresso_logic::{BoolExpr, expr};
203///
204/// let a = BoolExpr::variable("a");
205/// let b = BoolExpr::variable("b");
206///
207/// let xnor = expr!(a * b + !a * !b);  // Clean syntax!
208/// ```
209#[macro_export]
210macro_rules! expr {
211    // Parenthesized expression
212    (( $($inner:tt)* )) => {
213        expr!($($inner)*)
214    };
215
216    // NOT of identifier
217    (! $e:ident) => {
218        (&$e).not()
219    };
220
221    // NOT of parenthesized expression
222    (! ( $($inner:tt)* )) => {
223        expr!($($inner)*).not()
224    };
225
226    // Binary operations - just add & to everything
227    ($a:ident + $b:ident) => {
228        &$a + &$b
229    };
230    ($a:ident * $b:ident) => {
231        &$a * &$b
232    };
233
234    // Complex mixed expressions with +
235    ($a:ident * $b:ident + $c:ident * $d:ident * $e:ident) => {
236        $a.and(&$b).or(&$c.and(&$d).and(&$e))
237    };
238    ($a:ident * $b:ident + $c:ident * $d:ident) => {
239        $a.and(&$b).or(&$c.and(&$d))
240    };
241    ($a:ident * $b:ident + ! $c:ident * $d:ident) => {
242        $a.and(&$b).or(&$c.not().and(&$d))
243    };
244    ($a:ident * $b:ident + $c:ident * ! $d:ident) => {
245        $a.and(&$b).or(&$c.and(&$d.not()))
246    };
247    ($a:ident * $b:ident + ! $c:ident * ! $d:ident) => {
248        $a.and(&$b).or(&$c.not().and(&$d.not()))
249    };
250    ($a:ident * ! $b:ident + ! $c:ident * $d:ident) => {
251        $a.and(&$b.not()).or(&$c.not().and(&$d))
252    };
253    ($a:ident * ! $b:ident + $c:ident * ! $d:ident) => {
254        $a.and(&$b.not()).or(&$c.and(&$d.not()))
255    };
256    ($a:ident * ! $b:ident + ! $c:ident * ! $d:ident) => {
257        $a.and(&$b.not()).or(&$c.not().and(&$d.not()))
258    };
259    (! $a:ident * $b:ident + $c:ident * ! $d:ident) => {
260        $a.not().and(&$b).or(&$c.and(&$d.not()))
261    };
262    (! $a:ident * $b:ident + ! $c:ident * $d:ident) => {
263        $a.not().and(&$b).or(&$c.not().and(&$d))
264    };
265    (! $a:ident * $b:ident + ! $c:ident * ! $d:ident) => {
266        $a.not().and(&$b).or(&$c.not().and(&$d.not()))
267    };
268    (! $a:ident * ! $b:ident + $c:ident * $d:ident) => {
269        $a.not().and(&$b.not()).or(&$c.and(&$d))
270    };
271    (! $a:ident * ! $b:ident + $c:ident * ! $d:ident) => {
272        $a.not().and(&$b.not()).or(&$c.and(&$d.not()))
273    };
274    (! $a:ident * ! $b:ident + ! $c:ident * $d:ident) => {
275        $a.not().and(&$b.not()).or(&$c.not().and(&$d))
276    };
277    (! $a:ident * ! $b:ident + ! $c:ident * ! $d:ident) => {
278        $a.not().and(&$b.not()).or(&$c.not().and(&$d.not()))
279    };
280
281    // AND chains
282    ($a:ident * $b:ident * $c:ident) => {
283        &$a * &$b * &$c
284    };
285
286    // Parenthesized sub-expressions with AND
287    (( $($a:tt)* ) * $b:ident) => {
288        expr!(( $($a)* )).and(&$b)
289    };
290    ($a:ident * ( $($b:tt)* )) => {
291        $a.and(&expr!(( $($b)* )))
292    };
293    (( $($a:tt)* ) * ( $($b:tt)* )) => {
294        expr!(( $($a)* )).and(&expr!(( $($b)* )))
295    };
296    (! ( $($a:tt)* ) * $b:ident) => {
297        expr!(! ( $($a)* )).and(&$b)
298    };
299    ($a:ident * ! ( $($b:tt)* )) => {
300        $a.and(&expr!(! ( $($b)* )))
301    };
302    (( $($a:tt)* ) * ! $b:ident) => {
303        expr!(( $($a)* )).and(&$b.not())
304    };
305    (! $a:ident * ( $($b:tt)* )) => {
306        $a.not().and(&expr!(( $($b)* )))
307    };
308
309    // Parenthesized sub-expressions with OR
310    (( $($a:tt)* ) + $b:ident) => {
311        expr!(( $($a)* )).or(&$b)
312    };
313    ($a:ident + ( $($b:tt)* )) => {
314        $a.or(&expr!(( $($b)* )))
315    };
316    (( $($a:tt)* ) + ( $($b:tt)* )) => {
317        expr!(( $($a)* )).or(&expr!(( $($b)* )))
318    };
319    (! ( $($a:tt)* ) + $b:ident) => {
320        expr!(! ( $($a)* )).or(&$b)
321    };
322    ($a:ident + ! ( $($b:tt)* )) => {
323        $a.or(&expr!(! ( $($b)* )))
324    };
325    (( $($a:tt)* ) + ! $b:ident) => {
326        expr!(( $($a)* )).or(&$b.not())
327    };
328    (! $a:ident + ( $($b:tt)* )) => {
329        $a.not().or(&expr!(( $($b)* )))
330    };
331
332    // Fallback - simple identifier
333    ($e:ident) => {
334        $e
335    };
336}
337
338impl fmt::Debug for BoolExpr {
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340        match self.inner.as_ref() {
341            BoolExprInner::Variable(name) => write!(f, "{}", name),
342            BoolExprInner::And(left, right) => write!(f, "({:?} * {:?})", left, right),
343            BoolExprInner::Or(left, right) => write!(f, "({:?} + {:?})", left, right),
344            BoolExprInner::Not(expr) => write!(f, "~{:?}", expr),
345            BoolExprInner::Constant(val) => write!(f, "{}", if *val { "1" } else { "0" }),
346        }
347    }
348}
349
350impl fmt::Display for BoolExpr {
351    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352        write!(f, "{:?}", self)
353    }
354}
355
356// Operator overloading
357// Implemented for both owned and borrowed types
358// The expr! macro wraps expressions to enable clean `a * b + !a * !b` syntax
359
360/// Logical AND operator for references: `&a * &b`
361impl Mul for &BoolExpr {
362    type Output = BoolExpr;
363
364    fn mul(self, rhs: &BoolExpr) -> BoolExpr {
365        self.and(rhs)
366    }
367}
368
369/// Logical AND operator: `a * b` (delegates to reference version)
370impl Mul for BoolExpr {
371    type Output = BoolExpr;
372
373    fn mul(self, rhs: BoolExpr) -> BoolExpr {
374        self.and(&rhs)
375    }
376}
377
378/// Logical OR operator for references: `&a + &b`
379impl Add for &BoolExpr {
380    type Output = BoolExpr;
381
382    fn add(self, rhs: &BoolExpr) -> BoolExpr {
383        self.or(rhs)
384    }
385}
386
387/// Logical OR operator: `a + b` (delegates to reference version)
388impl Add for BoolExpr {
389    type Output = BoolExpr;
390
391    fn add(self, rhs: BoolExpr) -> BoolExpr {
392        self.or(&rhs)
393    }
394}
395
396/// Logical NOT operator for references: `!&a`
397impl Not for &BoolExpr {
398    type Output = BoolExpr;
399
400    fn not(self) -> BoolExpr {
401        BoolExpr::not(self)
402    }
403}
404
405/// Logical NOT operator: `!a` (delegates to reference version)
406impl Not for BoolExpr {
407    type Output = BoolExpr;
408
409    fn not(self) -> BoolExpr {
410        BoolExpr::not(&self)
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417
418    #[test]
419    fn test_variable_creation() {
420        let a = BoolExpr::variable("a");
421        let b = BoolExpr::variable("b");
422        let a2 = BoolExpr::variable("a");
423
424        // Variables are compared by structure
425        assert_eq!(a, a2);
426        assert_ne!(a, b);
427    }
428
429    #[test]
430    fn test_constant_creation() {
431        let t = BoolExpr::constant(true);
432        let f = BoolExpr::constant(false);
433
434        assert_eq!(t, BoolExpr::constant(true));
435        assert_ne!(t, f);
436    }
437
438    #[test]
439    fn test_method_api() {
440        let a = BoolExpr::variable("a");
441        let b = BoolExpr::variable("b");
442
443        // Test AND method - no clones in user code!
444        let and_expr = a.and(&b);
445        match and_expr.inner() {
446            BoolExprInner::And(_, _) => {}
447            _ => panic!("Expected And expression"),
448        }
449
450        // Test OR method - can still use a and b
451        let or_expr = a.or(&b);
452        match or_expr.inner() {
453            BoolExprInner::Or(_, _) => {}
454            _ => panic!("Expected Or expression"),
455        }
456
457        // Test NOT method
458        let not_expr = a.not();
459        match not_expr.inner() {
460            BoolExprInner::Not(_) => {}
461            _ => panic!("Expected Not expression"),
462        }
463    }
464
465    #[test]
466    fn test_complex_expression() {
467        let a = BoolExpr::variable("a");
468        let b = BoolExpr::variable("b");
469        let c = BoolExpr::variable("c");
470
471        // Build complex expression: (a AND b) OR (NOT a AND c)
472        let expr = a.and(&b).or(&a.not().and(&c));
473
474        match expr.inner() {
475            BoolExprInner::Or(_, _) => {}
476            _ => panic!("Expected Or at top level"),
477        }
478    }
479
480    #[test]
481    fn test_collect_variables() {
482        let a = BoolExpr::variable("a");
483        let b = BoolExpr::variable("b");
484        let c = BoolExpr::variable("c");
485
486        // Using method API
487        let expr = a.and(&b).or(&c);
488        let vars = expr.collect_variables();
489
490        assert_eq!(vars.len(), 3);
491        let var_names: Vec<String> = vars.iter().map(|s| s.to_string()).collect();
492        assert_eq!(var_names, vec!["a", "b", "c"]); // Should be alphabetical
493    }
494}