espresso_logic/expression/mod.rs
1//! Boolean expression types with operator overloading and parsing support
2//!
3//! This module provides a high-level boolean expression representation that can be constructed
4//! programmatically using operator overloading, the `expr!` macro, or parsed from strings.
5//! Expressions can be minimised directly using the Espresso algorithm.
6//!
7//! # Main Types
8//!
9//! - [`BoolExpr`] - A boolean expression supporting three construction methods:
10//! 1. **`expr!` macro**: `expr!(a * b + c)` - Recommended!
11//! 2. Method API: `a.and(&b).or(&c)`
12//! 3. Operator overloading: `&a * &b + &c`
13//!
14//! Expressions can be minimised directly with `.minimize()` or `.minimize_exact()`.
15//!
16//! # Implementation Details
17//!
18//! Starting in v3.1.1, `BoolExpr` uses Binary Decision Diagrams (BDDs) internally for
19//! efficient representation and operations. This is an implementation detail that enables:
20//!
21//! - **Efficient operations**: Polynomial-time AND/OR/NOT operations
22//! - **Canonical form**: Equivalent expressions have identical internal structure
23//! - **Memory efficiency**: Structural sharing via global singleton manager
24//! - **Automatic simplification**: Redundancy elimination during construction
25//!
26//! The BDD implementation makes the high-level API practical for complex expressions,
27//! but the primary purpose remains **Boolean function minimisation via Espresso**.
28//!
29//! **Deprecated methods:**
30//! - `to_bdd()` - Returns `self.clone()` (expression already uses BDD internally)
31//! - `Bdd::from_expr()` - Returns `expr.clone()` (redundant conversion)
32//! - `Bdd::to_expr()` - Returns `self.clone()` (redundant conversion)
33//!
34//! # Quick Start
35//!
36//! ## Using the `expr!` Macro (Recommended)
37//!
38//! The `expr!` macro provides the cleanest syntax with three usage styles:
39//!
40//! ```
41//! use espresso_logic::{BoolExpr, expr};
42//!
43//! // Style 1: String literals (most concise - no variable declarations!)
44//! let xor = expr!("a" * !"b" + !"a" * "b");
45//! println!("{}", xor); // Output: a * ~b + ~a * b
46//!
47//! // Style 2: Existing BoolExpr variables
48//! let a = BoolExpr::variable("a");
49//! let b = BoolExpr::variable("b");
50//! let expr = expr!(a * b + !a * !b);
51//!
52//! // Style 3: Mix both
53//! let result = expr!(a * "temp" + b);
54//! ```
55//!
56//! ## Parsing from Strings
57//!
58//! ```
59//! use espresso_logic::BoolExpr;
60//!
61//! # fn main() -> std::io::Result<()> {
62//! let expr = BoolExpr::parse("a * b + ~a * ~b")?;
63//! let complex = BoolExpr::parse("(a + b) * (c + d)")?;
64//! println!("{}", expr); // Minimal parentheses: a * b + ~a * ~b
65//! # Ok(())
66//! # }
67//! ```
68//!
69//! ## Minimising and Evaluating
70//!
71//! ```
72//! use espresso_logic::{BoolExpr, expr, Minimizable};
73//! use std::collections::HashMap;
74//! use std::sync::Arc;
75//!
76//! # fn main() -> std::io::Result<()> {
77//! let a = BoolExpr::variable("a");
78//! let b = BoolExpr::variable("b");
79//! let c = BoolExpr::variable("c");
80//!
81//! // Redundant expression
82//! let redundant = expr!(a * b + a * b * c);
83//!
84//! // Evaluate with specific values
85//! let mut assignment = HashMap::new();
86//! assignment.insert(Arc::from("a"), true);
87//! assignment.insert(Arc::from("b"), true);
88//! assignment.insert(Arc::from("c"), false);
89//! let result = redundant.evaluate(&assignment);
90//! assert_eq!(result, true);
91//!
92//! // Minimise it (returns new minimised instance)
93//! let minimised = redundant.minimize()?;
94//! println!("Minimised: {}", minimised); // Output: a * b
95//!
96//! // Check logical equivalence
97//! let redundant2 = expr!(a * b + a * b * c);
98//! assert!(redundant2.equivalent_to(&minimised));
99//! # Ok(())
100//! # }
101//! ```
102//!
103//! # 📚 Comprehensive Guide
104//!
105//! For a complete guide to the Boolean expression API including detailed examples,
106//! composition patterns, BDD architecture, performance considerations, and best practices,
107//! see the embedded documentation below.
108#![doc = include_str!("../../docs/BOOLEAN_EXPRESSIONS.md")]
109
110// Submodules
111mod ast;
112mod bdd;
113mod conversions;
114mod display;
115pub mod error;
116mod eval;
117pub(crate) mod factorization;
118mod manager;
119mod operators;
120mod parser;
121
122pub use error::{ExpressionParseError, ParseBoolExprError};
123
124// Re-export AST types
125pub(crate) use ast::BoolExprAst;
126pub use ast::ExprNode;
127
128// Re-export manager types for internal use
129use manager::{BddManager, NodeId, FALSE_NODE, TRUE_NODE};
130
131use std::sync::{Arc, OnceLock, RwLock};
132
133/// A boolean expression for logic minimisation
134///
135/// `BoolExpr` provides a high-level interface for building and minimising Boolean functions.
136/// Expressions can be constructed programmatically, parsed from strings, or composed from
137/// existing expressions, then minimised using the Espresso algorithm.
138///
139/// # Construction Methods
140///
141/// Three ways to build expressions:
142///
143/// 1. **`expr!` macro** (recommended) - Clean syntax without explicit references
144/// 2. **Method API** - Explicit `.and()`, `.or()`, `.not()` calls
145/// 3. **Operator overloading** - Requires `&` references
146///
147/// # Minimisation
148///
149/// Expressions support direct minimisation via:
150/// - `.minimize()` - Fast heuristic algorithm (~99% optimal)
151/// - `.minimize_exact()` - Slower but guaranteed minimal result
152///
153/// # Implementation Details
154///
155/// **Internal representation:** Uses Binary Decision Diagrams (BDDs) for efficient
156/// operations and canonical form. This is an implementation detail that makes the
157/// high-level API practical for complex expressions.
158///
159/// Each `BoolExpr` contains:
160/// - **Node ID** - Reference to BDD structure in global manager
161/// - **Manager Reference** - Shared access to global singleton BDD manager
162/// - **DNF Cache** - Lazily cached cubes for Espresso minimisation
163/// - **AST Cache** - Lazily cached syntax tree for display
164///
165/// The BDD manager is a global singleton (protected by `RwLock`) shared by all
166/// expressions, providing structural sharing and canonical representation.
167///
168/// # Cloning
169///
170/// Cloning is very cheap - copies Arc references and node ID. `OnceLock::clone()` copies
171/// the cached content (Arc pointers), so clones share the actual cached data. The BDD
172/// structure itself is shared via the global manager.
173///
174/// # Thread Safety
175///
176/// Thread-safe via global BDD manager with `RwLock` protection. Multiple threads
177/// can safely create and manipulate expressions concurrently.
178///
179/// # Examples
180///
181/// ## Method-based API
182/// ```
183/// use espresso_logic::BoolExpr;
184///
185/// let a = BoolExpr::variable("a");
186/// let b = BoolExpr::variable("b");
187/// let expr = a.and(&b).or(&a.not().and(&b.not()));
188/// println!("{}", expr); // Uses factored display
189/// ```
190///
191/// ## Using `expr!` macro (recommended)
192/// ```
193/// use espresso_logic::{BoolExpr, expr};
194///
195/// let a = BoolExpr::variable("a");
196/// let b = BoolExpr::variable("b");
197/// // No & references needed!
198/// let expr = expr!(a * b + !a * !b);
199/// println!("{}", expr);
200/// ```
201///
202/// ## BDD Operations
203/// ```
204/// use espresso_logic::BoolExpr;
205///
206/// let expr = BoolExpr::parse("a * b + b * c").unwrap();
207///
208/// // Query BDD properties
209/// println!("BDD nodes: {}", expr.node_count());
210/// println!("Variables: {}", expr.var_count());
211///
212/// // All operations are efficient BDD operations
213/// let vars = expr.collect_variables();
214/// println!("Variables: {:?}", vars);
215/// ```
216#[derive(Clone)]
217pub struct BoolExpr {
218 /// BDD manager (shared across all BoolExprs)
219 manager: Arc<RwLock<BddManager>>,
220 /// Root node ID in the BDD
221 root: NodeId,
222 /// Cached DNF (cubes) for this BDD
223 /// This avoids expensive BDD traversal when converting to DNF
224 /// OnceLock's Clone copies the content, so clones share cached data via Arc
225 dnf_cache: OnceLock<crate::cover::Dnf>,
226 /// Cached AST representation (reconstructed lazily when needed for display/fold)
227 /// OnceLock's Clone copies the content, so clones share cached data via Arc
228 pub(crate) ast_cache: OnceLock<Arc<BoolExprAst>>,
229}
230
231impl BoolExpr {
232 /// Create a variable expression with the given name
233 pub fn variable(name: &str) -> Self {
234 let manager = BddManager::get_or_create();
235 let mut mgr = manager.write().unwrap();
236 let var_id = mgr.get_or_create_var(name);
237 let node = mgr.make_node(var_id, FALSE_NODE, TRUE_NODE);
238 drop(mgr); // Explicitly release the lock
239 BoolExpr {
240 manager,
241 root: node,
242 dnf_cache: OnceLock::new(),
243 ast_cache: OnceLock::new(),
244 }
245 }
246
247 /// Create a constant expression (true or false)
248 pub fn constant(value: bool) -> Self {
249 let manager = BddManager::get_or_create();
250 BoolExpr {
251 manager,
252 root: if value { TRUE_NODE } else { FALSE_NODE },
253 dnf_cache: OnceLock::new(),
254 ast_cache: OnceLock::new(),
255 }
256 }
257
258 /// Convert this boolean expression to a Binary Decision Diagram
259 ///
260 /// **Deprecated:** `BoolExpr` is now implemented as a BDD internally.
261 /// This method simply clones the expression and exists for backwards compatibility.
262 ///
263 /// # Examples
264 ///
265 /// ```
266 /// use espresso_logic::BoolExpr;
267 ///
268 /// let a = BoolExpr::variable("a");
269 /// let b = BoolExpr::variable("b");
270 /// let expr = a.and(&b);
271 ///
272 /// let bdd = expr.to_bdd(); // Just returns a clone
273 /// ```
274 #[deprecated(
275 since = "3.1.1",
276 note = "BoolExpr is now a BDD internally. Use clone() instead."
277 )]
278 pub fn to_bdd(&self) -> Self {
279 self.clone()
280 }
281
282 /// Create a BoolExpr from a BDD (actually the same type)
283 ///
284 /// **Deprecated:** `BoolExpr` and `Bdd` are now the same type.
285 /// This method simply clones the expression and exists for backwards compatibility.
286 #[deprecated(
287 since = "3.1.1",
288 note = "BoolExpr and Bdd are now the same type. Use clone() instead."
289 )]
290 pub fn from_expr(expr: &BoolExpr) -> Self {
291 expr.clone()
292 }
293
294 /// Convert this BDD to a BoolExpr (actually the same type)
295 ///
296 /// **Deprecated:** `BoolExpr` and `Bdd` are now the same type.
297 /// This method simply clones the expression and exists for backwards compatibility.
298 #[deprecated(
299 since = "3.1.1",
300 note = "BoolExpr and Bdd are now the same type. Use clone() instead."
301 )]
302 pub fn to_expr(&self) -> Self {
303 self.clone()
304 }
305
306 /// Get or create the DNF representation with local caching
307 ///
308 /// Extracts cubes from BDD on first access and caches for subsequent calls.
309 fn get_or_create_dnf(&self) -> crate::cover::Dnf {
310 // Check local cache
311 if let Some(dnf) = self.dnf_cache.get() {
312 return dnf.clone(); // Cheap Arc clone
313 }
314
315 // Not cached - extract from BDD
316 let cubes = self.extract_cubes_from_bdd();
317 let dnf = crate::cover::Dnf::from_cubes(&cubes);
318
319 // Cache it locally
320 let _ = self.dnf_cache.set(dnf.clone());
321
322 dnf
323 }
324}
325
326/// PartialEq implementation that compares BDDs for canonical equality
327///
328/// Since BDDs are canonical, two BoolExprs are equal if and only if
329/// they represent the same logical function.
330impl PartialEq for BoolExpr {
331 fn eq(&self, other: &Self) -> bool {
332 // BDDs are equal if they share the same manager and have the same root node
333 // The singleton manager ensures consistent representation across all BoolExprs
334 Arc::ptr_eq(&self.manager, &other.manager) && self.root == other.root
335 }
336}
337
338impl Eq for BoolExpr {}
339
340/// Type alias for backwards compatibility.
341///
342/// **Deprecated:** Use [`BoolExpr`] directly instead.
343///
344/// `Bdd` and `BoolExpr` are now the same type in the unified architecture (v3.1.1+).
345/// This alias exists for backwards compatibility but should not be used in new code.
346///
347/// # Migration
348///
349/// Old code using `Bdd`:
350/// ```
351/// use espresso_logic::Bdd;
352///
353/// let a = Bdd::variable("a");
354/// let b = Bdd::variable("b");
355/// let result = a.and(&b);
356/// ```
357///
358/// Should be updated to use [`BoolExpr`] directly:
359/// ```
360/// use espresso_logic::BoolExpr;
361///
362/// let a = BoolExpr::variable("a");
363/// let b = BoolExpr::variable("b");
364/// let result = a.and(&b);
365/// ```
366#[deprecated(
367 since = "3.1.1",
368 note = "Use `BoolExpr` directly. `Bdd` is now just a type alias for backwards compatibility."
369)]
370pub type Bdd = BoolExpr;
371
372#[cfg(test)]
373mod tests;