Visitors
The visitor pattern traverses AST nodes without modifying them.
Visitor Trait
/// Visitor trait for traversing TOML AST nodes.
///
/// Implement the `visit_*` methods you care about. Default implementations
/// call the corresponding `walk_*` methods to traverse children.
pub trait TomlVisitor {
fn visit_document(&mut self, doc: &Document) {
self.walk_document(doc);
}
fn visit_document_item(&mut self, item: &DocumentItem) {
self.walk_document_item(item);
}
fn visit_key_value(&mut self, kv: &KeyValue) {
self.walk_key_value(kv);
}
fn visit_key(&mut self, key: &Key) {
self.walk_key(key);
}
fn visit_simple_key(&mut self, key: &SimpleKey) {
let _ = key; // leaf node
}
fn visit_value(&mut self, value: &Value) {
self.walk_value(value);
}
fn visit_table(&mut self, table: &Table) {
self.walk_table(table);
}
fn visit_array(&mut self, array: &Array) {
self.walk_array(array);
}
fn visit_inline_table(&mut self, table: &InlineTable) {
self.walk_inline_table(table);
}
// Walk methods traverse child nodes
fn walk_document(&mut self, doc: &Document) {
for item in &doc.items {
self.visit_document_item(item);
}
}
fn walk_document_item(&mut self, item: &DocumentItem) {
match item {
DocumentItem::Trivia(_) => {}
DocumentItem::KeyValue(kv) => self.visit_key_value(&kv.value),
DocumentItem::Table(table) => self.visit_table(&table.value),
}
}
fn walk_key_value(&mut self, kv: &KeyValue) {
self.visit_key(&kv.key.value);
self.visit_value(&kv.value.value);
}
fn walk_key(&mut self, key: &Key) {
match key {
Key::Bare(tok) => self.visit_simple_key(&SimpleKey::Bare(tok.clone())),
Key::Quoted(tok) => self.visit_simple_key(&SimpleKey::Quoted(tok.clone())),
Key::Dotted(dotted) => {
self.visit_simple_key(&dotted.first.value);
for (_, k) in &dotted.rest {
self.visit_simple_key(&k.value);
}
}
}
}
fn walk_value(&mut self, value: &Value) {
match value {
Value::Array(arr) => self.visit_array(arr),
Value::InlineTable(tbl) => self.visit_inline_table(tbl),
_ => {}
}
}
fn walk_table(&mut self, table: &Table) {
self.visit_key(&table.name.value);
for item in &table.items {
match item {
TableItem::Trivia(_) => {}
TableItem::KeyValue(kv) => self.visit_key_value(&kv.value),
}
}
}
fn walk_array(&mut self, array: &Array) {
for item in &array.items {
self.visit_value(&item.value.value);
}
}
fn walk_inline_table(&mut self, table: &InlineTable) {
for item in &table.items {
self.visit_key_value(&item.kv.value);
}
}
}
Two method types:
visit_*: Override to handle specific nodes, callswalk_*by defaultwalk_*: Traverses children, typically not overridden
Example: Collecting Keys
/// Example visitor: collects all keys in the document.
pub struct KeyCollector {
pub keys: Vec<String>,
}
impl KeyCollector {
pub fn new() -> Self {
Self { keys: Vec::new() }
}
pub fn collect(doc: &Document) -> Vec<String> {
let mut collector = Self::new();
collector.visit_document(doc);
collector.keys
}
}
impl Default for KeyCollector {
fn default() -> Self {
Self::new()
}
}
impl TomlVisitor for KeyCollector {
fn visit_simple_key(&mut self, key: &SimpleKey) {
let name = match key {
SimpleKey::Bare(tok) => tok.0.clone(),
SimpleKey::Quoted(tok) => tok.0.clone(),
};
self.keys.push(name);
}
}
Usage:
let mut collector = KeyCollector::new();
collector.visit_document(&doc.value);
// collector.keys now contains all key names
Example: Counting Values
/// Example visitor: counts values by type.
#[derive(Default, Debug)]
pub struct ValueCounter {
pub strings: usize,
pub integers: usize,
pub booleans: usize,
pub arrays: usize,
pub inline_tables: usize,
}
impl ValueCounter {
pub fn new() -> Self {
Self::default()
}
pub fn count(doc: &Document) -> Self {
let mut counter = Self::new();
counter.visit_document(doc);
counter
}
}
impl TomlVisitor for ValueCounter {
fn visit_value(&mut self, value: &Value) {
match value {
Value::String(_) => self.strings += 1,
Value::Integer(_) => self.integers += 1,
Value::True(_) | Value::False(_) => self.booleans += 1,
Value::Array(arr) => {
self.arrays += 1;
self.visit_array(arr);
}
Value::InlineTable(tbl) => {
self.inline_tables += 1;
self.visit_inline_table(tbl);
}
}
}
}
Example: Finding Tables
/// Example visitor: finds all table names.
pub struct TableFinder {
pub tables: Vec<String>,
}
impl TableFinder {
pub fn new() -> Self {
Self { tables: Vec::new() }
}
pub fn find(doc: &Document) -> Vec<String> {
let mut finder = Self::new();
finder.visit_document(doc);
finder.tables
}
fn key_to_string(key: &Key) -> String {
match key {
Key::Bare(tok) => tok.0.clone(),
Key::Quoted(tok) => format!("\"{}\"", tok.0),
Key::Dotted(dotted) => {
let mut parts = vec![Self::simple_key_to_string(&dotted.first.value)];
for (_, k) in &dotted.rest {
parts.push(Self::simple_key_to_string(&k.value));
}
parts.join(".")
}
}
}
fn simple_key_to_string(key: &SimpleKey) -> String {
match key {
SimpleKey::Bare(tok) => tok.0.clone(),
SimpleKey::Quoted(tok) => format!("\"{}\"", tok.0),
}
}
}
impl Default for TableFinder {
fn default() -> Self {
Self::new()
}
}
impl TomlVisitor for TableFinder {
fn visit_table(&mut self, table: &Table) {
self.tables.push(Self::key_to_string(&table.name.value));
self.walk_table(table);
}
}
Visitor vs Direct Traversal
Visitor pattern when:
- Multiple traversal operations needed
- Want to separate traversal from logic
- Building analysis tools
Direct recursion when:
- One-off transformation
- Simple structure
- Need mutation
Transforming Visitors
For mutation, use a mutable visitor or return new nodes:
pub trait TomlTransform {
fn transform_value(&mut self, value: Value) -> Value {
self.walk_value(value)
}
fn walk_value(&mut self, value: Value) -> Value {
match value {
Value::Array(arr) => Value::Array(self.transform_array(arr)),
other => other,
}
}
// ...
}
Visitor Tips
Selective Traversal
Override visit_* to stop descent:
fn visit_inline_table(&mut self, _table: &InlineTable) {
// Don't call walk_inline_table - skip inline table contents
}
Accumulating Results
Use struct fields:
struct Stats {
tables: usize,
keys: usize,
values: usize,
}
impl TomlVisitor for Stats {
fn visit_table(&mut self, table: &Table) {
self.tables += 1;
self.walk_table(table);
}
// ...
}
Context Tracking
Track path during traversal:
struct PathTracker {
path: Vec<String>,
paths: Vec<String>,
}
impl TomlVisitor for PathTracker {
fn visit_table(&mut self, table: &Table) {
self.path.push(table_name(table));
self.paths.push(self.path.join("."));
self.walk_table(table);
self.path.pop();
}
}