-
Notifications
You must be signed in to change notification settings - Fork 0
TreeWalker
David Sisco edited this page Jan 5, 2026
·
2 revisions
The TreeWalker provides DOM-style filtered tree traversal similar to the W3C DOM TreeWalker specification.
using TinyTokenizer.Ast;
var tree = SyntaxTree.Parse("foo { bar(x) }");
// Create walker from tree
var walker = tree.CreateTreeWalker();
// Or from a specific node
var walker = new TreeWalker(tree.Root);
// Enumerate all descendants
foreach (var node in walker.DescendantsAndSelf())
{
Console.WriteLine($"{node.Kind} at {node.Position}");
}Filter which node types the walker visits:
// Only leaf nodes
var leafWalker = new TreeWalker(tree.Root, NodeFilter.Leaves);
// Only block nodes
var blockWalker = new TreeWalker(tree.Root, NodeFilter.Blocks);
// All nodes (default)
var allWalker = new TreeWalker(tree.Root, NodeFilter.All);
// Combine flags
var walker = new TreeWalker(tree.Root, NodeFilter.Leaves | NodeFilter.Blocks);| Flag | Description |
|---|---|
NodeFilter.All |
All node types |
NodeFilter.Leaves |
Only leaf nodes (identifiers, operators, etc.) |
NodeFilter.Blocks |
Only block nodes ({ }, [ ], ( )) |
Use FilterResult for fine-grained control:
var walker = new TreeWalker(
tree.Root,
NodeFilter.All,
node => node.Kind == NodeKind.Ident
? FilterResult.Accept // Include this node
: FilterResult.Skip); // Skip node, check children
var idents = walker.DescendantsAndSelf().ToList();| Value | Behavior |
|---|---|
FilterResult.Accept |
Include this node in results |
FilterResult.Reject |
Exclude this node and its entire subtree |
FilterResult.Skip |
Exclude this node but still visit children |
// Only identifiers longer than 3 characters
var walker = new TreeWalker(tree.Root, NodeFilter.Leaves, node =>
{
if (node is SyntaxToken leaf && leaf.Kind == NodeKind.Ident && leaf.Width > 3)
return FilterResult.Accept;
return FilterResult.Skip;
});
// Skip entire comment blocks
var walker = new TreeWalker(tree.Root, NodeFilter.All, node =>
{
if (node.Kind == NodeKind.Comment)
return FilterResult.Reject; // Skip node AND children
return FilterResult.Accept;
});Enumerate current node and all descendants (depth-first):
foreach (var node in walker.DescendantsAndSelf())
{
Console.WriteLine(node.Kind);
}Enumerate all descendants (excluding self):
foreach (var node in walker.Descendants())
{
// Process descendants only
}Enumerate ancestors from parent to root:
var leaf = tree.Leaves.First();
var walker = new TreeWalker(leaf);
foreach (var ancestor in walker.Ancestors())
{
Console.WriteLine($"Ancestor: {ancestor.Kind}");
}Enumerate siblings after the current node:
foreach (var sibling in walker.FollowingSiblings())
{
// Process following siblings
}Enumerate siblings before the current node:
foreach (var sibling in walker.PrecedingSiblings())
{
// Process preceding siblings
}Navigate manually using cursor methods:
var walker = new TreeWalker(tree.Root);
// Move to first child
SyntaxNode? first = walker.FirstChild();
// Move to next sibling
SyntaxNode? next = walker.NextSibling();
// Move to parent
SyntaxNode? parent = walker.ParentNode();
// Move to last child
SyntaxNode? last = walker.LastChild();
// Move to previous sibling
SyntaxNode? prev = walker.PreviousSibling();var walker = new TreeWalker(tree.Root);
// Navigate to first identifier
var current = walker.FirstChild();
while (current != null && current.Kind != NodeKind.Ident)
{
current = walker.NextNode();
}
if (current != null)
{
Console.WriteLine($"First ident: {((SyntaxToken)current).Text}");
}Sequential traversal through the entire tree:
var walker = new TreeWalker(tree.Root);
// Forward traversal
SyntaxNode? node = walker.CurrentNode;
while (node != null)
{
Console.WriteLine(node.Kind);
node = walker.NextNode();
}
// Backward traversal
node = walker.CurrentNode;
while (node != null)
{
Console.WriteLine(node.Kind);
node = walker.PreviousNode();
}Access and set the walker's current position:
var walker = new TreeWalker(tree.Root);
// Get current position
SyntaxNode current = walker.CurrentNode;
// Set current position (must be within original root's subtree)
walker.CurrentNode = someOtherNode;var tree = SyntaxTree.Parse("foo(x) + bar(y)");
var walker = new TreeWalker(tree.Root, NodeFilter.Leaves, node =>
{
if (node is SyntaxToken leaf && leaf.Kind == NodeKind.Ident)
{
// Check if followed by paren block
var next = leaf.NextSibling();
if (next?.Kind == NodeKind.ParenBlock)
return FilterResult.Accept;
}
return FilterResult.Skip;
});
foreach (var funcName in walker.DescendantsAndSelf()) // [Ident("foo"), Ident("bar")]
{
Console.WriteLine($"Function: {((SyntaxToken)funcName).Text}");
}
// Output:
// Function: foo
// Function: barvar tree = SyntaxTree.Parse("if { outer { inner } }");
var walker = new TreeWalker(tree.Root, NodeFilter.Blocks);
var blocks = walker.DescendantsAndSelf() // [Block("{ outer { inner } }"), Block("{ inner }")]
.Cast<SyntaxBlock>()
.Where(b => b.Kind == NodeKind.BraceBlock)
.ToList();
Console.WriteLine($"Found {blocks.Count} brace blocks"); // "Found 2 brace blocks"// Walk tree but skip contents of string literals
var walker = new TreeWalker(tree.Root, NodeFilter.All, node =>
{
if (node.Kind == NodeKind.String)
return FilterResult.Reject; // Don't descend into strings
return FilterResult.Accept;
});| Feature | TreeWalker | Query API |
|---|---|---|
| Use case | Low-level traversal | Pattern matching |
| Filtering |
FilterResult callbacks |
Declarative combinators |
| Navigation | Cursor-based | Selector-based |
| Flexibility | Maximum control | Concise patterns |
| Best for | Custom traversal logic | Finding specific patterns |
Use TreeWalker when you need fine-grained traversal control. Use Query API for pattern-based node selection.
- Query API — Declarative node selection
- TinyAst Guide — Syntax tree basics
- Trivia — Whitespace handling during traversal