You are on page 1of 11

1

10. Tree Visitors

3/3/2018

John Roberts

2
Overview
• Visitor Design Pattern

• Assignment and Dispatching

• Double Dispatching

• Implementation

3
Context

• We’ve built an AST, but we’ll need to traverse the tree in


order to

• Print it

• Do type checking or decorating (constrainer)

• Generate bytecodes (codegen)

• etc.
4
Assumptions

• The structure of the tree is fixed (Program nodes will


always have only one child)

• We may have an unknown number of processors that


may need to examine the tree (printing, decorating, etc.)

• The AST classes should not need to know all of the


various processing that we may want to do

• Don’t want to change the AST code every time we


discover a new type of processing

5
Assumptions

• The processors should not be responsible for walking the


tree

• The processors need only know the types of nodes the


tree contains - we want to minimize coupling of
processor objects to tree objects

6
Approach

• Each AST node is told to accept a visitor - a processor


walking the tree

• Example: A PrintVisitor will process by printing


the nodes in the tree

• If the node accepts the visitor, then the node calls on the
visitor to visit the node’s tree, and process
7
Example (from ProgramTree)

package ast;

import visitor.*;

public class ProgramTree extends AST {

public ProgramTree() {
}

public Object accept( ASTVisitor visitor ) {


return visitor.visitProgramTree( this );
}
}

8
Example (from ProgramTree)

• The ProgramTree is asked to accept the visitor

• In our cases, we don’t need to check whether the


visitor has constraints on visitation; we simply allow
visitor to visit the ProgramTree

• Each visitor must define procedures to visit every type of


node (i.e. visitProgramTree, visitBlockTree,
etc.) - these methods will perform relevant computations
for the nodes they are visiting

9
Example

• Create a visitor
 Program

PrintVisitor visitor = new


PrintVisitor(); Block

Assuming tree references this 



Decl Assign

AST (ProgramTree node), 
 int i i +

tell tree to accept the 



visitor
 i 1

tree.accept( visitor );
10
Example

• The ProgramTree’s accept tells the visitor to perform the


appropriate action:

visitor.visitProgramTree( this );

• The processing logic (visitProgramTree) calls the print


method to print

print( “Program”, tree );

• print performs logic to display the line associated with this


node, then tells each child node to accept the visitor through
the visitKids method (which tells each child to accept the
visitor):

visitKids( tree );

11 We actually use a modified form, where the concrete visitors (and interface)
Visitor Design Pattern specify a visit method for each AST type…

<<interface>> <<interface>>
Element Visitor
+ visit( e : ElementOne )
+ visit( e : ElementTwo )
ElementOne ElementTwo
+ accept( v : Visitor ) + accept( v : Visitor )

ConcreteVisitor
+ visit( e : ElementOne )
v.visit( this ) + visit( e : ElementTwo )

12
Overview
• Visitor Design Pattern

• Assignment and Dispatching

• Double Dispatching

• Implementation
13
Apparent Type

• The Java compiler deduces an apparent type for each


object by using the information in variable and method
declarations

• The apparent type of object that tree refers to is AST:



AST tree;

14
Actual type

• Each object has an actual type that it receives when it is


created

• This is the type defined by the class that constructs it

• The actual type of tree is RelOpTree:



AST tree = new RelOpTree();

15
Apparent and Actual Types

• The Java compiler ensures the apparent type it deduces for an


object is always a super type of the actual type of the object

• The Java compiler determines what calls are legal based on the
object’s apparent type

• Example: there is a getSymbol method in the RelOpTree


class but not in the AST class

AST t = new RelOpTree();


We cannot use t.getSymbol() since the apparent class of t
does not contain the getSymbol method (we’ll see the
“cannot find symbol” error)
16
Dispatching

• Dispatching causes method calls to go to the code of the


object’s actual type

• Returning to our example:



AST t = new RelOpTree();


If we subsequently include

t.accept( pv );


the accept method in the actual class of t (RelOpTree)
will be called

17
Dispatching

• This form of dispatching can only be determined


dynamically

• We may not be able to determine which subclass t


refers to until runtime, and therefore which accept
method to call until runtime

• Also referred to as dynamic binding - we don’t bind/


associate a particular piece of code with the accept
reference until runtime

18
Dispatching

• This type of dispatching can be implemented by each


object containing (in addition to its instance variables) a
pointer to a dispatch vector, which contains pointers to
the implementation of the object’s methods.

• References to methods in the dispatch vector are


generated at run time based on the actual type of the
object
19 apparent type: AST

Dispatching actual type: ProgramTree


• What is the apparent type of tree?

• What is the actual type of tree?

/* much implementation omitted for clarity... */


public abstract class AST {
protected ArrayList<AST> kids;
protected int nodeNum;
public AST getKid( int i ) {}
public int kidCount() {}
public abstract Object accept( ASTVisitor visitor );
}

public class ProgramTree extends AST {


public ProgramTree() { }
public Object accept( ASTVisitor visitor ) {}
}

AST tree = new ProgramTree();


tree.accept();

20
Dispatching

• The apparent type of tree is AST; this means we can


access only those methods in the AST class such as
kidCount and accept

/* much implementation omitted for clarity... */


public abstract class AST {
protected ArrayList<AST> kids;
protected int nodeNum;
public AST getKid( int i ) {}
public int kidCount() {}
public abstract Object accept( ASTVisitor visitor );
}

public class ProgramTree extends AST {


public ProgramTree() { }
public Object accept( ASTVisitor visitor ) {}
}

AST tree = new ProgramTree();


tree.accept();

21 The accept in tree.accept will be bound to the accept code in the


Dispatching ProgramTree class (via dispatch vector)
• The actual type of the object tree refers to is a
ProgramTree; this means that tree.accept() will
be dispatched to the accept method in the
ProgramTree class
/* much implementation omitted for clarity... */
public abstract class AST {
protected ArrayList<AST> kids;
protected int nodeNum;
public AST getKid( int i ) {}
public int kidCount() {}
public abstract Object accept( ASTVisitor visitor );
}

public class ProgramTree extends AST {


public ProgramTree() { }
public Object accept( ASTVisitor visitor ) {}
}

AST tree = new ProgramTree();


tree.accept();
22 An object in memory is more than what we currently understand - variables
Dispatch Table references, and the dispatch vector, which is a vector of references to code
Dispatch Vector Code

tree kidCount for that instance


kids accept

nodeNum

/* much implementation omitted for clarity... */


public abstract class AST {
protected ArrayList<AST> kids;
protected int nodeNum;
public AST getKid( int i ) {}
public int kidCount() {}
public abstract Object accept( ASTVisitor visitor );
}

public class ProgramTree extends AST {


public ProgramTree() { }
public Object accept( ASTVisitor visitor ) {}
}

AST tree = new ProgramTree();


tree.accept();

23
Dispatching

• Let’s consider the following method once again, with respect to


dynamic binding

public Object accept( ASTVisitor visitor ) {

return visitor.visitProgramTree( this );

}

• The issue of concern is which piece of code is associated with the


method reference visitProgramTree

• It is not determinable until runtime which code will be associated with


(bound to) visitProgramTree

• This results from the fact that visitor’s actual type is not
determinable until runtime, so we don’t know what
visitProgramTree method will execute until runtime

24
Overview
• Visitor Design Pattern

• Assignment and Dispatching

• Double Dispatching

• Implementation
25
Double Dispatch

• The visitor pattern employs a double dispatch


mechanism which avoids the use of switch statements

• We want to call the appropriate visitor method, so we


call the appropriate accept method, which then calls
the appropriate visitor method

• No switch!

26
Wherein we malign switch

• Switch statements are dangerous since it is code that is


strongly coupled with the cases

• It is prone to errors when we consider more cases - we


could easily forget to add the new cases to all the switch
statements

27
Dynamic Binding

• gives power to polymorphism

• is needed when the compiler determines that there is


more than one possible method that could be executed
by a particular call

• prevents programmers from having to write conditional


statements to explicitly choose which code to run
28
Dynamic Dispatch

• Dynamic dispatch is the process of mapping a message


to a specific sequence of code (method) at runtime - uses
dynamic binding

• If the class of an object tree is not know at compile time,


then when tree.accept() is called, the program must
decide at runtime which implementation of accept() to
invoke, based on the runtime type of object tree

• This case is known as single dispatch because an


implementation is chosen based on a single type - the
this of the object

29
Double Dispatch

• Double dispatch is the ability to dynamically select a


method not only according to the run time type of the
receiver (single dispatch), but also according to the run-
time type of the argument

• Thus, dynamic binding is applied twice - it’s very


powerful! It enhances re-usability and separation of
responsibilities.

30
Double Dispatch

• tree is some AST - let’s assume we do not know what subtype


of AST

• its is determined at RUNTIME which accept method code to


execute

• dynamic dispatch is used - the code to execute is determined


based on the actual class that tree references
AST tree;
/* assume tree is created */

PrintVisitor visitor = new PrintVisitor();

tree.accept( visitor );

ProgramTree.accept( visitor );

visitor.visitProgramTree( this );
31
Double Dispatch

• suppose tree references a ProgramTree object

• Now, we consider the accept method in ProgramTree; it


takes an ASTVisitor as an argument

• Again, we use dynamic dispatch to determine at RUNTIME


which visitProgramTree method to execute
AST tree;
/* assume tree is created */

PrintVisitor visitor = new PrintVisitor();

tree.accept( visitor );

ProgramTree.accept( visitor );

visitor.visitProgramTree( this );

32
Overview
• Visitor Design Pattern

• Assignment and Dispatching

• Double Dispatching

• Implementation

33
Implementation

• Review implementation of following classes in the compiler


codebase (these links go to a private copy, you will need
to refer to these classes in your cloned assignment code):

• ASTVisitor.java

• PrintVisitor.java

• Compiler.java

• Note you will need to update Compiler to utilize a


different visitor for your assignment!

You might also like