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        } => {
115            children.push(condition);
116            collect_nodes(then_body, children);
117            if let Some(body) = else_body {
118                collect_nodes(body, children);
119            }
120        }
121        Node::ForIn {
122            pattern,
123            iterable,
124            body,
125        } => {
126            collect_binding_pattern(pattern, children);
127            children.push(iterable);
128            collect_nodes(body, children);
129        }
130        Node::MatchExpr { value, arms } => {
131            children.push(value);
132            for arm in arms {
133                collect_match_arm(arm, children);
134            }
135        }
136        Node::WhileLoop { condition, body } => {
137            children.push(condition);
138            collect_nodes(body, children);
139        }
140        Node::Retry { count, body } => {
141            children.push(count);
142            collect_nodes(body, children);
143        }
144        Node::CostRoute { options, body } => {
145            collect_option_values(options, children);
146            collect_nodes(body, children);
147        }
148        Node::ReturnStmt { value } | Node::YieldExpr { value } => {
149            if let Some(value) = value {
150                children.push(value);
151            }
152        }
153        Node::TryCatch {
154            has_catch: _,
155            body,
156            catch_body,
157            finally_body,
158            ..
159        } => {
160            collect_nodes(body, children);
161            collect_nodes(catch_body, children);
162            if let Some(body) = finally_body {
163                collect_nodes(body, children);
164            }
165        }
166        Node::TryExpr { body }
167        | Node::SpawnExpr { body }
168        | Node::ScopeBlock { body }
169        | Node::DeferStmt { body }
170        | Node::Block(body) => collect_nodes(body, children),
171        Node::Closure { params, body, .. } => {
172            collect_typed_param_defaults(params, children);
173            collect_nodes(body, children);
174        }
175        Node::MutexBlock { key, body } => {
176            if let Some(key) = key {
177                children.push(key);
178            }
179            collect_nodes(body, children);
180        }
181        Node::FnDecl { params, body, .. } | Node::ToolDecl { params, body, .. } => {
182            collect_typed_param_defaults(params, children);
183            collect_nodes(body, children);
184        }
185        Node::SkillDecl { fields, .. } => collect_field_values(fields, children),
186        Node::EvalPackDecl {
187            fields,
188            body,
189            summarize,
190            ..
191        } => {
192            collect_field_values(fields, children);
193            collect_nodes(body, children);
194            if let Some(body) = summarize {
195                collect_nodes(body, children);
196            }
197        }
198        Node::RangeExpr { start, end, .. } => {
199            children.push(start);
200            children.push(end);
201        }
202        Node::GuardStmt {
203            condition,
204            else_body,
205        } => {
206            children.push(condition);
207            collect_nodes(else_body, children);
208        }
209        Node::RequireStmt { condition, message } => {
210            children.push(condition);
211            if let Some(message) = message {
212                children.push(message);
213            }
214        }
215        Node::DeadlineBlock { duration, body } => {
216            children.push(duration);
217            collect_nodes(body, children);
218        }
219        Node::EmitExpr { value }
220        | Node::ThrowStmt { value }
221        | Node::Spread(value)
222        | Node::TryOperator { operand: value }
223        | Node::TryStar { operand: value }
224        | Node::NonNullAssert { operand: value }
225        | Node::UnaryOp { operand: value, .. } => children.push(value),
226        Node::HitlExpr { args, .. } => {
227            for arg in args {
228                children.push(&arg.value);
229            }
230        }
231        Node::Parallel {
232            expr,
233            body,
234            options,
235            ..
236        } => {
237            children.push(expr);
238            collect_option_values(options, children);
239            collect_nodes(body, children);
240        }
241        Node::SelectExpr {
242            cases,
243            timeout,
244            default_body,
245        } => {
246            for case in cases {
247                collect_select_case(case, children);
248            }
249            if let Some((duration, body)) = timeout {
250                children.push(duration);
251                collect_nodes(body, children);
252            }
253            if let Some(body) = default_body {
254                collect_nodes(body, children);
255            }
256        }
257        Node::FunctionCall { args, .. } | Node::EnumConstruct { args, .. } => {
258            collect_nodes(args, children);
259        }
260        Node::MethodCall { object, args, .. } | Node::OptionalMethodCall { object, args, .. } => {
261            children.push(object);
262            collect_nodes(args, children);
263        }
264        Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
265            children.push(object);
266        }
267        Node::SubscriptAccess { object, index }
268        | Node::OptionalSubscriptAccess { object, index } => {
269            children.push(object);
270            children.push(index);
271        }
272        Node::SliceAccess { object, start, end } => {
273            children.push(object);
274            if let Some(start) = start {
275                children.push(start);
276            }
277            if let Some(end) = end {
278                children.push(end);
279            }
280        }
281        Node::BinaryOp { left, right, .. } => {
282            children.push(left);
283            children.push(right);
284        }
285        Node::Ternary {
286            condition,
287            true_expr,
288            false_expr,
289        } => {
290            children.push(condition);
291            children.push(true_expr);
292            children.push(false_expr);
293        }
294        Node::Assignment { target, value, .. } => {
295            children.push(target);
296            children.push(value);
297        }
298        Node::StructConstruct { fields, .. } | Node::DictLiteral(fields) => {
299            collect_dict_entries(fields, children);
300        }
301        Node::ListLiteral(items) | Node::OrPattern(items) => collect_nodes(items, children),
302        Node::InterpolatedString(_)
303        | Node::StringLiteral(_)
304        | Node::RawStringLiteral(_)
305        | Node::IntLiteral(_)
306        | Node::FloatLiteral(_)
307        | Node::BoolLiteral(_)
308        | Node::NilLiteral
309        | Node::Identifier(_)
310        | Node::DurationLiteral(_) => {}
311    }
312}
313
314fn collect_nodes<'a>(nodes: &'a [SNode], children: &mut Vec<&'a SNode>) {
315    children.extend(nodes.iter());
316}
317
318fn collect_dict_entries<'a>(entries: &'a [DictEntry], children: &mut Vec<&'a SNode>) {
319    for entry in entries {
320        children.push(&entry.key);
321        children.push(&entry.value);
322    }
323}
324
325fn collect_field_values<'a>(fields: &'a [(String, SNode)], children: &mut Vec<&'a SNode>) {
326    for (_, value) in fields {
327        children.push(value);
328    }
329}
330
331fn collect_option_values<'a>(options: &'a [(String, SNode)], children: &mut Vec<&'a SNode>) {
332    for (_, value) in options {
333        children.push(value);
334    }
335}
336
337fn collect_typed_param_defaults<'a>(params: &'a [TypedParam], children: &mut Vec<&'a SNode>) {
338    for param in params {
339        if let Some(default) = &param.default_value {
340            children.push(default);
341        }
342    }
343}
344
345fn collect_match_arm<'a>(arm: &'a MatchArm, children: &mut Vec<&'a SNode>) {
346    children.push(&arm.pattern);
347    if let Some(guard) = &arm.guard {
348        children.push(guard);
349    }
350    collect_nodes(&arm.body, children);
351}
352
353fn collect_select_case<'a>(case: &'a SelectCase, children: &mut Vec<&'a SNode>) {
354    children.push(&case.channel);
355    collect_nodes(&case.body, children);
356}
357
358fn collect_binding_pattern<'a>(pattern: &'a BindingPattern, children: &mut Vec<&'a SNode>) {
359    match pattern {
360        BindingPattern::Identifier(_) | BindingPattern::Pair(_, _) => {}
361        BindingPattern::Dict(fields) => {
362            for field in fields {
363                if let Some(default) = &field.default_value {
364                    children.push(default);
365                }
366            }
367        }
368        BindingPattern::List(items) => {
369            for item in items {
370                if let Some(default) = &item.default_value {
371                    children.push(default);
372                }
373            }
374        }
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381    use crate::ast::{spanned, Node, TypedParam};
382    use harn_lexer::Span;
383
384    fn dummy(node: Node) -> SNode {
385        spanned(node, Span::dummy())
386    }
387
388    #[test]
389    fn walk_program_preserves_preorder() {
390        let program = vec![dummy(Node::LetBinding {
391            pattern: BindingPattern::Identifier("x".to_string()),
392            type_ann: None,
393            value: Box::new(dummy(Node::BinaryOp {
394                op: "+".to_string(),
395                left: Box::new(dummy(Node::IntLiteral(1))),
396                right: Box::new(dummy(Node::IntLiteral(2))),
397            })),
398            is_pub: false,
399        })];
400        let mut seen = Vec::new();
401
402        walk_program(&program, &mut |node| {
403            seen.push(match &node.node {
404                Node::LetBinding { .. } => "let",
405                Node::BinaryOp { .. } => "binary",
406                Node::IntLiteral(1) => "one",
407                Node::IntLiteral(2) => "two",
408                other => panic!("unexpected node {other:?}"),
409            });
410        });
411
412        assert_eq!(seen, vec!["let", "binary", "one", "two"]);
413    }
414
415    #[test]
416    fn walk_node_handles_deep_unary_chain_iteratively() {
417        let mut node = dummy(Node::IntLiteral(0));
418        for _ in 0..10_000 {
419            node = dummy(Node::UnaryOp {
420                op: "!".to_string(),
421                operand: Box::new(node),
422            });
423        }
424
425        let mut count = 0usize;
426        walk_node(&node, &mut |_| count += 1);
427
428        assert_eq!(count, 10_001);
429    }
430
431    #[test]
432    fn walk_node_visits_typed_param_defaults() {
433        let default = dummy(Node::Identifier("fallback".to_string()));
434        let node = dummy(Node::FnDecl {
435            name: "load".to_string(),
436            type_params: Vec::new(),
437            params: vec![TypedParam {
438                name: "root".to_string(),
439                type_expr: None,
440                default_value: Some(Box::new(default)),
441                rest: false,
442            }],
443            return_type: None,
444            throws: None,
445            where_clauses: Vec::new(),
446            body: Vec::new(),
447            is_pub: false,
448            is_stream: false,
449        });
450        let mut seen = Vec::new();
451
452        walk_node(&node, &mut |node| {
453            if let Node::Identifier(name) = &node.node {
454                seen.push(name.clone());
455            }
456        });
457
458        assert_eq!(seen, vec!["fallback"]);
459    }
460}