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 [`walk_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};
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    for node in program {
30        walk_node(node, visitor);
31    }
32}
33
34/// Visit `node`, then recurse into its children.
35pub fn walk_node(node: &SNode, visitor: &mut impl FnMut(&SNode)) {
36    visitor(node);
37    walk_children(node, 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    match &node.node {
45        Node::AttributedDecl { attributes, inner } => {
46            for attr in attributes {
47                for arg in &attr.args {
48                    walk_node(&arg.value, visitor);
49                }
50            }
51            walk_node(inner, visitor);
52        }
53        Node::Pipeline { body, .. } | Node::OverrideDecl { body, .. } => {
54            walk_nodes(body, visitor);
55        }
56        Node::LetBinding { pattern, value, .. } | Node::VarBinding { pattern, value, .. } => {
57            walk_binding_pattern(pattern, visitor);
58            walk_node(value, visitor);
59        }
60        Node::ConstBinding { value, .. } => {
61            walk_node(value, visitor);
62        }
63        Node::EnumDecl { .. }
64        | Node::StructDecl { .. }
65        | Node::InterfaceDecl { .. }
66        | Node::ImportDecl { .. }
67        | Node::SelectiveImport { .. }
68        | Node::TypeDecl { .. }
69        | Node::BreakStmt
70        | Node::ContinueStmt => {}
71        Node::ImplBlock { methods, .. } => walk_nodes(methods, visitor),
72        Node::IfElse {
73            condition,
74            then_body,
75            else_body,
76        } => {
77            walk_node(condition, visitor);
78            walk_nodes(then_body, visitor);
79            if let Some(body) = else_body {
80                walk_nodes(body, visitor);
81            }
82        }
83        Node::ForIn {
84            pattern,
85            iterable,
86            body,
87        } => {
88            walk_binding_pattern(pattern, visitor);
89            walk_node(iterable, visitor);
90            walk_nodes(body, visitor);
91        }
92        Node::MatchExpr { value, arms } => {
93            walk_node(value, visitor);
94            for arm in arms {
95                walk_match_arm(arm, visitor);
96            }
97        }
98        Node::WhileLoop { condition, body } => {
99            walk_node(condition, visitor);
100            walk_nodes(body, visitor);
101        }
102        Node::Retry { count, body } => {
103            walk_node(count, visitor);
104            walk_nodes(body, visitor);
105        }
106        Node::CostRoute { options, body } => {
107            walk_option_values(options, visitor);
108            walk_nodes(body, visitor);
109        }
110        Node::ReturnStmt { value } | Node::YieldExpr { value } => {
111            if let Some(value) = value {
112                walk_node(value, visitor);
113            }
114        }
115        Node::TryCatch {
116            has_catch: _,
117            body,
118            catch_body,
119            finally_body,
120            ..
121        } => {
122            walk_nodes(body, visitor);
123            walk_nodes(catch_body, visitor);
124            if let Some(body) = finally_body {
125                walk_nodes(body, visitor);
126            }
127        }
128        Node::TryExpr { body }
129        | Node::SpawnExpr { body }
130        | Node::DeferStmt { body }
131        | Node::MutexBlock { body }
132        | Node::Block(body)
133        | Node::Closure { body, .. } => walk_nodes(body, visitor),
134        Node::FnDecl { body, .. } | Node::ToolDecl { body, .. } => {
135            walk_nodes(body, visitor);
136        }
137        Node::SkillDecl { fields, .. } => walk_field_values(fields, visitor),
138        Node::EvalPackDecl {
139            fields,
140            body,
141            summarize,
142            ..
143        } => {
144            walk_field_values(fields, visitor);
145            walk_nodes(body, visitor);
146            if let Some(body) = summarize {
147                walk_nodes(body, visitor);
148            }
149        }
150        Node::RangeExpr { start, end, .. } => {
151            walk_node(start, visitor);
152            walk_node(end, visitor);
153        }
154        Node::GuardStmt {
155            condition,
156            else_body,
157        } => {
158            walk_node(condition, visitor);
159            walk_nodes(else_body, visitor);
160        }
161        Node::RequireStmt { condition, message } => {
162            walk_node(condition, visitor);
163            if let Some(message) = message {
164                walk_node(message, visitor);
165            }
166        }
167        Node::DeadlineBlock { duration, body } => {
168            walk_node(duration, visitor);
169            walk_nodes(body, visitor);
170        }
171        Node::EmitExpr { value }
172        | Node::ThrowStmt { value }
173        | Node::Spread(value)
174        | Node::TryOperator { operand: value }
175        | Node::TryStar { operand: value }
176        | Node::UnaryOp { operand: value, .. } => walk_node(value, visitor),
177        Node::HitlExpr { args, .. } => {
178            for arg in args {
179                walk_node(&arg.value, visitor);
180            }
181        }
182        Node::Parallel {
183            expr,
184            body,
185            options,
186            ..
187        } => {
188            walk_node(expr, visitor);
189            walk_option_values(options, visitor);
190            walk_nodes(body, visitor);
191        }
192        Node::SelectExpr {
193            cases,
194            timeout,
195            default_body,
196        } => {
197            for case in cases {
198                walk_select_case(case, visitor);
199            }
200            if let Some((duration, body)) = timeout {
201                walk_node(duration, visitor);
202                walk_nodes(body, visitor);
203            }
204            if let Some(body) = default_body {
205                walk_nodes(body, visitor);
206            }
207        }
208        Node::FunctionCall { args, .. } | Node::EnumConstruct { args, .. } => {
209            walk_nodes(args, visitor);
210        }
211        Node::MethodCall { object, args, .. } | Node::OptionalMethodCall { object, args, .. } => {
212            walk_node(object, visitor);
213            walk_nodes(args, visitor);
214        }
215        Node::PropertyAccess { object, .. } | Node::OptionalPropertyAccess { object, .. } => {
216            walk_node(object, visitor);
217        }
218        Node::SubscriptAccess { object, index }
219        | Node::OptionalSubscriptAccess { object, index } => {
220            walk_node(object, visitor);
221            walk_node(index, visitor);
222        }
223        Node::SliceAccess { object, start, end } => {
224            walk_node(object, visitor);
225            if let Some(start) = start {
226                walk_node(start, visitor);
227            }
228            if let Some(end) = end {
229                walk_node(end, visitor);
230            }
231        }
232        Node::BinaryOp { left, right, .. } => {
233            walk_node(left, visitor);
234            walk_node(right, visitor);
235        }
236        Node::Ternary {
237            condition,
238            true_expr,
239            false_expr,
240        } => {
241            walk_node(condition, visitor);
242            walk_node(true_expr, visitor);
243            walk_node(false_expr, visitor);
244        }
245        Node::Assignment { target, value, .. } => {
246            walk_node(target, visitor);
247            walk_node(value, visitor);
248        }
249        Node::StructConstruct { fields, .. } | Node::DictLiteral(fields) => {
250            walk_dict_entries(fields, visitor);
251        }
252        Node::ListLiteral(items) | Node::OrPattern(items) => walk_nodes(items, visitor),
253        Node::InterpolatedString(_)
254        | Node::StringLiteral(_)
255        | Node::RawStringLiteral(_)
256        | Node::IntLiteral(_)
257        | Node::FloatLiteral(_)
258        | Node::BoolLiteral(_)
259        | Node::NilLiteral
260        | Node::Identifier(_)
261        | Node::DurationLiteral(_) => {}
262    }
263}
264
265fn walk_nodes(nodes: &[SNode], visitor: &mut impl FnMut(&SNode)) {
266    for node in nodes {
267        walk_node(node, visitor);
268    }
269}
270
271fn walk_dict_entries(entries: &[DictEntry], visitor: &mut impl FnMut(&SNode)) {
272    for entry in entries {
273        walk_node(&entry.key, visitor);
274        walk_node(&entry.value, visitor);
275    }
276}
277
278fn walk_field_values(fields: &[(String, SNode)], visitor: &mut impl FnMut(&SNode)) {
279    for (_, value) in fields {
280        walk_node(value, visitor);
281    }
282}
283
284fn walk_option_values(options: &[(String, SNode)], visitor: &mut impl FnMut(&SNode)) {
285    for (_, value) in options {
286        walk_node(value, visitor);
287    }
288}
289
290fn walk_match_arm(arm: &MatchArm, visitor: &mut impl FnMut(&SNode)) {
291    walk_node(&arm.pattern, visitor);
292    if let Some(guard) = &arm.guard {
293        walk_node(guard, visitor);
294    }
295    walk_nodes(&arm.body, visitor);
296}
297
298fn walk_select_case(case: &SelectCase, visitor: &mut impl FnMut(&SNode)) {
299    walk_node(&case.channel, visitor);
300    walk_nodes(&case.body, visitor);
301}
302
303fn walk_binding_pattern(pattern: &BindingPattern, visitor: &mut impl FnMut(&SNode)) {
304    match pattern {
305        BindingPattern::Identifier(_) | BindingPattern::Pair(_, _) => {}
306        BindingPattern::Dict(fields) => {
307            for field in fields {
308                if let Some(default) = &field.default_value {
309                    walk_node(default, visitor);
310                }
311            }
312        }
313        BindingPattern::List(items) => {
314            for item in items {
315                if let Some(default) = &item.default_value {
316                    walk_node(default, visitor);
317                }
318            }
319        }
320    }
321}