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