My collegue suggested me to write a visitor pattern to navigate the AST. Can anyone tell me more how would I start writing it?
As far as I understand, each Node in AST would have visit()
method (?) that would somehow get called (from where?). That about concludes my understanding.
To simplify everything, suppose I have nodes Root
, Expression
, Number
, Op
and the tree looks like this:
Root
|
Op(+)
/ \
/ \
Number(5) \
Op(*)
/ \
/ \
/ \
Number(2) Number(444)
Can anyone think of how the visitor pattern would visit this tree to produce output:
5 + 2 * 444
Thanks, Boda Cydo.
Wikipedia has a great overview of how the Visitor pattern works, although the sample implementation that they use is in Java. You can easily port that to Python, though, no?
Basically, you want to implement a mechanism for double dispatch. Each node in your AST would need to implement an
accept()
method (NOT avisit()
method). The method takes, as an argument, a visitor object. In the implementation of thisaccept()
method, you call avisit()
method of the visitor object (there will be one for each AST node type; in Java, you'll use parameter overloading, in Python I suppose you can use differentvisit_*()
methods). The correct visitor will then be dispatched with the correct Node type as argument.The two variants to implement the Visitor pattern in Python that I encountered on the Internet most often:
Translated Example from the Desigh Patterns Book
This variant uses
accept()
methods in the data structure classes and correspondingvisit_Type()
methods in the visitors.The data structure
Infix Print Visitor
Prefix Print Visitor
Test
Output
Using additional modules
This variant uses
@functools.singledispatch()
decorator (available in the Python Standard Library since Python v3.4).The data structure
Infix Print Visitor
Prefix Print Visitor
Test
Output
The reason I prefer this variant is that it eliminates the
accept()
methods and completely separates the data structure from the operations implemented in the visitors. Extending the data structure with new elements does not require changing the visitors. The visitors ignore the unknown element types by default (see the definitions with thepass
keyword). A drawback of this method is thatsingledispatch
decorator cannot be used with instance methods directly, although, there are ways to make it work.For Python before v3.4 multimethods module can be used similar to the singledispatch decorator. One drawback of the multimethods module is that the visitor method that is applied to a given data-structure element is selected not only based on the element's type but also on the order in which the methods are declared. Keeping the method definitions in the right order can be cumbersome and error prone for data structures with a complex inheritance hierarchy.
See the docs for
ast.NodeVisitor
, e.g. a crude possibility might be:of course this doesn't emit parentheses even where needed, etc, so there's actually more work done, but, it's a start;-).