Lecture7 AST
Lecture7 AST
Compiling Techniques
Lecture 7: Abstract Syntax
Christophe Dubach
13 October 2015
Table of contents
1 Syntax Tree
Semantic Actions
Examples
Abstract Grammar
3 AST Processing
Object-Oriented Processing
Visitor Processing
Syntax Tree
number ’3’
’5’
Christophe Dubach Compiling Techniques
Syntax Tree Semantic Actions
Abstract Syntax Tree Examples
AST Processing Abstract Grammar
Abstract Grammar
These simplifications leads to a new simpler context-free grammar
caller Abstract Grammar.
Example: abstract grammar for arithmetic expressions
Expr : : = BinOp | i n t L i t e r a l
BinOp : : = Expr Op Expr
Op : : = add | sub | mul | d i v
5∗3
BinOp
intLiteral(5) intLiteral(3)
mul
c l a s s I n t L i t e r a l extends Expr {
int i ;
IntLiteral ( int i ){...}
}
c l a s s BinOp e x t e n d s E x p r {
Op op ;
Expr l h s ;
Expr r h s ;
BinOp (Op op , E x p r l h s , E x p r r h s ) { . . . }
}
Compiler Pass
AST pass
An AST pass is an action that process the AST in a single
traversal.
An pass can for instance:
assign a type to each node of the AST
perform an optimisation
generate code
It is important to ensure that the different passes can access the
AST in a flexible way. An inefficient solution would be to use
instanceof to find the type of syntax node
Example
i f ( tree instanceof I n t L i t e r a l )
(( I n t L i t e r a l ) tree ). i ;
Object-Oriented Processing
a b s t r a c t c l a s s Expr {
abstract String toStr ();
abstract int eval ();
}
c l a s s I n t L i t e r a l extends Expr {
int i ;
S t r i n g t o S t r ( ) { r e t u r n ” ”+i ; }
int eval () { return i ; }
}
c l a s s BinOp e x t e n d s E x p r {
Op op ;
Expr l h s ;
Expr r h s ;
String toStr () { return lhs . toStr () + op . name ( ) + r h s . t o S t r ( ) ; }
int eval () {
s w i t c h ( op ) {
c a s e ADD: l h s . e v a l ( ) + r h s . e v a l (); break ;
c a s e SUB : l h s . e v a l ( ) − r h s . e v a l (); break ;
c a s e MUL: l h s . e v a l ( ) ∗ r h s . e v a l (); break ;
c a s e DIV : l h s . e v a l ( ) / r h s . e v a l (); break ;
} } }
Main class
c l a s s Main {
v o i d main ( S t r i n g [ ] a r g s ) {
Expr e x p r = E x p r P a r s e r . p a r s e ( s o m e i n p u t f i l e ) ;
String s t r = expr . toStr ( ) ;
int r e s u l t = expr . eval ( ) ;
}
}
Visitor Processing
With this technique, all the methods from a pass are grouped in a
visitor.
For this, need a language that implements single dispatch:
the method is chosen based on the dynamic type of the object
(the AST node)
The visitor design pattern allows us to implement double dispatch,
the method is chosen based on:
the dynamic type of the object (the AST node)
the dynamic type of the argument (the visitor)
Note that if the language supports pattern matching, it is not
needed to use a visitor since double-dispatch can be implemented
more effectively.
In Java:
Single dispatch
class A {
v o i d p r i n t ( ) { System . o u t . p r i n t ( ”A” ) } ;
}
c l a s s B extends A {
v o i d p r i n t ( ) { System . o u t . p r i n t ( ”B” ) } ;
}
A a = new A ( ) ;
B b = new B ( ) ;
a . p r i n t ( ) ; // o u t p u t s A
b . p r i n t ( ) ; // o u t p u t s B
class A { }
c l a s s B extends A { }
class Print () {
v o i d p r i n t (A a ) { System . o u t . p r i n t ( ”A” ) } ;
v o i d p r i n t (B b ) { System . o u t . p r i n t ( ”B” ) } ;
}
A a = new A ( ) ;
B b = new B ( ) ;
A b2 = new B ( ) ;
P r i n t p = new P r i n t ( ) ;
p . p r i n t ( a ) ; // o u t p u t s A
p . p r i n t ( b ) ; // o u t p u t s B
p . p r i n t ( b2 ) ; // o u t p u t s A
Visitor Interface
i n t e r f a c e V i s i t o r <T> {
T visitIntLiteral ( IntLiteral i l );
T v i s i t B i n O p ( BinOp bo ) ;
}
ToStr Visitor
ToStr i m p l e m e n t s V i s i t o r <S t r i n g > {
String v i s i t I n t L i t e r a l ( IntLiteral i l ) {
r e t u r n ” ”+ i l . i ;
}
S t r i n g v i s i t B i n O p ( BinOp bo ) {
r e t u r n bo . l h s . a c c e p t ( t h i s ) + bo . op . name ( ) + bo . r h s . a c c e p t ( t h i s
} }
Eval Visitor
E v a l i m p l e m e n t s V i s i t o r <I n t e g e r > {
Integer v i s i t I n t L i t e r a l ( IntLiteral i l ) {
return i l . i ;
}
I n t e g e r v i s i t B i n O p ( BinOp bo ) {
s w i t c h ( bo . op ) {
c a s e ADD: l h s . a c c e p t ( t h i s ) + r h s . a c c e p t ( t h i s ); break ;
c a s e SUB : l h s . a c c e p t ( t h i s ) − r h s . a c c e p t ( t h i s ); break ;
c a s e MUL: l h s . a c c e p t ( t h i s ) ∗ r h s . a c c e p t ( t h i s ); break ;
c a s e DIV : l h s . a c c e p t ( t h i s ) / r h s . a c c e p t ( t h i s ); break ;
} }
Main class
c l a s s Main {
v o i d main ( S t r i n g [ ] a r g s ) {
Expr e x p r = E x p r P a r s e r . p a r s e ( s o m e i n p u t f i l e ) ;
S t r i n g s t r = e x p r . a c c e p t ( new ToStr ( ) ) ;
i n t r e s u l t = e x p r . a c c e p t ( new E v a l ( ) ) ;
}
}
Extensibility
Facilitate extensibility:
the object-oriented design makes it easy to add new type of
AST node
the visitor-based scheme makes it easy to write new passes
Facilitate modularity:
the object-oriented design allows for code and data to be
stored in the AST node and be shared between phases (e.g.,
types)
the visitor design allows for code and data to be shared
among the methods of the same pass
Next lecture
Context-sensitive Analysis