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