Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Working with Syntax Trees

This chapter shows how to traverse, query, and work with syntax trees in Sipha.

Creating a Red Tree

Create a red tree from a parse result:

use sipha::syntax::SyntaxNode;

let root = SyntaxNode::new_root(result.root.clone());

Traversal

Children

Iterate over direct children:

for child in root.children() {
    println!("Child: {:?}", child.kind());
}

Descendants

Iterate over all descendants (depth-first):

for descendant in root.descendants() {
    println!("Descendant: {:?}", descendant.kind());
}

Ancestors

Iterate over ancestors:

if let Some(node) = root.descendants().find(|n| n.kind() == MySyntaxKind::Expr) {
    for ancestor in node.ancestors() {
        println!("Ancestor: {:?}", ancestor.kind());
    }
}

Siblings

Access siblings:

if let Some(node) = root.descendants().find(|n| n.kind() == MySyntaxKind::Expr) {
    if let Some(next) = node.next_sibling() {
        println!("Next sibling: {:?}", next.kind());
    }
    if let Some(prev) = node.prev_sibling() {
        println!("Previous sibling: {:?}", prev.kind());
    }
}

Querying

Find by Kind

Find nodes by syntax kind:

let exprs: Vec<_> = root.descendants()
    .filter(|n| n.kind() == MySyntaxKind::Expr)
    .collect();

Find by Position

Find nodes at a specific position:

use sipha::syntax::TextSize;

let pos = TextSize::from(10);
if let Some(node) = root.token_at_offset(pos) {
    println!("Token at position {}: {:?}", pos, node.kind());
}

Find Parent

Find the parent of a node:

if let Some(parent) = node.parent() {
    println!("Parent: {:?}", parent.kind());
}

Text Access

Node Text

Get the text covered by a node:

let text = node.text();
println!("Node text: {}", text);

Token Text

Get the text of a token:

if let Some(token) = node.first_token() {
    println!("Token text: {}", token.text());
}

Text Range

Get the text range of a node:

let range = node.text_range();
println!("Range: {:?}", range);

Visitors

Use visitors to traverse trees:

#[cfg(feature = "visitor")]
use sipha::syntax::{SyntaxVisitor, SyntaxWalker};

struct MyVisitor;

impl SyntaxVisitor for MyVisitor {
    fn visit_node(&mut self, node: &SyntaxNode) {
        println!("Visiting: {:?}", node.kind());
    }
    
    fn visit_token(&mut self, token: &SyntaxToken) {
        println!("Token: {:?}", token.kind());
    }
}

let mut visitor = MyVisitor;
root.walk(&mut visitor);

Queries

Use XPath-like queries (requires query feature):

#[cfg(feature = "query")]
use sipha::syntax::{QueryBuilder, XPathQuery};

let query = QueryBuilder::new()
    .descendant(MySyntaxKind::Expr)
    .child(MySyntaxKind::Number)
    .build();

let results: Vec<_> = query.matches(&root).collect();

Pattern Matching

Match tree patterns:

// Match specific structure
if let Some(expr) = root.descendants().find(|n| {
    n.kind() == MySyntaxKind::Expr &&
    n.children().any(|c| c.kind() == MySyntaxKind::Number)
}) {
    println!("Found expression with number");
}

Collecting Information

Count Nodes

Count nodes by kind:

let expr_count = root.descendants()
    .filter(|n| n.kind() == MySyntaxKind::Expr)
    .count();

Collect Text

Collect text from nodes:

let all_text: String = root.descendants()
    .filter_map(|n| n.first_token())
    .map(|t| t.text().to_string())
    .collect();

Best Practices

  1. Use iterators: Prefer iterator methods over manual traversal
  2. Cache results: Cache frequently accessed nodes
  3. Filter early: Filter nodes as early as possible
  4. Use visitors: Use visitors for complex traversals
  5. Leverage queries: Use queries for pattern matching

Next Steps