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::NonNullAssert { operand: value }
224        | Node::UnaryOp { operand: value, .. } => children.push(value),
225        Node::HitlExpr { args, .. } => {
226            for arg in args {
227                children.push(&arg.value);
228            }
229        }
230        Node::Parallel {
231            expr,
232            body,
233            options,
234            ..
235        } => {
236            children.push(expr);
237            collect_option_values(options, children);
238            collect_nodes(body, children);
239        }
240        Node::SelectExpr {
241            cases,
242            timeout,
243            default_body,
244        } => {
245            for case in cases {
246                collect_select_case(case, children);
247            }
248            if let Some((duration, body)) = timeout {
249                children.push(duration);
250                collect_nodes(body, children);
251            }
252            if let Some(body) = default_body {
253                collect_nodes(body, children);
254            }
255        }
256        Node::FunctionCall { args, .. } | Node::EnumConstruct { args, .. } => {
257            collect_nodes(args, children);
258        }
259        Node::MethodCall { object, args, .. } | Node::OptionalMethodCall { object, args, .. } => {
260            children.push(object);
261            collect_nodes(args, children);
262        }
263        Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
264            children.push(object);
265        }
266        Node::SubscriptAccess { object, index }
267        | Node::OptionalSubscriptAccess { object, index } => {
268            children.push(object);
269            children.push(index);
270        }
271        Node::SliceAccess { object, start, end } => {
272            children.push(object);
273            if let Some(start) = start {
274                children.push(start);
275            }
276            if let Some(end) = end {
277                children.push(end);
278            }
279        }
280        Node::BinaryOp { left, right, .. } => {
281            children.push(left);
282            children.push(right);
283        }
284        Node::Ternary {
285            condition,
286            true_expr,
287            false_expr,
288        } => {
289            children.push(condition);
290            children.push(true_expr);
291            children.push(false_expr);
292        }
293        Node::Assignment { target, value, .. } => {
294            children.push(target);
295            children.push(value);
296        }
297        Node::StructConstruct { fields, .. } | Node::DictLiteral(fields) => {
298            collect_dict_entries(fields, children);
299        }
300        Node::ListLiteral(items) | Node::OrPattern(items) => collect_nodes(items, children),
301        Node::InterpolatedString(_)
302        | Node::StringLiteral(_)
303        | Node::RawStringLiteral(_)
304        | Node::IntLiteral(_)
305        | Node::FloatLiteral(_)
306        | Node::BoolLiteral(_)
307        | Node::NilLiteral
308        | Node::Identifier(_)
309        | Node::DurationLiteral(_) => {}
310    }
311}
312
313fn collect_nodes<'a>(nodes: &'a [SNode], children: &mut Vec<&'a SNode>) {
314    children.extend(nodes.iter());
315}
316
317fn collect_dict_entries<'a>(entries: &'a [DictEntry], children: &mut Vec<&'a SNode>) {
318    for entry in entries {
319        children.push(&entry.key);
320        children.push(&entry.value);
321    }
322}
323
324fn collect_field_values<'a>(fields: &'a [(String, SNode)], children: &mut Vec<&'a SNode>) {
325    for (_, value) in fields {
326        children.push(value);
327    }
328}
329
330fn collect_option_values<'a>(options: &'a [(String, SNode)], children: &mut Vec<&'a SNode>) {
331    for (_, value) in options {
332        children.push(value);
333    }
334}
335
336fn collect_typed_param_defaults<'a>(params: &'a [TypedParam], children: &mut Vec<&'a SNode>) {
337    for param in params {
338        if let Some(default) = &param.default_value {
339            children.push(default);
340        }
341    }
342}
343
344fn collect_match_arm<'a>(arm: &'a MatchArm, children: &mut Vec<&'a SNode>) {
345    children.push(&arm.pattern);
346    if let Some(guard) = &arm.guard {
347        children.push(guard);
348    }
349    collect_nodes(&arm.body, children);
350}
351
352fn collect_select_case<'a>(case: &'a SelectCase, children: &mut Vec<&'a SNode>) {
353    children.push(&case.channel);
354    collect_nodes(&case.body, children);
355}
356
357fn collect_binding_pattern<'a>(pattern: &'a BindingPattern, children: &mut Vec<&'a SNode>) {
358    match pattern {
359        BindingPattern::Identifier(_) | BindingPattern::Pair(_, _) => {}
360        BindingPattern::Dict(fields) => {
361            for field in fields {
362                if let Some(default) = &field.default_value {
363                    children.push(default);
364                }
365            }
366        }
367        BindingPattern::List(items) => {
368            for item in items {
369                if let Some(default) = &item.default_value {
370                    children.push(default);
371                }
372            }
373        }
374    }
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380    use crate::ast::{spanned, Node, TypedParam};
381    use harn_lexer::Span;
382
383    fn dummy(node: Node) -> SNode {
384        spanned(node, Span::dummy())
385    }
386
387    #[test]
388    fn walk_program_preserves_preorder() {
389        let program = vec![dummy(Node::LetBinding {
390            pattern: BindingPattern::Identifier("x".to_string()),
391            type_ann: None,
392            value: Box::new(dummy(Node::BinaryOp {
393                op: "+".to_string(),
394                left: Box::new(dummy(Node::IntLiteral(1))),
395                right: Box::new(dummy(Node::IntLiteral(2))),
396            })),
397            is_pub: false,
398        })];
399        let mut seen = Vec::new();
400
401        walk_program(&program, &mut |node| {
402            seen.push(match &node.node {
403                Node::LetBinding { .. } => "let",
404                Node::BinaryOp { .. } => "binary",
405                Node::IntLiteral(1) => "one",
406                Node::IntLiteral(2) => "two",
407                other => panic!("unexpected node {other:?}"),
408            });
409        });
410
411        assert_eq!(seen, vec!["let", "binary", "one", "two"]);
412    }
413
414    #[test]
415    fn walk_node_handles_deep_unary_chain_iteratively() {
416        let mut node = dummy(Node::IntLiteral(0));
417        for _ in 0..10_000 {
418            node = dummy(Node::UnaryOp {
419                op: "!".to_string(),
420                operand: Box::new(node),
421            });
422        }
423
424        let mut count = 0usize;
425        walk_node(&node, &mut |_| count += 1);
426
427        assert_eq!(count, 10_001);
428    }
429
430    #[test]
431    fn walk_node_visits_typed_param_defaults() {
432        let default = dummy(Node::Identifier("fallback".to_string()));
433        let node = dummy(Node::FnDecl {
434            name: "load".to_string(),
435            type_params: Vec::new(),
436            params: vec![TypedParam {
437                name: "root".to_string(),
438                type_expr: None,
439                default_value: Some(Box::new(default)),
440                rest: false,
441            }],
442            return_type: None,
443            throws: None,
444            where_clauses: Vec::new(),
445            body: Vec::new(),
446            is_pub: false,
447            is_stream: false,
448        });
449        let mut seen = Vec::new();
450
451        walk_node(&node, &mut |node| {
452            if let Node::Identifier(name) = &node.node {
453                seen.push(name.clone());
454            }
455        });
456
457        assert_eq!(seen, vec!["fallback"]);
458    }
459}