Skip to main content

logicaffeine_language/
debug.rs

1//! Debug display utilities for AST types with interner access.
2//!
3//! This module provides the [`DisplayWith`] trait for formatting AST types that
4//! contain interned symbols. Since symbols are just integer IDs, displaying them
5//! requires access to the interner to resolve the actual strings.
6//!
7//! Use the `.with(interner)` method to create a displayable wrapper:
8//!
9//! ```
10//! use logicaffeine_base::Interner;
11//! use logicaffeine_language::ast::{LogicExpr, Term};
12//! use logicaffeine_language::debug::DisplayWith;
13//!
14//! let mut interner = Interner::new();
15//! let mortal = interner.intern("Mortal");
16//! let socrates = interner.intern("Socrates");
17//! let args = [Term::Constant(socrates)];
18//! let expr = LogicExpr::Predicate { name: mortal, args: &args, world: None };
19//! assert_eq!(expr.with(&interner).to_string(), "Mortal(Socrates)");
20//! ```
21
22use std::fmt;
23
24use crate::ast::{
25    AspectOperator, BinaryTemporalOp, LogicExpr, NounPhrase, QuantifierKind, TemporalOperator,
26    VoiceOperator, Term,
27};
28use logicaffeine_base::{Interner, Symbol};
29use crate::token::TokenType;
30
31/// Trait for formatting types that require an interner for symbol resolution.
32pub trait DisplayWith {
33    fn fmt_with(&self, interner: &Interner, f: &mut fmt::Formatter<'_>) -> fmt::Result;
34
35    fn with<'a>(&'a self, interner: &'a Interner) -> WithInterner<'a, Self> {
36        WithInterner {
37            target: self,
38            interner,
39        }
40    }
41}
42
43pub struct WithInterner<'a, T: ?Sized> {
44    pub target: &'a T,
45    pub interner: &'a Interner,
46}
47
48impl<'a, T: DisplayWith> fmt::Display for WithInterner<'a, T> {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        self.target.fmt_with(self.interner, f)
51    }
52}
53
54pub struct DebugWorld<'a, T: ?Sized> {
55    pub target: &'a T,
56    pub interner: &'a Interner,
57}
58
59impl<'a, T: DisplayWith> fmt::Debug for DebugWorld<'a, T> {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        self.target.fmt_with(self.interner, f)
62    }
63}
64
65impl DisplayWith for Symbol {
66    fn fmt_with(&self, interner: &Interner, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        write!(f, "{}", interner.resolve(*self))
68    }
69}
70
71impl<'a> DisplayWith for Term<'a> {
72    fn fmt_with(&self, interner: &Interner, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            Term::Constant(s) => write!(f, "{}", interner.resolve(*s)),
75            Term::Variable(s) => write!(f, "{}", interner.resolve(*s)),
76            Term::Function(name, args) => {
77                write!(f, "{}(", interner.resolve(*name))?;
78                for (i, arg) in args.iter().enumerate() {
79                    if i > 0 {
80                        write!(f, ", ")?;
81                    }
82                    arg.fmt_with(interner, f)?;
83                }
84                write!(f, ")")
85            }
86            Term::Group(members) => {
87                write!(f, "[")?;
88                for (i, m) in members.iter().enumerate() {
89                    if i > 0 {
90                        write!(f, " ⊕ ")?;
91                    }
92                    m.fmt_with(interner, f)?;
93                }
94                write!(f, "]")
95            }
96            Term::Possessed { possessor, possessed } => {
97                possessor.fmt_with(interner, f)?;
98                write!(f, ".{}", interner.resolve(*possessed))
99            }
100            Term::Sigma(predicate) => {
101                write!(f, "σx.{}(x)", interner.resolve(*predicate))
102            }
103            Term::Intension(predicate) => {
104                write!(f, "^{}", interner.resolve(*predicate))
105            }
106            Term::Kind(kind) => {
107                write!(f, "^{}", interner.resolve(*kind))
108            }
109            Term::Proposition(expr) => {
110                write!(f, "[{:?}]", expr)
111            }
112            Term::Value { kind, unit, dimension } => {
113                use crate::ast::NumberKind;
114                match kind {
115                    NumberKind::Real(r) => write!(f, "{}", r)?,
116                    NumberKind::Integer(i) => write!(f, "{}", i)?,
117                    NumberKind::Symbolic(s) => write!(f, "{}", interner.resolve(*s))?,
118                }
119                if let Some(u) = unit {
120                    write!(f, " {}", interner.resolve(*u))?;
121                }
122                if let Some(d) = dimension {
123                    write!(f, " [{:?}]", d)?;
124                }
125                Ok(())
126            }
127        }
128    }
129}
130
131impl<'a> DisplayWith for NounPhrase<'a> {
132    fn fmt_with(&self, interner: &Interner, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        if let Some(def) = &self.definiteness {
134            write!(f, "{:?} ", def)?;
135        }
136        for adj in self.adjectives {
137            write!(f, "{} ", interner.resolve(*adj))?;
138        }
139        write!(f, "{}", interner.resolve(self.noun))
140    }
141}
142
143impl<'a> DisplayWith for LogicExpr<'a> {
144    fn fmt_with(&self, interner: &Interner, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        match self {
146            LogicExpr::Predicate { name, args, .. } => {
147                write!(f, "{}(", interner.resolve(*name))?;
148                for (i, arg) in args.iter().enumerate() {
149                    if i > 0 {
150                        write!(f, ", ")?;
151                    }
152                    arg.fmt_with(interner, f)?;
153                }
154                write!(f, ")")
155            }
156            LogicExpr::Identity { left, right } => {
157                left.fmt_with(interner, f)?;
158                write!(f, " = ")?;
159                right.fmt_with(interner, f)
160            }
161            LogicExpr::Metaphor { tenor, vehicle } => {
162                write!(f, "Metaphor(")?;
163                tenor.fmt_with(interner, f)?;
164                write!(f, ", ")?;
165                vehicle.fmt_with(interner, f)?;
166                write!(f, ")")
167            }
168            LogicExpr::Quantifier { kind, variable, body, .. } => {
169                let q = match kind {
170                    QuantifierKind::Universal => "∀",
171                    QuantifierKind::Existential => "∃",
172                    QuantifierKind::Most => "MOST",
173                    QuantifierKind::Few => "FEW",
174                    QuantifierKind::Many => "MANY",
175                    QuantifierKind::Generic => "Gen",
176                    QuantifierKind::Cardinal(n) => return write!(f, "∃={}{}.{}", n, interner.resolve(*variable), body.with(interner)),
177                    QuantifierKind::AtLeast(n) => return write!(f, "∃≥{}{}.{}", n, interner.resolve(*variable), body.with(interner)),
178                    QuantifierKind::AtMost(n) => return write!(f, "∃≤{}{}.{}", n, interner.resolve(*variable), body.with(interner)),
179                };
180                write!(f, "{}{}.{}", q, interner.resolve(*variable), body.with(interner))
181            }
182            LogicExpr::Categorical(data) => {
183                let q = match &data.quantifier {
184                    TokenType::All => "All",
185                    TokenType::Some => "Some",
186                    TokenType::No => "No",
187                    _ => "?",
188                };
189                let cop = if data.copula_negative { "are not" } else { "are" };
190                write!(f, "{} {} {} {}", q, data.subject.with(interner), cop, data.predicate.with(interner))
191            }
192            LogicExpr::Relation(data) => {
193                write!(f, "{}({}, {})", interner.resolve(data.verb), data.subject.with(interner), data.object.with(interner))
194            }
195            LogicExpr::Modal { vector, operand } => {
196                // Boundary must match the formatter (formatter.rs) and the Kripke
197                // lowering (semantics/kripke.rs): force == 0.5 (ability/epistemic
198                // can/could/may) is the weak/possibility side (◇), not necessity.
199                let op = match (vector.domain, vector.force > 0.5) {
200                    (crate::ast::ModalDomain::Alethic, true) => "□",
201                    (crate::ast::ModalDomain::Alethic, false) => "◇",
202                    (crate::ast::ModalDomain::Deontic, true) => "O",
203                    (crate::ast::ModalDomain::Deontic, false) => "P",
204                    (crate::ast::ModalDomain::Temporal, _) => "Temporal",
205                };
206                write!(f, "{}({})", op, operand.with(interner))
207            }
208            LogicExpr::Temporal { operator, body } => {
209                let op = match operator {
210                    TemporalOperator::Past => "P",
211                    TemporalOperator::Future => "F",
212                    TemporalOperator::Always => "G",
213                    TemporalOperator::Eventually
214                    | TemporalOperator::BoundedEventually(_) => "F",
215                    TemporalOperator::Next => "X",
216                };
217                write!(f, "{}({})", op, body.with(interner))
218            }
219            LogicExpr::Aspectual { operator, body } => {
220                let op = match operator {
221                    AspectOperator::Progressive => "PROG",
222                    AspectOperator::Perfect => "PERF",
223                    AspectOperator::Habitual => "HAB",
224                    AspectOperator::Iterative => "ITER",
225                };
226                write!(f, "{}({})", op, body.with(interner))
227            }
228            LogicExpr::Voice { operator, body } => {
229                let op = match operator {
230                    VoiceOperator::Passive => "PASS",
231                };
232                write!(f, "{}({})", op, body.with(interner))
233            }
234            LogicExpr::BinaryOp { left, op, right } => {
235                let sym = match op {
236                    TokenType::And => "∧",
237                    TokenType::Or => "∨",
238                    TokenType::If => "→",
239                    TokenType::Iff => "↔",
240                    _ => "?",
241                };
242                write!(f, "({} {} {})", left.with(interner), sym, right.with(interner))
243            }
244            LogicExpr::UnaryOp { op, operand } => {
245                let sym = match op {
246                    TokenType::Not => "¬",
247                    _ => "?",
248                };
249                write!(f, "{}({})", sym, operand.with(interner))
250            }
251            LogicExpr::Question { wh_variable, body } => {
252                write!(f, "?{}.{}", interner.resolve(*wh_variable), body.with(interner))
253            }
254            LogicExpr::YesNoQuestion { body } => {
255                write!(f, "?{}", body.with(interner))
256            }
257            LogicExpr::Atom(s) => write!(f, "{}", interner.resolve(*s)),
258            LogicExpr::Lambda { variable, body } => {
259                write!(f, "λ{}.{}", interner.resolve(*variable), body.with(interner))
260            }
261            LogicExpr::App { function, argument } => {
262                write!(f, "({})({})", function.with(interner), argument.with(interner))
263            }
264            LogicExpr::Intensional { operator, content } => {
265                write!(f, "{}({})", interner.resolve(*operator), content.with(interner))
266            }
267            LogicExpr::Event { predicate, adverbs } => {
268                predicate.fmt_with(interner, f)?;
269                for adv in *adverbs {
270                    write!(f, "[{}]", interner.resolve(*adv))?;
271                }
272                Ok(())
273            }
274            LogicExpr::NeoEvent(data) => {
275                write!(f, "∃{}({}({})", interner.resolve(data.event_var), interner.resolve(data.verb), interner.resolve(data.event_var))?;
276                for (role, term) in data.roles.iter() {
277                    write!(f, " ∧ {:?}({}, {})", role, interner.resolve(data.event_var), term.with(interner))?;
278                }
279                for mod_sym in data.modifiers.iter() {
280                    write!(f, " ∧ {}({})", interner.resolve(*mod_sym), interner.resolve(data.event_var))?;
281                }
282                write!(f, ")")
283            }
284            LogicExpr::Imperative { action } => {
285                write!(f, "!({})", action.with(interner))
286            }
287            LogicExpr::Exclamative { degree_var, body } => {
288                write!(f, "Exclaim(∃{}({} ∧ {} ≫ θ))",
289                    interner.resolve(*degree_var), body.with(interner), interner.resolve(*degree_var))
290            }
291            LogicExpr::Optative { wish } => {
292                write!(f, "Wish(Speaker, ⟨{}⟩)", wish.with(interner))
293            }
294            LogicExpr::Implicature { assertion, implicature } => {
295                write!(f, "{} +> Implicature({})", assertion.with(interner), implicature.with(interner))
296            }
297            LogicExpr::SpeechAct { performer, act_type, content } => {
298                write!(f, "{}:{}({})", interner.resolve(*performer), interner.resolve(*act_type), content.with(interner))
299            }
300            LogicExpr::Counterfactual { antecedent, consequent } => {
301                write!(f, "({} □→ {})", antecedent.with(interner), consequent.with(interner))
302            }
303            LogicExpr::Causal { effect, cause } => {
304                write!(f, "Cause({}, {})", cause.with(interner), effect.with(interner))
305            }
306            LogicExpr::Concessive { main, concession } => {
307                write!(f, "{} ∧ Concessive({})", main.with(interner), concession.with(interner))
308            }
309            LogicExpr::Comparative { adjective, subject, object, difference, .. } => {
310                if let Some(diff) = difference {
311                    write!(f, "{}({}, {}, by: {})", interner.resolve(*adjective), subject.with(interner), object.with(interner), diff.with(interner))
312                } else {
313                    write!(f, "{}({}, {})", interner.resolve(*adjective), subject.with(interner), object.with(interner))
314                }
315            }
316            LogicExpr::Superlative { adjective, subject, domain } => {
317                write!(f, "MOST-{}({}, {})", interner.resolve(*adjective), subject.with(interner), interner.resolve(*domain))
318            }
319            LogicExpr::Scopal { operator, body } => {
320                write!(f, "{}({})", interner.resolve(*operator), body.with(interner))
321            }
322            LogicExpr::Control { verb, subject, object, infinitive } => {
323                write!(f, "{}(", interner.resolve(*verb))?;
324                subject.fmt_with(interner, f)?;
325                if let Some(obj) = object {
326                    write!(f, ", ")?;
327                    obj.fmt_with(interner, f)?;
328                }
329                write!(f, ", {})", infinitive.with(interner))
330            }
331            LogicExpr::Presupposition { assertion, presupposition } => {
332                write!(f, "[{} | {}]", assertion.with(interner), presupposition.with(interner))
333            }
334            LogicExpr::Focus { kind, focused, scope } => {
335                let k = match kind {
336                    crate::token::FocusKind::Only => "ONLY",
337                    crate::token::FocusKind::Even => "EVEN",
338                    crate::token::FocusKind::Just => "JUST",
339                    crate::token::FocusKind::Cleft => "CLEFT",
340                };
341                write!(f, "{}[", k)?;
342                focused.fmt_with(interner, f)?;
343                write!(f, "]({})", scope.with(interner))
344            }
345            LogicExpr::TemporalAnchor { anchor, body } => {
346                write!(f, "@{}({})", interner.resolve(*anchor), body.with(interner))
347            }
348            LogicExpr::Distributive { predicate } => {
349                write!(f, "*")?;
350                predicate.fmt_with(interner, f)
351            }
352            LogicExpr::GroupQuantifier { group_var, count, member_var, restriction, body } => {
353                write!(
354                    f,
355                    "∃{}(Group({}) ∧ Count({}, {}) ∧ ∀{}(Member({}, {}) → ",
356                    interner.resolve(*group_var),
357                    interner.resolve(*group_var),
358                    interner.resolve(*group_var),
359                    count,
360                    interner.resolve(*member_var),
361                    interner.resolve(*member_var),
362                    interner.resolve(*group_var)
363                )?;
364                restriction.fmt_with(interner, f)?;
365                write!(f, ") ∧ ")?;
366                body.fmt_with(interner, f)?;
367                write!(f, ")")
368            }
369            LogicExpr::TemporalBinary { operator, left, right } => {
370                let op = match operator {
371                    BinaryTemporalOp::Until => "U",
372                    BinaryTemporalOp::Release => "R",
373                    BinaryTemporalOp::WeakUntil => "W",
374                };
375                write!(f, "({} {} {})", left.with(interner), op, right.with(interner))
376            }
377        }
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384    use logicaffeine_base::Arena;
385
386    #[test]
387    fn symbol_display_with_interner() {
388        let mut interner = Interner::new();
389        let sym = interner.intern("Socrates");
390        assert_eq!(sym.with(&interner).to_string(), "Socrates");
391    }
392
393    #[test]
394    fn symbol_empty_displays_empty() {
395        let interner = Interner::new();
396        assert_eq!(Symbol::EMPTY.with(&interner).to_string(), "");
397    }
398
399    #[test]
400    fn term_constant_display() {
401        let mut interner = Interner::new();
402        let sym = interner.intern("John");
403        let term = Term::Constant(sym);
404        assert_eq!(term.with(&interner).to_string(), "John");
405    }
406
407    #[test]
408    fn term_variable_display() {
409        let mut interner = Interner::new();
410        let x = interner.intern("x");
411        let term = Term::Variable(x);
412        assert_eq!(term.with(&interner).to_string(), "x");
413    }
414
415    #[test]
416    fn term_function_display() {
417        let mut interner = Interner::new();
418        let term_arena: Arena<Term> = Arena::new();
419        let f = interner.intern("father");
420        let j = interner.intern("John");
421        let term = Term::Function(f, term_arena.alloc_slice([Term::Constant(j)]));
422        assert_eq!(term.with(&interner).to_string(), "father(John)");
423    }
424
425    #[test]
426    fn term_group_display() {
427        let mut interner = Interner::new();
428        let term_arena: Arena<Term> = Arena::new();
429        let j = interner.intern("John");
430        let m = interner.intern("Mary");
431        let term = Term::Group(term_arena.alloc_slice([Term::Constant(j), Term::Constant(m)]));
432        assert_eq!(term.with(&interner).to_string(), "[John ⊕ Mary]");
433    }
434
435    #[test]
436    fn term_possessed_display() {
437        let mut interner = Interner::new();
438        let term_arena: Arena<Term> = Arena::new();
439        let j = interner.intern("John");
440        let dog = interner.intern("dog");
441        let term = Term::Possessed {
442            possessor: term_arena.alloc(Term::Constant(j)),
443            possessed: dog,
444        };
445        assert_eq!(term.with(&interner).to_string(), "John.dog");
446    }
447
448    #[test]
449    fn expr_predicate_display() {
450        let mut interner = Interner::new();
451        let term_arena: Arena<Term> = Arena::new();
452        let mortal = interner.intern("Mortal");
453        let x = interner.intern("x");
454        let expr = LogicExpr::Predicate {
455            name: mortal,
456            args: term_arena.alloc_slice([Term::Variable(x)]),
457            world: None,
458        };
459        assert_eq!(expr.with(&interner).to_string(), "Mortal(x)");
460    }
461
462    #[test]
463    fn expr_quantifier_display() {
464        let mut interner = Interner::new();
465        let expr_arena: Arena<LogicExpr> = Arena::new();
466        let term_arena: Arena<Term> = Arena::new();
467        let x = interner.intern("x");
468        let mortal = interner.intern("Mortal");
469        let body = expr_arena.alloc(LogicExpr::Predicate {
470            name: mortal,
471            args: term_arena.alloc_slice([Term::Variable(x)]),
472            world: None,
473        });
474        let expr = LogicExpr::Quantifier {
475            kind: QuantifierKind::Universal,
476            variable: x,
477            body,
478            island_id: 0,
479        };
480        assert_eq!(expr.with(&interner).to_string(), "∀x.Mortal(x)");
481    }
482
483    #[test]
484    fn expr_atom_display() {
485        let mut interner = Interner::new();
486        let p = interner.intern("P");
487        let expr = LogicExpr::Atom(p);
488        assert_eq!(expr.with(&interner).to_string(), "P");
489    }
490
491    #[test]
492    fn expr_binary_op_display() {
493        let mut interner = Interner::new();
494        let expr_arena: Arena<LogicExpr> = Arena::new();
495        let p = interner.intern("P");
496        let q = interner.intern("Q");
497        let expr = LogicExpr::BinaryOp {
498            left: expr_arena.alloc(LogicExpr::Atom(p)),
499            op: TokenType::And,
500            right: expr_arena.alloc(LogicExpr::Atom(q)),
501        };
502        assert_eq!(expr.with(&interner).to_string(), "(P ∧ Q)");
503    }
504
505    #[test]
506    fn expr_lambda_display() {
507        let mut interner = Interner::new();
508        let expr_arena: Arena<LogicExpr> = Arena::new();
509        let x = interner.intern("x");
510        let p = interner.intern("P");
511        let expr = LogicExpr::Lambda {
512            variable: x,
513            body: expr_arena.alloc(LogicExpr::Atom(p)),
514        };
515        assert_eq!(expr.with(&interner).to_string(), "λx.P");
516    }
517
518    #[test]
519    fn debug_world_works_with_dbg_pattern() {
520        let mut interner = Interner::new();
521        let sym = interner.intern("test");
522        let term = Term::Constant(sym);
523        let debug_str = format!("{:?}", DebugWorld { target: &term, interner: &interner });
524        assert!(debug_str.contains("test"));
525    }
526
527    #[test]
528    fn expr_temporal_display() {
529        let mut interner = Interner::new();
530        let expr_arena: Arena<LogicExpr> = Arena::new();
531        let p = interner.intern("Run");
532        let expr = LogicExpr::Temporal {
533            operator: TemporalOperator::Past,
534            body: expr_arena.alloc(LogicExpr::Atom(p)),
535        };
536        assert_eq!(expr.with(&interner).to_string(), "P(Run)");
537    }
538
539    #[test]
540    fn expr_modal_display() {
541        let mut interner = Interner::new();
542        let expr_arena: Arena<LogicExpr> = Arena::new();
543        let p = interner.intern("Rain");
544        let expr = LogicExpr::Modal {
545            vector: crate::ast::ModalVector {
546                domain: crate::ast::ModalDomain::Alethic,
547                force: 1.0,
548                flavor: crate::ast::ModalFlavor::Root, modal_base: None, ordering_source: None
549            },
550            operand: expr_arena.alloc(LogicExpr::Atom(p)),
551        };
552        assert_eq!(expr.with(&interner).to_string(), "□(Rain)");
553    }
554
555    /// BUG-033: force == 0.5 (ability/epistemic can/could/may) is the
556    /// possibility side (◇), as the formatter and Kripke lowering both treat it.
557    /// The debug display must agree, not render □.
558    #[test]
559    fn expr_modal_display_force_half_is_possibility_not_necessity() {
560        let mut interner = Interner::new();
561        let expr_arena: Arena<LogicExpr> = Arena::new();
562        let fly = interner.intern("Fly");
563        let expr = LogicExpr::Modal {
564            vector: crate::ast::ModalVector {
565                domain: crate::ast::ModalDomain::Alethic,
566                force: 0.5,
567                flavor: crate::ast::ModalFlavor::Root,
568                modal_base: None,
569                ordering_source: None,
570            },
571            operand: expr_arena.alloc(LogicExpr::Atom(fly)),
572        };
573        assert_eq!(expr.with(&interner).to_string(), "◇(Fly)");
574    }
575}