logicaffeine_language/
transpile.rs

1//! Transpilation from AST to first-order logic notation.
2//!
3//! This module converts the internal AST representation to human-readable
4//! logical formulas in various output formats.
5//!
6//! ## Output Formats
7//!
8//! | Format | Example | Use Case |
9//! |--------|---------|----------|
10//! | **Unicode** | ∀x(Cat(x) → Sleeps(x)) | Terminal display |
11//! | **LaTeX** | `\forall x(Cat(x) \to Sleeps(x))` | Academic papers |
12//! | **ASCII** | `Ax(Cat(x) -> Sleeps(x))` | Plain text |
13//! | **Kripke** | `□(P) @w0` | Modal logic diagrams |
14//!
15//! ## Neo-Davidsonian Events
16//!
17//! Verb semantics use event variables following Davidson's event semantics:
18//!
19//! ```text
20//! "John loves Mary"
21//! → ∃e(Love(e) ∧ Agent(e, john) ∧ Theme(e, mary))
22//! ```
23//!
24//! This representation enables reasoning about event modification,
25//! aspect, and temporal relations.
26
27use std::fmt::Write;
28
29use crate::ast::{LogicExpr, NounPhrase, Term, QuantifierKind};
30use crate::ast::logic::NumberKind;
31use crate::formatter::{KripkeFormatter, LatexFormatter, LogicFormatter, SimpleFOLFormatter, UnicodeFormatter};
32use logicaffeine_base::{Interner, Symbol};
33use crate::registry::SymbolRegistry;
34use crate::token::TokenType;
35use crate::{OutputFormat, TranspileContext};
36
37/// Collect event variables from NeoEvents with suppress_existential=true
38/// Returns unique event variables (coordinated weather verbs share the same var)
39fn collect_suppress_existential_events<'a>(expr: &LogicExpr<'a>) -> Vec<Symbol> {
40    let mut events = Vec::new();
41    collect_suppress_existential_events_inner(expr, &mut events);
42    // Deduplicate - coordinated weather verbs share the same event variable
43    // Symbol is Copy so we can use a simple O(n^2) dedup
44    let mut unique = Vec::new();
45    for e in events {
46        if !unique.iter().any(|x| *x == e) {
47            unique.push(e);
48        }
49    }
50    unique
51}
52
53fn collect_suppress_existential_events_inner<'a>(expr: &LogicExpr<'a>, events: &mut Vec<Symbol>) {
54    match expr {
55        LogicExpr::NeoEvent(data) => {
56            if data.suppress_existential {
57                events.push(data.event_var);
58            }
59        }
60        LogicExpr::BinaryOp { left, right, .. } => {
61            collect_suppress_existential_events_inner(left, events);
62            collect_suppress_existential_events_inner(right, events);
63        }
64        LogicExpr::UnaryOp { operand, .. } => {
65            collect_suppress_existential_events_inner(operand, events);
66        }
67        LogicExpr::Temporal { body, .. } => {
68            collect_suppress_existential_events_inner(body, events);
69        }
70        LogicExpr::TemporalBinary { left, right, .. } => {
71            collect_suppress_existential_events_inner(left, events);
72            collect_suppress_existential_events_inner(right, events);
73        }
74        LogicExpr::Aspectual { body, .. } => {
75            collect_suppress_existential_events_inner(body, events);
76        }
77        LogicExpr::Modal { operand, .. } => {
78            collect_suppress_existential_events_inner(operand, events);
79        }
80        _ => {}
81    }
82}
83
84/// Capitalizes the first character of a string.
85///
86/// Used for predicate names in logic output (e.g., "cat" → "Cat").
87pub fn capitalize_first(s: &str) -> String {
88    let mut chars = s.chars();
89    match chars.next() {
90        None => String::new(),
91        Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
92    }
93}
94
95fn write_capitalized<W: Write>(w: &mut W, s: &str) -> std::fmt::Result {
96    let mut chars = s.chars();
97    match chars.next() {
98        None => Ok(()),
99        Some(c) => {
100            for uc in c.to_uppercase() {
101                write!(w, "{}", uc)?;
102            }
103            write!(w, "{}", chars.as_str())
104        }
105    }
106}
107
108impl<'a> NounPhrase<'a> {
109    /// Converts the noun phrase to a short logic symbol (e.g., "C" for "cat").
110    pub fn to_symbol(&self, registry: &mut SymbolRegistry, interner: &Interner) -> String {
111        registry.get_symbol(self.noun, interner)
112    }
113
114    /// Converts the noun phrase to a full logic symbol (e.g., "Cat" for "cat").
115    pub fn to_symbol_full(&self, registry: &SymbolRegistry, interner: &Interner) -> String {
116        registry.get_symbol_full(self.noun, interner)
117    }
118}
119
120impl<'a> Term<'a> {
121    /// Writes the term to a writer using abbreviated symbols.
122    pub fn write_to<W: Write>(
123        &self,
124        w: &mut W,
125        registry: &mut SymbolRegistry,
126        interner: &Interner,
127    ) -> std::fmt::Result {
128        self.write_to_inner(w, registry, interner, false)
129    }
130
131    /// Writes the term to a writer using full (unabbreviated) symbols.
132    pub fn write_to_full<W: Write>(
133        &self,
134        w: &mut W,
135        registry: &mut SymbolRegistry,
136        interner: &Interner,
137    ) -> std::fmt::Result {
138        self.write_to_inner(w, registry, interner, true)
139    }
140
141    /// Writes the term preserving original case (for code generation).
142    pub fn write_to_raw<W: Write>(
143        &self,
144        w: &mut W,
145        interner: &Interner,
146    ) -> std::fmt::Result {
147        match self {
148            Term::Constant(name) | Term::Variable(name) => {
149                write!(w, "{}", interner.resolve(*name))
150            }
151            Term::Function(name, args) => {
152                write!(w, "{}(", interner.resolve(*name))?;
153                for (i, arg) in args.iter().enumerate() {
154                    if i > 0 {
155                        write!(w, ", ")?;
156                    }
157                    arg.write_to_raw(w, interner)?;
158                }
159                write!(w, ")")
160            }
161            Term::Group(members) => {
162                write!(w, "(")?;
163                for (i, m) in members.iter().enumerate() {
164                    if i > 0 {
165                        write!(w, ", ")?;
166                    }
167                    m.write_to_raw(w, interner)?;
168                }
169                write!(w, ")")
170            }
171            Term::Possessed { possessor, possessed } => {
172                possessor.write_to_raw(w, interner)?;
173                write!(w, ".{}", interner.resolve(*possessed))
174            }
175            Term::Value { kind, .. } => match kind {
176                NumberKind::Integer(n) => write!(w, "{}", n),
177                NumberKind::Real(f) => write!(w, "{}", f),
178                NumberKind::Symbolic(s) => write!(w, "{}", interner.resolve(*s)),
179            }
180            Term::Sigma(predicate) => write!(w, "σ({})", interner.resolve(*predicate)),
181            Term::Intension(predicate) => write!(w, "^{}", interner.resolve(*predicate)),
182            Term::Proposition(expr) => write!(w, "[proposition]"),
183        }
184    }
185
186    fn write_to_inner<W: Write>(
187        &self,
188        w: &mut W,
189        registry: &mut SymbolRegistry,
190        interner: &Interner,
191        use_full_names: bool,
192    ) -> std::fmt::Result {
193        match self {
194            Term::Constant(name) => {
195                if use_full_names {
196                    write!(w, "{}", registry.get_symbol_full(*name, interner))
197                } else {
198                    write!(w, "{}", registry.get_symbol(*name, interner))
199                }
200            }
201            Term::Variable(name) => write!(w, "{}", interner.resolve(*name)),
202            Term::Function(name, args) => {
203                let fn_name = if use_full_names {
204                    registry.get_symbol_full(*name, interner)
205                } else {
206                    registry.get_symbol(*name, interner)
207                };
208                write!(w, "{}(", fn_name)?;
209                for (i, arg) in args.iter().enumerate() {
210                    if i > 0 {
211                        write!(w, ", ")?;
212                    }
213                    arg.write_to_inner(w, registry, interner, use_full_names)?;
214                }
215                write!(w, ")")
216            }
217            Term::Group(members) => {
218                for (i, m) in members.iter().enumerate() {
219                    if i > 0 {
220                        write!(w, " ⊕ ")?;
221                    }
222                    m.write_to_inner(w, registry, interner, use_full_names)?;
223                }
224                Ok(())
225            }
226            Term::Possessed { possessor, possessed } => {
227                let poss_name = if use_full_names {
228                    registry.get_symbol_full(*possessed, interner)
229                } else {
230                    registry.get_symbol(*possessed, interner)
231                };
232                write!(w, "Poss(")?;
233                possessor.write_to_inner(w, registry, interner, use_full_names)?;
234                write!(w, ", {})", poss_name)
235            }
236            Term::Sigma(predicate) => {
237                let pred_name = if use_full_names {
238                    registry.get_symbol_full(*predicate, interner)
239                } else {
240                    registry.get_symbol(*predicate, interner)
241                };
242                write!(w, "σ{}", pred_name)
243            }
244            Term::Intension(predicate) => {
245                // Use full word for intensional terms, not abbreviated symbol
246                let word = interner.resolve(*predicate);
247                let capitalized = word.chars().next()
248                    .map(|c| c.to_uppercase().collect::<String>() + &word[1..])
249                    .unwrap_or_default();
250                write!(w, "^{}", capitalized)
251            }
252            Term::Proposition(expr) => {
253                write!(w, "[")?;
254                expr.write_logic(w, registry, interner, &UnicodeFormatter)?;
255                write!(w, "]")
256            }
257            Term::Value { kind, unit, dimension: _ } => {
258                use crate::ast::NumberKind;
259                match kind {
260                    NumberKind::Real(r) => write!(w, "{}", r)?,
261                    NumberKind::Integer(i) => write!(w, "{}", i)?,
262                    NumberKind::Symbolic(s) => write!(w, "{}", interner.resolve(*s))?,
263                }
264                if let Some(u) = unit {
265                    write!(w, " {}", interner.resolve(*u))?;
266                }
267                Ok(())
268            }
269        }
270    }
271
272    /// Transpiles the term to a logic formula string.
273    pub fn transpile(&self, registry: &mut SymbolRegistry, interner: &Interner) -> String {
274        let mut buf = String::new();
275        let _ = self.write_to(&mut buf, registry, interner);
276        buf
277    }
278}
279
280/// Extracts top-level conjuncts from a discourse (sentences combined with AND).
281/// Returns a vector of individual sentence expressions.
282fn collect_discourse_conjuncts<'a>(expr: &'a LogicExpr<'a>) -> Vec<&'a LogicExpr<'a>> {
283    let mut conjuncts = Vec::new();
284    collect_discourse_conjuncts_inner(expr, &mut conjuncts);
285    conjuncts
286}
287
288fn collect_discourse_conjuncts_inner<'a>(expr: &'a LogicExpr<'a>, conjuncts: &mut Vec<&'a LogicExpr<'a>>) {
289    match expr {
290        LogicExpr::BinaryOp { left, op: TokenType::And, right } => {
291            // Recursively collect from both sides
292            collect_discourse_conjuncts_inner(left, conjuncts);
293            collect_discourse_conjuncts_inner(right, conjuncts);
294        }
295        _ => {
296            // This is a leaf sentence (not a top-level conjunction)
297            conjuncts.push(expr);
298        }
299    }
300}
301
302impl<'a> LogicExpr<'a> {
303    /// Transpile a discourse (multiple sentences) as numbered formulas.
304    /// If the expression is a top-level conjunction of sentences, formats as:
305    /// ```text
306    /// 1) formula1
307    /// 2) formula2
308    /// 3) formula3
309    /// ```
310    /// If it's a single sentence, just returns the formula without numbering.
311    pub fn transpile_discourse(
312        &self,
313        registry: &mut SymbolRegistry,
314        interner: &Interner,
315        format: OutputFormat,
316    ) -> String {
317        let conjuncts = collect_discourse_conjuncts(self);
318
319        if conjuncts.len() <= 1 {
320            // Single sentence - no numbering needed
321            return self.transpile(registry, interner, format);
322        }
323
324        // Multiple sentences - format as numbered list
325        let mut result = String::new();
326        for (i, conjunct) in conjuncts.iter().enumerate() {
327            if i > 0 {
328                result.push('\n');
329            }
330            let formula = conjunct.transpile(registry, interner, format);
331            result.push_str(&format!("{}) {}", i + 1, formula));
332        }
333        result
334    }
335
336    pub fn write_logic<W: Write, F: LogicFormatter>(
337        &self,
338        w: &mut W,
339        registry: &mut SymbolRegistry,
340        interner: &Interner,
341        fmt: &F,
342    ) -> std::fmt::Result {
343        match self {
344            LogicExpr::Predicate { name, args, world } => {
345                let pred_name = if fmt.use_full_names() {
346                    registry.get_symbol_full(*name, interner)
347                } else {
348                    registry.get_symbol(*name, interner)
349                };
350
351                // If formatter wants world arguments and we have one, append it
352                if fmt.include_world_arguments() {
353                    if let Some(w_sym) = world {
354                        // Build extended args with world variable appended
355                        let mut extended: Vec<Term> = args.to_vec();
356                        extended.push(Term::Variable(*w_sym));
357                        return fmt.write_predicate(w, &pred_name, &extended, registry, interner);
358                    }
359                }
360                fmt.write_predicate(w, &pred_name, args, registry, interner)
361            }
362
363            LogicExpr::Identity { left, right } => {
364                if fmt.wrap_identity() {
365                    write!(w, "(")?;
366                }
367                if fmt.preserve_case() {
368                    left.write_to_raw(w, interner)?;
369                } else if fmt.use_full_names() {
370                    left.write_to_full(w, registry, interner)?;
371                } else {
372                    left.write_to(w, registry, interner)?;
373                }
374                write!(w, "{}", fmt.identity())?;
375                if fmt.preserve_case() {
376                    right.write_to_raw(w, interner)?;
377                } else if fmt.use_full_names() {
378                    right.write_to_full(w, registry, interner)?;
379                } else {
380                    right.write_to(w, registry, interner)?;
381                }
382                if fmt.wrap_identity() {
383                    write!(w, ")")?;
384                }
385                Ok(())
386            }
387
388            LogicExpr::Metaphor { tenor, vehicle } => {
389                write!(w, "Metaphor(")?;
390                tenor.write_to(w, registry, interner)?;
391                write!(w, ", ")?;
392                vehicle.write_to(w, registry, interner)?;
393                write!(w, ")")
394            }
395
396            LogicExpr::Quantifier { kind, variable, body, .. } => {
397                let var_str = interner.resolve(*variable);
398
399                // In SimpleFOL mode, skip event quantifiers (variables named "e" or starting with "e" followed by digits)
400                if fmt.use_simple_events() && (var_str == "e" || var_str.starts_with("e") && var_str[1..].chars().all(|c| c.is_ascii_digit())) {
401                    return body.write_logic(w, registry, interner, fmt);
402                }
403
404                let mut body_buf = String::new();
405                body.write_logic(&mut body_buf, registry, interner, fmt)?;
406                write!(w, "{}", fmt.quantifier(kind, var_str, &body_buf))
407            }
408
409            LogicExpr::Categorical(data) => {
410                let s = if fmt.use_full_names() {
411                    fmt.sanitize(&data.subject.to_symbol_full(registry, interner))
412                } else {
413                    fmt.sanitize(&data.subject.to_symbol(registry, interner))
414                };
415                let p = if fmt.use_full_names() {
416                    fmt.sanitize(&data.predicate.to_symbol_full(registry, interner))
417                } else {
418                    fmt.sanitize(&data.predicate.to_symbol(registry, interner))
419                };
420                match (&data.quantifier, data.copula_negative) {
421                    (TokenType::All, false) => write!(w, "{} {} is {}", fmt.categorical_all(), s, p),
422                    (TokenType::No, false) => write!(w, "{} {} is {}", fmt.categorical_no(), s, p),
423                    (TokenType::Some, false) => write!(w, "{} {} is {}", fmt.categorical_some(), s, p),
424                    (TokenType::Some, true) => write!(w, "{} {} is {} {}", fmt.categorical_some(), s, fmt.categorical_not(), p),
425                    (TokenType::All, true) => write!(w, "{} {} is {} {}", fmt.categorical_some(), s, fmt.categorical_not(), p),
426                    _ => write!(w, "Invalid Syllogism"),
427                }
428            }
429
430            LogicExpr::Relation(data) => {
431                let s = if fmt.use_full_names() {
432                    data.subject.to_symbol_full(registry, interner)
433                } else {
434                    data.subject.to_symbol(registry, interner)
435                };
436                let v = if fmt.use_full_names() {
437                    fmt.sanitize(&registry.get_symbol_full(data.verb, interner))
438                } else {
439                    fmt.sanitize(&registry.get_symbol(data.verb, interner))
440                };
441                let o = if fmt.use_full_names() {
442                    data.object.to_symbol_full(registry, interner)
443                } else {
444                    data.object.to_symbol(registry, interner)
445                };
446                write!(w, "{}({}, {})", v, s, o)
447            }
448
449            LogicExpr::Modal { vector, operand } => {
450                let mut o = String::new();
451                operand.write_logic(&mut o, registry, interner, fmt)?;
452                write!(w, "{}", fmt.modal(vector.domain, vector.force, &o))
453            }
454
455            LogicExpr::BinaryOp { left, op, right } => {
456                let mut l = String::new();
457                let mut r = String::new();
458                left.write_logic(&mut l, registry, interner, fmt)?;
459                right.write_logic(&mut r, registry, interner, fmt)?;
460
461                // For conditionals (If), check if there are suppress_existential events
462                // that need universal quantification (DRS semantics for generic conditionals)
463                if matches!(op, TokenType::If | TokenType::Implies) {
464                    let events = collect_suppress_existential_events(self);
465                    if !events.is_empty() {
466                        // Wrap with universal quantifiers for each event variable
467                        let mut result = fmt.binary_op(op, &l, &r);
468                        for event_var in events.into_iter().rev() {
469                            let var_str = interner.resolve(event_var);
470                            result = fmt.quantifier(&QuantifierKind::Universal, var_str, &result);
471                        }
472                        return write!(w, "{}", result);
473                    }
474                }
475
476                write!(w, "{}", fmt.binary_op(op, &l, &r))
477            }
478
479            LogicExpr::UnaryOp { op, operand } => {
480                let mut o = String::new();
481                operand.write_logic(&mut o, registry, interner, fmt)?;
482                write!(w, "{}", fmt.unary_op(op, &o))
483            }
484
485            LogicExpr::Temporal { operator, body } => {
486                let mut inner = String::new();
487                body.write_logic(&mut inner, registry, interner, fmt)?;
488                write!(w, "{}", fmt.temporal(operator, &inner))
489            }
490
491            LogicExpr::TemporalBinary { operator, left, right } => {
492                let mut l = String::new();
493                let mut r = String::new();
494                left.write_logic(&mut l, registry, interner, fmt)?;
495                right.write_logic(&mut r, registry, interner, fmt)?;
496                write!(w, "{}", fmt.temporal_binary(operator, &l, &r))
497            }
498
499            LogicExpr::Aspectual { operator, body } => {
500                let mut inner = String::new();
501                body.write_logic(&mut inner, registry, interner, fmt)?;
502                write!(w, "{}", fmt.aspectual(operator, &inner))
503            }
504
505            LogicExpr::Voice { operator, body } => {
506                let mut inner = String::new();
507                body.write_logic(&mut inner, registry, interner, fmt)?;
508                write!(w, "{}", fmt.voice(operator, &inner))
509            }
510
511            LogicExpr::Question { wh_variable, body } => {
512                let mut body_str = String::new();
513                body.write_logic(&mut body_str, registry, interner, fmt)?;
514                write!(w, "{}", fmt.lambda(interner.resolve(*wh_variable), &body_str))
515            }
516
517            LogicExpr::YesNoQuestion { body } => {
518                write!(w, "?")?;
519                body.write_logic(w, registry, interner, fmt)
520            }
521
522            LogicExpr::Atom(s) => {
523                let name = if fmt.preserve_case() {
524                    interner.resolve(*s).to_string()
525                } else if fmt.use_full_names() {
526                    registry.get_symbol_full(*s, interner)
527                } else {
528                    registry.get_symbol(*s, interner)
529                };
530                write!(w, "{}", fmt.sanitize(&name))
531            }
532
533            LogicExpr::Lambda { variable, body } => {
534                let mut b = String::new();
535                body.write_logic(&mut b, registry, interner, fmt)?;
536                write!(w, "{}", fmt.lambda(interner.resolve(*variable), &b))
537            }
538
539            LogicExpr::App { function, argument } => {
540                write!(w, "(")?;
541                function.write_logic(w, registry, interner, fmt)?;
542                write!(w, ")(")?;
543                argument.write_logic(w, registry, interner, fmt)?;
544                write!(w, ")")
545            }
546
547            LogicExpr::Intensional { operator, content } => {
548                write!(w, "{}[", fmt.sanitize(&registry.get_symbol(*operator, interner)))?;
549                content.write_logic(w, registry, interner, fmt)?;
550                write!(w, "]")
551            }
552
553            LogicExpr::Event { predicate, adverbs } => {
554                let mut pred_str = String::new();
555                predicate.write_logic(&mut pred_str, registry, interner, fmt)?;
556                let adverb_preds: Vec<String> = adverbs
557                    .iter()
558                    .map(|a| format!("{}(e)", fmt.sanitize(&registry.get_symbol(*a, interner))))
559                    .collect();
560                write!(w, "{}", fmt.event_quantifier(&pred_str, &adverb_preds))
561            }
562
563            LogicExpr::NeoEvent(data) => {
564                use crate::ast::{QuantifierKind, ThematicRole};
565
566                if fmt.use_simple_events() {
567                    write!(w, "{}", registry.get_symbol_full(data.verb, interner))?;
568                    write!(w, "(")?;
569                    let mut first = true;
570                    for (role, term) in data.roles.iter() {
571                        // Include core thematic roles in SimpleFOL output
572                        if matches!(role, ThematicRole::Agent | ThematicRole::Patient | ThematicRole::Theme | ThematicRole::Goal | ThematicRole::Location) {
573                            if !first {
574                                write!(w, ", ")?;
575                            }
576                            first = false;
577                            term.write_to_full(w, registry, interner)?;
578                        }
579                    }
580                    write!(w, ")")
581                } else {
582                    let e = interner.resolve(data.event_var);
583                    let mut body = String::new();
584
585                    // Get world argument suffix if Kripke format
586                    let world_suffix = if fmt.include_world_arguments() {
587                        data.world.map(|w| format!(", {}", interner.resolve(w))).unwrap_or_default()
588                    } else {
589                        String::new()
590                    };
591
592                    write_capitalized(&mut body, interner.resolve(data.verb))?;
593                    write!(body, "({}{})", e, world_suffix)?;
594                    for (role, term) in data.roles.iter() {
595                        let role_str = match role {
596                            ThematicRole::Agent => "Agent",
597                            ThematicRole::Patient => "Patient",
598                            ThematicRole::Theme => "Theme",
599                            ThematicRole::Recipient => "Recipient",
600                            ThematicRole::Goal => "Goal",
601                            ThematicRole::Source => "Source",
602                            ThematicRole::Instrument => "Instrument",
603                            ThematicRole::Location => "Location",
604                            ThematicRole::Time => "Time",
605                            ThematicRole::Manner => "Manner",
606                        };
607                        write!(body, " {} {}({}, ", fmt.and(), role_str, e)?;
608                        if fmt.use_full_names() {
609                            term.write_to_full(&mut body, registry, interner)?;
610                        } else {
611                            term.write_to(&mut body, registry, interner)?;
612                        }
613                        write!(body, "{})", world_suffix)?;
614                    }
615                    for mod_sym in data.modifiers.iter() {
616                        write!(body, " {} ", fmt.and())?;
617                        write_capitalized(&mut body, interner.resolve(*mod_sym))?;
618                        write!(body, "({}{})", e, world_suffix)?;
619                    }
620                    if data.suppress_existential {
621                        // Event var will be bound by outer ∀ from DRS (generic conditionals)
622                        write!(w, "{}", body)
623                    } else {
624                        // Normal case: emit ∃e(...)
625                        write!(w, "{}", fmt.quantifier(&QuantifierKind::Existential, e, &body))
626                    }
627                }
628            }
629
630            LogicExpr::Imperative { action } => {
631                write!(w, "!")?;
632                action.write_logic(w, registry, interner, fmt)
633            }
634
635            LogicExpr::SpeechAct { performer, act_type, content } => {
636                write!(w, "SpeechAct({}, {}, ", interner.resolve(*act_type), fmt.sanitize(&registry.get_symbol(*performer, interner)))?;
637                content.write_logic(w, registry, interner, fmt)?;
638                write!(w, ")")
639            }
640
641            LogicExpr::Counterfactual { antecedent, consequent } => {
642                let mut a = String::new();
643                let mut c = String::new();
644                antecedent.write_logic(&mut a, registry, interner, fmt)?;
645                consequent.write_logic(&mut c, registry, interner, fmt)?;
646                write!(w, "{}", fmt.counterfactual(&a, &c))
647            }
648
649            LogicExpr::Causal { effect, cause } => {
650                write!(w, "Cause(")?;
651                cause.write_logic(w, registry, interner, fmt)?;
652                write!(w, ", ")?;
653                effect.write_logic(w, registry, interner, fmt)?;
654                write!(w, ")")
655            }
656
657            LogicExpr::Comparative { adjective, subject, object, difference } => {
658                let adj = interner.resolve(*adjective);
659                let mut subj_buf = String::new();
660                if fmt.preserve_case() {
661                    subject.write_to_raw(&mut subj_buf, interner)?;
662                } else {
663                    subject.write_to(&mut subj_buf, registry, interner)?;
664                }
665                let mut obj_buf = String::new();
666                if fmt.preserve_case() {
667                    object.write_to_raw(&mut obj_buf, interner)?;
668                } else {
669                    object.write_to(&mut obj_buf, registry, interner)?;
670                }
671                let diff_str = if let Some(diff) = difference {
672                    let mut diff_buf = String::new();
673                    if fmt.preserve_case() {
674                        diff.write_to_raw(&mut diff_buf, interner)?;
675                    } else {
676                        diff.write_to(&mut diff_buf, registry, interner)?;
677                    }
678                    Some(diff_buf)
679                } else {
680                    None
681                };
682                fmt.write_comparative(w, adj, &subj_buf, &obj_buf, diff_str.as_deref())
683            }
684
685            LogicExpr::Superlative { adjective, subject, domain } => {
686                let mut s = String::new();
687                subject.write_to(&mut s, registry, interner)?;
688                let mut d = String::new();
689                write_capitalized(&mut d, interner.resolve(*domain))?;
690                let comp = format!("{}er", interner.resolve(*adjective));
691                write!(w, "{}", fmt.superlative(&comp, &d, &s))
692            }
693
694            LogicExpr::Scopal { operator, body } => {
695                write!(w, "{}(", interner.resolve(*operator))?;
696                body.write_logic(w, registry, interner, fmt)?;
697                write!(w, ")")
698            }
699
700            LogicExpr::TemporalAnchor { anchor, body } => {
701                write!(w, "{}(", interner.resolve(*anchor))?;
702                body.write_logic(w, registry, interner, fmt)?;
703                write!(w, ")")
704            }
705
706            LogicExpr::Control { verb, subject, object, infinitive } => {
707                write!(w, "{}(", fmt.sanitize(&registry.get_symbol(*verb, interner)))?;
708                subject.write_to(w, registry, interner)?;
709                if let Some(obj) = object {
710                    write!(w, ", ")?;
711                    obj.write_to(w, registry, interner)?;
712                }
713                write!(w, ", ")?;
714                infinitive.write_logic(w, registry, interner, fmt)?;
715                write!(w, ")")
716            }
717
718            LogicExpr::Presupposition { assertion, presupposition } => {
719                assertion.write_logic(w, registry, interner, fmt)?;
720                write!(w, " [Presup: ")?;
721                presupposition.write_logic(w, registry, interner, fmt)?;
722                write!(w, "]")
723            }
724
725            LogicExpr::Focus { kind, focused, scope } => {
726                use crate::token::FocusKind;
727                let prefix = match kind {
728                    FocusKind::Only => "Only",
729                    FocusKind::Even => "Even",
730                    FocusKind::Just => "Just",
731                };
732                write!(w, "{}(", prefix)?;
733                focused.write_to(w, registry, interner)?;
734                write!(w, ", ")?;
735                scope.write_logic(w, registry, interner, fmt)?;
736                write!(w, ")")
737            }
738
739            LogicExpr::Distributive { predicate } => {
740                write!(w, "*")?;
741                predicate.write_logic(w, registry, interner, fmt)
742            }
743
744            LogicExpr::GroupQuantifier { group_var, count, member_var, restriction, body } => {
745                let g = interner.resolve(*group_var);
746                let x = interner.resolve(*member_var);
747
748                // ∃g(Group(g) ∧ Count(g,n) ∧ ∀x(Member(x,g) → restriction) ∧ body)
749                write!(w, "{}{}(Group({}) {} Count({}, {}) {} {}{}(Member({}, {}) {} ",
750                    fmt.existential(), g, g,
751                    fmt.and(), g, count,
752                    fmt.and(), fmt.universal(), x, x, g, fmt.implies())?;
753
754                restriction.write_logic(w, registry, interner, fmt)?;
755
756                write!(w, ") {} ", fmt.and())?;
757
758                body.write_logic(w, registry, interner, fmt)?;
759
760                write!(w, ")")
761            }
762        }
763    }
764
765    /// Transpiles to a logic formula string using a custom formatter.
766    pub fn transpile_with<F: LogicFormatter>(
767        &self,
768        registry: &mut SymbolRegistry,
769        interner: &Interner,
770        fmt: &F,
771    ) -> String {
772        let mut buf = String::new();
773        let _ = self.write_logic(&mut buf, registry, interner, fmt);
774        buf
775    }
776
777    /// Transpiles to a logic formula string in the specified output format.
778    ///
779    /// # Formats
780    ///
781    /// - [`OutputFormat::Unicode`]: ∀x(Cat(x) → Sleeps(x))
782    /// - [`OutputFormat::LaTeX`]: `\forall x(Cat(x) \to Sleeps(x))`
783    /// - [`OutputFormat::SimpleFOL`]: Ax(Cat(x) -> Sleeps(x))
784    /// - [`OutputFormat::Kripke`]: □(P) @w0
785    pub fn transpile(
786        &self,
787        registry: &mut SymbolRegistry,
788        interner: &Interner,
789        format: OutputFormat,
790    ) -> String {
791        match format {
792            OutputFormat::Unicode => self.transpile_with(registry, interner, &UnicodeFormatter),
793            OutputFormat::LaTeX => self.transpile_with(registry, interner, &LatexFormatter),
794            OutputFormat::SimpleFOL => self.transpile_with(registry, interner, &SimpleFOLFormatter),
795            OutputFormat::Kripke => self.transpile_with(registry, interner, &KripkeFormatter),
796        }
797    }
798
799    /// Transpiles using a [`TranspileContext`] and custom formatter.
800    pub fn transpile_ctx<F: LogicFormatter>(
801        &self,
802        ctx: &mut TranspileContext<'_>,
803        fmt: &F,
804    ) -> String {
805        self.transpile_with(ctx.registry, ctx.interner, fmt)
806    }
807
808    /// Transpiles to Unicode format using a [`TranspileContext`].
809    pub fn transpile_ctx_unicode(&self, ctx: &mut TranspileContext<'_>) -> String {
810        self.transpile_ctx(ctx, &UnicodeFormatter)
811    }
812
813    /// Transpiles to LaTeX format using a [`TranspileContext`].
814    pub fn transpile_ctx_latex(&self, ctx: &mut TranspileContext<'_>) -> String {
815        self.transpile_ctx(ctx, &LatexFormatter)
816    }
817}