Skip to main content

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