Skip to main content

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}