logicaffeine_language/
formatter.rs

1//! Output formatters for logical expressions.
2//!
3//! This module defines the [`LogicFormatter`] trait and its implementations for
4//! rendering logical expressions in different notations:
5//!
6//! - **Unicode**: Standard FOL symbols (∀, ∃, ∧, ∨, ¬, →)
7//! - **LaTeX**: TeX math mode notation (\\forall, \\exists, \\land, etc.)
8//! - **ASCII**: Plain text fallback (ALL, EXISTS, AND, OR, NOT)
9//!
10//! Formatters handle quantifiers, connectives, modal operators, temporal operators,
11//! and special constructs like plurals and possessives.
12
13use std::fmt::Write;
14
15use crate::ast::{AspectOperator, BinaryTemporalOp, ModalDomain, QuantifierKind, TemporalOperator, Term, VoiceOperator};
16use logicaffeine_base::Interner;
17use crate::registry::SymbolRegistry;
18use crate::token::TokenType;
19
20/// Trait for formatting logical expressions in various notations.
21pub trait LogicFormatter {
22    // Quantifiers
23    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    // Binary operators
45    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 | TokenType::Implies => 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    // Unary operators
61    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    // Identity operator (used in Identity expressions)
71    fn identity(&self) -> &'static str {
72        " = "
73    }
74
75    // Whether to wrap identity expressions in parentheses
76    fn wrap_identity(&self) -> bool {
77        false
78    }
79
80    // Modal operators
81    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            ModalDomain::Temporal => "Temporal",
88        };
89        format!("{}_{{{:.1}}} {}", sym, force, body)
90    }
91
92    fn necessity(&self) -> &'static str;
93    fn possibility(&self) -> &'static str;
94
95    // Temporal operators
96    fn temporal(&self, op: &TemporalOperator, body: &str) -> String {
97        let sym = match op {
98            TemporalOperator::Past => self.past(),
99            TemporalOperator::Future => self.future(),
100            TemporalOperator::Always => "G",
101            TemporalOperator::Eventually => "F",
102            TemporalOperator::BoundedEventually(n) => return format!("F≤{}({})", n, body),
103            TemporalOperator::Next => "X",
104        };
105        format!("{}({})", sym, body)
106    }
107
108    fn past(&self) -> &'static str;
109    fn future(&self) -> &'static str;
110
111    // Binary temporal operators
112    fn temporal_binary(&self, op: &BinaryTemporalOp, left: &str, right: &str) -> String {
113        let sym = match op {
114            BinaryTemporalOp::Until => "U",
115            BinaryTemporalOp::Release => "R",
116            BinaryTemporalOp::WeakUntil => "W",
117        };
118        format!("({} {} {})", left, sym, right)
119    }
120
121    // Aspectual operators
122    fn aspectual(&self, op: &AspectOperator, body: &str) -> String {
123        let sym = match op {
124            AspectOperator::Progressive => self.progressive(),
125            AspectOperator::Perfect => self.perfect(),
126            AspectOperator::Habitual => self.habitual(),
127            AspectOperator::Iterative => self.iterative(),
128        };
129        format!("{}({})", sym, body)
130    }
131
132    fn progressive(&self) -> &'static str;
133    fn perfect(&self) -> &'static str;
134    fn habitual(&self) -> &'static str;
135    fn iterative(&self) -> &'static str;
136
137    // Voice operators
138    fn voice(&self, op: &VoiceOperator, body: &str) -> String {
139        let sym = match op {
140            VoiceOperator::Passive => self.passive(),
141        };
142        format!("{}({})", sym, body)
143    }
144
145    fn passive(&self) -> &'static str;
146
147    // Lambda
148    fn lambda(&self, var: &str, body: &str) -> String;
149
150    // Counterfactual
151    fn counterfactual(&self, antecedent: &str, consequent: &str) -> String;
152
153    // Superlative expansion
154    fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String;
155
156    // Event quantification (uses existential + and)
157    fn event_quantifier(&self, pred: &str, adverbs: &[String]) -> String {
158        if adverbs.is_empty() {
159            format!("{}e({})", self.existential(), pred)
160        } else {
161            let conj = self.and();
162            format!(
163                "{}e({} {} {})",
164                self.existential(),
165                pred,
166                conj,
167                adverbs.join(&format!(" {} ", conj))
168            )
169        }
170    }
171
172    // Categorical (legacy)
173    fn categorical_all(&self) -> &'static str;
174    fn categorical_no(&self) -> &'static str;
175    fn categorical_some(&self) -> &'static str;
176    fn categorical_not(&self) -> &'static str;
177
178    // Sanitization hook for LaTeX special characters
179    fn sanitize(&self, s: &str) -> String {
180        s.to_string()
181    }
182
183    // Whether to use simple predicate form instead of event semantics
184    fn use_simple_events(&self) -> bool {
185        false
186    }
187
188    // Whether to use full predicate names instead of abbreviations
189    fn use_full_names(&self) -> bool {
190        false
191    }
192
193    // Whether to preserve original case (for code generation)
194    fn preserve_case(&self) -> bool {
195        false
196    }
197
198    // Whether to include world arguments in predicates (for Kripke semantics)
199    fn include_world_arguments(&self) -> bool {
200        false
201    }
202
203    /// Hook for customizing how comparatives are rendered.
204    /// Default implementation uses standard logic notation: tallER(subj, obj) or tallER(subj, obj, diff)
205    fn write_comparative<W: Write>(
206        &self,
207        w: &mut W,
208        adjective: &str,
209        subject: &str,
210        object: &str,
211        difference: Option<&str>,
212    ) -> std::fmt::Result {
213        if let Some(diff) = difference {
214            write!(w, "{}er({}, {}, {})", adjective, subject, object, diff)
215        } else {
216            write!(w, "{}er({}, {})", adjective, subject, object)
217        }
218    }
219
220    /// Hook for customizing how predicates are rendered.
221    /// Default implementation uses standard logic notation: Name(Arg1, Arg2)
222    fn write_predicate<W: Write>(
223        &self,
224        w: &mut W,
225        name: &str,
226        args: &[Term],
227        registry: &mut SymbolRegistry,
228        interner: &Interner,
229    ) -> std::fmt::Result {
230        write!(w, "{}(", self.sanitize(name))?;
231        for (i, arg) in args.iter().enumerate() {
232            if i > 0 {
233                write!(w, ", ")?;
234            }
235            if self.use_full_names() {
236                arg.write_to_full(w, registry, interner)?;
237            } else {
238                arg.write_to(w, registry, interner)?;
239            }
240        }
241        write!(w, ")")
242    }
243}
244
245pub struct UnicodeFormatter;
246
247impl LogicFormatter for UnicodeFormatter {
248    fn universal(&self) -> String { "∀".to_string() }
249    fn existential(&self) -> String { "∃".to_string() }
250    fn cardinal(&self, n: u32) -> String { format!("∃={}.", n) }
251    fn at_least(&self, n: u32) -> String { format!("∃≥{}", n) }
252    fn at_most(&self, n: u32) -> String { format!("∃≤{}", n) }
253
254    fn and(&self) -> &'static str { "∧" }
255    fn or(&self) -> &'static str { "∨" }
256    fn implies(&self) -> &'static str { "→" }
257    fn iff(&self) -> &'static str { "↔" }
258    fn not(&self) -> &'static str { "¬" }
259
260    fn necessity(&self) -> &'static str { "□" }
261    fn possibility(&self) -> &'static str { "◇" }
262
263    fn past(&self) -> &'static str { "P" }
264    fn future(&self) -> &'static str { "F" }
265
266    fn progressive(&self) -> &'static str { "Prog" }
267    fn perfect(&self) -> &'static str { "Perf" }
268    fn habitual(&self) -> &'static str { "HAB" }
269    fn iterative(&self) -> &'static str { "ITER" }
270    fn passive(&self) -> &'static str { "Pass" }
271
272    fn lambda(&self, var: &str, body: &str) -> String {
273        format!("λ{}.{}", var, body)
274    }
275
276    fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
277        format!("({} □→ {})", antecedent, consequent)
278    }
279
280    fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
281        format!(
282            "∀x(({}(x) ∧ x ≠ {}) → {}({}, x))",
283            domain, subject, comp, subject
284        )
285    }
286
287    fn categorical_all(&self) -> &'static str { "∀" }
288    fn categorical_no(&self) -> &'static str { "∀¬" }
289    fn categorical_some(&self) -> &'static str { "∃" }
290    fn categorical_not(&self) -> &'static str { "¬" }
291
292    // Use full predicate names (e.g., "Wet" not "W")
293    fn use_full_names(&self) -> bool { true }
294}
295
296pub struct LatexFormatter;
297
298impl LogicFormatter for LatexFormatter {
299    fn universal(&self) -> String { "\\forall ".to_string() }
300    fn existential(&self) -> String { "\\exists ".to_string() }
301    fn cardinal(&self, n: u32) -> String { format!("\\exists_{{={}}} ", n) }
302    fn at_least(&self, n: u32) -> String { format!("\\exists_{{\\geq {}}} ", n) }
303    fn at_most(&self, n: u32) -> String { format!("\\exists_{{\\leq {}}} ", n) }
304
305    fn and(&self) -> &'static str { "\\cdot" }
306    fn or(&self) -> &'static str { "\\vee" }
307    fn implies(&self) -> &'static str { "\\supset" }
308    fn iff(&self) -> &'static str { "\\equiv" }
309    fn not(&self) -> &'static str { "\\sim " }
310
311    fn necessity(&self) -> &'static str { "\\Box" }
312    fn possibility(&self) -> &'static str { "\\Diamond" }
313
314    fn past(&self) -> &'static str { "\\mathsf{P}" }
315    fn future(&self) -> &'static str { "\\mathsf{F}" }
316
317    fn progressive(&self) -> &'static str { "\\mathsf{Prog}" }
318    fn perfect(&self) -> &'static str { "\\mathsf{Perf}" }
319    fn habitual(&self) -> &'static str { "\\mathsf{HAB}" }
320    fn iterative(&self) -> &'static str { "\\mathsf{ITER}" }
321    fn passive(&self) -> &'static str { "\\mathsf{Pass}" }
322
323    fn lambda(&self, var: &str, body: &str) -> String {
324        format!("\\lambda {}.{}", var, body)
325    }
326
327    fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
328        format!("({} \\boxright {})", antecedent, consequent)
329    }
330
331    fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
332        format!(
333            "\\forall x(({}(x) \\land x \\neq {}) \\supset {}({}, x))",
334            domain, subject, comp, subject
335        )
336    }
337
338    fn categorical_all(&self) -> &'static str { "All" }
339    fn categorical_no(&self) -> &'static str { "No" }
340    fn categorical_some(&self) -> &'static str { "Some" }
341    fn categorical_not(&self) -> &'static str { "not" }
342
343    fn sanitize(&self, s: &str) -> String {
344        s.replace('_', r"\_")
345            .replace('^', r"\^{}")
346            .replace('&', r"\&")
347            .replace('%', r"\%")
348            .replace('#', r"\#")
349            .replace('$', r"\$")
350    }
351}
352
353pub struct SimpleFOLFormatter;
354
355impl LogicFormatter for SimpleFOLFormatter {
356    fn universal(&self) -> String { "∀".to_string() }
357    fn existential(&self) -> String { "∃".to_string() }
358    fn cardinal(&self, n: u32) -> String { format!("∃={}", n) }
359    fn at_least(&self, n: u32) -> String { format!("∃≥{}", n) }
360    fn at_most(&self, n: u32) -> String { format!("∃≤{}", n) }
361
362    fn and(&self) -> &'static str { "∧" }
363    fn or(&self) -> &'static str { "∨" }
364    fn implies(&self) -> &'static str { "→" }
365    fn iff(&self) -> &'static str { "↔" }
366    fn not(&self) -> &'static str { "¬" }
367
368    fn necessity(&self) -> &'static str { "□" }
369    fn possibility(&self) -> &'static str { "◇" }
370
371    fn past(&self) -> &'static str { "Past" }
372    fn future(&self) -> &'static str { "Future" }
373
374    fn progressive(&self) -> &'static str { "" }
375    fn perfect(&self) -> &'static str { "" }
376    fn habitual(&self) -> &'static str { "" }
377    fn iterative(&self) -> &'static str { "" }
378    fn passive(&self) -> &'static str { "" }
379
380    fn lambda(&self, var: &str, body: &str) -> String {
381        format!("λ{}.{}", var, body)
382    }
383
384    fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
385        format!("({} □→ {})", antecedent, consequent)
386    }
387
388    fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
389        format!(
390            "∀x(({}(x) ∧ x ≠ {}) → {}({}, x))",
391            domain, subject, comp, subject
392        )
393    }
394
395    fn categorical_all(&self) -> &'static str { "∀" }
396    fn categorical_no(&self) -> &'static str { "∀¬" }
397    fn categorical_some(&self) -> &'static str { "∃" }
398    fn categorical_not(&self) -> &'static str { "¬" }
399
400    fn modal(&self, _domain: ModalDomain, _force: f32, body: &str) -> String {
401        body.to_string()
402    }
403
404    fn aspectual(&self, _op: &AspectOperator, body: &str) -> String {
405        body.to_string()
406    }
407
408    fn use_simple_events(&self) -> bool {
409        true
410    }
411
412    fn use_full_names(&self) -> bool {
413        true
414    }
415}
416
417/// Formatter for Kripke lowered output with explicit world arguments.
418/// Modals are already lowered to quantifiers; this formatter just renders
419/// the result with world arguments appended to predicates.
420pub struct KripkeFormatter;
421
422impl LogicFormatter for KripkeFormatter {
423    fn universal(&self) -> String { "ForAll ".to_string() }
424    fn existential(&self) -> String { "Exists ".to_string() }
425    fn cardinal(&self, n: u32) -> String { format!("Exists={} ", n) }
426    fn at_least(&self, n: u32) -> String { format!("Exists>={} ", n) }
427    fn at_most(&self, n: u32) -> String { format!("Exists<={} ", n) }
428
429    fn and(&self) -> &'static str { " And " }
430    fn or(&self) -> &'static str { " Or " }
431    fn implies(&self) -> &'static str { " Implies " }
432    fn iff(&self) -> &'static str { " Iff " }
433    fn not(&self) -> &'static str { "Not " }
434
435    fn necessity(&self) -> &'static str { "Box" }
436    fn possibility(&self) -> &'static str { "Diamond" }
437
438    fn past(&self) -> &'static str { "Past" }
439    fn future(&self) -> &'static str { "Future" }
440
441    fn progressive(&self) -> &'static str { "Prog" }
442    fn perfect(&self) -> &'static str { "Perf" }
443    fn habitual(&self) -> &'static str { "HAB" }
444    fn iterative(&self) -> &'static str { "ITER" }
445    fn passive(&self) -> &'static str { "Pass" }
446
447    fn lambda(&self, var: &str, body: &str) -> String {
448        format!("Lambda {}.{}", var, body)
449    }
450
451    fn counterfactual(&self, antecedent: &str, consequent: &str) -> String {
452        format!("({} Counterfactual {})", antecedent, consequent)
453    }
454
455    fn superlative(&self, comp: &str, domain: &str, subject: &str) -> String {
456        format!(
457            "ForAll x(({}(x) And x != {}) Implies {}({}, x))",
458            domain, subject, comp, subject
459        )
460    }
461
462    fn categorical_all(&self) -> &'static str { "ForAll" }
463    fn categorical_no(&self) -> &'static str { "ForAll Not" }
464    fn categorical_some(&self) -> &'static str { "Exists" }
465    fn categorical_not(&self) -> &'static str { "Not" }
466
467    fn modal(&self, _domain: ModalDomain, _force: f32, body: &str) -> String {
468        // Modals already lowered to quantifiers - just pass through
469        body.to_string()
470    }
471
472    fn use_full_names(&self) -> bool { true }
473
474    fn include_world_arguments(&self) -> bool { true }
475}
476
477/// Formatter that produces Rust boolean expressions for runtime assertions.
478/// Used by codegen to convert LogicExpr into debug_assert!() compatible code.
479pub struct RustFormatter;
480
481impl LogicFormatter for RustFormatter {
482    // Operators map to Rust boolean operators
483    fn and(&self) -> &'static str { "&&" }
484    fn or(&self) -> &'static str { "||" }
485    fn not(&self) -> &'static str { "!" }
486    fn implies(&self) -> &'static str { "||" } // Handled via binary_op override
487    fn iff(&self) -> &'static str { "==" }
488    fn identity(&self) -> &'static str { " == " } // Rust equality
489    fn wrap_identity(&self) -> bool { true } // Wrap in parens for valid Rust
490
491    // Use full variable names, not abbreviations
492    fn use_full_names(&self) -> bool { true }
493    fn preserve_case(&self) -> bool { true } // Keep original variable case
494
495    // Quantifiers: runtime can't check universal quantification, emit comments
496    fn universal(&self) -> String { "/* ∀ */".to_string() }
497    fn existential(&self) -> String { "/* ∃ */".to_string() }
498    fn cardinal(&self, n: u32) -> String { format!("/* ∃={} */", n) }
499    fn at_least(&self, n: u32) -> String { format!("/* ∃≥{} */", n) }
500    fn at_most(&self, n: u32) -> String { format!("/* ∃≤{} */", n) }
501
502    // Modal/Temporal operators are stripped for runtime (not checkable)
503    fn necessity(&self) -> &'static str { "" }
504    fn possibility(&self) -> &'static str { "" }
505    fn past(&self) -> &'static str { "" }
506    fn future(&self) -> &'static str { "" }
507    fn progressive(&self) -> &'static str { "" }
508    fn perfect(&self) -> &'static str { "" }
509    fn habitual(&self) -> &'static str { "" }
510    fn iterative(&self) -> &'static str { "" }
511    fn passive(&self) -> &'static str { "" }
512    fn categorical_all(&self) -> &'static str { "" }
513    fn categorical_no(&self) -> &'static str { "" }
514    fn categorical_some(&self) -> &'static str { "" }
515    fn categorical_not(&self) -> &'static str { "" }
516
517    fn lambda(&self, var: &str, body: &str) -> String {
518        format!("|{}| {{ {} }}", var, body)
519    }
520
521    fn counterfactual(&self, a: &str, c: &str) -> String {
522        format!("/* if {} then {} */", a, c)
523    }
524
525    fn superlative(&self, _: &str, _: &str, _: &str) -> String {
526        "/* superlative */".to_string()
527    }
528
529    // Override comparative for Rust: map adjectives to comparison operators
530    fn write_comparative<W: Write>(
531        &self,
532        w: &mut W,
533        adjective: &str,
534        subject: &str,
535        object: &str,
536        _difference: Option<&str>,
537    ) -> std::fmt::Result {
538        let adj_lower = adjective.to_lowercase();
539        match adj_lower.as_str() {
540            "great" | "big" | "large" | "tall" | "old" | "high" | "more" | "greater" => {
541                write!(w, "({} > {})", subject, object)
542            }
543            "small" | "little" | "short" | "young" | "low" | "less" | "fewer" => {
544                write!(w, "({} < {})", subject, object)
545            }
546            _ => write!(w, "({} > {})", subject, object) // default to greater-than
547        }
548    }
549
550    // Override unary_op to wrap in parens for valid Rust
551    fn unary_op(&self, op: &TokenType, operand: &str) -> String {
552        match op {
553            TokenType::Not => format!("(!{})", operand),
554            _ => format!("/* unknown unary */({})", operand),
555        }
556    }
557
558    // Override binary_op for implication desugaring: A → B = !A || B
559    fn binary_op(&self, op: &TokenType, left: &str, right: &str) -> String {
560        match op {
561            TokenType::If | TokenType::Implies | TokenType::Then => format!("(!({}) || ({}))", left, right),
562            TokenType::And => format!("({} && {})", left, right),
563            TokenType::Or => format!("({} || {})", left, right),
564            TokenType::Iff => format!("({} == {})", left, right),
565            _ => "/* unknown op */".to_string(),
566        }
567    }
568
569    // Core predicate mapping: semantic interpretation of predicates to Rust operators
570    fn write_predicate<W: Write>(
571        &self,
572        w: &mut W,
573        name: &str,
574        args: &[Term],
575        _registry: &mut SymbolRegistry,
576        interner: &Interner,
577    ) -> std::fmt::Result {
578        // Helper to render a term at given index to a string, preserving original case
579        let render = |idx: usize| -> String {
580            let mut buf = String::new();
581            if let Some(arg) = args.get(idx) {
582                let _ = arg.write_to_raw(&mut buf, interner);
583            }
584            buf
585        };
586
587        match name.to_lowercase().as_str() {
588            // Comparisons
589            "greater" if args.len() == 2 => write!(w, "({} > {})", render(0), render(1)),
590            "less" if args.len() == 2 => write!(w, "({} < {})", render(0), render(1)),
591            "equal" | "equals" if args.len() == 2 => write!(w, "({} == {})", render(0), render(1)),
592            "notequal" | "not_equal" if args.len() == 2 => write!(w, "({} != {})", render(0), render(1)),
593            "greaterequal" | "atleast" | "at_least" if args.len() == 2 => write!(w, "({} >= {})", render(0), render(1)),
594            "lessequal" | "atmost" | "at_most" if args.len() == 2 => write!(w, "({} <= {})", render(0), render(1)),
595
596            // Unary checks
597            "positive" if args.len() == 1 => write!(w, "({} > 0)", render(0)),
598            "negative" if args.len() == 1 => write!(w, "({} < 0)", render(0)),
599            "zero" if args.len() == 1 => write!(w, "({} == 0)", render(0)),
600            "empty" if args.len() == 1 => write!(w, "{}.is_empty()", render(0)),
601
602            // Collection membership
603            "in" if args.len() == 2 => write!(w, "{}.contains(&{})", render(1), render(0)),
604            "contains" if args.len() == 2 => write!(w, "{}.contains(&{})", render(0), render(1)),
605
606            // Fallback: method call for 1 arg, function call for N args
607            _ if args.len() == 1 => write!(w, "{}.is_{}()", render(0), name.to_lowercase()),
608            _ => {
609                write!(w, "{}(", name.to_lowercase())?;
610                for i in 0..args.len() {
611                    if i > 0 { write!(w, ", ")?; }
612                    write!(w, "{}", render(i))?;
613                }
614                write!(w, ")")
615            }
616        }
617    }
618}
619
620#[cfg(test)]
621mod tests {
622    use super::*;
623
624    #[test]
625    fn unicode_binary_operators() {
626        let f = UnicodeFormatter;
627        assert_eq!(f.binary_op(&TokenType::And, "P", "Q"), "(P ∧ Q)");
628        assert_eq!(f.binary_op(&TokenType::Or, "P", "Q"), "(P ∨ Q)");
629        assert_eq!(f.binary_op(&TokenType::If, "P", "Q"), "(P → Q)");
630        assert_eq!(f.binary_op(&TokenType::Iff, "P", "Q"), "(P ↔ Q)");
631    }
632
633    #[test]
634    fn latex_binary_operators() {
635        let f = LatexFormatter;
636        assert_eq!(f.binary_op(&TokenType::And, "P", "Q"), r"(P \cdot Q)");
637        assert_eq!(f.binary_op(&TokenType::Or, "P", "Q"), r"(P \vee Q)");
638        assert_eq!(f.binary_op(&TokenType::If, "P", "Q"), r"(P \supset Q)");
639        assert_eq!(f.binary_op(&TokenType::Iff, "P", "Q"), r"(P \equiv Q)");
640    }
641
642    #[test]
643    fn unicode_quantifiers() {
644        let f = UnicodeFormatter;
645        assert_eq!(f.quantifier(&QuantifierKind::Universal, "x", "P(x)"), "∀x(P(x))");
646        assert_eq!(f.quantifier(&QuantifierKind::Existential, "x", "P(x)"), "∃x(P(x))");
647        assert_eq!(f.quantifier(&QuantifierKind::Cardinal(3), "x", "P(x)"), "∃=3.x(P(x))");
648    }
649
650    #[test]
651    fn latex_quantifiers() {
652        let f = LatexFormatter;
653        assert_eq!(f.quantifier(&QuantifierKind::Universal, "x", "P(x)"), "\\forall x(P(x))");
654        assert_eq!(f.quantifier(&QuantifierKind::Existential, "x", "P(x)"), "\\exists x(P(x))");
655    }
656
657    #[test]
658    fn latex_sanitization() {
659        let f = LatexFormatter;
660        assert_eq!(f.sanitize("foo_bar"), r"foo\_bar");
661        assert_eq!(f.sanitize("x^2"), r"x\^{}2");
662        assert_eq!(f.sanitize("a&b"), r"a\&b");
663    }
664
665    #[test]
666    fn unicode_no_sanitization() {
667        let f = UnicodeFormatter;
668        assert_eq!(f.sanitize("foo_bar"), "foo_bar");
669    }
670
671    #[test]
672    fn unicode_lambda() {
673        let f = UnicodeFormatter;
674        assert_eq!(f.lambda("x", "P(x)"), "λx.P(x)");
675    }
676
677    #[test]
678    fn latex_lambda() {
679        let f = LatexFormatter;
680        assert_eq!(f.lambda("x", "P(x)"), "\\lambda x.P(x)");
681    }
682
683    #[test]
684    fn unicode_counterfactual() {
685        let f = UnicodeFormatter;
686        assert_eq!(f.counterfactual("P", "Q"), "(P □→ Q)");
687    }
688
689    #[test]
690    fn latex_counterfactual() {
691        let f = LatexFormatter;
692        assert_eq!(f.counterfactual("P", "Q"), r"(P \boxright Q)");
693    }
694
695    // RustFormatter tests
696    #[test]
697    fn rust_binary_operators() {
698        let f = RustFormatter;
699        assert_eq!(f.binary_op(&TokenType::And, "P", "Q"), "(P && Q)");
700        assert_eq!(f.binary_op(&TokenType::Or, "P", "Q"), "(P || Q)");
701        assert_eq!(f.binary_op(&TokenType::Iff, "P", "Q"), "(P == Q)");
702    }
703
704    #[test]
705    fn rust_implication_desugaring() {
706        let f = RustFormatter;
707        // A → B desugars to !A || B
708        assert_eq!(f.binary_op(&TokenType::If, "P", "Q"), "(!(P) || (Q))");
709    }
710
711    #[test]
712    fn rust_lambda() {
713        let f = RustFormatter;
714        assert_eq!(f.lambda("x", "x > 0"), "|x| { x > 0 }");
715    }
716
717    #[test]
718    fn rust_quantifiers_as_comments() {
719        let f = RustFormatter;
720        assert_eq!(f.universal(), "/* ∀ */");
721        assert_eq!(f.existential(), "/* ∃ */");
722    }
723}