1#[derive(Clone, Debug, PartialEq, Eq, Hash)]
11pub enum Formula {
12 Atom(String),
15 NegAtom(String),
17
18 Tensor(Box<Formula>, Box<Formula>),
21 Par(Box<Formula>, Box<Formula>),
23 One,
25 Bottom,
27
28 With(Box<Formula>, Box<Formula>),
31 Plus(Box<Formula>, Box<Formula>),
33 Top,
35 Zero,
37
38 OfCourse(Box<Formula>),
41 WhyNot(Box<Formula>),
43
44 Lolli(Box<Formula>, Box<Formula>),
47}
48
49impl Formula {
50 pub fn negate(&self) -> Formula {
64 match self {
65 Formula::Atom(a) => Formula::NegAtom(a.clone()),
66 Formula::NegAtom(a) => Formula::Atom(a.clone()),
67
68 Formula::Tensor(a, b) => {
69 Formula::Par(Box::new(a.negate()), Box::new(b.negate()))
70 }
71 Formula::Par(a, b) => {
72 Formula::Tensor(Box::new(a.negate()), Box::new(b.negate()))
73 }
74 Formula::One => Formula::Bottom,
75 Formula::Bottom => Formula::One,
76
77 Formula::With(a, b) => {
78 Formula::Plus(Box::new(a.negate()), Box::new(b.negate()))
79 }
80 Formula::Plus(a, b) => {
81 Formula::With(Box::new(a.negate()), Box::new(b.negate()))
82 }
83 Formula::Top => Formula::Zero,
84 Formula::Zero => Formula::Top,
85
86 Formula::OfCourse(a) => Formula::WhyNot(Box::new(a.negate())),
87 Formula::WhyNot(a) => Formula::OfCourse(Box::new(a.negate())),
88
89 Formula::Lolli(a, b) => {
90 Formula::Tensor(a.clone(), Box::new(b.negate()))
92 }
93 }
94 }
95
96 pub fn desugar(&self) -> Formula {
98 match self {
99 Formula::Lolli(a, b) => {
100 Formula::Par(Box::new(a.negate().desugar()), Box::new(b.desugar()))
101 }
102 Formula::Tensor(a, b) => {
103 Formula::Tensor(Box::new(a.desugar()), Box::new(b.desugar()))
104 }
105 Formula::Par(a, b) => {
106 Formula::Par(Box::new(a.desugar()), Box::new(b.desugar()))
107 }
108 Formula::With(a, b) => {
109 Formula::With(Box::new(a.desugar()), Box::new(b.desugar()))
110 }
111 Formula::Plus(a, b) => {
112 Formula::Plus(Box::new(a.desugar()), Box::new(b.desugar()))
113 }
114 Formula::OfCourse(a) => Formula::OfCourse(Box::new(a.desugar())),
115 Formula::WhyNot(a) => Formula::WhyNot(Box::new(a.desugar())),
116 _ => self.clone(),
117 }
118 }
119
120 pub fn is_positive(&self) -> bool {
124 matches!(
125 self,
126 Formula::Atom(_)
127 | Formula::Tensor(_, _)
128 | Formula::One
129 | Formula::Plus(_, _)
130 | Formula::Zero
131 | Formula::OfCourse(_)
132 )
133 }
134
135 pub fn is_negative(&self) -> bool {
139 !self.is_positive()
140 }
141
142 pub fn pretty(&self) -> String {
144 match self {
145 Formula::Atom(a) => a.clone(),
146 Formula::NegAtom(a) => format!("{}⊥", a),
147 Formula::Tensor(a, b) => format!("({} ⊗ {})", a.pretty(), b.pretty()),
148 Formula::Par(a, b) => format!("({} ⅋ {})", a.pretty(), b.pretty()),
149 Formula::Lolli(a, b) => format!("({} ⊸ {})", a.pretty(), b.pretty()),
150 Formula::With(a, b) => format!("({} & {})", a.pretty(), b.pretty()),
151 Formula::Plus(a, b) => format!("({} ⊕ {})", a.pretty(), b.pretty()),
152 Formula::OfCourse(a) => format!("!{}", a.pretty()),
153 Formula::WhyNot(a) => format!("?{}", a.pretty()),
154 Formula::One => "1".to_string(),
155 Formula::Bottom => "⊥".to_string(),
156 Formula::Top => "⊤".to_string(),
157 Formula::Zero => "0".to_string(),
158 }
159 }
160
161 pub fn pretty_ascii(&self) -> String {
163 match self {
164 Formula::Atom(a) => a.clone(),
165 Formula::NegAtom(a) => format!("{}^", a),
166 Formula::Tensor(a, b) => format!("({} * {})", a.pretty_ascii(), b.pretty_ascii()),
167 Formula::Par(a, b) => format!("({} | {})", a.pretty_ascii(), b.pretty_ascii()),
168 Formula::Lolli(a, b) => format!("({} -o {})", a.pretty_ascii(), b.pretty_ascii()),
169 Formula::With(a, b) => format!("({} & {})", a.pretty_ascii(), b.pretty_ascii()),
170 Formula::Plus(a, b) => format!("({} + {})", a.pretty_ascii(), b.pretty_ascii()),
171 Formula::OfCourse(a) => format!("!{}", a.pretty_ascii()),
172 Formula::WhyNot(a) => format!("?{}", a.pretty_ascii()),
173 Formula::One => "1".to_string(),
174 Formula::Bottom => "bot".to_string(),
175 Formula::Top => "top".to_string(),
176 Formula::Zero => "0".to_string(),
177 }
178 }
179
180 pub fn pretty_latex(&self) -> String {
182 match self {
183 Formula::Atom(a) => a.clone(),
184 Formula::NegAtom(a) => format!("{}^{{\\bot}}", a),
185 Formula::Tensor(a, b) => {
186 format!("({} \\otimes {})", a.pretty_latex(), b.pretty_latex())
187 }
188 Formula::Par(a, b) => {
189 format!("({} \\parr {})", a.pretty_latex(), b.pretty_latex())
190 }
191 Formula::Lolli(a, b) => {
192 format!("({} \\multimap {})", a.pretty_latex(), b.pretty_latex())
193 }
194 Formula::With(a, b) => {
195 format!("({} \\with {})", a.pretty_latex(), b.pretty_latex())
196 }
197 Formula::Plus(a, b) => {
198 format!("({} \\oplus {})", a.pretty_latex(), b.pretty_latex())
199 }
200 Formula::OfCourse(a) => format!("{{!}}{}", a.pretty_latex()),
201 Formula::WhyNot(a) => format!("{{?}}{}", a.pretty_latex()),
202 Formula::One => "\\mathbf{1}".to_string(),
203 Formula::Bottom => "\\bot".to_string(),
204 Formula::Top => "\\top".to_string(),
205 Formula::Zero => "\\mathbf{0}".to_string(),
206 }
207 }
208
209 pub fn atom(name: impl Into<String>) -> Self {
211 Formula::Atom(name.into())
212 }
213
214 pub fn neg_atom(name: impl Into<String>) -> Self {
216 Formula::NegAtom(name.into())
217 }
218
219 pub fn tensor(a: Formula, b: Formula) -> Self {
221 Formula::Tensor(Box::new(a), Box::new(b))
222 }
223
224 pub fn par(a: Formula, b: Formula) -> Self {
226 Formula::Par(Box::new(a), Box::new(b))
227 }
228
229 pub fn lolli(a: Formula, b: Formula) -> Self {
231 Formula::Lolli(Box::new(a), Box::new(b))
232 }
233
234 pub fn with(a: Formula, b: Formula) -> Self {
236 Formula::With(Box::new(a), Box::new(b))
237 }
238
239 pub fn plus(a: Formula, b: Formula) -> Self {
241 Formula::Plus(Box::new(a), Box::new(b))
242 }
243
244 pub fn of_course(a: Formula) -> Self {
246 Formula::OfCourse(Box::new(a))
247 }
248
249 pub fn why_not(a: Formula) -> Self {
251 Formula::WhyNot(Box::new(a))
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn test_negation_involutive() {
261 let a = Formula::atom("A");
262 assert_eq!(a.negate().negate(), a);
263
264 let complex = Formula::tensor(Formula::atom("A"), Formula::atom("B"));
265 assert_eq!(complex.negate().negate(), complex);
266
267 let formulas = vec![
269 Formula::One,
270 Formula::Bottom,
271 Formula::Top,
272 Formula::Zero,
273 Formula::of_course(Formula::atom("X")),
274 Formula::why_not(Formula::atom("Y")),
275 Formula::with(Formula::atom("A"), Formula::atom("B")),
276 Formula::plus(Formula::atom("A"), Formula::atom("B")),
277 Formula::par(Formula::atom("A"), Formula::atom("B")),
278 ];
279
280 for f in formulas {
281 assert_eq!(f.negate().negate(), f, "Failed for: {:?}", f);
282 }
283
284 let lolli = Formula::lolli(Formula::atom("A"), Formula::atom("B"));
287 let neg_lolli = lolli.negate();
288 assert_eq!(
290 neg_lolli,
291 Formula::tensor(Formula::atom("A"), Formula::neg_atom("B"))
292 );
293 }
294
295 #[test]
296 fn test_de_morgan() {
297 let tensor = Formula::tensor(Formula::atom("A"), Formula::atom("B"));
299 let expected = Formula::par(Formula::neg_atom("A"), Formula::neg_atom("B"));
300 assert_eq!(tensor.negate(), expected);
301
302 let par = Formula::par(Formula::atom("A"), Formula::atom("B"));
304 let expected = Formula::tensor(Formula::neg_atom("A"), Formula::neg_atom("B"));
305 assert_eq!(par.negate(), expected);
306
307 let with = Formula::with(Formula::atom("A"), Formula::atom("B"));
309 let expected = Formula::plus(Formula::neg_atom("A"), Formula::neg_atom("B"));
310 assert_eq!(with.negate(), expected);
311
312 let plus = Formula::plus(Formula::atom("A"), Formula::atom("B"));
314 let expected = Formula::with(Formula::neg_atom("A"), Formula::neg_atom("B"));
315 assert_eq!(plus.negate(), expected);
316
317 assert_eq!(Formula::One.negate(), Formula::Bottom);
319
320 assert_eq!(Formula::Bottom.negate(), Formula::One);
322
323 assert_eq!(Formula::Top.negate(), Formula::Zero);
325
326 assert_eq!(Formula::Zero.negate(), Formula::Top);
328
329 let bang = Formula::of_course(Formula::atom("A"));
331 let expected = Formula::why_not(Formula::neg_atom("A"));
332 assert_eq!(bang.negate(), expected);
333
334 let whynot = Formula::why_not(Formula::atom("A"));
336 let expected = Formula::of_course(Formula::neg_atom("A"));
337 assert_eq!(whynot.negate(), expected);
338 }
339
340 #[test]
341 fn test_polarity() {
342 assert!(Formula::atom("A").is_positive());
344 assert!(Formula::tensor(Formula::atom("A"), Formula::atom("B")).is_positive());
345 assert!(Formula::One.is_positive());
346 assert!(Formula::plus(Formula::atom("A"), Formula::atom("B")).is_positive());
347 assert!(Formula::Zero.is_positive());
348 assert!(Formula::of_course(Formula::atom("A")).is_positive());
349
350 assert!(Formula::neg_atom("A").is_negative());
352 assert!(Formula::par(Formula::atom("A"), Formula::atom("B")).is_negative());
353 assert!(Formula::Bottom.is_negative());
354 assert!(Formula::with(Formula::atom("A"), Formula::atom("B")).is_negative());
355 assert!(Formula::Top.is_negative());
356 assert!(Formula::why_not(Formula::atom("A")).is_negative());
357
358 assert!(Formula::lolli(Formula::atom("A"), Formula::atom("B")).is_negative());
360 }
361
362 #[test]
363 fn test_desugar() {
364 let lolli = Formula::lolli(Formula::atom("A"), Formula::atom("B"));
366 let desugared = lolli.desugar();
367 let expected = Formula::par(Formula::neg_atom("A"), Formula::atom("B"));
368 assert_eq!(desugared, expected);
369
370 let nested = Formula::lolli(
372 Formula::lolli(Formula::atom("A"), Formula::atom("B")),
373 Formula::atom("C"),
374 );
375 let desugared = nested.desugar();
376 let expected = Formula::par(
378 Formula::tensor(Formula::atom("A"), Formula::neg_atom("B")),
379 Formula::atom("C"),
380 );
381 assert_eq!(desugared, expected);
382 }
383
384 #[test]
385 fn test_pretty_print() {
386 let f = Formula::lolli(Formula::atom("A"), Formula::atom("B"));
387 assert_eq!(f.pretty(), "(A ⊸ B)");
388 assert_eq!(f.pretty_ascii(), "(A -o B)");
389 assert_eq!(f.pretty_latex(), "(A \\multimap B)");
390
391 let f = Formula::tensor(
392 Formula::of_course(Formula::atom("A")),
393 Formula::why_not(Formula::atom("B")),
394 );
395 assert_eq!(f.pretty(), "(!A ⊗ ?B)");
396 assert_eq!(f.pretty_ascii(), "(!A * ?B)");
397 }
398
399 #[test]
400 fn test_units() {
401 assert_eq!(Formula::One.pretty(), "1");
402 assert_eq!(Formula::Bottom.pretty(), "⊥");
403 assert_eq!(Formula::Top.pretty(), "⊤");
404 assert_eq!(Formula::Zero.pretty(), "0");
405
406 assert_eq!(Formula::One.pretty_ascii(), "1");
407 assert_eq!(Formula::Bottom.pretty_ascii(), "bot");
408 assert_eq!(Formula::Top.pretty_ascii(), "top");
409 assert_eq!(Formula::Zero.pretty_ascii(), "0");
410 }
411}