sipha/walk.rs
1//! # Tree walk (visitor)
2//!
3//! Generic pre- and post-order traversal over a sipha red tree. Use this for
4//! formatting, scope analysis, type checking, linting, and other passes that
5//! need to visit every node and optionally every token.
6//!
7//! Implement [`Visitor`] and call [`SyntaxNode::walk`] (or the free function
8//! [`walk`]) with your visitor and [`WalkOptions`]. Dispatch on node kind via
9//! `node.kind()` or `node.kind_as::<YourKind>()` (see [`crate::types::FromSyntaxKind`]).
10
11use std::ops::ControlFlow;
12
13use crate::red::{SyntaxElement, SyntaxNode, SyntaxToken};
14
15/// Options that control how the tree is traversed.
16#[derive(Clone, Debug)]
17pub struct WalkOptions {
18 /// Visit leaf tokens (identifiers, literals, operators, etc.).
19 /// If false, only syntax nodes are visited.
20 pub visit_tokens: bool,
21 /// When visiting tokens, include trivia (whitespace, comments).
22 /// Ignored if `visit_tokens` is false.
23 pub visit_trivia: bool,
24}
25
26impl Default for WalkOptions {
27 fn default() -> Self {
28 Self {
29 visit_tokens: true,
30 visit_trivia: false,
31 }
32 }
33}
34
35impl WalkOptions {
36 /// Only visit syntax nodes (no tokens). Useful for structure-only analysis.
37 #[must_use]
38 pub const fn nodes_only() -> Self {
39 Self {
40 visit_tokens: false,
41 visit_trivia: false,
42 }
43 }
44
45 /// Visit nodes and semantic tokens only (no trivia).
46 #[must_use]
47 pub fn semantic_only() -> Self {
48 Self::default()
49 }
50
51 /// Visit nodes and all tokens including trivia. Useful for formatting.
52 #[must_use]
53 pub const fn full() -> Self {
54 Self {
55 visit_tokens: true,
56 visit_trivia: true,
57 }
58 }
59}
60
61/// Result of a visitor callback: continue walking or stop.
62pub type WalkResult = ControlFlow<(), ()>;
63
64/// Trait for visiting a sipha syntax tree.
65///
66/// Override only the methods you need; default implementations do nothing and
67/// return `WalkResult::Continue(())`. Use `node.kind()` or
68/// `node.kind_as::<YourKind>()` to dispatch on node type.
69///
70/// # Order of calls
71///
72/// For each node, `enter_node` is called first (pre-order), then children are
73/// visited, then `leave_node` (post-order). This supports scoping: push scope
74/// in `enter_node` for block/function nodes, pop in `leave_node`.
75///
76/// # Early termination
77///
78/// Return `WalkResult::Break(())` from any callback to stop the walk; the
79/// break propagates to the caller of [`walk`].
80///
81/// # Collecting results
82///
83/// Store any result in your visitor struct and read it after `walk` returns.
84pub trait Visitor {
85 /// Called before visiting this node's children (pre-order).
86 fn enter_node(&mut self, _node: &SyntaxNode) -> WalkResult {
87 WalkResult::Continue(())
88 }
89
90 /// Called after visiting this node's children (post-order).
91 fn leave_node(&mut self, _node: &SyntaxNode) -> WalkResult {
92 WalkResult::Continue(())
93 }
94
95 /// Called for each token when `WalkOptions::visit_tokens` is true.
96 /// Trivia is included only when `WalkOptions::visit_trivia` is true.
97 fn visit_token(&mut self, _token: &SyntaxToken) -> WalkResult {
98 WalkResult::Continue(())
99 }
100}
101
102fn walk_node(node: &SyntaxNode, visitor: &mut impl Visitor, options: &WalkOptions) -> WalkResult {
103 if visitor.enter_node(node) == WalkResult::Break(()) {
104 return WalkResult::Break(());
105 }
106
107 if options.visit_tokens {
108 for elem in node.children() {
109 match elem {
110 SyntaxElement::Node(child) => {
111 if walk_node(&child, visitor, options) == WalkResult::Break(()) {
112 let _ = visitor.leave_node(node);
113 return WalkResult::Break(());
114 }
115 }
116 SyntaxElement::Token(token) => {
117 if token.is_trivia() && !options.visit_trivia {
118 continue;
119 }
120 if visitor.visit_token(&token) == WalkResult::Break(()) {
121 let _ = visitor.leave_node(node);
122 return WalkResult::Break(());
123 }
124 }
125 }
126 }
127 } else {
128 for child in node.child_nodes() {
129 if walk_node(&child, visitor, options) == WalkResult::Break(()) {
130 let _ = visitor.leave_node(node);
131 return WalkResult::Break(());
132 }
133 }
134 }
135
136 if visitor.leave_node(node) == WalkResult::Break(()) {
137 return WalkResult::Break(());
138 }
139 WalkResult::Continue(())
140}
141
142impl SyntaxNode {
143 /// Walks the subtree rooted at this node with the given visitor and options.
144 ///
145 /// Returns `ControlFlow::Break(())` if the visitor requested early termination.
146 pub fn walk(&self, visitor: &mut impl Visitor, options: &WalkOptions) -> WalkResult {
147 walk_node(self, visitor, options)
148 }
149}
150
151/// Walks the tree starting at `root` with the given visitor and options.
152///
153/// Equivalent to `root.walk(visitor, options)`. Returns `ControlFlow::Break(())`
154/// if the visitor requested early termination.
155#[inline]
156pub fn walk(root: &SyntaxNode, visitor: &mut impl Visitor, options: &WalkOptions) -> WalkResult {
157 root.walk(visitor, options)
158}