Creating an AST Visitor
Now that we can parse source code into an AST we need a mechanism for traversing
the tree. The way this is commonly done in Rust is by defining a Visitor
trait
which, by default, will recursively walk the tree.
A good example of this is syn::visit::Visit
.
The Visitor Trait
Our AST is nowhere near as complex as the Rust AST, so we shouldn't require
as many methods as syn
's Visit
trait.
# #![allow(unused_variables)] #fn main() { pub trait Visitor { fn visit_expr(&mut self, e: &Expr) { walk_expr(self, e); } fn visit_binary_op(&mut self, b: &BinaryOp) { walk_binary_op(self, b); } fn visit_function_call(&mut self, f: &FunctionCall) { walk_function_call(self, f); } fn visit_atom(&mut self, atom: &Atom) {} } #}
We also define several walk_*()
functions that will allow the Visitor
to
recursively visit each node in the tree using the default traversal order. By
making them pub
we allow users to use them to continue walking the tree when
they've done something at a particular node.
# #![allow(unused_variables)] #fn main() { pub fn walk_expr<V: Visitor + ?Sized>(visitor: &mut V, e: &Expr) { match *e { Expr::Atom(ref a) => visitor.visit_atom(a), _ => unimplemented!() } } pub fn walk_binary_op<V: Visitor + ?Sized>(visitor: &mut V, b: &BinaryOp) { visitor.visit_expr(&b.left); visitor.visit_expr(&b.right); } pub fn walk_function_call<V: Visitor + ?Sized>(visitor: &mut V, f: &FunctionCall) { for arg in &f.arguments { visitor.visit_expr(arg); } } #}
Don't forget to update mod.rs
so this visit
module is included in the crate.
# #![allow(unused_variables)] #fn main() { // src/syntax/mod.rs mod ast; mod grammar; pub mod visit; pub use self::ast::*; #}