Elegant implementation of the Visitor Pattern in Rust
```rust use visita::*;
pub enum Operation { Add, Sub, Mul, Div }
// Use the node_group
macro to annotate your group of nodes
// the data
field allows you to attach additional data to your nodes
[node_group(data = ())]
pub enum Expr {
NumLit(f64),
Binary {
op: Operation,
lhs: Expr,
rhs: Expr,
}
}
// use the visitor
macro to annotate your visitor structs
// the output
field marks the result of the visit operation
// this macro will require that your Visitor implements Visit for every variant in the enum
struct Interpreter;
impl Visit
impl Visit
the implementation of the pattern is split between 4 traits:
```rust
// Marks a type as a family of nodes
// and is responsible for routing the visitor to the appropriate visitor methods
pub trait NodeFamily
// Marks a type as being a node belonging to a family
pub trait Node
fn accept(&self, v: &mut V, data: &Data<V, Self>) -> V::Output {
v.visit(self, data)
}
}
// Marks a type as being a visitor to a family of nodes
pub trait Visitor
// Implements the actual visiting logic for a specific node
// This is the only trait you'll need to implement manually
pub trait Visit
the node_group
macro will perform the following:
- extract the enum variants into their own structs;
- create a new enum which groups said structs;
- create a new struct which holds the node variant and the additional data;
- implement NodeFamily for said struct;
- implement Node for the struct variants;
```rust
enum Expr { NumLit(f32), Binary(Expr, Operation, Expr), }
// Becomes:
struct NumLit(f32);
impl
impl NumLit { pub fn to_node(self, data: ()) -> Expr { Expr { node: ExprNode::NumLit(self), data, } } }
struct Binary(Expr, Operation, Expr);
impl
impl Binary { pub fn to_node(self, data: ()) -> Expr { Expr { node: Box::new(ExprNode::NumLit(self)), data, } } }
enum ExprNode { NumLit(NumLit), Binary(Binary), }
struct Expr {
node: Box
impl
To construct a node you'd use: NumLit(23.0).to_node(())
.
The visitor
macro simply implements the Visitor trait for a type:
```rust
struct Interpreter;
// Becomes:
struct Interpreter;
impl Visitor
Because of the bounds made by the node_group
macro, marking a type as a visitor
will also require that it implements Visit
for every possible node inside that node family.