# visita   - [**crates.io**](https://crates.io/crates/visita) - [**github**](https://github.com/jvcmarcenes/visita) Elegant implementation of the [**Visitor Pattern**](https://en.wikipedia.org/wiki/Visitor_pattern) in Rust --- ## Usage: ```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 #[visitor(Expr, output = f32)] struct Interpreter; impl Visit for Interpreter { fn visit(&mut self, node: &NumLit, _data: &Data) -> Self::Output { node.0 } } impl Visit for Interpreter { fn visit(&mut self, node: &Binary_data: &Data) -> Self::Output { match node.op { Operation::Add => node.lhs.accept(self) + node.rhs.accept(self), Operation::Sub => node.lhs.accept(self) - node.rhs.accept(self), Operation::Mul => node.lhs.accept(self) * node.rhs.accept(self), Operation::Div => node.lhs.accept(self) / node.rhs.accept(self), } } } ``` --- ## Explanation: 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 : Sized where V : Visitor { // The additional data we want to tag with the nodes type Data; // The method responsible for the routing fn accept(&self, v: &mut V) -> V::Output; } // Marks a type as being a node belonging to a family pub trait Node : Sized where V : Visitor + Visit { type Family : NodeFamily; fn accept(&self, v: &mut V, data: &Data) -> V::Output { v.visit(self, data) } } // Marks a type as being a visitor to a family of nodes pub trait Visitor : Sized where F : NodeFamily { // The output of performing this operation type Output; } // Implements the actual visiting logic for a specific node // This is the only trait you'll need to implement manually pub trait Visit : Visitor where N : Node { fn visit(&mut self, node: &N, data: &Data) -> Self::Output; } ``` 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 #[node_group(data = ())] enum Expr { NumLit(f32), Binary(Expr, Operation, Expr), } // Becomes: struct NumLit(f32); impl Node for NumLit where V : Visitor + Visit + Visit { type Family = Expr; } impl NumLit { pub fn to_node(self, data: ()) -> Expr { Expr { node: ExprNode::NumLit(self), data, } } } struct Binary(Expr, Operation, Expr); impl Node for Binary where V : Visitor + Visit + Visit { type Family = Expr; } 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, data: (), } impl NodeFamily for Expr where V : Visitor + Visit + Visit { type Data = (); fn accept(&self, v: &mut V) -> V::Output { match self.node.as_ref() { ExprNode::NumLit(node) => v.visit(node, &self.data), ExprNode::Binary(node) => v.visit(node, &self.data), } } } ``` To construct a node you'd use: `NumLit(23.0).to_node(())`. The `visitor` macro simply implements the Visitor trait for a type: ```rust #[visitor(Expr, output = f32)] struct Interpreter; // Becomes: struct Interpreter; impl Visitor for Interpreter { type Output = f32; } ``` 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.