Skip to main content

logicaffeine_language/
visitor.rs

1//! AST visitor pattern for traversing logical expressions.
2//!
3//! This module provides a visitor trait for walking the AST without mutation.
4//! It follows the standard visitor pattern with `walk_*` functions that handle
5//! recursive traversal and `visit_*` methods that can be overridden.
6//!
7//! # Usage
8//!
9//! Implement [`Visitor`] and override the `visit_*` methods you need:
10//!
11//! ```no_run
12//! # use logicaffeine_base::Symbol;
13//! # use logicaffeine_language::ast::Term;
14//! # use logicaffeine_language::visitor::{Visitor, walk_term};
15//! struct VariableCollector {
16//!     vars: Vec<Symbol>,
17//! }
18//!
19//! impl<'a> Visitor<'a> for VariableCollector {
20//!     fn visit_term(&mut self, term: &'a Term<'a>) {
21//!         if let Term::Variable(sym) = term {
22//!             self.vars.push(*sym);
23//!         }
24//!         walk_term(self, term);
25//!     }
26//! }
27//! ```
28
29use crate::ast::{LogicExpr, NounPhrase, Term};
30
31/// Trait for visiting AST nodes.
32pub trait Visitor<'a>: Sized {
33    fn visit_expr(&mut self, expr: &'a LogicExpr<'a>) {
34        walk_expr(self, expr);
35    }
36
37    fn visit_term(&mut self, term: &'a Term<'a>) {
38        walk_term(self, term);
39    }
40
41    fn visit_np(&mut self, np: &'a NounPhrase<'a>) {
42        walk_np(self, np);
43    }
44}
45
46pub fn walk_expr<'a, V: Visitor<'a>>(v: &mut V, expr: &'a LogicExpr<'a>) {
47    match expr {
48        LogicExpr::Predicate { args, .. } => {
49            for arg in *args {
50                v.visit_term(arg);
51            }
52        }
53
54        LogicExpr::Identity { left, right } => {
55            v.visit_term(left);
56            v.visit_term(right);
57        }
58
59        LogicExpr::Metaphor { tenor, vehicle } => {
60            v.visit_term(tenor);
61            v.visit_term(vehicle);
62        }
63
64        LogicExpr::Quantifier { body, .. } => {
65            v.visit_expr(body);
66        }
67
68        LogicExpr::Categorical(data) => {
69            v.visit_np(&data.subject);
70            v.visit_np(&data.predicate);
71        }
72
73        LogicExpr::Relation(data) => {
74            v.visit_np(&data.subject);
75            v.visit_np(&data.object);
76        }
77
78        LogicExpr::Modal { operand, .. } => {
79            v.visit_expr(operand);
80        }
81
82        LogicExpr::Temporal { body, .. } => {
83            v.visit_expr(body);
84        }
85
86        LogicExpr::TemporalBinary { left, right, .. } => {
87            v.visit_expr(left);
88            v.visit_expr(right);
89        }
90
91        LogicExpr::Aspectual { body, .. } => {
92            v.visit_expr(body);
93        }
94
95        LogicExpr::Voice { body, .. } => {
96            v.visit_expr(body);
97        }
98
99        LogicExpr::BinaryOp { left, right, .. } => {
100            v.visit_expr(left);
101            v.visit_expr(right);
102        }
103
104        LogicExpr::UnaryOp { operand, .. } => {
105            v.visit_expr(operand);
106        }
107
108        LogicExpr::Question { body, .. } => {
109            v.visit_expr(body);
110        }
111
112        LogicExpr::YesNoQuestion { body } => {
113            v.visit_expr(body);
114        }
115
116        LogicExpr::Atom(_) => {}
117
118        LogicExpr::Lambda { body, .. } => {
119            v.visit_expr(body);
120        }
121
122        LogicExpr::App { function, argument } => {
123            v.visit_expr(function);
124            v.visit_expr(argument);
125        }
126
127        LogicExpr::Intensional { content, .. } => {
128            v.visit_expr(content);
129        }
130
131        LogicExpr::Event { predicate, .. } => {
132            v.visit_expr(predicate);
133        }
134
135        LogicExpr::NeoEvent(data) => {
136            for (_, term) in data.roles.iter() {
137                v.visit_term(term);
138            }
139        }
140
141        LogicExpr::Exclamative { body, .. } => {
142            v.visit_expr(body);
143        }
144        LogicExpr::Optative { wish } => {
145            v.visit_expr(wish);
146        }
147        LogicExpr::Implicature { assertion, implicature } => {
148            v.visit_expr(assertion);
149            v.visit_expr(implicature);
150        }
151        LogicExpr::Imperative { action } => {
152            v.visit_expr(action);
153        }
154
155        LogicExpr::SpeechAct { content, .. } => {
156            v.visit_expr(content);
157        }
158
159        LogicExpr::Counterfactual { antecedent, consequent } => {
160            v.visit_expr(antecedent);
161            v.visit_expr(consequent);
162        }
163
164        LogicExpr::Causal { effect, cause } => {
165            v.visit_expr(cause);
166            v.visit_expr(effect);
167        }
168        LogicExpr::Concessive { main, concession } => {
169            v.visit_expr(main);
170            v.visit_expr(concession);
171        }
172
173        LogicExpr::Comparative { subject, object, .. } => {
174            v.visit_term(subject);
175            v.visit_term(object);
176        }
177
178        LogicExpr::Superlative { subject, .. } => {
179            v.visit_term(subject);
180        }
181
182        LogicExpr::Scopal { body, .. } => {
183            v.visit_expr(body);
184        }
185
186        LogicExpr::Control { subject, object, infinitive, .. } => {
187            v.visit_term(subject);
188            if let Some(obj) = object {
189                v.visit_term(obj);
190            }
191            v.visit_expr(infinitive);
192        }
193
194        LogicExpr::Presupposition { assertion, presupposition } => {
195            v.visit_expr(assertion);
196            v.visit_expr(presupposition);
197        }
198
199        LogicExpr::Focus { focused, scope, .. } => {
200            v.visit_term(focused);
201            v.visit_expr(scope);
202        }
203
204        LogicExpr::TemporalAnchor { body, .. } => {
205            v.visit_expr(body);
206        }
207
208        LogicExpr::Distributive { predicate } => {
209            v.visit_expr(predicate);
210        }
211
212        LogicExpr::GroupQuantifier { restriction, body, .. } => {
213            v.visit_expr(restriction);
214            v.visit_expr(body);
215        }
216    }
217}
218
219pub fn walk_term<'a, V: Visitor<'a>>(v: &mut V, term: &'a Term<'a>) {
220    match term {
221        Term::Constant(_) | Term::Variable(_) | Term::Sigma(_) | Term::Intension(_) | Term::Kind(_) | Term::Value { .. } => {}
222
223        Term::Function(_, args) => {
224            for arg in *args {
225                v.visit_term(arg);
226            }
227        }
228
229        Term::Group(members) => {
230            for m in *members {
231                v.visit_term(m);
232            }
233        }
234
235        Term::Possessed { possessor, .. } => {
236            v.visit_term(possessor);
237        }
238
239        Term::Proposition(expr) => {
240            v.visit_expr(expr);
241        }
242    }
243}
244
245pub fn walk_np<'a, V: Visitor<'a>>(v: &mut V, np: &'a NounPhrase<'a>) {
246    if let Some(poss) = np.possessor {
247        v.visit_np(poss);
248    }
249    for pp in np.pps.iter() {
250        v.visit_expr(pp);
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use logicaffeine_base::Symbol;
258
259    struct VariableCollector {
260        variables: Vec<Symbol>,
261    }
262
263    impl<'a> Visitor<'a> for VariableCollector {
264        fn visit_term(&mut self, term: &'a Term<'a>) {
265            if let Term::Variable(sym) = term {
266                self.variables.push(*sym);
267            }
268            walk_term(self, term);
269        }
270    }
271
272    struct ExprCounter {
273        count: usize,
274    }
275
276    impl<'a> Visitor<'a> for ExprCounter {
277        fn visit_expr(&mut self, expr: &'a LogicExpr<'a>) {
278            self.count += 1;
279            walk_expr(self, expr);
280        }
281    }
282
283    #[test]
284    fn variable_collector_finds_variables() {
285        use logicaffeine_base::Arena;
286        use logicaffeine_base::Interner;
287
288        let mut interner = Interner::new();
289        let x = interner.intern("x");
290        let y = interner.intern("y");
291
292        let term_arena: Arena<Term> = Arena::new();
293        let terms = term_arena.alloc_slice([Term::Variable(x), Term::Variable(y)]);
294
295        let expr_arena: Arena<LogicExpr> = Arena::new();
296        let pred = interner.intern("P");
297        let expr = expr_arena.alloc(LogicExpr::Predicate { name: pred, args: terms, world: None });
298
299        let mut collector = VariableCollector { variables: vec![] };
300        collector.visit_expr(expr);
301
302        assert_eq!(collector.variables.len(), 2);
303        assert!(collector.variables.contains(&x));
304        assert!(collector.variables.contains(&y));
305    }
306
307    #[test]
308    fn expr_counter_counts_nested() {
309        use logicaffeine_base::Arena;
310        use logicaffeine_base::Interner;
311        use crate::token::TokenType;
312
313        let mut interner = Interner::new();
314        let p = interner.intern("P");
315        let q = interner.intern("Q");
316
317        let expr_arena: Arena<LogicExpr> = Arena::new();
318
319        let left = expr_arena.alloc(LogicExpr::Atom(p));
320        let right = expr_arena.alloc(LogicExpr::Atom(q));
321        let binary = expr_arena.alloc(LogicExpr::BinaryOp {
322            left,
323            op: TokenType::And,
324            right,
325        });
326
327        let mut counter = ExprCounter { count: 0 };
328        counter.visit_expr(binary);
329
330        assert_eq!(counter.count, 3);
331    }
332}