Visitor Pattern
The visit module provides recursive AST traversal. It exposes:
- a
Visit<'ast>trait with hook methods - free functions like
visit_expressionandvisit_function_definitionthat recurse into children
The important rule
When you override a method, call the free function from parc::visit, not the trait method on
self. Calling self.visit_* from inside the override will recurse back into your override.
Count function definitions
#![allow(unused)]
fn main() {
use parc::{ast, span, visit};
use parc::visit::Visit;
struct FunctionCounter {
count: usize,
}
impl<'ast> Visit<'ast> for FunctionCounter {
fn visit_function_definition(
&mut self,
node: &'ast ast::FunctionDefinition,
span: &'ast span::Span,
) {
self.count += 1;
visit::visit_function_definition(self, node, span);
}
}
}
Collect identifiers from expressions
#![allow(unused)]
fn main() {
use parc::{ast, span, visit};
use parc::visit::Visit;
struct IdentifierCollector {
names: Vec<String>,
}
impl<'ast> Visit<'ast> for IdentifierCollector {
fn visit_identifier(&mut self, node: &'ast ast::Identifier, span: &'ast span::Span) {
self.names.push(node.name.clone());
visit::visit_identifier(self, node, span);
}
}
}
Use the visitor
#![allow(unused)]
fn main() {
use parc::driver::{parse, Config};
use parc::visit::Visit;
let parsed = parse(&Config::default(), "examples/sample.c")?;
let mut counter = FunctionCounter { count: 0 };
counter.visit_translation_unit(&parsed.unit);
println!("functions: {}", counter.count);
Ok::<(), parc::driver::Error>(())
}
When to override which method
- Override
visit_translation_unitfor whole-file summaries - Override
visit_function_definitionfor function-level analysis - Override
visit_declarationfor declaration inspection - Override
visit_expressionfor expression-wide checks - Override narrow hooks like
visit_call_expressionwhen you only care about one form
Traversal style
Two common styles work well:
Pre-order
Do work before recursing:
#![allow(unused)]
fn main() {
fn visit_expression(&mut self, node: &'ast ast::Expression, span: &'ast span::Span) {
self.seen += 1;
visit::visit_expression(self, node, span);
}
}
Selective traversal
Only recurse when the node passes a filter:
#![allow(unused)]
fn main() {
fn visit_statement(&mut self, node: &'ast ast::Statement, span: &'ast span::Span) {
if matches!(node, ast::Statement::Return(_)) {
self.returns += 1;
}
visit::visit_statement(self, node, span);
}
}
Practical advice
- Start with a broad hook like
visit_expressionwhile learning the tree. - Narrow to specific hooks once you understand the shapes you care about.
- Pair the visitor with
Printerwhen a subtree is unclear.