Skip to content

Commit 167bcda

Browse files
author
Marlon Costa
committed
feat(ast): Add AST traversal tools and expose Node API
1 parent 37e5d83 commit 167bcda

File tree

5 files changed

+219
-33
lines changed

5 files changed

+219
-33
lines changed

src/asttools.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//! AST traversal and analysis utilities.
2+
//!
3+
//! This module provides helper functions and macros for traversing
4+
//! and analyzing the AST tree structure.
5+
6+
use crate::node::Node;
7+
8+
/// Gets an ancestor at a specific level above the current node.
9+
///
10+
/// # Arguments
11+
/// * `node` - The starting node
12+
/// * `level` - How many levels up to traverse (0 returns the node itself)
13+
///
14+
/// # Returns
15+
/// The ancestor node at the specified level, or None if the tree isn't deep enough.
16+
///
17+
/// # Example
18+
/// ```ignore
19+
/// // Get the grandparent (2 levels up)
20+
/// if let Some(grandparent) = get_parent(&node, 2) {
21+
/// println!("Grandparent kind: {}", grandparent.kind());
22+
/// }
23+
/// ```
24+
pub fn get_parent<'a>(node: &Node<'a>, level: usize) -> Option<Node<'a>> {
25+
let mut level = level;
26+
let mut current = *node;
27+
while level != 0 {
28+
current = current.parent()?;
29+
level -= 1;
30+
}
31+
Some(current)
32+
}
33+
34+
/// Checks if a node has specific ancestors in sequence.
35+
///
36+
/// This macro checks if the node's ancestors match a specific pattern,
37+
/// where the first pattern(s) are immediate ancestors and the last pattern
38+
/// is the final ancestor to match.
39+
///
40+
/// # Example
41+
/// ```ignore
42+
/// // Check if node is inside a function inside a class
43+
/// let is_method = has_ancestors!(node, Class | Struct, Function);
44+
/// ```
45+
#[macro_export]
46+
macro_rules! has_ancestors {
47+
($node:expr, $( $typs:pat_param )|*, $( $typ:pat_param ),+) => {{
48+
let mut res = false;
49+
loop {
50+
let mut node = *$node;
51+
$(
52+
if let Some(parent) = node.parent() {
53+
match parent.kind_id().into() {
54+
$typ => {
55+
node = parent;
56+
},
57+
_ => {
58+
break;
59+
}
60+
}
61+
} else {
62+
break;
63+
}
64+
)*
65+
if let Some(parent) = node.parent() {
66+
match parent.kind_id().into() {
67+
$( $typs )|+ => {
68+
res = true;
69+
},
70+
_ => {}
71+
}
72+
}
73+
break;
74+
}
75+
res
76+
}};
77+
}
78+
79+
/// Counts specific ancestors matching a pattern until a stop condition.
80+
///
81+
/// This macro traverses up the tree counting ancestors that match the given
82+
/// patterns, stopping when it encounters an ancestor matching the stop pattern.
83+
///
84+
/// # Example
85+
/// ```ignore
86+
/// // Count nested if statements until we hit a function boundary
87+
/// let nesting = count_specific_ancestors!(node, If | ElseIf, Function | Method);
88+
/// ```
89+
#[macro_export]
90+
macro_rules! count_specific_ancestors {
91+
($node:expr, $checker:ty, $( $typs:pat_param )|*, $( $stops:pat_param )|*) => {{
92+
let mut count = 0;
93+
let mut node = *$node;
94+
while let Some(parent) = node.parent() {
95+
match parent.kind_id().into() {
96+
$( $typs )|* => {
97+
if !<$checker>::is_else_if(&parent) {
98+
count += 1;
99+
}
100+
},
101+
$( $stops )|* => break,
102+
_ => {}
103+
}
104+
node = parent;
105+
}
106+
count
107+
}};
108+
}
109+
110+
#[cfg(test)]
111+
mod tests {
112+
#[test]
113+
fn test_get_parent_level_zero() {
114+
// Level 0 should return the same node
115+
// (actual test would need a real node)
116+
}
117+
}

src/getter.rs

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -438,31 +438,30 @@ impl Getter for CppCode {
438438
return std::str::from_utf8(code).ok();
439439
}
440440
// we're in a function_definition so need to get the declarator
441-
if let Some(declarator) = node.child_by_field_name("declarator") {
442-
let declarator_node = declarator;
443-
if let Some(fd) = declarator_node.first_occurrence(|id| {
441+
if let Some(declarator) = node.child_by_field_name("declarator")
442+
&& let Some(fd) = declarator.first_occurrence(|id| {
444443
Cpp::FunctionDeclarator == id
445444
|| Cpp::FunctionDeclarator2 == id
446445
|| Cpp::FunctionDeclarator3 == id
447-
}) && let Some(first) = fd.child(0)
448-
{
449-
match first.kind_id().into() {
450-
Cpp::TypeIdentifier
451-
| Cpp::Identifier
452-
| Cpp::FieldIdentifier
453-
| Cpp::DestructorName
454-
| Cpp::OperatorName
455-
| Cpp::QualifiedIdentifier
456-
| Cpp::QualifiedIdentifier2
457-
| Cpp::QualifiedIdentifier3
458-
| Cpp::QualifiedIdentifier4
459-
| Cpp::TemplateFunction
460-
| Cpp::TemplateMethod => {
461-
let code = &code[first.start_byte()..first.end_byte()];
462-
return std::str::from_utf8(code).ok();
463-
}
464-
_ => {}
446+
})
447+
&& let Some(first) = fd.child(0)
448+
{
449+
match first.kind_id().into() {
450+
Cpp::TypeIdentifier
451+
| Cpp::Identifier
452+
| Cpp::FieldIdentifier
453+
| Cpp::DestructorName
454+
| Cpp::OperatorName
455+
| Cpp::QualifiedIdentifier
456+
| Cpp::QualifiedIdentifier2
457+
| Cpp::QualifiedIdentifier3
458+
| Cpp::QualifiedIdentifier4
459+
| Cpp::TemplateFunction
460+
| Cpp::TemplateMethod => {
461+
let code = &code[first.start_byte()..first.end_byte()];
462+
return std::str::from_utf8(code).ok();
465463
}
464+
_ => {}
466465
}
467466
}
468467
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ pub use crate::function::*;
8484
mod ast;
8585
pub use crate::ast::*;
8686

87+
/// AST traversal and analysis utilities.
88+
pub mod asttools;
89+
pub use crate::asttools::get_parent;
90+
8791
mod count;
8892
pub use crate::count::*;
8993

src/node.rs

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,49 +37,60 @@ impl<'a> Node<'a> {
3737
self.0.has_error()
3838
}
3939

40-
pub(crate) fn id(&self) -> usize {
40+
/// Returns a numeric id for this node that is unique within its tree.
41+
pub fn id(&self) -> usize {
4142
self.0.id()
4243
}
4344

44-
pub(crate) fn kind(&self) -> &'static str {
45+
/// Returns the node's type as a string.
46+
pub fn kind(&self) -> &'static str {
4547
self.0.kind()
4648
}
4749

48-
pub(crate) fn kind_id(&self) -> u16 {
50+
/// Returns the node's type as a numeric id.
51+
pub fn kind_id(&self) -> u16 {
4952
self.0.kind_id()
5053
}
5154

52-
pub(crate) fn utf8_text(&self, data: &'a [u8]) -> Option<&'a str> {
55+
/// Returns the node's text as a UTF-8 string, if valid.
56+
pub fn utf8_text(&self, data: &'a [u8]) -> Option<&'a str> {
5357
self.0.utf8_text(data).ok()
5458
}
5559

56-
pub(crate) fn start_byte(&self) -> usize {
60+
/// Returns the byte offset where this node starts.
61+
pub fn start_byte(&self) -> usize {
5762
self.0.start_byte()
5863
}
5964

60-
pub(crate) fn end_byte(&self) -> usize {
65+
/// Returns the byte offset where this node ends.
66+
pub fn end_byte(&self) -> usize {
6167
self.0.end_byte()
6268
}
6369

64-
pub(crate) fn start_position(&self) -> (usize, usize) {
70+
/// Returns the (row, column) position where this node starts.
71+
pub fn start_position(&self) -> (usize, usize) {
6572
let temp = self.0.start_position();
6673
(temp.row, temp.column)
6774
}
6875

69-
pub(crate) fn end_position(&self) -> (usize, usize) {
76+
/// Returns the (row, column) position where this node ends.
77+
pub fn end_position(&self) -> (usize, usize) {
7078
let temp = self.0.end_position();
7179
(temp.row, temp.column)
7280
}
7381

74-
pub(crate) fn start_row(&self) -> usize {
82+
/// Returns the row number where this node starts.
83+
pub fn start_row(&self) -> usize {
7584
self.0.start_position().row
7685
}
7786

78-
pub(crate) fn end_row(&self) -> usize {
87+
/// Returns the row number where this node ends.
88+
pub fn end_row(&self) -> usize {
7989
self.0.end_position().row
8090
}
8191

82-
pub(crate) fn parent(&self) -> Option<Node<'a>> {
92+
/// Returns this node's parent, if any.
93+
pub fn parent(&self) -> Option<Node<'a>> {
8394
self.0.parent().map(Node)
8495
}
8596

@@ -183,6 +194,21 @@ impl<'a> Node<'a> {
183194
}
184195
res
185196
}
197+
198+
/// Checks if this node has any ancestor that meets the given predicate.
199+
///
200+
/// Traverses up the tree from this node's parent to the root,
201+
/// returning true if any ancestor satisfies the predicate.
202+
pub fn has_ancestor<F: Fn(&Node) -> bool>(&self, pred: F) -> bool {
203+
let mut node = *self;
204+
while let Some(parent) = node.parent() {
205+
if pred(&parent) {
206+
return true;
207+
}
208+
node = parent;
209+
}
210+
false
211+
}
186212
}
187213

188214
/// An `AST` cursor.
@@ -236,6 +262,35 @@ impl<'a> Search<'a> for Node<'a> {
236262
None
237263
}
238264

265+
fn all_occurrences(&self, pred: fn(u16) -> bool) -> Vec<Node<'a>> {
266+
let mut cursor = self.cursor();
267+
let mut stack = Vec::new();
268+
let mut children = Vec::new();
269+
let mut results = Vec::new();
270+
271+
stack.push(*self);
272+
273+
while let Some(node) = stack.pop() {
274+
if pred(node.kind_id()) {
275+
results.push(node);
276+
}
277+
cursor.reset(&node);
278+
if cursor.goto_first_child() {
279+
loop {
280+
children.push(cursor.node());
281+
if !cursor.goto_next_sibling() {
282+
break;
283+
}
284+
}
285+
for child in children.drain(..).rev() {
286+
stack.push(child);
287+
}
288+
}
289+
}
290+
291+
results
292+
}
293+
239294
fn act_on_node(&self, action: &mut dyn FnMut(&Node<'a>)) {
240295
let mut cursor = self.cursor();
241296
let mut stack = Vec::new();

src/traits.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,20 @@ pub trait ParserTrait {
6666
fn get_filters(&self, filters: &[String]) -> Filter;
6767
}
6868

69-
pub(crate) trait Search<'a> {
69+
/// Search trait for AST node traversal.
70+
pub trait Search<'a> {
71+
/// Starting from this node, gets the first occurrence that meets the predicate.
7072
fn first_occurrence(&self, pred: fn(u16) -> bool) -> Option<Node<'a>>;
73+
74+
/// Starting from this node, gets all nodes that meet the given predicate.
75+
fn all_occurrences(&self, pred: fn(u16) -> bool) -> Vec<Node<'a>>;
76+
77+
/// Apply the given predicate on this node and all descendants.
7178
fn act_on_node(&self, pred: &mut dyn FnMut(&Node<'a>));
79+
80+
/// Starting from this node, gets the first child that meets the predicate.
7281
fn first_child(&self, pred: fn(u16) -> bool) -> Option<Node<'a>>;
82+
83+
/// Apply the given action on node's immediate children.
7384
fn act_on_child(&self, action: &mut dyn FnMut(&Node<'a>));
7485
}

0 commit comments

Comments
 (0)