1use std::fmt::Write;
14
15use crate::ast::{AspectOperator, ModalDomain, QuantifierKind, TemporalOperator, Term, VoiceOperator};
16use logicaffeine_base::Interner;
17use crate::registry::SymbolRegistry;
18use crate::token::TokenType;
19
20pub trait LogicFormatter {
22 fn quantifier(&self, kind: &QuantifierKind, var: &str, body: &str) -> String {
24 let sym = match kind {
25 QuantifierKind::Universal => self.universal(),
26 QuantifierKind::Existential => self.existential(),
27 QuantifierKind::Most => "MOST ".to_string(),
28 QuantifierKind::Few => "FEW ".to_string(),
29 QuantifierKind::Many => "MANY ".to_string(),
30 QuantifierKind::Cardinal(n) => self.cardinal(*n),
31 QuantifierKind::AtLeast(n) => self.at_least(*n),
32 QuantifierKind::AtMost(n) => self.at_most(*n),
33 QuantifierKind::Generic => "Gen ".to_string(),
34 };
35 format!("{}{}({})", sym, var, body)
36 }
37
38 fn universal(&self) -> String;
39 fn existential(&self) -> String;
40 fn cardinal(&self, n: u32) -> String;
41 fn at_least(&self, n: u32) -> String;
42 fn at_most(&self, n: u32) -> String;
43
44 fn binary_op(&self, op: &TokenType, left: &str, right: &str) -> String {
46 match op {
47 TokenType::And => format!("({} {} {})", left, self.and(), right),
48 TokenType::Or => format!("({} {} {})", left, self.or(), right),
49 TokenType::If => format!("({} {} {})", left, self.implies(), right),
50 TokenType::Iff => format!("({} {} {})", left, self.iff(), right),
51 _ => String::new(),
52 }
53 }
54
55 fn and(&self) -> &'static str;
56 fn or(&self) -> &'static str;
57 fn implies(&self) -> &'static str;
58 fn iff(&self) -> &'static str;
59
60 fn unary_op(&self, op: &TokenType, operand: &str) -> String {
62 match op {
63 TokenType::Not => format!("{}{}", self.not(), operand),
64 _ => String::new(),
65 }
66 }
67
68 fn not(&self) -> &'static str;
69
70 fn identity(&self) -> &'static str {
72 " = "
73 }
74
75 fn wrap_identity(&self) -> bool {
77 false
78 }
79
80 fn modal(&self, domain: ModalDomain, force: f32, body: &str) -> String {
82 let sym = match domain {
83 ModalDomain::Alethic if force > 0.0 && force <= 0.5 => self.possibility(),
84 ModalDomain::Alethic => self.necessity(),
85 ModalDomain::Deontic if force <= 0.5 => "P",
86 ModalDomain::Deontic => "O",
87 };
88 format!("{}_{{{:.1}}} {}", sym, force, body)
89 }
90
91 fn necessity(&self) -> &'static str;
92 fn possibility(&self) -> &'static str;
93
94 fn temporal(&self, op: &TemporalOperator, body: &str) -> String {
96 let sym = match op {
97 TemporalOperator::Past => self.past(),
98 TemporalOperator::Future => self.future(),
99 };
100 format!("{}({})", sym, body)
101 }
102
103 fn past(&self) -> &'static str;
104 fn future(&self) -> &'static str;
105
106 fn aspectual(&self, op: &AspectOperator, body: &str) -> String {
108 let sym = match op {
109 AspectOperator::Progressive => self.progressive(),
110 AspectOperator::Perfect => self.perfect(),
111 AspectOperator::Habitual => self.habitual(),
112 AspectOperator::Iterative => self.iterative(),
113 };
114 format!("{}({})", sym, body)
115 }
116
117 fn progressive(&self) -> &'static str;
118 fn perfect(&self) -> &'static str;
119 fn habitual(&self) -> &'static str;
120 fn iterative(&self) -> &'static str;
121
122 fn voice(&self, op: &VoiceOperator, body: &str) -> String {
124 let sym = match op {
125 VoiceOperator::Passive => self.passive(),
126 };
127 format!("{}({})", sym, body)
128 }
129
130 fn passive(&self) -> &'static str;
131
132 fn lambda(&self, var: &str, body: &str) -> String;
134
135 fn counterfactual(&self, antecedent: &str, consequent: &str) -> String;
137
138 fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String;
140
141 fn event_quantifier(&self, pred: &str, adverbs: &[String]) -> String {
143 if adverbs.is_empty() {
144 format!("{}e({})", self.existential(), pred)
145 } else {
146 let conj = self.and();
147 format!(
148 "{}e({} {} {})",
149 self.existential(),
150 pred,
151 conj,
152 adverbs.join(&format!(" {} ", conj))
153 )
154 }
155 }
156
157 fn categorical_all(&self) -> &'static str;
159 fn categorical_no(&self) -> &'static str;
160 fn categorical_some(&self) -> &'static str;
161 fn categorical_not(&self) -> &'static str;
162
163 fn sanitize(&self, s: &str) -> String {
165 s.to_string()
166 }
167
168 fn use_simple_events(&self) -> bool {
170 false
171 }
172
173 fn use_full_names(&self) -> bool {
175 false
176 }
177
178 fn preserve_case(&self) -> bool {
180 false
181 }
182
183 fn include_world_arguments(&self) -> bool {
185 false
186 }
187
188 fn write_comparative<W: Write>(
191 &self,
192 w: &mut W,
193 adjective: &str,
194 subject: &str,
195 object: &str,
196 difference: Option<&str>,
197 ) -> std::fmt::Result {
198 if let Some(diff) = difference {
199 write!(w, "{}er({}, {}, {})", adjective, subject, object, diff)
200 } else {
201 write!(w, "{}er({}, {})", adjective, subject, object)
202 }
203 }
204
205 fn write_predicate<W: Write>(
208 &self,
209 w: &mut W,
210 name: &str,
211 args: &[Term],
212 registry: &mut SymbolRegistry,
213 interner: &Interner,
214 ) -> std::fmt::Result {
215 write!(w, "{}(", self.sanitize(name))?;
216 for (i, arg) in args.iter().enumerate() {
217 if i > 0 {
218 write!(w, ", ")?;
219 }
220 if self.use_full_names() {
221 arg.write_to_full(w, registry, interner)?;
222 } else {
223 arg.write_to(w, registry, interner)?;
224 }
225 }
226 write!(w, ")")
227 }
228}
229
230pub struct UnicodeFormatter;
231
232impl LogicFormatter for UnicodeFormatter {
233 fn universal(&self) -> String { "∀".to_string() }
234 fn existential(&self) -> String { "∃".to_string() }
235 fn cardinal(&self, n: u32) -> String { format!("∃={}.", n) }
236 fn at_least(&self, n: u32) -> String { format!("∃≥{}", n) }
237 fn at_most(&self, n: u32) -> String { format!("∃≤{}", n) }
238
239 fn and(&self) -> &'static str { "∧" }
240 fn or(&self) -> &'static str { "∨" }
241 fn implies(&self) -> &'static str { "→" }
242 fn iff(&self) -> &'static str { "↔" }
243 fn not(&self) -> &'static str { "¬" }
244
245 fn necessity(&self) -> &'static str { "□" }
246 fn possibility(&self) -> &'static str { "◇" }
247
248 fn past(&self) -> &'static str { "P" }
249 fn future(&self) -> &'static str { "F" }
250
251 fn progressive(&self) -> &'static str { "Prog" }
252 fn perfect(&self) -> &'static str { "Perf" }
253 fn habitual(&self) -> &'static str { "HAB" }
254 fn iterative(&self) -> &'static str { "ITER" }
255 fn passive(&self) -> &'static str { "Pass" }
256
257 fn lambda(&self, var: &str, body: &str) -> String {
258 format!("λ{}.{}", var, body)
259 }
260
261 fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
262 format!("({} □→ {})", antecedent, consequent)
263 }
264
265 fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
266 format!(
267 "∀x(({}(x) ∧ x ≠ {}) → {}({}, x))",
268 domain, subject, comp, subject
269 )
270 }
271
272 fn categorical_all(&self) -> &'static str { "∀" }
273 fn categorical_no(&self) -> &'static str { "∀¬" }
274 fn categorical_some(&self) -> &'static str { "∃" }
275 fn categorical_not(&self) -> &'static str { "¬" }
276
277 fn use_full_names(&self) -> bool { true }
279}
280
281pub struct LatexFormatter;
282
283impl LogicFormatter for LatexFormatter {
284 fn universal(&self) -> String { "\\forall ".to_string() }
285 fn existential(&self) -> String { "\\exists ".to_string() }
286 fn cardinal(&self, n: u32) -> String { format!("\\exists_{{={}}} ", n) }
287 fn at_least(&self, n: u32) -> String { format!("\\exists_{{\\geq {}}} ", n) }
288 fn at_most(&self, n: u32) -> String { format!("\\exists_{{\\leq {}}} ", n) }
289
290 fn and(&self) -> &'static str { "\\cdot" }
291 fn or(&self) -> &'static str { "\\vee" }
292 fn implies(&self) -> &'static str { "\\supset" }
293 fn iff(&self) -> &'static str { "\\equiv" }
294 fn not(&self) -> &'static str { "\\sim " }
295
296 fn necessity(&self) -> &'static str { "\\Box" }
297 fn possibility(&self) -> &'static str { "\\Diamond" }
298
299 fn past(&self) -> &'static str { "\\mathsf{P}" }
300 fn future(&self) -> &'static str { "\\mathsf{F}" }
301
302 fn progressive(&self) -> &'static str { "\\mathsf{Prog}" }
303 fn perfect(&self) -> &'static str { "\\mathsf{Perf}" }
304 fn habitual(&self) -> &'static str { "\\mathsf{HAB}" }
305 fn iterative(&self) -> &'static str { "\\mathsf{ITER}" }
306 fn passive(&self) -> &'static str { "\\mathsf{Pass}" }
307
308 fn lambda(&self, var: &str, body: &str) -> String {
309 format!("\\lambda {}.{}", var, body)
310 }
311
312 fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
313 format!("({} \\boxright {})", antecedent, consequent)
314 }
315
316 fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
317 format!(
318 "\\forall x(({}(x) \\land x \\neq {}) \\supset {}({}, x))",
319 domain, subject, comp, subject
320 )
321 }
322
323 fn categorical_all(&self) -> &'static str { "All" }
324 fn categorical_no(&self) -> &'static str { "No" }
325 fn categorical_some(&self) -> &'static str { "Some" }
326 fn categorical_not(&self) -> &'static str { "not" }
327
328 fn sanitize(&self, s: &str) -> String {
329 s.replace('_', r"\_")
330 .replace('^', r"\^{}")
331 .replace('&', r"\&")
332 .replace('%', r"\%")
333 .replace('#', r"\#")
334 .replace('$', r"\$")
335 }
336}
337
338pub struct SimpleFOLFormatter;
339
340impl LogicFormatter for SimpleFOLFormatter {
341 fn universal(&self) -> String { "∀".to_string() }
342 fn existential(&self) -> String { "∃".to_string() }
343 fn cardinal(&self, n: u32) -> String { format!("∃={}", n) }
344 fn at_least(&self, n: u32) -> String { format!("∃≥{}", n) }
345 fn at_most(&self, n: u32) -> String { format!("∃≤{}", n) }
346
347 fn and(&self) -> &'static str { "∧" }
348 fn or(&self) -> &'static str { "∨" }
349 fn implies(&self) -> &'static str { "→" }
350 fn iff(&self) -> &'static str { "↔" }
351 fn not(&self) -> &'static str { "¬" }
352
353 fn necessity(&self) -> &'static str { "□" }
354 fn possibility(&self) -> &'static str { "◇" }
355
356 fn past(&self) -> &'static str { "Past" }
357 fn future(&self) -> &'static str { "Future" }
358
359 fn progressive(&self) -> &'static str { "" }
360 fn perfect(&self) -> &'static str { "" }
361 fn habitual(&self) -> &'static str { "" }
362 fn iterative(&self) -> &'static str { "" }
363 fn passive(&self) -> &'static str { "" }
364
365 fn lambda(&self, var: &str, body: &str) -> String {
366 format!("λ{}.{}", var, body)
367 }
368
369 fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
370 format!("({} □→ {})", antecedent, consequent)
371 }
372
373 fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
374 format!(
375 "∀x(({}(x) ∧ x ≠ {}) → {}({}, x))",
376 domain, subject, comp, subject
377 )
378 }
379
380 fn categorical_all(&self) -> &'static str { "∀" }
381 fn categorical_no(&self) -> &'static str { "∀¬" }
382 fn categorical_some(&self) -> &'static str { "∃" }
383 fn categorical_not(&self) -> &'static str { "¬" }
384
385 fn modal(&self, _domain: ModalDomain, _force: f32, body: &str) -> String {
386 body.to_string()
387 }
388
389 fn aspectual(&self, _op: &AspectOperator, body: &str) -> String {
390 body.to_string()
391 }
392
393 fn use_simple_events(&self) -> bool {
394 true
395 }
396
397 fn use_full_names(&self) -> bool {
398 true
399 }
400}
401
402pub struct KripkeFormatter;
406
407impl LogicFormatter for KripkeFormatter {
408 fn universal(&self) -> String { "ForAll ".to_string() }
409 fn existential(&self) -> String { "Exists ".to_string() }
410 fn cardinal(&self, n: u32) -> String { format!("Exists={} ", n) }
411 fn at_least(&self, n: u32) -> String { format!("Exists>={} ", n) }
412 fn at_most(&self, n: u32) -> String { format!("Exists<={} ", n) }
413
414 fn and(&self) -> &'static str { " And " }
415 fn or(&self) -> &'static str { " Or " }
416 fn implies(&self) -> &'static str { " Implies " }
417 fn iff(&self) -> &'static str { " Iff " }
418 fn not(&self) -> &'static str { "Not " }
419
420 fn necessity(&self) -> &'static str { "Box" }
421 fn possibility(&self) -> &'static str { "Diamond" }
422
423 fn past(&self) -> &'static str { "Past" }
424 fn future(&self) -> &'static str { "Future" }
425
426 fn progressive(&self) -> &'static str { "Prog" }
427 fn perfect(&self) -> &'static str { "Perf" }
428 fn habitual(&self) -> &'static str { "HAB" }
429 fn iterative(&self) -> &'static str { "ITER" }
430 fn passive(&self) -> &'static str { "Pass" }
431
432 fn lambda(&self, var: &str, body: &str) -> String {
433 format!("Lambda {}.{}", var, body)
434 }
435
436 fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
437 format!("({} Counterfactual {})", antecedent, consequent)
438 }
439
440 fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
441 format!(
442 "ForAll x(({}(x) And x != {}) Implies {}({}, x))",
443 domain, subject, comp, subject
444 )
445 }
446
447 fn categorical_all(&self) -> &'static str { "ForAll" }
448 fn categorical_no(&self) -> &'static str { "ForAll Not" }
449 fn categorical_some(&self) -> &'static str { "Exists" }
450 fn categorical_not(&self) -> &'static str { "Not" }
451
452 fn modal(&self, _domain: ModalDomain, _force: f32, body: &str) -> String {
453 body.to_string()
455 }
456
457 fn use_full_names(&self) -> bool { true }
458
459 fn include_world_arguments(&self) -> bool { true }
460}
461
462pub struct RustFormatter;
465
466impl LogicFormatter for RustFormatter {
467 fn and(&self) -> &'static str { "&&" }
469 fn or(&self) -> &'static str { "||" }
470 fn not(&self) -> &'static str { "!" }
471 fn implies(&self) -> &'static str { "||" } fn iff(&self) -> &'static str { "==" }
473 fn identity(&self) -> &'static str { " == " } fn wrap_identity(&self) -> bool { true } fn use_full_names(&self) -> bool { true }
478 fn preserve_case(&self) -> bool { true } fn universal(&self) -> String { "/* ∀ */".to_string() }
482 fn existential(&self) -> String { "/* ∃ */".to_string() }
483 fn cardinal(&self, n: u32) -> String { format!("/* ∃={} */", n) }
484 fn at_least(&self, n: u32) -> String { format!("/* ∃≥{} */", n) }
485 fn at_most(&self, n: u32) -> String { format!("/* ∃≤{} */", n) }
486
487 fn necessity(&self) -> &'static str { "" }
489 fn possibility(&self) -> &'static str { "" }
490 fn past(&self) -> &'static str { "" }
491 fn future(&self) -> &'static str { "" }
492 fn progressive(&self) -> &'static str { "" }
493 fn perfect(&self) -> &'static str { "" }
494 fn habitual(&self) -> &'static str { "" }
495 fn iterative(&self) -> &'static str { "" }
496 fn passive(&self) -> &'static str { "" }
497 fn categorical_all(&self) -> &'static str { "" }
498 fn categorical_no(&self) -> &'static str { "" }
499 fn categorical_some(&self) -> &'static str { "" }
500 fn categorical_not(&self) -> &'static str { "" }
501
502 fn lambda(&self, var: &str, body: &str) -> String {
503 format!("|{}| {{ {} }}", var, body)
504 }
505
506 fn counterfactual(&self, a: &str, c: &str) -> String {
507 format!("/* if {} then {} */", a, c)
508 }
509
510 fn superlative(&self, _: &str, _: &str, _: &str) -> String {
511 "/* superlative */".to_string()
512 }
513
514 fn write_comparative<W: Write>(
516 &self,
517 w: &mut W,
518 adjective: &str,
519 subject: &str,
520 object: &str,
521 _difference: Option<&str>,
522 ) -> std::fmt::Result {
523 let adj_lower = adjective.to_lowercase();
524 match adj_lower.as_str() {
525 "great" | "big" | "large" | "tall" | "old" | "high" | "more" | "greater" => {
526 write!(w, "({} > {})", subject, object)
527 }
528 "small" | "little" | "short" | "young" | "low" | "less" | "fewer" => {
529 write!(w, "({} < {})", subject, object)
530 }
531 _ => write!(w, "({} > {})", subject, object) }
533 }
534
535 fn unary_op(&self, op: &TokenType, operand: &str) -> String {
537 match op {
538 TokenType::Not => format!("(!{})", operand),
539 _ => format!("/* unknown unary */({})", operand),
540 }
541 }
542
543 fn binary_op(&self, op: &TokenType, left: &str, right: &str) -> String {
545 match op {
546 TokenType::If | TokenType::Then => format!("(!({}) || ({}))", left, right),
547 TokenType::And => format!("({} && {})", left, right),
548 TokenType::Or => format!("({} || {})", left, right),
549 TokenType::Iff => format!("({} == {})", left, right),
550 _ => "/* unknown op */".to_string(),
551 }
552 }
553
554 fn write_predicate<W: Write>(
556 &self,
557 w: &mut W,
558 name: &str,
559 args: &[Term],
560 _registry: &mut SymbolRegistry,
561 interner: &Interner,
562 ) -> std::fmt::Result {
563 let render = |idx: usize| -> String {
565 let mut buf = String::new();
566 if let Some(arg) = args.get(idx) {
567 let _ = arg.write_to_raw(&mut buf, interner);
568 }
569 buf
570 };
571
572 match name.to_lowercase().as_str() {
573 "greater" if args.len() == 2 => write!(w, "({} > {})", render(0), render(1)),
575 "less" if args.len() == 2 => write!(w, "({} < {})", render(0), render(1)),
576 "equal" | "equals" if args.len() == 2 => write!(w, "({} == {})", render(0), render(1)),
577 "notequal" | "not_equal" if args.len() == 2 => write!(w, "({} != {})", render(0), render(1)),
578 "greaterequal" | "atleast" | "at_least" if args.len() == 2 => write!(w, "({} >= {})", render(0), render(1)),
579 "lessequal" | "atmost" | "at_most" if args.len() == 2 => write!(w, "({} <= {})", render(0), render(1)),
580
581 "positive" if args.len() == 1 => write!(w, "({} > 0)", render(0)),
583 "negative" if args.len() == 1 => write!(w, "({} < 0)", render(0)),
584 "zero" if args.len() == 1 => write!(w, "({} == 0)", render(0)),
585 "empty" if args.len() == 1 => write!(w, "{}.is_empty()", render(0)),
586
587 "in" if args.len() == 2 => write!(w, "{}.contains(&{})", render(1), render(0)),
589 "contains" if args.len() == 2 => write!(w, "{}.contains(&{})", render(0), render(1)),
590
591 _ if args.len() == 1 => write!(w, "{}.is_{}()", render(0), name.to_lowercase()),
593 _ => {
594 write!(w, "{}(", name.to_lowercase())?;
595 for i in 0..args.len() {
596 if i > 0 { write!(w, ", ")?; }
597 write!(w, "{}", render(i))?;
598 }
599 write!(w, ")")
600 }
601 }
602 }
603}
604
605#[cfg(test)]
606mod tests {
607 use super::*;
608
609 #[test]
610 fn unicode_binary_operators() {
611 let f = UnicodeFormatter;
612 assert_eq!(f.binary_op(&TokenType::And, "P", "Q"), "(P ∧ Q)");
613 assert_eq!(f.binary_op(&TokenType::Or, "P", "Q"), "(P ∨ Q)");
614 assert_eq!(f.binary_op(&TokenType::If, "P", "Q"), "(P → Q)");
615 assert_eq!(f.binary_op(&TokenType::Iff, "P", "Q"), "(P ↔ Q)");
616 }
617
618 #[test]
619 fn latex_binary_operators() {
620 let f = LatexFormatter;
621 assert_eq!(f.binary_op(&TokenType::And, "P", "Q"), r"(P \cdot Q)");
622 assert_eq!(f.binary_op(&TokenType::Or, "P", "Q"), r"(P \vee Q)");
623 assert_eq!(f.binary_op(&TokenType::If, "P", "Q"), r"(P \supset Q)");
624 assert_eq!(f.binary_op(&TokenType::Iff, "P", "Q"), r"(P \equiv Q)");
625 }
626
627 #[test]
628 fn unicode_quantifiers() {
629 let f = UnicodeFormatter;
630 assert_eq!(f.quantifier(&QuantifierKind::Universal, "x", "P(x)"), "∀x(P(x))");
631 assert_eq!(f.quantifier(&QuantifierKind::Existential, "x", "P(x)"), "∃x(P(x))");
632 assert_eq!(f.quantifier(&QuantifierKind::Cardinal(3), "x", "P(x)"), "∃=3.x(P(x))");
633 }
634
635 #[test]
636 fn latex_quantifiers() {
637 let f = LatexFormatter;
638 assert_eq!(f.quantifier(&QuantifierKind::Universal, "x", "P(x)"), "\\forall x(P(x))");
639 assert_eq!(f.quantifier(&QuantifierKind::Existential, "x", "P(x)"), "\\exists x(P(x))");
640 }
641
642 #[test]
643 fn latex_sanitization() {
644 let f = LatexFormatter;
645 assert_eq!(f.sanitize("foo_bar"), r"foo\_bar");
646 assert_eq!(f.sanitize("x^2"), r"x\^{}2");
647 assert_eq!(f.sanitize("a&b"), r"a\&b");
648 }
649
650 #[test]
651 fn unicode_no_sanitization() {
652 let f = UnicodeFormatter;
653 assert_eq!(f.sanitize("foo_bar"), "foo_bar");
654 }
655
656 #[test]
657 fn unicode_lambda() {
658 let f = UnicodeFormatter;
659 assert_eq!(f.lambda("x", "P(x)"), "λx.P(x)");
660 }
661
662 #[test]
663 fn latex_lambda() {
664 let f = LatexFormatter;
665 assert_eq!(f.lambda("x", "P(x)"), "\\lambda x.P(x)");
666 }
667
668 #[test]
669 fn unicode_counterfactual() {
670 let f = UnicodeFormatter;
671 assert_eq!(f.counterfactual("P", "Q"), "(P □→ Q)");
672 }
673
674 #[test]
675 fn latex_counterfactual() {
676 let f = LatexFormatter;
677 assert_eq!(f.counterfactual("P", "Q"), r"(P \boxright Q)");
678 }
679
680 #[test]
682 fn rust_binary_operators() {
683 let f = RustFormatter;
684 assert_eq!(f.binary_op(&TokenType::And, "P", "Q"), "(P && Q)");
685 assert_eq!(f.binary_op(&TokenType::Or, "P", "Q"), "(P || Q)");
686 assert_eq!(f.binary_op(&TokenType::Iff, "P", "Q"), "(P == Q)");
687 }
688
689 #[test]
690 fn rust_implication_desugaring() {
691 let f = RustFormatter;
692 assert_eq!(f.binary_op(&TokenType::If, "P", "Q"), "(!(P) || (Q))");
694 }
695
696 #[test]
697 fn rust_lambda() {
698 let f = RustFormatter;
699 assert_eq!(f.lambda("x", "x > 0"), "|x| { x > 0 }");
700 }
701
702 #[test]
703 fn rust_quantifiers_as_comments() {
704 let f = RustFormatter;
705 assert_eq!(f.universal(), "/* ∀ */");
706 assert_eq!(f.existential(), "/* ∃ */");
707 }
708}