Tree Inspection

Query Nodes Manually

When you have a NodeRef reference, you can inspect the structure of the tree locally around this node by directly dereferencing node instances.

use lady_deirdre::{syntax::SyntaxTree, units::Document};

let doc = Document::<JsonNode>::new_immutable(r#"{
   "foo": true,
   "bar": [123, null]
}"#);

let root_ref = doc.root_node_ref();

let Some(JsonNode::Root { object, .. }) = root_ref.deref(&doc) else {
    panic!();
};

let Some(JsonNode::Object { entries, .. }) = object.deref(&doc) else {
    panic!();
};

let Some(JsonNode::Entry { value, .. }) = entries[1].deref(&doc) else {
    panic!();
};

let Some(JsonNode::Array { items, .. }) = value.deref(&doc) else {
    panic!();
};

let Some(JsonNode::Number { value, .. }) = items[0].deref(&doc) else {
    panic!();
};

let Some(string) = value.string(&doc) else {
    panic!();
};

assert_eq!(string, "123");

Alternatively, the above code could be rewritten in a more compact way using the NodeRef's inspection functions without breaking the call chain.

use lady_deirdre::{syntax::SyntaxTree, units::Document};

let doc = Document::<JsonNode>::new_immutable(r#"{
   "foo": true,
   "bar": [123, null]
}"#);

let string = doc
    .root_node_ref()
    .get_child(&doc, "object")
    .get_child(&doc, "entries") // returns the first entry
    .next_sibling(&doc) // gets the second entry
    .get_child(&doc, "value")
    .get_child(&doc, "items") // returns the first item
    .get_token(&doc, "value")
    .string(&doc)
    .unwrap();

assert_eq!(string, "123");

Each of these functions is infallible; they will return a nil NodeRef if they cannot fulfill the request. Therefore, we should be confident about the node configuration we are trying to query.

Depth-First Traversing

You can perform a depth-first traversal of the entire syntax tree or a specific branch using the SyntaxTree::traverse_tree and SyntaxTree::traverse_subtree functions, respectively.

Both functions require a visitor object to be passed as an argument. This object should implement a Visitor trait, which includes functions that will be triggered when the traversal procedure visits a node or token in the tree, according to the node-child relationships between the nodes.

use lady_deirdre::{
    lexis::TokenRef,
    syntax::{PolyRef, SyntaxTree, Visitor},
    units::Document,
};

let doc = Document::<JsonNode>::new_immutable( r#"{
    "foo": true,
    "bar": [123, null]
}"#);

doc.traverse_tree(&mut MyVisitor(&doc));

struct MyVisitor<'a>(&'a Document<JsonNode>);

impl<'a> Visitor for MyVisitor<'a> {
    fn visit_token(&mut self, token_ref: &TokenRef) {
        println!("Token\n{}", token_ref.display(self.0));
    }
    
    fn enter_node(&mut self, node_ref: &NodeRef) -> bool {
        println!("Enter\n{}", node_ref.display(self.0));
    
        // Tells the traverser to continue descending into this node's branch.
        true
    }
    
    fn leave_node(&mut self, node_ref: &NodeRef) {
        println!("Leave\n{}", node_ref.display(self.0));
    }
}

The visitor is a stateful object that you can mutate during tree traversal. You can use this mechanism to collect common metadata from the syntax tree.

The enter_node function returns a boolean value that controls whether to further descend into the entered node branch.

The leave_node function effectively visits the tree in reverse order.