libprop_sat_solver/formula/propositional_formula.rs
1//! A propositional formula.
2
3use std::convert::{From, Into};
4
5use super::Variable;
6
7/// A propositional formula is defined inductively, conforming to the following BNF:
8///
9/// ```ebnf
10/// <formula>
11/// ::= <propositional-variable>
12/// | ( - <formula> )
13/// | ( <formula> ^ <formula>)
14/// | ( <formula> | <formula> )
15/// | ( <formula> -> <formula> )
16/// | ( <formula> <-> <formula> )
17/// ```
18///
19/// Notice the requirement for explicit parentheses around the unary and binary operators, which
20/// eliminates the requirement for operator precedence due to grammar ambiguity at the cost of being
21/// more verbose.
22///
23/// # Ownership, Interior Mutability and Optional Sub-formulas
24///
25/// Since we don't need any fancy multiple-threading or multi-owner business, we'll stick with the
26/// most trivial `Box` pointer indirection instead of the fancier alternatives:
27///
28/// - Reference-counted smart pointer `Rc`
29/// - Atomically-reference-counted smart pointer `Arc`
30/// - Lifetime-bounded references `&'a`
31///
32/// We do not support interior mutability as we do not need it for our use cases with respect to the
33/// propositional formula AST.
34///
35/// We need sub-formulas to be wrapped in `Option` for construction purposes, so we can build a
36/// `PropPropositionalFormula` during parsing.
37///
38/// # No Default
39///
40/// We cannot soundly define a sane default for a `PropositionalFormula` – even in the base case of
41/// a single propositional variable, what would the default propositional variable be?
42#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
43pub enum PropositionalFormula {
44 /// Base case: a single propositional variable.
45 Variable(Variable),
46 /// Unary case: negated formula.
47 Negation(Option<Box<PropositionalFormula>>),
48 /// Binary formula with the main connective being the logical AND connective.
49 Conjunction(
50 Option<Box<PropositionalFormula>>,
51 Option<Box<PropositionalFormula>>,
52 ),
53 /// Binary formula with the main connective being the logical OR operator.
54 Disjunction(
55 Option<Box<PropositionalFormula>>,
56 Option<Box<PropositionalFormula>>,
57 ),
58 /// Binary formula with the main connective being the implication operator.
59 Implication(
60 Option<Box<PropositionalFormula>>,
61 Option<Box<PropositionalFormula>>,
62 ),
63 /// Binary formula with the main connective being the biimplication operator.
64 Biimplication(
65 Option<Box<PropositionalFormula>>,
66 Option<Box<PropositionalFormula>>,
67 ),
68}
69
70// Convenience methods for constructing a `PropositionalFormula`.
71//
72// Inspired by the blog post at https://endler.dev/2017/boxes-and-trees/.
73impl PropositionalFormula {
74 /// Construct a new propositional formula from a propositional `Variable`.
75 ///
76 /// # Example
77 ///
78 /// ```
79 /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
80 /// let formula = PropositionalFormula::variable(Variable::new("a"));
81 /// println!("{:#?}", formula);
82 /// ```
83 #[inline]
84 pub fn variable(v: Variable) -> Self {
85 Self::Variable(v)
86 }
87
88 /// Construct a new propositional formula from a sub propositional formula with negation.
89 ///
90 /// # Example
91 ///
92 /// ```
93 /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
94 /// let sub_formula = PropositionalFormula::variable(Variable::new("a"));
95 /// let formula = PropositionalFormula::negated(Box::new(sub_formula.clone()));
96 /// println!("{:#?}", formula);
97 /// ```
98 #[inline]
99 pub fn negated(formula: Box<PropositionalFormula>) -> Self {
100 Self::Negation(Some(formula))
101 }
102
103 /// Construct a new propositional formula from two propositional sub-formulas with a conjunction
104 /// main connective.
105 ///
106 /// # Example
107 ///
108 /// ```
109 /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
110 /// let sub_formula = PropositionalFormula::variable(Variable::new("a"));
111 /// let formula = PropositionalFormula::conjunction(Box::new(sub_formula.clone()), Box::new(sub_formula.clone()));
112 /// println!("{:#?}", formula);
113 /// ```
114 #[inline]
115 pub fn conjunction(
116 left_sub_formula: Box<PropositionalFormula>,
117 right_sub_formula: Box<PropositionalFormula>,
118 ) -> Self {
119 Self::Conjunction(Some(left_sub_formula), Some(right_sub_formula))
120 }
121
122 /// Construct a new propositional formula from two propositional sub-formulas with a disjunction
123 /// main connective.
124 ///
125 /// # Example
126 ///
127 /// ```
128 /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
129 /// let sub_formula = PropositionalFormula::variable(Variable::new("a"));
130 /// let formula = PropositionalFormula::disjunction(Box::new(sub_formula.clone()), Box::new(sub_formula.clone()));
131 /// println!("{:#?}", formula);
132 /// ```
133 #[inline]
134 pub fn disjunction(
135 left_sub_formula: Box<PropositionalFormula>,
136 right_sub_formula: Box<PropositionalFormula>,
137 ) -> Self {
138 Self::Disjunction(Some(left_sub_formula), Some(right_sub_formula))
139 }
140
141 /// Construct a new propositional formula from two propositional sub-formulas with an
142 /// implication main connective.
143 ///
144 /// # Example
145 ///
146 /// ```
147 /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
148 /// let sub_formula = PropositionalFormula::variable(Variable::new("a"));
149 /// let formula = PropositionalFormula::implication(Box::new(sub_formula.clone()), Box::new(sub_formula.clone()));
150 /// println!("{:#?}", formula);
151 /// ```
152 #[inline]
153 pub fn implication(
154 left_sub_formula: Box<PropositionalFormula>,
155 right_sub_formula: Box<PropositionalFormula>,
156 ) -> Self {
157 Self::Implication(Some(left_sub_formula), Some(right_sub_formula))
158 }
159
160 /// Construct a new propositional formula from two propositional sub-formulas with a
161 /// biimplication main connective.
162 ///
163 /// # Example
164 ///
165 /// ```
166 /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
167 /// let sub_formula = PropositionalFormula::variable(Variable::new("a"));
168 /// let formula = PropositionalFormula::biimplication(Box::new(sub_formula.clone()), Box::new(sub_formula.clone()));
169 /// println!("{:#?}", formula);
170 /// ```
171 #[inline]
172 pub fn biimplication(
173 left_sub_formula: Box<PropositionalFormula>,
174 right_sub_formula: Box<PropositionalFormula>,
175 ) -> Self {
176 Self::Biimplication(Some(left_sub_formula), Some(right_sub_formula))
177 }
178
179 /// Checks if the given `PropositionalFormula` is a literal (either a propositional variable
180 /// like `p` or its negation `-p`).
181 pub fn is_literal(&self) -> bool {
182 match self {
183 // A propositional variable `p` is trivially a literal.
184 Self::Variable(_) => true,
185 // The negation of a propositional variable `(-p)` is also trivially a literal.
186 Self::Negation(Some(ref inner_formula)) if inner_formula.is_literal() => true,
187 // Any other complex propositional formula is not a literal.
188 _ => false,
189 }
190 }
191}
192
193impl<V> From<V> for PropositionalFormula
194where
195 V: Into<Variable>,
196{
197 fn from(v: V) -> Self {
198 Self::Variable(v.into())
199 }
200}