Semantic Analyzer: - The Result Is A Syntax-Directed Translation, - Attribute Grammars
Semantic Analyzer: - The Result Is A Syntax-Directed Translation, - Attribute Grammars
• A semantic analyzer checks the source program for semantic errors and
collects the type information for the code generation.
• Type-checking is an important part of semantic analyzer.
• Normally semantic information cannot be represented by a context-free
language used in syntax analyzers.
• Context-free grammars used in the syntax analysis are integrated with
attributes (semantic rules)
– the result is a syntax-directed translation,
– Attribute grammars
• Ex:
newval := oldval + 12
• The type of the identifier newval must match with type of the expression (oldval+12)
E.val=17 return
E.val=5 + T.val=12
digit.lexval=5 digit.lexval=3
digit.lexval=5 digit.lexval=3
UCS802 Compiler Construction 12
A Dependency Graph
• If an attribute b at a node in a parse tree depends on an attribute c, then
the semantic rule for b at that node must be evaluated after the semantic
rule that defines c.
• The interdependencies among the inherited and synthesized attributes at
the node in the parse tree can be depicted with by a graph, called
dependency graph
id.loc=‘a’ id := E
E.loc=‘t1’
E.loc=‘b’
E.code=‘ ‘ E + E E.code=‘t1:=c*d ‘
E.loc=‘d’
id.loc=‘b’ id E * E E.code=‘ ‘
E.loc=‘c’
E.code=‘ ‘
id.loc=‘c’ id id id.loc=‘d’
D T.type=real L.in=real
T L L1.in=real addtype(q,real)
id id.entry=p
stack parallel-stack
top ® Z Z.z
Y Y.y
X X.x è top ® A A.a
. . . .
UCS802 Compiler Construction 19
Bottom-Up Eval. of S-Attributed Definitions (cont.)
Production Semantic Rules
L → E return print(val[top-1])
E → E1 + T val[ntop] = val[top-2] + val[top]
E→T
T → T1 * F val[ntop] = val[top-2] * val[top]
T→F
F→(E) val[ntop] = val[top-1]
F → digit
• At each shift of digit, we also push digit.lexval into val-stack.
• At all other shifts, we do not put anything into val-stack because
other terminals do not have attributes (but we increment the
stack pointer for val-stack).
.. .. .
r T
L→ Er T →T *F
E
..
E→ E+T I2: L →E r + I8: E →E+ T F 4
..
E→ T E →E +T T → T*F (
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
F→ d 4 8
. (
d 5
I6: F →d d
6
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→e
else error(“unexpected token”);
}
Semantic Actions
UCS802 Compiler Construction 29
Translation Schemes
• When designing a translation scheme, some restrictions should be
observed to ensure that an attribute value is available when a semantic
action refers to that attribute.
• These restrictions (motivated by L-attributed definitions) ensure that
a semantic action does not refer to an attribute that has not yet
computed.
• In translation schemes, we use semantic action terminology instead of
semantic rule terminology used in syntax-directed definitions.
• The position of the semantic action on the right side indicates when that
semantic action will be evaluated.
ß
E → E1 + T { E.val = E1.val + T.val } è the production of the corresponding
translation scheme
E→TR
R → + T { print(“+”) } R1
R→e
T → id { print(id.name) }
a+b+c è ab+c+
T R
id {print(“a”)} + T {print(“+”)} R
id {print(“b”)} + T {print(“+”)} R
id {print(“c”)} e
The depth first traversal of the parse tree (executing the semantic actions in that order)
will produce the postfix representation of the infix expression.
UCS802 Compiler Construction 33
Inherited Attributes in Translation Schemes
• If a translation scheme has to contain both synthesized and inherited
attributes, we have to observe the following rules:
1. An inherited attribute of a symbol on the right side of a production
must be computed in a semantic action before that symbol.
2. A semantic action must not refer to a synthesized attribute of a
symbol to the right of that semantic action.
3. A synthesized attribute for the non-terminal on the left can only be
computed after all attributes it references have been computed (we
normally put this semantic action at the end of the right side of the
production).
• With a L-attributed syntax-directed definition, it is always possible
to construct a corresponding translation scheme which satisfies
these three conditions (This may not be possible for a general
syntax-directed translation).
UCS802 Compiler Construction 34
Top-Down Translation
• We will look at the implementation of L-attributed definitions during
predictive parsing.
• Instead of the syntax-directed translations, we will work with
translation schemes.
• We will see how to evaluate inherited attributes (in L-attributed
definitions) during recursive predictive parsing.
• We will also look at what happens to attributes during the left-recursion
elimination in the left-recursive grammars.
• When we eliminate the left recursion from the grammar (to get a
suitable grammar for the top-down parsing) we also have to change
semantic actions
UCS802 Compiler Construction 38
Eliminating Left Recursion (in general)
A → A1 Y { A.a = g(A1.a,Y.y) } a left recursive grammar with
A → X { A.a=f(X.x) } synthesized attributes (a,y,x).
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 → e { R.syn = R.in }
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)
e R1.syn=g(f(X.x),Y.y)
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 → e { 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 → e { B.syn = B.in }
F → ( E ) { F.val = E.val }
F → digit { F.val = digit.lexval }
ß
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®e {M1.s=M1.i}
M2®e {M2.s=M2.i}
M3®e {M3.s=M3.i}
S ® M1 A { s[ntop]=k(s[top-1],s[top]) }
M1® e { 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® e { s[ntop]=f(s[top]) }
M3® e { s[ntop]=g(s[top-2],s[top-1],s[top])}
B®b { s[ntop]=m(s[top-1],s[top]) }
C®c { s[ntop]=n(s[top-1],s[top]) }
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