Skip to main content

harn_parser/
visit.rs

1//! Generic AST visitor used by the linter, formatter, and any other
2//! crate that needs to walk every `SNode` in a parsed program.
3//!
4//! Centralizing this here keeps a single source of truth for which
5//! children each `Node` variant has — adding a new variant requires
6//! one edit (in `collect_children`) and every consumer benefits.
7//!
8//! # Usage
9//!
10//! ```ignore
11//! use harn_parser::visit::walk_program;
12//! let mut count = 0;
13//! walk_program(&program, &mut |node| {
14//!     if matches!(&node.node, harn_parser::Node::FunctionCall { .. }) {
15//!         count += 1;
16//!     }
17//! });
18//! ```
19//!
20//! The visitor invokes the closure on each node *before* recursing
21//! into its children (pre-order). To stop recursion at a particular
22//! node, prefer using [`walk_children`] directly.
23
24use crate::ast::{BindingPattern, DictEntry, MatchArm, Node, SNode, SelectCase, TypedParam};
25
26/// Walk every node in `program` in pre-order, invoking `visitor` on
27/// each.
28pub fn walk_program(program: &[SNode], visitor: &mut impl FnMut(&SNode)) {
29    let mut stack = Vec::with_capacity(program.len());
30    push_nodes_reversed(program, &mut stack);
31    walk_stack(&mut stack, visitor);
32}
33
34/// Visit `node`, then recurse into its children.
35pub fn walk_node(node: &SNode, visitor: &mut impl FnMut(&SNode)) {
36    let mut stack = vec![node];
37    walk_stack(&mut stack, visitor);
38}
39
40/// Recurse into `node`'s children without re-visiting `node` itself.
41/// Useful when a caller wants to handle the parent specially and then
42/// continue the default traversal.
43pub fn walk_children(node: &SNode, visitor: &mut impl FnMut(&SNode)) {
44    let mut stack = Vec::new();
45    push_children_reversed(node, &mut stack);
46    walk_stack(&mut stack, visitor);
47}
48
49fn walk_stack(stack: &mut Vec<&SNode>, visitor: &mut impl FnMut(&SNode)) {
50    while let Some(node) = stack.pop() {
51        visitor(node);
52        push_children_reversed(node, stack);
53    }
54}
55
56fn push_children_reversed<'a>(node: &'a SNode, stack: &mut Vec<&'a SNode>) {
57    let mut children = Vec::new();
58    collect_children(node, &mut children);
59    stack.extend(children.into_iter().rev());
60}
61
62fn push_nodes_reversed<'a>(nodes: &'a [SNode], stack: &mut Vec<&'a SNode>) {
63    stack.extend(nodes.iter().rev());
64}
65
66/// Collect `node`'s immediate children without recursing. Lets callers walk
67/// selectively (e.g. stop descending at nested loops) while still relying on
68/// this module's single source of truth for each variant's children.
69pub fn immediate_children(node: &SNode) -> Vec<&SNode> {
70    let mut children = Vec::new();
71    collect_children(node, &mut children);
72    children
73}
74
75fn collect_children<'a>(node: &'a SNode, children: &mut Vec<&'a SNode>) {
76    match &node.node {
77        Node::AttributedDecl { attributes, inner } => {
78            for attr in attributes {
79                for arg in &attr.args {
80                    children.push(&arg.value);
81                }
82            }
83            children.push(inner);
84        }
85        Node::Pipeline { body, .. } | Node::OverrideDecl { body, .. } => {
86            collect_nodes(body, children);
87        }
88        Node::LetBinding { pattern, value, .. } | Node::ConstBinding { pattern, value, .. } => {
89            collect_binding_pattern(pattern, children);
90            children.push(value);
91        }
92        Node::EnumDecl { variants, .. } => {
93            for variant in variants {
94                collect_typed_param_defaults(&variant.fields, children);
95            }
96        }
97        Node::StructDecl { .. }
98        | Node::ImportDecl { .. }
99        | Node::SelectiveImport { .. }
100        | Node::TypeDecl { .. }
101        | Node::BreakStmt
102        | Node::ContinueStmt => {}
103        Node::InterfaceDecl { methods, .. } => {
104            for method in methods {
105                collect_typed_param_defaults(&method.params, children);
106            }
107        }
108        Node::ImplBlock { methods, .. } => collect_nodes(methods, children),
109        Node::IfElse {
110            condition,
111            then_body,
112            else_body,
113        } => {
114            children.push(condition);
115            collect_nodes(then_body, children);
116            if let Some(body) = else_body {
117                collect_nodes(body, children);
118            }
119        }
120        Node::ForIn {
121            pattern,
122            iterable,
123            body,
124        } => {
125            collect_binding_pattern(pattern, children);
126            children.push(iterable);
127            collect_nodes(body, children);
128        }
129        Node::MatchExpr { value, arms } => {
130            children.push(value);
131            for arm in arms {
132                collect_match_arm(arm, children);
133            }
134        }
135        Node::WhileLoop { condition, body } => {
136            children.push(condition);
137            collect_nodes(body, children);
138        }
139        Node::Retry { count, body } => {
140            children.push(count);
141            collect_nodes(body, children);
142        }
143        Node::CostRoute { options, body } => {
144            collect_option_values(options, children);
145            collect_nodes(body, children);
146        }
147        Node::ReturnStmt { value } | Node::YieldExpr { value } => {
148            if let Some(value) = value {
149                children.push(value);
150            }
151        }
152        Node::TryCatch {
153            has_catch: _,
154            body,
155            catch_body,
156            finally_body,
157            ..
158        } => {
159            collect_nodes(body, children);
160            collect_nodes(catch_body, children);
161            if let Some(body) = finally_body {
162                collect_nodes(body, children);
163            }
164        }
165        Node::TryExpr { body }
166        | Node::SpawnExpr { body }
167        | Node::ScopeBlock { body }
168        | Node::DeferStmt { body }
169        | Node::Block(body) => collect_nodes(body, children),
170        Node::Closure { params, body, .. } => {
171            collect_typed_param_defaults(params, children);
172            collect_nodes(body, children);
173        }
174        Node::MutexBlock { key, body } => {
175            if let Some(key) = key {
176                children.push(key);
177            }
178            collect_nodes(body, children);
179        }
180        Node::FnDecl { params, body, .. } | Node::ToolDecl { params, body, .. } => {
181            collect_typed_param_defaults(params, children);
182            collect_nodes(body, children);
183        }
184        Node::SkillDecl { fields, .. } => collect_field_values(fields, children),
185        Node::EvalPackDecl {
186            fields,
187            body,
188            summarize,
189            ..
190        } => {
191            collect_field_values(fields, children);
192            collect_nodes(body, children);
193            if let Some(body) = summarize {
194                collect_nodes(body, children);
195            }
196        }
197        Node::RangeExpr { start, end, .. } => {
198            children.push(start);
199            children.push(end);
200        }
201        Node::GuardStmt {
202            condition,
203            else_body,
204        } => {
205            children.push(condition);
206            collect_nodes(else_body, children);
207        }
208        Node::RequireStmt { condition, message } => {
209            children.push(condition);
210            if let Some(message) = message {
211                children.push(message);
212            }
213        }
214        Node::DeadlineBlock { duration, body } => {
215            children.push(duration);
216            collect_nodes(body, children);
217        }
218        Node::EmitExpr { value }
219        | Node::ThrowStmt { value }
220        | Node::Spread(value)
221        | Node::TryOperator { operand: value }
222        | Node::TryStar { operand: value }
223        | Node::UnaryOp { operand: value, .. } => children.push(value),
224        Node::HitlExpr { args, .. } => {
225            for arg in args {
226                children.push(&arg.value);
227            }
228        }
229        Node::Parallel {
230            expr,
231            body,
232            options,
233            ..
234        } => {
235            children.push(expr);
236            collect_option_values(options, children);
237            collect_nodes(body, children);
238        }
239        Node::SelectExpr {
240            cases,
241            timeout,
242            default_body,
243        } => {
244            for case in cases {
245                collect_select_case(case, children);
246            }
247            if let Some((duration, body)) = timeout {
248                children.push(duration);
249                collect_nodes(body, children);
250            }
251            if let Some(body) = default_body {
252                collect_nodes(body, children);
253            }
254        }
255        Node::FunctionCall { args, .. } | Node::EnumConstruct { args, .. } => {
256            collect_nodes(args, children);
257        }
258        Node::MethodCall { object, args, .. } | Node::OptionalMethodCall { object, args, .. } => {
259            children.push(object);
260            collect_nodes(args, children);
261        }
262        Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
263            children.push(object);
264        }
265        Node::SubscriptAccess { object, index }
266        | Node::OptionalSubscriptAccess { object, index } => {
267            children.push(object);
268            children.push(index);
269        }
270        Node::SliceAccess { object, start, end } => {
271            children.push(object);
272            if let Some(start) = start {
273                children.push(start);
274            }
275            if let Some(end) = end {
276                children.push(end);
277            }
278        }
279        Node::BinaryOp { left, right, .. } => {
280            children.push(left);
281            children.push(right);
282        }
283        Node::Ternary {
284            condition,
285            true_expr,
286            false_expr,
287        } => {
288            children.push(condition);
289            children.push(true_expr);
290            children.push(false_expr);
291        }
292        Node::Assignment { target, value, .. } => {
293            children.push(target);
294            children.push(value);
295        }
296        Node::StructConstruct { fields, .. } | Node::DictLiteral(fields) => {
297            collect_dict_entries(fields, children);
298        }
299        Node::ListLiteral(items) | Node::OrPattern(items) => collect_nodes(items, children),
300        Node::InterpolatedString(_)
301        | Node::StringLiteral(_)
302        | Node::RawStringLiteral(_)
303        | Node::IntLiteral(_)
304        | Node::FloatLiteral(_)
305        | Node::BoolLiteral(_)
306        | Node::NilLiteral
307        | Node::Identifier(_)
308        | Node::DurationLiteral(_) => {}
309    }
310}
311
312fn collect_nodes<'a>(nodes: &'a [SNode], children: &mut Vec<&'a SNode>) {
313    children.extend(nodes.iter());
314}
315
316fn collect_dict_entries<'a>(entries: &'a [DictEntry], children: &mut Vec<&'a SNode>) {
317    for entry in entries {
318        children.push(&entry.key);
319        children.push(&entry.value);
320    }
321}
322
323fn collect_field_values<'a>(fields: &'a [(String, SNode)], children: &mut Vec<&'a SNode>) {
324    for (_, value) in fields {
325        children.push(value);
326    }
327}
328
329fn collect_option_values<'a>(options: &'a [(String, SNode)], children: &mut Vec<&'a SNode>) {
330    for (_, value) in options {
331        children.push(value);
332    }
333}
334
335fn collect_typed_param_defaults<'a>(params: &'a [TypedParam], children: &mut Vec<&'a SNode>) {
336    for param in params {
337        if let Some(default) = &param.default_value {
338            children.push(default);
339        }
340    }
341}
342
343fn collect_match_arm<'a>(arm: &'a MatchArm, children: &mut Vec<&'a SNode>) {
344    children.push(&arm.pattern);
345    if let Some(guard) = &arm.guard {
346        children.push(guard);
347    }
348    collect_nodes(&arm.body, children);
349}
350
351fn collect_select_case<'a>(case: &'a SelectCase, children: &mut Vec<&'a SNode>) {
352    children.push(&case.channel);
353    collect_nodes(&case.body, children);
354}
355
356fn collect_binding_pattern<'a>(pattern: &'a BindingPattern, children: &mut Vec<&'a SNode>) {
357    match pattern {
358        BindingPattern::Identifier(_) | BindingPattern::Pair(_, _) => {}
359        BindingPattern::Dict(fields) => {
360            for field in fields {
361                if let Some(default) = &field.default_value {
362                    children.push(default);
363                }
364            }
365        }
366        BindingPattern::List(items) => {
367            for item in items {
368                if let Some(default) = &item.default_value {
369                    children.push(default);
370                }
371            }
372        }
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379    use crate::ast::{spanned, Node, TypedParam};
380    use harn_lexer::Span;
381
382    fn dummy(node: Node) -> SNode {
383        spanned(node, Span::dummy())
384    }
385
386    #[test]
387    fn walk_program_preserves_preorder() {
388        let program = vec![dummy(Node::LetBinding {
389            pattern: BindingPattern::Identifier("x".to_string()),
390            type_ann: None,
391            value: Box::new(dummy(Node::BinaryOp {
392                op: "+".to_string(),
393                left: Box::new(dummy(Node::IntLiteral(1))),
394                right: Box::new(dummy(Node::IntLiteral(2))),
395            })),
396        })];
397        let mut seen = Vec::new();
398
399        walk_program(&program, &mut |node| {
400            seen.push(match &node.node {
401                Node::LetBinding { .. } => "let",
402                Node::BinaryOp { .. } => "binary",
403                Node::IntLiteral(1) => "one",
404                Node::IntLiteral(2) => "two",
405                other => panic!("unexpected node {other:?}"),
406            });
407        });
408
409        assert_eq!(seen, vec!["let", "binary", "one", "two"]);
410    }
411
412    #[test]
413    fn walk_node_handles_deep_unary_chain_iteratively() {
414        let mut node = dummy(Node::IntLiteral(0));
415        for _ in 0..10_000 {
416            node = dummy(Node::UnaryOp {
417                op: "!".to_string(),
418                operand: Box::new(node),
419            });
420        }
421
422        let mut count = 0usize;
423        walk_node(&node, &mut |_| count += 1);
424
425        assert_eq!(count, 10_001);
426    }
427
428    #[test]
429    fn walk_node_visits_typed_param_defaults() {
430        let default = dummy(Node::Identifier("fallback".to_string()));
431        let node = dummy(Node::FnDecl {
432            name: "load".to_string(),
433            type_params: Vec::new(),
434            params: vec![TypedParam {
435                name: "root".to_string(),
436                type_expr: None,
437                default_value: Some(Box::new(default)),
438                rest: false,
439            }],
440            return_type: None,
441            where_clauses: Vec::new(),
442            body: Vec::new(),
443            is_pub: false,
444            is_stream: false,
445        });
446        let mut seen = Vec::new();
447
448        walk_node(&node, &mut |node| {
449            if let Node::Identifier(name) = &node.node {
450                seen.push(name.clone());
451            }
452        });
453
454        assert_eq!(seen, vec!["fallback"]);
455    }
456}