Lec04 Sematic Analysis
Lec04 Sematic Analysis
1
BEYOND SYNTAX
The type of the identifier newval must match with type of the expression
(oldval+12) 5
SYNTAX-DIRECTED TRANSLATION
9
SYNTAX-DIRECTED DEFINITION
Synthesized attributes depend only on the attributes of children. They are the most
common attribute type.
Synthesized attributes never take values from their parent nodes or any sibling
nodes.
The non terminal concerned must be in the head of production.
Terminals have synthesized attributes which are the lexical values (denoted by
lexval) generated by the lexical analyzer. 11
EXAMPLE SDD: DESK CALCULATOR (EX. 1)
Symbols E, T, and F are associated with synthesized attributes loc and code.
The token id has a synthesized attribute name (it is assumed that it is evaluated by the
lexical analyzer).
It is assumed that || is the string concatenation operator.
13
CALCULATING SYNTHESIZED ATTRIBUTES
Input string: 3*5+4 return
Annotated tree:
14
INHERITED ATTRIBUTES
An SDD with both inherited and synthesized attributes does not ensure any
guaranteed order; even it may not have an order at all.
For example, consider nonterminals A and B, with synthesized and inherited
attributes A.s and B.i, respectively, along with the production and rules as:
Production Semantic Rule
A →B A.s = B.i
B.i= A.s+1
These rules are circular; it is impossible to evaluate either A.s at a node N or B.i at
the child of N without first evaluating the other. The circular dependency of A.s
and B.i at some pair of nodes in a parse tree.
19
20
DEPENDENCY GRAPHS
Dependency Graph predicts the flow of information among the
attribute instances in a particular parse tree.
The interdependencies among the inherited and synthesized attributes
at the node in the parse tree is depicted by dependency graph.
If an attribute b depends on attribute c, then attribute b has to be
evaluated AFTER c.
DEPENDENCY GRAPHS visualize:
22
DEPENDENCY GRAPH EXAMPLE
Input: 5+3*4 L
E.val=17
E.val=5 T.val=12
digit.lexval=5 digit.lexval=3 23
DEPENDENCY GRAPH – SYNTHETIZED ATTRIBUTES
S.code=‘t1:=c*d t3=b+t1 a=t3’
Input a:=b+c*d S
E.loc=‘t3’
E.code=‘t1:=c*d t3=b+t1’
id.loc=‘a’ id := E
E.loc=‘t1’
E.loc=‘b’ E.code=‘t1:=c*d ‘
E.code=‘ ‘ E + E
E.loc=‘d’
id.loc=‘b’ id E.loc=‘c’
E * E E.code=‘ ‘
E.code=‘ ‘
id.loc=‘c’ id id id.loc=‘d’
25
DEPENDENCY GRAPH – INHERITED ATTRIBUTES
26
DEPENDENCY GRAPH – INHERITED ATTRIBUTES
27
DEPENDENCY GRAPH
Input: real p q
D T.type=real L.in=real
T L L1.in=real addtype(q,real)
real L id addtype(p,real)
Id.entry=q
id id.entry=p
28
parse tree dependency graph
FINDING A VALID EVALUATION ORDER
TOPOLOGICAL SORT OF THE DEPENDENCY GRAPH
30
ANNOTATED PARSE TREE
31
ANNOTATED PARSE TREE -- EXAMPLE
Input: 5+3*4 L
E.val=17 return
E.val=5 + T.val=12
digit.lexval=5 digit.lexval=3
32
SYNTAX TREE CONSTRUCTION
33
SYNTAX TREES
37
S-ATTRIBUTED DEFINITIONS
Syntax-directed definitions are used to specify syntax-directed
translations.
39
BOTTOM-UP EVALUATION OF S-ATTRIBUTED DEFINITIONS
40
DESK CALCULATOR EXAMPLE
Production Semantic Rule Code
L -> E return print( E.val ) print val[top-1]
E -> E1 + T E.val := E1.val + T.val val[newtop] = val[top-2]+val[top]
E -> T E.val := T.val /*newtop==top, so nothing to do*/
T -> T1 * F T.val := T1.val x F.val val[newtop] = val[top-2]+val[top]
T -> F T.val := F.val /*newtop==top, so nothing to do*/
F -> ( E ) F.val := E.val val[newtop] = val[top-1]
F -> digit F.val := digit.lexval /*newtop==top, so nothing to do*/
.. .. .
L→ Er r T
E T →T *F
E→
E→
..
E+T
T
I2: L →E r
E →E +T
+
..
I8: E →E+ T
T → T*F (
F 4
5
T→
T→
F→..
T*F
F
(E)
T I3: E →T ..
T →T *F
..
T→ F
F → (E)
F→ d
d
6
F→ d
F I4: T →F . *
.
. ..
I9: T →T* F F
I12: T →T*F .
..
( I5: F → ( E) F → (E)
(
E→ E+T E
F→ d 5
..
E→ T d
6
T→
T→
F→..
T*F
F
(E)
T
3
..
I10: F →(E )
E →E +T
)
+
I13: F →(E) .
F→ d F
4 8
d I6: F →d . (
d
5
6
42
BOTTOM-UP EVALUATION -- EXAMPLE
At each shift of digit, we also push digit.lexval into val-stack.
stack val-stack input action semantic rule
0 5+3*4r s6 d.lexval(5) into val-stack
0id6 5 +3*4r F→id F.val=id.lexval – do nothing
0F4 5 +3*4r T→F T.val=F.val – do nothing
0T3 5 +3*4r E→T E.val=T.val – do nothing
0E2 5 +3*4r s8 push empty slot into val-stack
0E2+8 5- 3*4r s6 d.lexval(3) into val-stack
0E2+8id6 5-3 *4r F→id F.val=d.lexval – do nothing
0E2+8F4 5-3 *4r T→F T.val=F.val – do nothing
0E2+8T11 5-3 *4r s9 push empty slot into val-stack
0E2+8T11*9 5-3- 4r s6 d.lexval(4) into val-stack
0E2+8T11*9id6 5-3-4 r F→id F.val=d.lexval – do nothing
0E2+8T11*9F12 5-3-4 r T→T*F T.val=T1.val*F.val
0E2+8T11 5-12 r E→E+T E.val=E1.val*T.val
0E2 17 r s7 push empty slot into val-stack
0E2r7 17- $ L→Er print(17), pop empty slot from val-stack
43
0L1 17 $ acc
TOP-DOWN EVALUATION OF S-ATTRIBUTED
DEFINITIONS
Productions Semantic Rules
A→B print(B.n0), print(B.n1)
B → 0 B1 B.n0=B1.n0+1, B.n1=B1.n1
B → 1 B1 B.n0=B1.n0, B.n1=B1.n1+1
B→ B.n0=0, B.n1=0
44
TOP-DOWN EVALUATION OF S-ATTRIBUTED
DEFINITIONS
Remember that: In a recursive predicate parser, each non-terminal
corresponds to a procedure.
procedure A() {
call B(); A→B
}
procedure B() {
if (currtoken=0) { consume 0; call B(); } B→0B
else if (currtoken=1) { consume 1; call B(); } B→1B
else if (currtoken=$) {} // $ is end-marker B→
else error(“unexpected token”);
45
}
TOP-DOWN EVALUATION (OF S-ATTRIBUTED
procedure A() {
DEFINITIONS)
int n0,n1; Synthesized attributes of non-terminal B
call B(&n0,&n1); are the output parameters of procedure B.
print(n0); print(n1);
} All the semantic rules can be evaluated
procedure B(int *n0, int *n1) { at the end of parsing of production rules
if (currtoken=0)
{int a,b; consume 0; call B(&a,&b); *n0=a+1; *n1=b;}
else if (currtoken=1)
{ int a,b; consume 1; call B(&a,&b); *n0=a; *n1=b+1; }
else if (currtoken=$) {*n0=0; *n1=0; } // $ is end-marker
else error(“unexpected token”);
} 46
L-ATTRIBUTED DEFINITIONS
47
L-ATTRIBUTED DEFINITIONS
48
L-ATTRIBUTED GRAMMAR
Informally – dependency-graph edges may go from left to right, not
other way around.
Given production A → X1X2 ···Xn, inherited attributes of Xj depend only
on:
’ inherited attributes of A
’ arbitrary attributes of X1,X2,···Xj−1
Synthesized attributes of A depend only on its inherited attributes and
arbitrary RHS attributes
Synthesized attributes of an action depends only on its inherited
attributes
’ i.e., evaluation order: Inh(A), Inh(X1), Syn(X1), . . . , Inh(Xn),
Syn(Xn), Syn(A)
’ This is precisely the order of evaluation for an LL parser 49
L-ATTRIBUTED DEFINITIONS
52
3.4 TRANSLATION SCHEMES
53
TRANSLATION SCHEMES
In a syntax-directed definition, we do not say anything about the
evaluation times of the semantic rules (when the semantic rules
associated with a production should be evaluated?).
A translation scheme is a context-free grammar in which:
Semantic Actions
Translation schemes are closer to a real implementation because they54
specify when, during the parse, attributes should be computed.
TRANSLATION SCHEMES
For a translation scheme to work, it must be the case that an attribute is computed
BEFORE it is used.
If the SDD is S-attributed, it is easy to create the translation scheme implementing it:
Translation scheme:
T -> T1 * F { T.val = T1.val * F.val }
E→TR
R → + T { print(“+”) } R1
R→
T → id { print(id.name) }
a+b+c ➔ ab+c+
id{print(“a”)} + T {print(“+”)} R
id {print(“b”)} + T {print(“+”)} R
id {print(“c”)}
The depth first traversal of the parse tree (executing the semantic actions in that
58
order) will produce the postfix representation of the infix expression.
COMPARISON BETWEEN SDD AND SDT
59
EXAMPLE OF SDD AND SDT
If we traverse the parse tree depth first, A1.in has not been set
when referred to in the action print( A.in )
63
TOP-DOWN TRANSLATION
65
PREDICTIVE PARSING (OF INHERITED ATTRIBUTES)
procedure D() {
int Ttype,Lin,identry;
call T(&Ttype); consume(id,&identry);
addtype(identry,Ttype); Lin=Ttype;
call L(Lin); a synthesized attribute (an output parameter)
}
procedure T(int *Ttype) {
if (currtoken is int) { consume(int); *Ttype=TYPEINT; }
else if (currtoken is real) { consume(real); *Ttype=TYPEREAL; }
else { error(“unexpected type”); } an inherited attribute (an input parameter)
}
procedure L(int Lin) {
if (currtoken is id) { int L1in,identry; consume(id,&identry);
addtype(identry,Lin); L1in=Lin; call L(L1in); }
else if (currtoken is endmarker) { }
else { error(“unexpected token”); } 66
}
ELIMINATING LEFT RECURSION FROM TRANSLATION SCHEME
When we eliminate the left recursion from the grammar (to get a 67
suitable grammar for the top-down parsing) we also have to change
semantic actions
AA|
A → X { R.in=f(X.x) } R { A.a=R.syn }
R → Y { R1.in=g(R.in,Y.y) } R1 { R.syn = R1.syn}
R → { R.syn = R.in } 68
EVALUATING ATTRIBUTES
Parse tree of left recursive grammar
A
A Y A.a=g(f(X.x),Y.y)
Parse tree of non-left-recursive grammar
X X.x=f(X.x) A
X R.in=f(X.x) R A.a=g(f(X.x,Y.y)
Y R1.in=g(f(X.x),Y.y) R1 R.syn=g(f(X.x),Y.y)
R1.syn=g(f(X.x),Y.y)
69
ELIMINATING LEFT RECURSION (CONT.)
inherited attribute synthesized attribute
E → T { A.in=T.val } A { E.val=A.syn }
A → + T { A1.in=A.in+T.val } A1 { A.syn = A1.syn}
A → - T { A1.in=A.in-T.val } A1 { A.syn = A1.syn}
A → { A.syn = A.in }
T → F { B.in=F.val } B { T.val=B.syn }
B → * F { B1.in=B.in*F.val } B1 { B.syn = B1.syn}
B → { B.syn = B.in }
F → ( E ) { F.val = E.val }
F → digit { F.val = digit.lexval } 70
TRANSLATION SCHEME - INTERMEDIATE CODE
GENERATION
E → T { A.in=T.loc } A { E.loc=A.loc }
A → + T { A1.in=newtemp(); emit(add,A.in,T.loc,A1.in) }
A1 { A.loc = A1.loc}
A → { A.loc = A.in }
T → F { B.in=F.loc } B { T.loc=B.loc }
B → * F { B1.in=newtemp(); emit(mult,B.in,F.loc,B1.in) }
B1 { B.loc = B1.loc}
B → { B.loc = B.in }
F → ( E ) { F.loc = E.loc }
F → id { F.loc = id.name } 71
PREDICTIVE PARSING – INTERMEDIATE CODE
GENERATION
procedure E(char **Eloc) {
char *Ain, *Tloc, *Aloc;
call T(&Tloc); Ain=Tloc;
call A(Ain,&Aloc); *Eloc=Aloc;
}
procedure A(char *Ain, char **Aloc) {
if (currtok is +) {
char *A1in, *Tloc, *A1loc;
consume(+); call T(&Tloc); A1in=newtemp(); emit(“add”,Ain,Tloc,A1in);
call A(A1in,&A1loc); *Aloc=A1loc;
}
else { *Aloc = Ain }
}
72
PREDICTIVE PARSING (CONT.)
procedure T(char **Tloc) {
char *Bin, *Floc, *Bloc;
call F(&Floc); Bin=Floc;
call B(Bin,&Bloc); *Tloc=Bloc;
}
procedure B(char *Bin, char **Bloc) {
if (currtok is *) {
char *B1in, *Floc, *B1loc;
consume(+); call F(&Floc); B1in=newtemp(); emit(“mult”,Bin,Floc,B1in);
call B(B1in,&B1loc); Bloc=B1loc;
}
else { *Bloc = Bin }
}
procedure F(char **Floc) {
if (currtok is “(“) { char *Eloc; consume(“(“); call E(&Eloc); consume(“)”); *Floc=Eloc }
73
else { char *idname; consume(id,&idname); *Floc=idname }
}
BOTTOM-UP EVALUATION OF INHERITED ATTRIBUTES
74
REMOVING EMBEDDING SEMANTIC ACTIONS
76
REMOVING EMBEDDING SEMANTIC ACTIONS
E→TR
R → + T { print(“+”) } R1
R→
T → id { print(id.name) }
M → { print(“+”) }
TRANSLATION WITH INHERITED ATTRIBUTES
S {A.i=1} A {S.s=k(A.i,A.s)}
A {B.i=f(A.i)} B {C.i=g(A.i,B.i,B.s)} C {A.s= h(A.i,B.i,B.s,C.i,C.s)}
Bb {B.s=m(B.i,b.s)}
Cc {C.s=n(C.i,c.s)}
S {M1.i=1} M1 {A.i=M1.s} A {S.s=k(M1.s,A.s)}
A {M2.i=f(A.i)} M2 {B.i=M2.s} B
{M3.i=g(A.i,M2.s,B.s)} M3 {C.i=M3.s} C
{A.s= h(A.i, M2.s,B.s, M3.s,C.s)}
Bb {B.s=m(B.i,b.s)}
Cc {C.s=n(C.i,c.s)}
M1 {M1.s=M1.i}
80
M2 {M2.s=M2.i}
M3 {M3.s=M3.i}
ACTUAL TRANSLATION SCHEME
S {M1.i=1} M1 {A.i=M1.s} A {S.s=k(M1.s,A.s)}
A {M2.i=f(A.i)} M2 {B.i=M2.s} B {M3.i=g(A.i,M2.s,B.s)} M3 {C.i=M3.s} C {A.s= h(A.i, M2.s,B.s, M3.s,C.s)}
B b {B.s=m(B.i,b.s)}
C c {C.s=n(C.i,c.s)}
M1 {M1.s= M1.i}
M2 {M2.s=M2.i}
M3 {M3.s=M3.i}
S M1 A { s[ntop]=k(s[top-1],s[top]) }
M1 { s[ntop]=1 }
A M2 B M3 C { s[ntop]=h(s[top-4],s[top-3],s[top-2],s[top-1],s[top]) }
M2 { s[ntop]=f(s[top]) }
M3 { s[ntop]=g(s[top-2],s[top-1],s[top])}
Bb { s[ntop]=m(s[top-1],s[top]) } 81
Cc { s[ntop]=n(s[top-1],s[top]) }
EVALUATION OF ATTRIBUTES
S
S.s=k(1,h(..))
A.i=1
A
A.s=h(1,f(1),m(..),g(..),n(..))
B.i=f(1) C.i=g(1,f(1),m(..))
B C
B.s=m(f(1),b.s) C.s=n(g(..),c.s)
b c
82
EVALUATION OF ATTRIBUTES
stack input s-attribute stack
bc$
M1 bc$ 1
M1 M 2 bc$ 1 f(1)
M1 M 2 b c$ 1 f(1) b.s
M1 M 2 B c$ 1 f(1) m(f(1),b.s)
M1 M2 B M3 c$ 1 f(1) m(f(1),b.s) g(1,f(1),m(f(1),b.s))
M1 M2 B M3 c $ 1 f(1) m(f(1),b.s) g(1,f(1),m(f(1),b.s)) c.s
M1 M2 B M3 C $ 1 f(1) m(f(1),b.s) g(1,f(1),m(f(1),b.s)) n(g(..),c.s)
M1 A $ 1 h(f(1),m(..),g(..),n(..))
S $ k(1,h(..))
83