You are on page 1of 247

UE20CS353: Compiler Design

Chapter 5:

Syntax Directed Translation

Mr. Prakash C O
Asst. Professor,
Dept. of CSE, PESU,
coprakasha@pes.edu
Introduction
Semantics

 Semantics of a language provide meaning to its constructs, like


tokens and syntax structure.

 Semantics help interpret symbols, their types, and their relations


with each other.
Introduction
 We have learnt how a parser constructs parse trees in the syntax analysis
phase.

 The plain parse tree constructed in the syntax analysis phase is generally of
no use for a compiler for further processing, as it does not carry any
information of how to evaluate the tree.

Parse tree

 The productions of CFG, which makes the rules of the language, do not
accommodate how to interpret them.

For example: E→E+T|T


T→ T*F
F → id
The above CFG production has no semantic rule associated with it, and it
cannot help in making any sense of the production.
Introduction
Semantic Analysis
 Once the syntactic structure is known, Semantic Analysis computes
additional information related to the meaning of the program.

In language like C, semantic analysis involves adding information


to the symbol table and performing type checking.

 Semantic analysis judges whether the syntax structure constructed


in the source program derives any meaning or not.

 For semantic analysis, we need both


▪ Syntax Directed Definitions(SDD) = CFG + semantic rules

▪ Syntax Directed Translations(SDT) = CFG + semantic actions


Introduction
Semantic Analysis

 For example:

• int a = “value”;

• Should not issue an error in lexical and syntax analysis phase, as it is


lexically and structurally correct, but it should generate a semantic
error as the type of the assignment differs.

• These rules are set by the grammar of the language and evaluated
in semantic analysis.

 For Semantic Analysis we need both

▪ a Representation Formalism(SDD) and an Implementation


Mechanism(SDT).
Introduction
What Does Semantic Analysis Produce?
❑ Part of semantic analysis is producing some sort of representation of the
program, either

▪ an object code of the program or

▪ an intermediate representation of the program.

❑ One-pass compilers will generate object code without using an


intermediate representation; code generation is part of the semantic
actions performed during parsing.

❑ Other compilers will produce an intermediate representation during


semantic analysis; most often it will be an abstract syntax tree(AST) or
quadruples.
Introduction
 We have built a parse-tree, now what?

Grammar Input Parse Tree

E → E+T|T id * id
T → T * F| F
F → (E) | id

 How will this tree and production rules help in translation/semantic analysis?

 This means we have to associate something

▪ with each production and

▪ with each grammar symbol/tree node.


Introduction
 Syntax-Directed Translation: The translation of languages guided by CFG
(i.e., augmented context-free grammar)

 The SDT techniques of this chapter will be applied in Chapter 6 to type


checking and intermediate-code generation.

 The SDT techniques are also useful for implementing little languages for
specialized tasks.

 Syntax-directed translations are used


1. for sematic analysis
2. for intermediate code generation
3. to translate infix expressions into postfix notation,
4. to evaluate expressions, and
5. to build syntax trees for programming constructs.
Introduction

❑ Syntax-Directed Translation(SDT) refers to a method where the source


language translation is completely driven by the parser.

❑ The parsing process and parse trees are used to direct

▪ Semantic Analysis and

▪ the translation of the source program into intermediate code or


object code.

❑ Our conventional grammar is augmented with information

▪ to control the semantic analysis and

▪ to do the translation of the source program.

❑ The augmented grammars are called attribute grammars.


Introduction
 Attribute grammar: An attribute grammar is a CFG with the addition of
attributes and attribute evaluation rules called semantic rules.

 Attributes: Attributes are variables to which values are assigned.

▪ Each attribute variable is associated with one or more nonterminals or


terminals of the grammar.

▪ Attributes are mainly classified in to Synthesized and Inherited attributes.

 Semantic rules: They are used to specify how attribute values are
computed. Attribute computation semantic rules are associated with
grammar productions.

 Examples:
Production Semantic rule Production Semantic rule

E→E1+T E.val=E1.val+T.val E→E1+T E.code=E1.code||T.code||’+’


SDD for arithmetic expression evaluation SDD for infix to postfix conversion
Introduction
 Associate information with a language construct by attaching attributes to
the grammar symbol(s).

 A syntax directed definition(SDD) specifies the values of attributes by


associating semantic rules with the grammar productions.

 For example, an infix-to-postfix translator might have a production and rule

The semantic rule specifies that the string E.code is formed by


concatenating E1.code, T.code, and the character '+'. Both E and T have
a string-valued attribute code.
Introduction
 Evaluation of Semantic Rules may:
➢ Generate Intermediate Code;
➢ Insert information into the Symbol Table;
➢ Perform Semantic Check (eg: Type checking);
➢ Issue error messages;
➢ etc.

 Semantic Errors: Mentioned some of the semantics errors that the semantic
analyzer is expected to recognize:
➢ Type mismatch

➢ Undeclared variable

➢ Reserved identifier misuse.

➢ Multiple declaration of variable in a scope (Scope resolution)

➢ Accessing an out of scope variable.

➢ Actual and formal parameter mismatch.


Introduction

Synthesized & Inherited Attributes


Introduction

 Synthesized attribute (The attribute which takes data values from its child nodes)
➢ A synthesized attribute for a nonterminal A at a parse-tree node N is
defined by a semantic rule associated with the production at N.

➢ A synthesized attribute at node N is defined only in terms of attribute


values at the children of N and at N itself.

➢ Synthesized attributes are those attributes that are passed up a parse


tree, i.e., the left-side attribute is computed from the right-side
attributes.

➢ The lexical analyzer usually supplies the attributes of terminals and the
synthesized ones are built up for the nonterminals and passed up the
tree.
Introduction
 Synthesized attribute
 Synthesized Attribute of the node-N is defined in terms of:

▪ Attribute(Synthesized) values at the children of node-N and/or

▪ Attribute(Synthesized/Inherited) value at node-N itself

 Synthesized attributes can be contained by both the terminals or non-


terminals.

 Synthesized attribute is used by both S-attributed SDT and L-attributed SDT.

 SDD involving only synthesized attributes is called S-attributed

N
Introduction

 Inherited attribute (The attribute which takes values from parents or sibling nodes or both)
➢ An inherited attribute for a nonterminal B at a parse-tree node N is defined
by a semantic rule associated with the production at the parent of N. Note
that the production must have B as a symbol in its body.

➢ An inherited attribute at node N is defined only in terms of attribute


values at N's parent, N's siblings, and N itself.

➢ Inherited attributes are those that are passed down a parse tree, i.e., the
right-side attributes are derived from the left-side attributes (or other
right-side attributes).

➢ Inherited attributes are used for passing information about the context to
nodes further down the tree.
Introduction

 Inherited attribute
➢ Unlike synthesized attributes, the order in which the inherited

attributes of the children are computed is important.

➢ Inherited attributes can’t be contained by both, It is only contained

by non-terminals.

➢ Inherited attribute is used by only L-attributed SDT.

➢ Evaluation Order - Inherited attributes cannot be evaluated by a

simple PreOrder traversal of the parse-tree.

▪ Exception - Inherited attributes that do not depend on right

siblings can be evaluated by a classical Preorder traversal.


Introduction

 Inherited attribute
 Inherited attribute of the node-N is defined in terms of:

➢ Attribute(Inherited) value at parent of the node and/or

➢ Attribute(Inherited or synthesized) values at siblings and/or

➢ Attribute(Inherited or synthesized) value at node itself.

An inherited attribute at node N is defined only in terms of attribute values at N's parent, N
itself, and N's siblings.
It can be evaluated during a single top-down and sideways traversal of parse tree.
Introduction

 Between the two notations,

▪ Syntax-directed definitions (SDD) can be more readable, and


hence more useful for specifications.

▪ Syntax-directed translation (SDT) schemes can be more efficient,


and hence more useful for implementations.

Translation Schemes(SDTs) are more implementation oriented than SDDs


since they indicate the order in which semantic rules and attributes are to
be evaluated.
Introduction

Syntax-directed translation(SDT)
Introduction
Syntax-directed translation(SDT)
 SDT scheme embeds program fragments called semantic actions within
production bodies of CFG, as in

E → E+T { printf(“+”); } (5.2)

By convention, semantic actions are enclosed within curly braces. (If curly
braces occur as grammar symbols, we enclose them within single quotes).

 The position of a semantic action in a production body determines the

order in which the action is executed.

 In the above production (5.2), the action occurs at the end; in general,

semantic actions may occur at any position in a production body.


Introduction
Syntax-directed translation(SDT)

 SDT is an CFG + Program fragments embedded inside production


bodies that facilitate Semantic analysis/Intermediate code
generation/AST generation/Arithmetic expression evaluation/Infix to
Postfix conversion etc…

 SDT involves passing information bottom-up or top-down the parse


tree in form of attributes attached to the nodes.

 Syntax directed translation(SDT) rules use


1. lexical values of nodes(value of the token (Real or Integer) found by the lexical analyzer)
2. constants &
3. attributes associated to the non-terminals in their definitions.
Introduction
Syntax-directed translation(SDT)

 A syntax-directed translation(SDT) is an executable specification of SDD.


Fragments of programs are associated to different points in the
production rules. The order of execution is important in this case.

 Example

A → {Action1} B {Action2} C {Action3}

o Action1: takes place before parsing the input corresponding to B.

o Action2: takes place after consuming the input for B, but before
consuming the input for C.

o Action3: takes place at the time of reduction of BC to A or after


consuming the input corresponding to BC.
Introduction
Syntax-directed translation(SDT)

 The most general approach to SDT is to construct a parse tree or


syntax tree and compute the values of attributes at the nodes of
the tree by visiting them in some order.

 In many cases, translation can be done during parsing, without


building an explicit tree.

 Syntax-directed translations(SDTs) are classified in to

1. L-attributed SDT and

2. S-attributed SDT
Introduction
Syntax-directed translation(SDT)

 L-attributed SDT: (L for left-to-right)


▪ L-attributed SDT uses both synthesized and inherited attributes with
restriction of not taking values from right siblings.

▪ Attributes in L-attributed SDTs are evaluated by depth-first and left-to-


right parsing manner.

▪ Semantic actions are placed anywhere in RHS(i.e., Production body).

▪ L-attributed SDTs are evaluated in Top-down parsing.

▪ L-attributed translations includes virtually all translations that can be


performed during parsing.
Introduction
Syntax-directed translation(SDT)

 If a definition is S-attributed, then it is also L-attributed but NOT vice-


versa.

 Example – Consider the given below SDT.


▪ P1: S -> MN { S.val = M.val + N.val }

▪ P2: M -> PQ { M.val = P.val * Q.val and P.val = Q.val }

▪ Select the correct option.


A. Both P1 and P2 are S attributed.
B. P1 is S attributed and P2 is L-attributed.
C. P1 is L attributed but P2 is not L-attributed.
D. None of the above
Introduction
Syntax-directed translation(SDT)

 If a definition is S-attributed, then it is also L-attributed but NOT vice-


versa.

 Example – Consider the given below SDT.


▪ P1: S -> MN { S.val = M.val + N.val }

▪ P2: M -> PQ { M.val = P.val * Q.val and P.val = Q.val }

▪ Select the correct option.


A. Both P1 and P2 are S attributed.
B. P1 is S attributed and P2 is L-attributed.
C. P1 is L attributed but P2 is not L-attributed.
D. None of the above
The correct answer is option C as, In P1, S is a synthesized attribute and in L-attribute definition synthesized is allowed. So P1 follows the L-
attributed definition. But P2 doesn’t follow L-attributed definition as P is depending on Q which is RHS to it.
Introduction
Syntax-directed translation(SDT)

 S-attributed SDT: (S for synthesized),

▪ It’s a smaller class.

▪ S-attributed SDT involves only synthesized attributes. If an SDT uses


only synthesized attributes, it is called as S-attributed SDT.

▪ S-attributed SDTs are evaluated in bottom-up parsing (or Post-


Order traversal of the parse-tree), as the values of the parent
nodes depend upon the values of the child nodes.

▪ Semantic actions are placed in rightmost place of RHS.


Introduction
Syntax-directed translation(SDT)

 If a definition is S-attributed, then it is also L-attributed but NOT vice-


versa.

L-attributed SDT

S-attributed SDT
Introduction
Syntax-directed translation(SDT)

 S-attributed SDT: Example

L -> E n {L.val = E.val}


E -> E+T {E.val = E.val + T.val}
E -> T {E.val = T.val}
T -> T*F {T.val = T.val * F.val}
T -> F {T.val = F.val}
F -> (E) {F.val = E.val }
F -> digit {F.val = digit.lexval}

❑ The SDT scheme is used to evaluate the order of semantic rules.


❑ In translation scheme, the semantic rules are embedded within the
body(RHS) of the productions.
Introduction

Syntax-Directed Definition(SDD)
Syntax Directed Definitions(SDD)
 A SDD is a Context Free Grammar(CFG) with attributes and semantic rules.

➢ Attributes are associated with grammar symbols and

➢ Semantic rules are associated with productions

 Attribute values may be of many kinds:


numbers, types, table references, strings, etc.

 There are two types of attributes for nonterminals we might encounter:

1. synthesized attributes or

2. Inherited attributes.

Figure 5.1: SDD of a simple desk calculator


Example of S-attributed SDD

Name the Synthesized attributes in the above SDD.


Example of S-attributed SDD

➢ SDD(Figure 5.1)evaluates expressions terminated by an endmarker n.


➢ In the SDD, each of the nonterminals has a single synthesized attribute,
called val.
➢ The terminal digit has a synthesized attribute lexval, which is an integer
value returned by the lexical analyzer.
Example of S-attributed SDD

➢ Production 2), E → E1 + T, also has one rule, which computes the val
attribute for the head E as the sum of the values at E1 and T.
➢ Production 7 gives F.val the value of a digit, that is, the numerical value of
the token digit that the lexical analyzer returned.
Syntax Directed Definitions
 An SDD that involves only synthesized attributes is called S-attributed; the
SDD in Fig. 5.1 has this property.

 In an S-attributed SDD, each rule computes an attribute for the nonterminal


at the head of a production from attributes taken from the body of the
production.
Syntax Directed Definitions
 An S-attributed SDD can be implemented naturally in conjunction with an
LR parser.

 In fact, the SDD in Fig. 5.1 mirrors the Yacc program of Fig. 4.58, which
illustrates translation during LR parsing. The difference is that, in the rule for
production 1, the Yacc program prints the value E.val as a side effect,
instead of defining the attribute L.va1.

Fig. 4.58
Evaluating SDDs - Parse Tree method

 Evaluating an SDD over a given input consists of the following steps:

1. Construct Parse Tree for given input.

2. Construct Dependency graph.

3. Topologically sort the nodes of Dependency graph.

4. Produce as output Annotated Parse Tree.


Evaluating an SDD at the Nodes of a Parse Tree

 Annotated Parse tree: A parse tree showing the values of its


attributes is called annotated parse tree.
Evaluating an SDD at the Nodes of a Parse Tree

 How do we construct an annotated parse


tree? In what order do we evaluate attributes?

▪ For example, if all attributes are synthesized,


as in SDD in Fig. 5.1, then we must evaluate
the val attributes at all of the children of a
node before we can evaluate the val attribute
at the node itself.

▪ With synthesized attributes, we can evaluate


attributes in any bottom-up order.
Evaluating an SDD at the Nodes of a Parse Tree

 Example 5.2 : Figure 5.3 shows an annotated parse


tree for the input string 3 * 5 + 4 n, constructed using
the grammar and rules of Fig. 5.1.

▪ The values of lexval are presumed supplied by


the lexical analyzer.

▪ Each of the nodes for the nonterminals has


attribute val computed in a bottom-up order,
and we see the resulting values associated with
each node.

▪ For instance, at the node with a child labeled *,


after computing T.val= 3 and F.val = 5 at its first
and third children, we apply the rule that says
T.val is the product of these two values, or 15.
Evaluating an SDD at the Nodes of a Parse Tree

❑ Exercise 5.1.1: For the SDD of Fig. 5.1, give annotated parse trees for
the following expressions:
When Are Inherited Attributes Useful?

➢ The CFG in Fig. 5.8 takes a simple declaration D consisting of a basic type T
followed by a list L of identifiers. T can be int or float.

➢ Write an SDD, that enters, for each identifier on the list, the type info into the
symbol-table entry for the identifier.
D

T L

float L , id2

id1
Fig: Parse tree for float x,y
Symbol table
Name Type Size Scope
Leftmost Derivation: 1 x
2 y
D ⇒ TL ⇒ float L ⇒ float L,id2 ⇒ float id1,id2 …..
……
When Are Inherited Attributes Useful?
L-Attributed SDT:
D → T { L.inh = T.type } L
T → int { T.type=integer }
T → float { T.type=float }
L → { L1.inh=L.inh } L1 , id { addType(id.entry, L.inh) }
L → id { addType(id.entry, L.inh) }

Figure 5.8: SDD for simple type declarations

➢ The SDD in Fig. 5.8 takes a simple declaration D consisting of a basic type
T followed by a list L of identifiers. T can be int or float.

➢ For each identifier on the list, the type is entered into the symbol-table
entry for the identifier.

➢ The purpose of L.inh is to pass the declared type down the list of identifiers,
so that it can be added to the appropriate symbol-table entries.
When Are Inherited Attributes Useful?
L-Attributed SDT:
D → T { L.inh = T.type } L
T → int { T.type=integer }
T → float { T.type=float }
L → { L1.inh=L.inh } L1 , id { addType(id.entry, L.inh) }
L → id { addType(id.entry, L.inh) }

Figure 5.8: SDD for simple type declarations

➢ Productions 4 and 5 also have a rule in which a function addType is called


with two arguments:

➢ id.entry, a lexical value that points to a symbol-table object, and

➢ L.inh, the type being assigned to every identifier on the list.


Evaluating an SDD at the Nodes of a Parse Tree

 Inherited attributes are useful when the structure of a parse tree does not
match the abstract syntax of the source code.

 The next example shows how inherited attributes can be used to


overcome such a mismatch due to a grammar designed for parsing
rather than translation.


Evaluating an SDD at the Nodes of a Parse Tree

 Note:
 In L-attributed, if there is a production A → X1X2…Xn and there is an inherited
attribute inh of symbol Xi computed by a rule associated with this production,
then the rule may only use:

▪ Inherited attributes associated with the head symbol A

▪ Either inherited or synthesized attributes associated with the left siblings of Xi

▪ Inherited or synthesized attributes associated with this occurrence of Xi itself.

 Inherited attributes never flows from child node to parent node. (i.e., from body
symbols to head symbol in production)
Evaluating an SDD at the Nodes of a Parse Tree

 Inherited attributes are useful when the structure of a parse tree does not
match the abstract syntax of the source code.

 The next example shows how inherited attributes can be used to


overcome such a mismatch due to a grammar designed for parsing rather
than evaluation/translation.


Evaluating an SDD at the Nodes of a Parse Tree

 Example 5.3 : The SDD in Fig. 5.4 computes terms like 3 * 5 and 3 * 5 * 7.
➢ The top-down parse of input 3 * 5 begins with the production T → F T'
Here, F generates the digit 3, but the operator * is generated by T'.

the left operand 3 appears in a different subtree of the parse tree from *.
An inherited attribute will therefore be used to pass the operand to the operator.

T => FT’ => digitT’ => digit*FT’ => digit*digitT’ => digit*digit

➢ Each of the nonterminals T and F has a synthesized attribute val; the terminal digit has a synthesized attribute lexval.

➢ The nonterminal T' has two attributes: an inherited attribute inh and a synthesized attribute syn.
Evaluating an SDD at the Nodes of a Parse Tree

 The semantic rules are based on the idea that the left operand of the operator * is
inherited. More precisely, the head T' of the production T’ → * F T’1 inherits the left
operand of * in the production body.

 Given a term x * y * z, the root of the subtree for * y * z inherits x. Then, the root of the
subtree for * z inherits the value of x * y, and so on, if there are more factors in the
term.

 Once all the factors have been accumulated, the result is passed back up the tree
using synthesized attributes.
Evaluating an SDD at the Nodes of a Parse Tree
Evaluating an SDD at the Nodes of a Parse Tree
Evaluation orders for SDD’s
 Dependency graph:

▪ "Dependency graphs" are a useful tool for determining an evaluation order


for the attribute instances in a given parse tree.

▪ An annotated parse tree shows the values of attributes, while,


a dependency graph helps us to determine how those values can be
computed.

Annotated parse tree


Dependency graph

Figure 5.6: E.val is synthesized from


E1.val and T.val
Evaluation orders for SDD’s
 Dependency graph:

▪ A dependency graph depicts the flow of information among the


attribute instances in a particular parse tree.

▪ An edge from one attribute instance to another means that the value of
the first is needed to compute the second.

Figure 5.6: E.val is synthesized from


E1.val and T.val

Figure 5.7: Dependency graph for the annotated parse tree of


Fig. 5.5
Evaluation orders for SDD’s
 Dependency graph
▪ For each parse tree node, the dependency graph has a node for each attribute
associated with that node

▪ If a semantic rule defines the value of synthesized attribute A.b in terms of the value
of X.c then the dependency graph has an edge from X.c to A.b

▪ If a semantic rule defines the value of inherited attribute B.c in terms of the value of
X.a then the dependency graph has an edge from X.a to B.c

Figure 5.4: An SDD based on a grammar


suitable for top-down parsing

Figure 5.7: Dependency graph for the annotated


parse tree of Fig. 5.5
Ordering the evaluation of attributes
 Ordering the evaluation of attributes:
1. If dependency graph has an edge from M to N then M must be evaluated
before the attribute of N.

2. Thus the only allowable orders of evaluation are those sequence of nodes
1,2,3…..k such that if there is an edge from i to j then i<j.

3. Such an ordering is called a topological sort of a graph.

▪ Example:
The dependency graph of Fig. 5.7 has no cycles,
then it can be evaluated.
Topological sort 1:
1,2,3,4,5,6,7,8,9 (Evaluation order(1) for attributes)
Topological sort 2:
1,3,5,2,4,6,7,8,9 (Evaluation order(2) for attributes)

Note: Topological sorting for DAG is a linear ordering of


vertices such that for every directed edge u v, vertex u
Figure 5.7: Dependency graph for the
comes before v in the ordering.
annotated parse tree of Fig. 5.5
Ordering the evaluation of attributes

❑Exercise 1: Write dependency graph for the declaration statement


float x,y,z Token stream: <float> <id,1> <,> <id,2> <,> <id,3>

Symbol table
Name Type Size Scope
1 X
2 y
3 z
……

L-Attributed SDT:
Figure 5.8: SDD for simple type declarations
D → T { L.inh = T.type } L
T → int { T.type=integer }
T → float { T.type=float }
L → { L1.inh=L.inh } L1 , id { addType(id.entry, L.inh) }
L → id {addType(id.entry, L.inh)}
Ordering the evaluation of attributes
❑Exercise 1: Write dependency graph for the declaration statement float x,y,z

L-Attributed SDT:
D → T { L.inh = T.type } L
T → int { T.type=integer } addType(id3.entry, L.inh)
T → float { T.type=float }
L → { L1.inh=L.inh } L1 , id { addType(id.entry, L.inh) }
L → id {addType(id.entry, L.inh)}
float

addType(id2.entry, L.inh)
❑ Nodes 1, 2, and 3 represent the attribute entry
associated with each of the leaves labeled id.
❑ Nodes 6, 8, and 10 are the dummy attributes that
represent the application of the function addType
addType(id1.entry, L.inh)
to a type and one of these entry values.
❑ id.entry, a lexical value that points to a symbol-
table object. a lexical value that points to a
symbol-table object
Ordering the evaluation of attributes
Symbol table
Name Type Size Scope

❑Exercise 1: float x,y,z 1 X


2 Y
Token stream: 3 z
……

<float> <id,1> <,> <id,2> <,> <id,3>

L-Attributed SDT: addType(id3.entry, L.inh)


D → T { L.inh = T.type } L
T → int { T.type=integer }
T → float { T.type=float } float
L → { L1.inh=L.inh } L1 , id { addType(id.entry, L.inh) }
addType(id2.entry, L.inh)
L → id { addType(id.entry, L.inh) }

Leftmost Derivation:
D ⇒ TL addType(id1.entry, L.inh)
⇒ float L
⇒ float L,id3
⇒ float L,id2,id3
⇒ float id1,id2,id3
Ordering the evaluation of attributes

❑ Exercise 2: Write dependency graph and annotated parse tree for the
expression 3*5*7. Token stream: <digit,3> <*> <digit,5> <*> <digit,7>

Leftmost Derivation:
T ⇒ FT’
⇒ digit T’
⇒ digit *FT’
⇒ digit * digit T’
⇒ digit * digit *FT’
⇒ digit * digit * digit T’
⇒ digit * digit * digit
Ordering the evaluation of attributes

❑ Exercise 2: Write dependency graph and annotated parse tree for the
expression 3*5*7. Token stream: <digit,3> <*> <digit,5> <*> <digit,7>

Leftmost Derivation:
T ⇒ FT’
⇒ digit T’
⇒ digit *FT’
⇒ digit * digit T’
⇒ digit * digit *FT’
⇒ digit * digit * digit T’
⇒ digit * digit * digit
S-Attributed definitions

S-Attributed SDD Examples


S-Attributed definitions

 When an SDD is S-attributed, we can evaluate its attributes in any bottomup


order of the nodes of the parse tree.

 It is simple to evaluate the attributes by performing a postorder traversal of


the parse tree and evaluating the attributes at a node N when the traversal
leaves N for the last time.
That is, we apply the function postorder, defined below, to the root of the
parse tree:
S-Attributed definitions

 S-attributed definitions can be implemented during bottom-up parsing,


since bottom-up parse corresponds to a postorder traversal.

 Specifically, postorder corresponds exactly to the order in which an LR


parser reduces a production body to its head.
This fact will be used in Section 5.4.2 to evaluate synthesized attributes and store them on the
stack during LR parsing, without creating the tree nodes explicitly.
Examples of S-Attributed SDDs

1. SDD to count number of 1's in a Binary Number

2. SDD to count number of 0's in a Binary Number

3. SDD to count number of bits in a Binary Number

4. SDD to convert Binary Number to Decimal

5. SDD to convert Binary Fraction to Decimal

6. SDD to count number of Balanced Parenthesis

7. SDD to convert a infix to postfix


Examples of S-Attributed SDDs

1. SDD to count number of 1's in a Binary Number

Productions
L → L1 B
L → B
B → 0
B → 1

L => LB => L1 => LB1 => L11 => LB11 => L011 => B011 => 1011
Examples of S-Attributed SDDs

1. SDD to count number of 1's in a Binary Number

SDD:

Production Semantic Rules


L → L1 B L.count = L1.count + B.count
L → B L.count = B.count
B → 0 B.count = 0
B → 1 B.count = 1

L => LB => L1 => LB1 => L11 => LB11 => L011 => B011 => 1011

L.count = L1.count + B.count B.count=0 L.count= B.count B.count=1


L.count = 1+0=1 L.count= 1
Examples of S-Attributed SDDs

2. SDD to count number of 0's in a Binary Number

SDD:

Production Semantic Rules


L → L1 B L.count = L1.count + B.count
L → B L.count = B.count
B → 0 B.count = 1
B → 1 B.count = 0
Examples of S-Attributed SDDs

3. SDD to count number of bits in a Binary Number

SDD:

Production Semantic Rules


L → L1 B L.count = L1.count + B.count
L → B L.count = B.count
B → 0 B.count = 1
B → 1 B.count = 1
Examples of S-Attributed SDDs

4. SDD to convert Binary Number to Decimal number

CFG:

Production
L → L1 B
L → B
B → 0
B → 1
Examples of S-Attributed SDDs

4. SDD to convert Binary Number to Decimal number

SDD:

Production Semantic Rules


L → L1 B L.val = 2 * L1.val + B.val
L → B L.val = B.val
B → 0 B.val = 0
B → 1 B.val = 1
Examples of S-Attributed SDDs

4. SDD to convert Binary Number to Decimal number

Production Semantic Rules


L → L1 B L.val = 2 * L1.val + B.val
L → B L.val = B.val
B → 0 B.val = 0

Input : (1100)2= (12)10 B → 1 B.val = 1

L => LB => L0 => LB0 => L00 => LB00 => L100 => B100 => 1100

B.val=0 B.val=0 B.val=1 L.val =B.val B.val=1


L.val =1

L.val = 2*6+0 = 12 L.val = 2*3+0 = 6 L.val = 2*1+1 = 3


Examples of S-Attributed SDDs

5. SDD to convert Binary Fraction to Decimal number

Production Semantic Rules


S → L1.L2 S.val = L1.val + L2.val/2L2.count
L → L1 B L.val = 2 * L1.val + B.val
L.count = L1.count + B.count
L → B L.val = B.val
L.count = B.count
B → 0 B.val = 0
B.count = 1
B → 1 B.val = 1
B.count = 1
Examples of S-Attributed SDDs

5. SDD to convert Binary Fraction to Decimal number


Production Semantic Rules
S → L1.L2 S.val = L1.val + L2.val/2L2.count
L → L1 B L.val = 2 * L1.val + B.val
L.count = L1.count + B.count
L → B L.val = B.val
L.count = B.count
B → 0 B.val = 0
B.count = 1
Input : (11.11)2= (3.75)10 B → 1 B.val = 1
B.count = 1

S => L.L => L.LB => L.L1 => L.B1 => L.11 => LB.11 => L1.11 => B1.11 => 11.11
B.val=1 L.val=B.val B.val=1 B.val=1 L.val=B.val B.val=1
B.count=1 L.count=B.count B.count=1
B.count=1 L.count=B.count B.count=1

L.val = 2*1+1 = 3 L.val = 2*1+1 = 3

S.val = 3.(3/22)= 3.75 L.count=1+1=2 L.count=1+1=2


Examples of S-Attributed SDDs

6. SDD to count number of Balanced Parenthesis

CFG:

Production Semantic Rules

S → (S1)

S → x
Examples of S-Attributed SDDs

6. SDD to count number of Balanced Parenthesis

SDD:

Production Semantic Rules

S → (S1) S.count = S1.count + 1

S → x S.count = 0

Input: (((x)))

S => (S) => ((S)) => (((S))) => (((x)))

S.count=3 S.count=2 S.count=0+1=1 S.count=0


Examples of S-Attributed SDDs

7. SDD to convert an infix expression to postfix expression

Production Semantic Rules


E → E + T
E → E - T
E → T
T → T * F
T → T / F
T → F
F → num

Input: 4+5*6 Output: 456*+


Examples of S-Attributed SDDs

7. SDD to convert an infix expression to postfix expression

Production Semantic Rules


E → E + T print(“+”)
E → E - T print(“-”)
E → T
T → T * F print(“*”)
T → T / F print(“/”)
T → F
F → num print(num.lexval)

Input: 4+5*6 Output: 456*+


Examples of S-Attributed SDDs

7. SDD to convert an infix expression Production Semantic Rules


E → E + T print(“+”)
to postfix expression E → E - T print(“-”)
E → T

Input: 4+5*6 T → T * F print(“*”)


T → T / F print(“/”)
Token stream: <num,4> <+> <num,5> <*> <num,6>
T → F
Note: Only token names are used during parsing
F → num print(num.lexval)

Rightmost derivation:
E => E+T => E+T*F => E+T*num => E+F*num => E+num*num => T+num*num
=> F+num*num => num+num*num

Bottom up parsing(rightmost derivation in reverse) and infix to postfix conversion:


E => E+T => E+T*F => E+T*num => E+F*num => E+num*num => T+num*num
No action No action
print(‘+’) print(‘*’) print(num.lexval)

=> F+num*num => num+num*num print(num.lexval)

print(num.lexval)
Postfix expression: 456*+
Examples of S-Attributed SDDs

 Exercise 1: Below is a grammar for expressions involving operator +

and integer or floating-point operands. Floating-point numbers are


distinguished by having a decimal point.
E → E + T | T
T → num . num | num

Give an SDD to determine the type of each term T and expression E.


Production Semantic Rules
E → E1 + T

E → T
T → num.num
T → num
Examples of S-Attributed SDDs
 Exercise 1: Below is a grammar for expressions involving operator + and integer or
floating-point operands. Floating-point numbers are distinguished by having a
decimal point.
E → E + T | T
T → num . num | num

Give an SDD to determine the type of each term T and expression E.

Production Semantic Rules


E → E1 + T if(E1.type == float || T.type == float)
E.type = float
else
E.type = integer
E → T E.type = T.type
T → num.num T.type = float
T → num T.type = integer
Examples of S-Attributed SDDs

 Exercise 2: Give an SDD to determine whether the arithmetic value of an


expression E is positive or negative.
Here is an attribute grammar G for arithmetic expressions using multiplication, unary -, and unary +.

Production Semantic Rules


S → E
E → num
E → + E1
E → - E1
E → E1 * E2

Add semantic rules to compute an attribute sign for non-terminal E to record


whether the arithmetic value of E is positive or negative.
The attribute sign can have two values, either POS or NEG.
Examples of S-Attributed SDDs

 Exercise 2:
Production Semantic Rules
S→E if(E.sign == POS)
print(“Result is Positive”);
else
print(“Result is Negative”);
E → num E.sign = POS;
E → + E1 E.sign = E1.sign
E → - E1 if(E1.sign == POS)
E.sign = NEG;
else
E.sign = POS;
E → E1 * E2 if(E1.sign == E2.sign)
E.sign = POS;
else
E.sign = NEG;
Examples of S-Attributed SDDs

 Exercise 2:
Production Semantic Rules
S → E If(E.sign == POS)
print(“Result is Positive”);
else
print(“Result is Negative”);
E → num E.sign = POS;
E → + E1 E.sign = E1.sign
E → - E1 If(E1.sign == POS)
E.sign = NEG;
else
E.sign = POS;
E → E1 * E2 If(E1.sign == E2.sign)
E.sign = POS;
else
E.sign = NEG;

1) Is E.sign an inherited attribute or a synthesized attribute?

2) Show the parse tree for input 2 * - 3 (where 2 and 3 are “unsigned_ints”).
Indicate at each node what the values of associated attributes are.
Examples of S-Attributed SDDs
Production Semantic Rules
 Exercise 2: S → E If(E.sign == POS)
print(“Result is Positive”);
else
1) Is E.sign an inherited attribute or a print(“Result is Negative”);
E → num E.sign = POS;
synthesized attribute?
E → + E1 E.sign = E1.sign
Answer: Synthesized attribute
E → - E1 If(E1.sign == POS) E.sign = NEG;
else E.sign = POS;

E → E1 * E2 If(E1.sign == E2.sign) E.sign = POS;


else E.sign = NEG;

2) Show the parse tree for input 2 * - 3 (where 2 and 3 are “unsigned_ints”).
Indicate at each node what the values of associated attributes are.
L-Attributed definitions

L-Attributed SDD & Examples


L-Attributed definitions
 A SDD is L-Attributed if the edges in dependency graph goes from Left
to Right but not from Right to Left.

 In L-Attributed, each attribute must be either Synthesized, or Inherited

 If there is a production A → X1X2…Xn and there is an inherited attribute Xi.a


computed by a rule associated with this production, then the rule may only use:

▪ Inherited attributes associated with the head A

▪ Either inherited or synthesized attributes associated with the occurrences


of symbols X1,X2,…,Xi-1 located to the left of Xi

▪ Inherited or synthesized attributes associated with this occurrence of Xi


itself, but in such a way that there is no cycle in the dependency graph
formed by the attributes of this Xi
L-Attributed definitions

 A SDD is L-Attributed if the edges in dependency graph goes from


Left to Right but not from Right to Left.

 L-attributed SDD uses inherited attributes with a restriction that it


can inherit values from left siblings only.

 Attributes in L-attributed SDDs are evaluated by depth-first and left-


to-right parsing manner.

 Note: Every S-attributed SDD is also L-attributed.


L-Attributed definitions
 Example 1: L-attributed SDD and dependency graph for the declaration
Symbol table
statement float x,y,z Name Type
Token stream: <float> <id,1> <,> <id,2> <,> <id,3> 1 x
2 y
3 z
……

L-Attributed SDT:
D → T { L.inh = T.type } L
T → int { T.type=integer }
T → float { T.type=float }
L → { L1.inh=L.inh } L1 , id { addType(id.entry, L.inh) }
L → id {addType(id.entry, L.inh)}
L-Attributed definitions
 Example 2: L-attributed SDD to implement Desk Calculator (arithmetic
expression evaluation)
L-Attributed definitions

 Exercise 1: Suppose that we have a production A → BCD.


Each of the four nonterminals A, B, C, and D have two attributes:
s is a synthesized attribute, and i is an inherited attribute.
For each of the sets of rules below, tell whether
(1) the rules are consistent with an S-attributed definition
(2) the rules are consistent with an L-attributed definition, and
(3) whether the rules are consistent with any evaluation order at all?
a) A.s = B.i + C.s
b) A.s = B.s + C.s and D.i = A.i + B.s
c) A.s = B.s + D.s
d) D.i = C.i
C.i = B.i
B.i = D.i + A.i
L-Attributed definitions

 Exercise 1: Solution: A → BCD

a) A.s = B.i + C.s


1) No - contains inherited attribute
2) No - inherited attribute never flows upward

b) A.s = B.s + C.s and D.i = A.i + B.s


1) No - contains inherited attributes
2) Yes - “Inherited attribute: Inheriting from above and/or from the left
and/or itself, Synthesized attribute: Computed from child nodes
and/or itself”
3) Yes - L-attributed so no cycles

c) A.s = B.s + D.s


1) Yes - all attributes are synthesized
2) Yes - all attributes are synthesized
3) Yes - S-attributed and L-attributed, no cycles
L-Attributed definitions

 Exercise 1: Solution: A → BCD

d) D.i = C.i

C.i = B.i

B.i = D.i + A.i

1) No - contains inherited attributes


2) No - Inheriting from right is not allowed
3) No - D.i uses C.i, which depends on B.i, which depends on D.i (cycle)
Cycle implies no topological sorts (evaluation orders) using the rules.
L-Attributed definitions

 Exercise 2: Write an SDD to find out whether the given type is a Basic Type or
an array type and calculate the width required . (Note: int-4 bytes, float-8 bytes)
L-Attributed definitions

 Exercise 2: Write an SDD to find out whether the given type is a Basic Type or
an array type and calculate the width required . (Note: int-4 bytes, float-8 bytes)
L-Attributed definitions
17)T.Type=C.Type i.e., T.Type = array(2, array(3,integer))
18)T.Width=C.Width i.e., T.Width = 24

3)B.Type = integer 5)C.inhType = B.Type i.e., C.inhType = integer


4)B.Width = 4 6)C.inhWidth = B.Width i.e., C.inhWidth = 4
15)C.Type=array(num.lexval,C1.Type) i.e., C.Type=array(2, array(3,inte
16)C.Width=num.lexval* C1.width =2*12 = 24

int
7)C1.inhType = C.inhType i.e., C.inhType =integer
[ 1)num.lexval=2 ] 8)C1.inhWidth = C.inhWidth i.e., C.inhWidth = 4
13)C.Type= array(num.lexval,C1.Type)
i.e., C.Type=array(3,integer)
14)C.Width = num.lexval* C1.width = 3*4= 12

[ 2)num.lexval=3 ] 9)C1.inhType=C.inhType i.e., C. =integer


inhType

10)C1.inhWidth=C.inhWidth i.e., C =4
.inhWidth

11)C.Type=C.inhType i.e., integer


An annotated parse tree for the input string int [2][3] 12)C.Width=C.inhWidth i.e., 4
is shown in Fig.
ε
L-Attributed definitions

 Exercise 3: Construct an SDD to do the following: Given a declaration of


type float [4]x, y

❑ Update the type and storage required for the identifier x and y in the
symbol table.

❑ Note : Kindly provide two versions of the SDD:

1. According to the C semantics ( only x is an array type and y is a


basic type).

2. According to the Java semantics( both x and y are of array type)


L-Attributed definitions

 Exercise 3: 1. SDD According to the C semantics


L-Attributed definitions

 Exercise 3: 1. SDD According to the C semantics


L-Attributed definitions

 Exercise 4: Write a SDD that computes the number of executed statements


for a program conforming to the grammar. The no of times the loop
executes can be computed from the two constants that specify the range
of the loop variable. You may assume that the lower bound of the range is
less than or equal to the upper bound.
L-Attributed definitions

 Exercise 4: Write a SDD that computes the number of executed statements


for a program conforming to the grammar. The no of times the loop
executes can be computed from the two constants that specify the range
of the loop variable. You may assume that the lower bound of the range is
less than or equal to the upper bound.
L-Attributed definitions

 Exercise 4: Write a SDD that computes the number of executed statements


for a program conforming to the grammar. The no of times the loop
executes can be computed from the two constants that specify the range
of the loop variable. You may assume that the lower bound of the range is
less than or equal to the upper bound.
Applications of S-Attributed definitions

Applications of S-Attributed SDD


Application of Syntax Directed Definition

 The syntax-directed translation techniques will be applied to (Chapter 6)

1. Type checking and

2. Intermediate-code generation.

 The main application in this section is the construction of syntax trees.

 Some compilers use syntax trees as an intermediate representation,


a common form of SDD turns its input string into a syntax tree.

To complete the translation to intermediate code, the compiler may


then walk the syntax tree, using another set of rules that are in effect
an SDD on the syntax tree rather than the parse tree.
Application of Syntax Directed Definition

 Syntax and Parse tree examples


Grammar: E → E * E | E + E | id Input: a + b * c

▪ Parse tree: interior nodes are non-terminals, leaves are terminals


▪ Syntax tree: interior nodes are “operators”, leaves are operands
▪ Parse tree: Represents the concrete syntax of a program
▪ Syntax tree: Represents the abstract syntax of a program (the semantics).
A syntax tree is often called abstract syntax tree or AST
Application of Syntax Directed Definition

 We want a Syntax tree as an intermediate representation because:

o It corresponds more closely to the meaning of the various


program constructs (to the semantics).

o It is more compact.
Application of Syntax Directed Definition

Construction of syntax trees


Application of Syntax Directed Definition
Construction of syntax trees
 The nodes of a syntax tree are implemented by objects with a suitable number
of fields.

Fig: Syntax Tree for a-4+c


Token stream: Symbol table

<id,1> <-> <num,4><+> <id,2> Name Type

(Note: Only token names will be used during parsing) 1 a

2 c
Application of Syntax Directed Definition
Construction of syntax trees
 The nodes of a syntax tree are implemented by objects with a suitable number
of fields.
Pointer to child-1
op field Pointer to child-2

Fig: Syntax Tree for a-4+c


Token stream: Symbol table

<id,1> <-> <num,4><+> <id,2> Name Type

1 a
 Each object will have an op field that is the label of the node.
2 c
Application of Syntax Directed Definition
Construction of syntax trees
 The nodes of a syntax tree are implemented by objects with a suitable number
of fields.
Pointer to child-1
op field Pointer to child-2

Fig: Syntax Tree for a-4+c

 The objects will have additional fields as follows:


 If the node is a leaf, an additional field holds the lexical value for the leaf.

A constructor function Leaf (op, val) creates a leaf object.


Application of Syntax Directed Definition
Construction of syntax trees
 The nodes of a syntax tree are implemented by objects with a suitable number
of fields.
Pointer to child-1
op field Pointer to child-2

Fig: Syntax Tree for a-4+c

 If the node is an interior node, there are as many additional fields as the node
has children in the syntax tree.
A constructor function Node takes two or more arguments:
Node(op,c1,c2,... ,ck) creates an object with first field op and k additional fields for
the k children c1,c2,... ,ck.
Construction of syntax trees
Example 1: (Constructing syntax trees during bottom-up parsing)

 The SDD in Table below constructs syntax trees for a simple


expression grammar involving only the binary operators + and -.

▪ All nonterminals have one synthesized attribute node, which represents a


node of the syntax tree.
Construction of syntax trees
Example 1: Cont..
Construction of syntax trees
Example 1: Cont..

 Figure 5.11 shows the construction of a syntax tree for the input a - 4 + c.
The nodes of the syntax tree are shown as records, with the op field first.

▪ Syntax-tree edges are shown as solid lines.

▪ The underlying parse tree is shown with dotted edges.

▪ The dashed lines, represents the values of E.node and T.node; each line points to
the appropriate syntax-tree node.
Construction of syntax trees
SDT: E → E1+T {E.node=new Node(‘+’,E1.node,T.node)}
Example 1: Input: a-4+c E → E1-T {E.node=new Node(‘-’,E1.node,T.node)}
Token stream: E → T {E.node=T.node}
<id,entry for a> <-> <num,4> <+> <id,entry for c> T → (E) {T.node=E.node }
Rightmost Derivation: T → id {T.node= new Leaf(id,id.entry)}
E => E+T => E+id => E-T+id => E-num+id => T-num+id => id-num+id T → num {T.
node=new Leaf(num,num.val)}

6) 5) 4) 3) 2) 1)

Fig. 5.12: Steps in the construction of the syntax tree for a-4+c
1) T.node = new Leaf (id, entry-a)
2) E.node = T.node
3) T.node = new Leaf (num, 4);
4) E.node = new Node(‘-’, E.node, T.node )
5) T.node = new Leaf (id, entry-c)
6) E.node = new Node(‘+’, E.node, T.node )

 If the rules are evaluated during a postorder traversal of the parse tree, or with
reductions during a bottom-up parse, then the sequence of steps shown in Fig. 5.12
ends with E.node(Step 6) pointing to the root of the constructed syntax tree.
Construction of syntax trees
Example 2: (Constructing syntax trees during Top-down parsing)

❑ The idea is to build a syntax tree for x + y by passing x as an inherited


attribute, since x and + y appear in different subtrees.

❑ Nonterminal E' has an inherited attribute inh and a synthesized attribute syn.
Attribute E'.inh represents the partial syntax tree constructed so far. Specifically,
it represents the root of the tree for the prefix of the input string that is to the left
of the subtree for E'.
Construction of syntax trees
Example 2: Cont...
❑ At node 5 in the dependency graph for expression a-4+c in Fig. 5.14, E'.inh denotes
the root of the partial syntax tree for the identifier a; that is, the leaf for a.
At node 6, E'.inh denotes the root for the partial syntax tree for the input a - 4.
At node 9, E'.inh denotes the syntax tree for a - 4 + c.

Note: id.entry, a lexical value that points to a


symbol-table object
Construction of syntax trees
Example 2: Cont...
❑ Since there is no more input, at node 9, E'.inh points to the root of the entire syntax
tree.
The syn attributes pass this value back up the parse tree until it becomes the value of
E.node.
❑ Specifically, the attribute value at node 10 is defined by the rule E'.syn = E'.inh associated with the production
E' →ϵ. The attribute value at node 11 is defined by the rule E'.syn = E'1. syn associated with production 2 in Fig. 5.13.
Similar rules define the attribute values at nodes 12 and 13.
Construction of syntax trees
Example 3: SDD to construct Syntax tree for Statements
Construction of syntax trees
Example 3: SDD to construct Syntax tree for Statements
Construction of syntax trees
❑ Exercise: Construct Syntax tree for:

if ( x > 10)
{
x = 10;
}
Construction of syntax trees
❑ Exercise: Construct Syntax tree for:
Construction of syntax trees
❑ Exercise: Construct Syntax tree for:
Construction of syntax trees
❑ Exercise: Construct Syntax tree for:
Application of Syntax Directed Translation
The Structure of a Type
❑ Example 5.13:
❑ In C, the type int [2][3] can be read as, "array of 2 arrays of 3 integers."

❑ The corresponding type expression array(2, array(3, integer)) is


represented by the tree in Fig. 5.15.

❑ The operator array takes two parameters, a number and a type.


If types are represented by trees, then this operator returns a tree node
labeled array with two children for a number and a type.
Application of Syntax Directed Translation

The Structure of a Type


❑ With the CFG in Fig. 5.16,
▪ Nonterminal T generates either a basic type or an array type.
▪ Nonterminal B generates one of the basic types int and float.
▪ Nonterminal T generates
o a basic type when T derives BC and C derives ϵ.
o an array type when T derives BC and C generates array components
consisting of a sequence of integers, each integer surrounded by brackets.
Application of Syntax Directed Translation

The Structure of a Type


❑ The nonterminals B and T have a synthesized attribute t representing a type.
❑ The nonterminal C has two attributes:
o an inherited attribute b and
o a synthesized attribute t.
❑ The inherited b attribute pass a basic type down the tree, and
the synthesized t attribute accumulate the result.
Application of Syntax Directed Translation

The Structure of a Type


❑ Exercise: Construct an annotated parse tree for the input string int [2] [3].
Application of Syntax Directed Translation

The Structure of a Type


❑ An annotated parse tree for the input string int [2] [3] is shown in Fig. 5.17.

b-inherited attribute
t-synthesized attribute

❑ The inherited b attribute pass a basic type down the tree, and
the synthesized t attribute accumulate the result.
Syntax Directed Translation(SDT) schemes

 Syntax-Directed Translation(SDT) schemes are a complementary


notation to Syntax Directed Definitions(SDD).

 All of the applications of syntax-directed definitions in Section 5.3


can be implemented using syntax-directed translation schemes.

 Note:
 SDD is easier to read; easy for specification.

 SDT scheme – can be more efficient; easy for implementation.


Syntax Directed Translation(SDT) schemes

 An SDT is a CFG with program fragments embedded within production


bodies.

Those program fragments are called semantic actions and can appear at
any position within a production body.

Examples: E → { print(‘+’); } E1 + T

F → digit { print(digit.lexval); }

 Any SDT can be implemented by first building a parse tree and then
performing the actions in a left-to-right depth-first order; that is, during a
preorder traversal.

 Typically SDT’s are implemented during parsing without building a parse


tree.
Syntax Directed Translation(SDT) schemes

 The use of SDT's to implement two important classes of SDD's:

1. The underlying grammar is LR-parsable, and the SDD is S-attributed.

2. The underlying grammar is LL-parsable, and the SDD is L-attributed.

 In both these cases, the semantic rules in an SDD can be converted into an
SDT with actions that are executed at the right time.

 During parsing, an action in a production body is executed as soon as all


the grammar symbols to the left of the action have been matched.
Syntax Directed Translation(SDT) schemes

 Exercise 1: Consider the following Syntax Directed Translation

Scheme (SDTS), with non-terminals {S, A} and terminals {a, b}}.

Using the above SDTs, the output printed by a bottom-up parser, for
the input aab is
Syntax Directed Translation(SDT) schemes

 Exercise 1: Consider the following Syntax Directed Translation

Scheme (SDTS), with non-terminals {S, A} and terminals {a, b}}.

Using the above SDTs, the output printed by a bottom-up parser, for
the input aab is 231

S => aA => aSb => aab

1 3 2
Syntax Directed Translation(SDT) schemes

 Exercise 2: Consider The following SDT, if Top-Down Parsing is used to

evaluate below SDT, what is the output produced for the input
id+id*id
E → T * F {print(‘+’)}
T → id {}
F → G * id {print(‘*’)}
G → id {}

Using the above SDTs, the output printed by a bottom-up parser, for
the input id+id*id is
Syntax Directed Translation(SDT) schemes

 Exercise 2: Consider The following SDT, if Top-Down Parsing is used to

evaluate below SDT, what is the output produced for the input
id+id*id
E → T * F {print(‘+’)}
T → id {}
F → G * id {print(‘*’)}
G → id {}

Using the above SDTs, the output printed by a bottom-up parser, for
the input id+id*id is *+
Syntax Directed Translation(SDT) schemes

 Exercise 3: Consider The following SDT, if Top-Down Parsing is used to

evaluate below SDT, what is the output produced for the input
babcc
S → S1S2c { S.val = S1.val + S2.val }
S → a { S.val = 4 }
S → b { S.val = 7 }

Using the above SDTs, the output printed by a bottom-up parser, for
the input babcc is

S => SSc => SSScc => SSbcc => Sabcc => babcc
Syntax Directed Translation(SDT) schemes

 Exercise 3: Consider The following SDT, if Top-Down Parsing is used to

evaluate below SDT, what is the output produced for the input
babcc
S → S1S2c { S.val = S1.val + S2.val }
S → a { S.val = 4 }
S → b { S.val = 7 }

Using the above SDTs, the output printed by a bottom-up parser, for
the input babcc is 18

S => SSc => SSScc => SSbcc => Sabcc => babcc
Syntax Directed Translation(SDT) schemes

 Exercise 4: The given SDT scheme for expression, translates

❑ infix to postfix
❑ prefix to postfix
❑ postfix to prefix
❑ infix to prefix
❑ postfix to infix
❑ prefix to infix
Syntax Directed Translation(SDT) schemes

 Exercise 4: The given SDT scheme for expression, translates

❑ infix to postfix
❑ prefix to postfix
❑ postfix to prefix
❑ infix to prefix
❑ postfix to infix
❑ prefix to infix
Syntax Directed Translation(SDT) schemes

 Exercise 5: consider the following SDT


Syntax Directed Translation(SDT) schemes

 Exercise 5: consider the following SDT

abc*+$
Postfix translation schemes
 Simplest SDDs are those that we can parse the grammar bottom-up and

the SDD is S-attributed

For such cases we can construct SDT where each action is placed at the

end of the production and is executed along with the reduction of the

body to the head of that production

 SDT’s with all semantic actions at the right ends of the production bodies

are called postfix SDT’s


Example of postfix SDT
 The postfix SDT in Fig. 5.18 implements the desk calculator SDD of Fig. 5.1,
with one change: the action for the first production prints a value.

 Since the underlying grammar is LR, and the SDD is S-attributed, these
actions can be correctly performed along with the reduction steps of the
parser.
Example of postfix SDT
 Evaluate an expression 2+3*4n using the following SDT
Postfix translation schemes

Parser-Stack implementation of postfix


SDT’s
Parser-Stack implementation of postfix SDT’s
 Postfix SDT’s can be implemented during LR parsing by executing the
actions when reductions occur.
 In a shift-reduce parser we can easily implement semantic action using the
parser stack
 For each nonterminal (or state) on the stack we can associate a record
holding its attributes

o Then in a reduction step we can execute the semantic action at the end of a

production to evaluate the attribute(s) of the non-terminal at the LHS of the

production and put the value on the stack in replace of the RHS of production

E → E1 + T {stack[top-2].val = stack[top-2].val + stack[top].val; top=top-2;}


Parser-Stack implementation of postfix SDT’s

 The attribute(s) of each grammar symbol can be put on the stack(as a


separate record) along with the symbol.

T → T1 * F

 When reduction
T -> T1*F {stack[top-2].val = stack[top-2].val * stack[top].val;
top = top – 2; }
Parser-Stack implementation of postfix SDT’s

Example
Parser-Stack implementation of postfix SDT’s

Exercise 1: Show the evaluation of input string 4*5n using Parser-Stack


implementation of postfix SDT of desk calculator.
(Token stream: <digit,4> <*> <digit,5>) (Note: Only token names will be used during parsing)
Parser-Stack implementation of postfix SDT’s

 Exercise 1:Show the evaluation of input string 4*5n using Parser-Stack


implementation of postfix SDT of desk calculator. (Token stream: <digit,4> <*> <digit,5>)

top

top

top

top
Parser-Stack implementation of postfix SDT’s

 Exercise 1:Show the evaluation of input string 4*5n using Parser-Stack


implementation of postfix SDT of desk calculator. (Token stream: <digit,4> <*> <digit,5>)

top

top

top

top
Parser-Stack implementation of postfix SDT’s

 Exercise 1:Show the evaluation of input string 4*5n using Parser-Stack


implementation of postfix SDT of desk calculator.

top

top

top
SDT’s with actions inside productions
SDT’s with actions inside productions

 An action may be placed at any position within the body of a


production.

 Semantic Action is performed immediately after all symbols to its left


are processed.

Thus, if we have a production B → X {a} Y, the action “a” is done


after we have recognized X (if X is a terminal) or all the terminals
derived from X (if X is a nonterminal).
SDT’s with actions inside productions
 For a production B → X {a} Y

▪ If the parse is bottom-up then we perform action “a” as soon as


this occurrence of X appears on the top of the parser stack

▪ If the parser is top down we perform action “a” just before we

o expand Y (if Y is a nonterminal)

o check for Y on the input (if Y is a terminal).

 Sometimes we can't do things as easily as explained above. Not all


SDT's can be implemented during parsing, we shall see in the next
example.
SDT’s with actions inside productions
 Example 5.16 : An example of a problematic SDT that prints the prefix form
of an expression, rather than evaluating the expression.

 It is impossible to implement this SDT during either topdown or bottom-up


parsing, because the parser would have to perform critical actions, like
printing instances of * or +, long before it knows whether these symbols will
appear in its input.
SDT’s with actions inside productions
 Any SDT can be implemented as follows
1. Ignoring the actions, parse the input and
produce a parse tree as a result.

2. Then, examine each interior node N, say


one for production A → α.
Add additional children to N for the
actions in α, so the children of N from
left to right have exactly the symbols
and actions of α.

3. Perform a preorder traversal and


execute actions when their nodes are
visited

Fig. 5.22 shows the parse tree for expression 3 * 5 + 4


with actions inserted. If we visit the nodes in preorder,
we get the prefix form of the expression: + * 3 5 4.
Eliminating Left Recursion From SDT’s
 Since no grammar with left recursion can be parsed deterministically top-
down, when the grammar is part of an SDT, we also need to worry about
how the actions are handled.

 Consider the simple case, in which the only thing we care about is the
order in which the actions in an SDT are performed.
▪ Example:

▪ If each action simply prints a string, we care only about the order in which
the strings are printed.

▪ When transforming the grammar, treat the actions as if they were terminal
symbols.

▪ Since, grammar transformation preserves the order of the terminals in the


generated string, The actions are therefore executed in the same order in
any left-to-right parse, top-down or bottom-up.
Eliminating Left Recursion From SDT’s
 To eliminate left recursion:

A → A |  A → A'
A' → A' | 

E → TE'
E → E1 + T {print(‘+’);}
E' → + T {print(‘+’);} E'
E → T
E'→ 
Eliminating Left Recursion From SDT’s
 When the actions of an SDD compute attributes rather than merely printing
output, we must be more careful about how we eliminate left recursion from a
grammar !

 Consider

A → A1 Y {A.a = g(A1.a, Y.y)}


A → X { A.a = f(X.x)}

 Here, A.a is the synthesized attribute of left-recursive nonterminal A, and X and


Y are single grammar symbols with synthesized attributes X.x and Y.y,

 We want to turn the underlying grammar into:

A→XR
R→YR|
Eliminating Left Recursion From SDT’s

A → A1 Y {A.a = g(A1.a, Y.y)}


A → X { A.a = f(X.x)}

The effect of the postfix SDT on the


original grammar

We apply f once, corresponding to the


use of production
A -> X

Then apply g as many times as we use


the production
A -> A Y
Eliminating Left Recursion From SDT’s
A → X {R.i = f(X.x)} R {A.a = R.s}
R → Y {R1.i = g(R.i, Y.y} R1 {R.s = R1.s}
R →  { R.s = R.i}

Since R generates a “remainder" of Y 's,


its translation depends on the string to its left,
a string of the form XY Y ....Y

Each use of the production R ->Y R results in


an application of g

For R, we use an inherited attribute R.i to


accumulate the result of successively
applying g, starting with the value of A.a
Eliminating Left Recursion From SDT’s
A → X {R.i = f(X.x)} R {A.a = R.s}
R → Y {R1.i = g(R.i, Y.y} R1 {R.s = R1.s}
R →  { R.s = R.i}

In addition, R has a synthesized attribute R.s,


(not shown)

This attribute is first computed when R ends its


eneration of Y symbols, as signaled by the use
of production
R -> epsilon

R:s is then copied up the tree, so it can


become the value of A.a for the entire
expression XY Y ... Y

The inherited attribute R.i is evaluated


immediately before a use of R in the body,
while the synthesized attributes A.a and R.s are
evaluated at the ends of the productions.
SDT’s for L-Attributed definitions

L-Attributed SDT’s for Intermediate


code generation
SDT’s for L-Attributed definitions
S-attributed SDD to S-attributed SDT(Postfix SDT) Conversion:
 S-attributed SDD’s can be converted into postfix SDT’s, with actions at the right ends
of productions.

 As long as the underlying grammar is LR, postfix SDT's can be parsed and translated
bottom-up.

Example:

S-attributed SDD S-attributed SDT/Postfix SDT


SDT’s for L-Attributed definitions
L-attributed SDD to L-attributed SDT Conversion:

 L-attributed SDD is converted into an SDT using following two rules:


1. Embed the action that computes the inherited attributes for a nonterminal A
immediately before that occurrence of A.
If several inherited attributes of A are dependent on one another in an acyclic
fashion, order them so that those needed first are computed first.

2. Place the actions that compute a synthesized attribute for the head of a
production at the end of the body of that production.

Example: L-Attributed SDT:


D → T {L.inh = T.type} L
T → int {T.type=integer}
T → float {T.type=float}
L → {L1.inh=L.inh} L1 , id {addType(id.entry, L.inh)}
L → id {addType(id.entry, L.inh)}
Figure 5.8: L-attributed SDD for simple type declarations
SDT’s for L-Attributed definitions: Example

 Generation of intermediate code for a construct: a form of


while-statement.

 Consider,

S → while ( C ) S1
o In the above CFG, S is the nonterminal that generates all kinds of
statements

o C stands for a conditional expression or a Boolean expression that


evaluates to true or false.
SDT’s for L-Attributed definitions: Example

 Generation of intermediate code for a construct: a form of while-


statement.

 Consider,

S → while ( C ) S1
o The meaning of our while-statement is that the conditional C is
evaluated. If it is true, control goes to the beginning of the code for S1.
If false, then control goes to the code that follows the while-statement's
code.

o The code for S1 must be designed to jump to the beginning of the code
for the while-statement when finished.
SDT’s for L-Attributed definitions: Example
S → while (C) S1

 We use the following attributes to generate the proper intermediate code:


1. The inherited attribute S.next labels the beginning of the code that must be
executed after S is finished.

2. The synthesized attribute S.code is the sequence of intermediate-code steps


that implements a statement S and ends with a jump to S.next.

3. The inherited attribute C.true labels the beginning of the code that must be
executed if C is true.

4. The inherited attribute C.false labels the beginning of the code that must be
executed if C is false.

5. The synthesized attribute C.code is the sequence of intermediate-code steps


that implements the condition C and jumps either to C.true or to C.false,
depending on whether C is true or false.
SDT’s for L-Attributed definitions: Example
Production Semantic Rules
S -> while (C) S1 L1=new(); L 1:
L2: S
L2=new();
S.next:
S1.next=L1;
C.false=S.next;
C.true=L2;
S.code=label||L1||C.code||label||L2|| S1.code

 The function new generates new labels.


The variables L1 and L2 hold labels that we need in the code.
▪ L1 is the beginning of the code for the while-statement, and we need to arrange that S1
jumps there after it finishes. That is why we set S1.next to L1.

▪ L2 is the beginning of the code for S1, and it becomes the value of C.true, because we
branch there when C is true.
SDT’s for L-Attributed definitions: Example
Production Semantic Rules
S -> while (C) S1 L1=new();

L2=new();
S1.next=L1;
C.false=S.next;
C.true=L2;
S.code=label||L1||C.code||label||L2|| S1.code

 C.false is set to S.next, because when the condition is false, we execute


whatever code must follow the code for S.

 || symbol is used for the concatenation of intermediate-code fragments.


The value of S.code thus begins with the label L1, then the code for

condition C, another label L2, and the code for S1.


SDT’s for L-Attributed definitions: Example
Production Semantic Rules
S -> while (C) S1 L1=new();

L2=new();
S1.next=L1;
C.false=S.next;
C.true=L2;
S.code=label||L1||C.code||label||L2|| S1.code

Figure 5.27: SDD (to generate intermediate code) for while-statement

While generating intermediate code, we generate explicit instructions of the form label Li
where Li is an identifier, to indicate that Li is the label of the instruction that follows.
SDT’s for L-Attributed definitions: Example
Production Semantic Rules

S -> while (C) S1 L1=new();


L2=new();
S1.next=L1;
C.false=S.next;
C.true=L2;
S.code=label||L1||C.code||label||L2|| S1.code
Figure 5.27: SDD (to generate intermediate code) for while-statements

S -> while({ L1=new(); L2=new(); C.false=S.next; C.true=L2;} C)


{ S1.next=L1;} S1
{ S.code = label || L1 || C.code || label || L2 ||S1.code; }
Figure 5.28: SDT (to generate intermediate code) for while-statements

Since L1 and L2 do not depend on any other attributes, they can be assigned to the first
action in the production.
SDT’s for L-Attributed definitions: Exercise 1
Production Semantic Rules
S -> do S1 while(C) L 1:
L2: S
S.next:

Figure 5.27: SDD (to generate intermediate code) for do-while-statements

Figure 5.28: SDT (to generate intermediate code) for do-while-statements


SDT’s for L-Attributed definitions: Exercise 1
Production Semantic Rules
S -> do S1 while(C) L 1:
L1=new();
L2: S
L2=new();
S.next:
S1.next=L2;
C.false=S.next;
C.true=L1;
S.code=label||L1||S1.code||label||L2||C.code

Figure 5.27: SDD (to generate intermediate code) for do-while-statements

S -> do {L2=new(); S1.next=L2;} S1 while({ L1=new(); C.false=S.next;


C.true=L1;} C)
{ S.code = label || L1 || S1.code || label || L2 ||C.code }
Figure 5.28: SDT (to generate intermediate code) for do-while-statements
SDT’s for L-Attributed definitions: Exercise 2
Production Semantic Rules
S -> if(C) S1 else S2
L1: S
L2:
S.next:

Figure 5.27: SDD (to generate intermediate code) for if-else-statements

Figure 5.28: SDT (to generate intermediate code) for if-else-statements


SDT’s for L-Attributed definitions: Exercise 2
Production Semantic Rules
S -> if(C) S1 else S2 L1=new();
L1: S
L2=new();
L2:
S1.next=S.next;
S.next:
S2.next=S.next;
C.true=L1;
C.false=L2;
S.code=C.code||label||L1||S1.code||label||L2||S2.code

Figure 5.27: SDD (to generate intermediate code) for if-else-statements

S -> if ({ L1=new(); L2=new(); C.false=L2; C.true=L1;} C)


{S1.next=S.next;} S1 else {S2.next=S.next;} S2
{S.code=C.code || label || L1 || S1.code ||label ||L2|| S2.code}
Figure 5.28: SDT (to generate intermediate code) for if-else-statements
SDT’s for L-Attributed definitions: Exercise 3
Production Semantic Rules
S -> for(S1; C; S3)S4

Figure 5.27: SDD (to generate intermediate code) for for-statement

Figure 5.28: SDT (to generate intermediate code) for for-statement


SDT’s for L-Attributed definitions: Exercise 3
Production Semantic Rules
S -> for(S1; C; S3)S4

L1:

L2: S
L3:

S.next:

Figure 5.27: SDD (to generate intermediate code) for for-statement

Figure 5.28: SDT (to generate intermediate code) for for-statement


SDT’s for L-Attributed definitions: Exercise 3
Production Semantic Rules
S -> for(S1; C; S3)S4 L1=new(); L2=new(); L3=new(); L 1:

C.true=L2; L2: S
L3:
C.false=S.next;
S.next:
S4.next=L3;
S3.next=L1;
S.code=S1.code||label||L1||C.code||label||L2||S4.code
||label||L3||S3.code

Figure 5.27: SDD (to generate intermediate code) for for-statement

Figure 5.28: SDT (to generate intermediate code) for for-statement


SDT’s for L-Attributed definitions: Exercise 3
Production Semantic Rules
S -> for(S1; C; S3)S4 L1=new(); L2=new(); L3=new(); L 1:

C.true=L2; L2: S
L3:
C.false=S.next;
S.next:
S4.next=L3;
S3.next=L1;
S.code=S1.code||label||L1||C.code||label||L2||S4.code
||label||L3||S3.code

Figure 5.27: SDD (to generate intermediate code) for for-statement

S -> for(S1; {L1=new(); L2=new(); L3=new(); C.true=L2; C.false=S.next;} C;


{S3.next=L1} S3) {S4.next=L3} S4
S.code=S1.code||label||L1||C.code||label||L2||S4.code||label||L3||S3.code

Figure 5.28: SDT (to generate intermediate code) for for-statement


SDT’s for L-Attributed definitions: Exercise 4

 Exercise: Convert the following L-attributed SDD into an SDT.

Figure 5.8: L-attributed SDD for simple type declarations

L-Attributed SDT:
D → T { L.inh = T.type } L
T → int { T.type=integer }
T → float { T.type=float }
L → { L1.inh=L.inh } L1 , id { addType(id.entry, L.inh) }
L → id {addType(id.entry, L.inh)}
SDT’s for L-Attributed definitions
Implementing L-Attributed SDD's

Implementing L-Attributed SDD's


Implementing L-Attributed SDD's
 L-attributed definitions can be used in many translation applications. We
shall consider their implementation in more detail in this section.
Implementing L-Attributed SDD's
 L-attributed definitions can be used in many translation applications. We
shall consider their implementation in more detail in this section.

1. The following methods do translation by traversing a parse tree:


a) Build the parse tree and annotate. This method works for any noncircular SDD
whatsoever.

b) Build the parse tree, add actions, and execute the actions in preorder. This
approach works for any L-attributed definition.
Ex., Arithmetic expression
evaluation/ Infix to Postfix
conversion

Ex., Infix to Prefix


conversion
Implementing L-Attributed SDD's
2. The following methods do translation during parsing:
a) Use a recursive-descent parser with one function for each nonterminal. The
function for nonterminal A receives the inherited attributes of A as
arguments and returns the synthesized attributes of A.

b) Implement an SDT in conjunction with an LL-parser. The attributes are kept on


the parsing stack, and the rules fetch the needed attributes from known
locations on the stack.

c) Implement an SDT in conjunction with an LR-parser. This method may be


surprising, since the SDT for an L-attributed SDD typically has actions in the
middle of productions, and we cannot be sure during an LR parse that we
are even in that production until its entire body has been constructed. We
shall see, however, that if the underlying grammar is LL, we can always
handle both the parsing and translation bottom-up.
Implementing L-Attributed SDD's
 In this section, we discuss the following methods for translation during parsing:
Bottom-Up Parsing of L-Attributed SDD’s

Bottom-Up Parsing of L-Attributed


SDD’s for Intermediate code
generation
Bottom-Up Parsing of L-Attributed SDD’s
 We can do bottom-up every translation that we can do top-down.

 Given an L-attributed SDD on an LL grammar, we can adapt the grammar to


compute the same SDD on the new grammar during an LR parse.
The "trick" has three parts:

1. Start with the SDT constructed, which places embedded actions before
each nonterminal to compute its inherited attributes and an action at
the end of the production to compute synthesized attributes.

2. Introduce into the grammar a marker nonterminal in place of each


embedded action. Each such place gets a distinct marker, and there is
one production for any marker M, namely M → ϵ.
For example:
A → M B C
A→ {B.i = f(A.i);} B C M → ϵ { M.i = A.i; M.s = f(M.i); }
Bottom-Up Parsing of L-Attributed SDD’s

3. Modify the action a if marker nonterminal M replaces it in some production

A →  {a} β, and associate with M → ϵ an action a’ (i.e., M → ϵ { a' } ) that

(a) Copies, as inherited attributes of M, any attributes of A or symbols of  that action a needs.

(b) Computes attributes in the same way as a, but makes those attributes be synthesized
attributes of M.

o This change appears illegal, since typically the action a' associated with production
M → ϵ will have to access attributes belonging to grammar symbols that do not
appear in this production.

o However, we shall implement the actions on the LR parsing stack, so the necessary
attributes will always be available a known number of positions down the stack.

For example:
A → M B C
A→ {B.i = f(A.i);} B C M → ϵ { M.i = A.i; M.s = f(M.i); }
Bottom-Up Parsing of L-Attributed SDD’s
 Example: Consider a production A → B C in an LL grammar, and the inherited
attribute B.i is computed from inherited attribute A.i by some formula B.i = f(A.i).
That is, the fragment of an SDT is

A → {B.i = f(A.i);} B C

 We introduce marker M with inherited attribute M.i and synthesized attribute


M.s. The SDT will be written

L-attributed SDT: SDT(Suitable for bottom-up parsing):


A → M B C
A→ {B.i = f(A.i);} B C M → ϵ { M.i = A.i; M.s = f(M.i); }

Note: The rule for M does not have A.i available to it, but in fact we shall arrange that every
inherited attribute for a nonterminal such as A appears on the stack immediately below where
the reduction to A will later take place.
When we reduce ϵ to M, we shall find A.i immediately below it, from where it may be read.
Also, the value of M.s, which is left on the stack along with M, is really B.i and properly is found
right below where the reduction to B will later occur.
Bottom-Up Parsing of L-Attributed SDD's
 Example 1: Consider, SDT for while-statement

S -> while({ L1=new(); L2=new(); C.false=S.next; C.true=L2;} C)


{ S1.next=L1;} S1
{ S.code = label || L1 || C.code || label || L2 ||S1.code; }

Let us turn this SDT(L-attributed) into an SDT that can operate with an LR parse.
o We introduce a marker nonterminal M before C and a marker nonterminal N before
S1 in place of each embedded action, so the underlying grammar becomes
Bottom-Up Parsing of L-Attributed SDD's
 Example 1: Consider, SDT for while-statement

S -> while({ L1=new(); L2=new(); C.false=S.next; C.true=L2;} C)


{ S1.next=L1;} S1
{ S.code = label || L1 || C.code || label || L2 ||S1.code; }

▪ Let us turn this SDT(L-attributed) into an SDT that can operate with an LR
parse.
o We introduce a marker nonterminal M before C and a marker nonterminal N before
S1 in place of each embedded action, so the underlying grammar becomes

S -> while ( M C) N S1 {S.code=label||L1||C.code||label||L2||S1.code;}


M -> ϵ {L1=new(); L2=new(); C.false=S.next; C.true=L2;}
N -> ϵ {S1.next=L1;}
Bottom-Up Parsing of L-Attributed SDD's
 Example 2: Consider, SDT for do-while statement

S -> do {L2=new(); S1.next=L2;} S1 while({ L1=new(); C.false=S.next;


C.true=L1;} C)
{ S.code = label || L1 || S1.code || label || L2 ||C.code }

▪ Let us turn this SDT(L-attributed) into an SDT that can operate with an LR
parse.
Bottom-Up Parsing of L-Attributed SDD's
 Example 2: Consider, SDT for do-while statement

S -> do {L2=new(); S1.next=L2;} S1 while({ L1=new(); C.false=S.next;


C.true=L1;} C)
{ S.code = label || L1 || S1.code || label || L2 ||C.code }

▪ Let us turn this SDT(L-attributed) into an SDT that can operate with an LR
parse.

S -> do M S1 while(N C) {S.code=label||L1||S1.code||label||L2||C.code}


M ->ϵ {L2=new(); S1.next=L2;}
N ->ϵ { L1=new(); C.false=S.next; C.true=L1;}
Bottom-Up Parsing of L-Attributed SDD's
 Example 3: Consider, SDT for if-else statement

S -> if ({ L1=new(); L2=new(); C.false=L2; C.true=L1;} C)


{S1.next=S.next;} S1 else {S2.next=S.next;} S2
{S.code=C.code || label || L1 || S1.code ||label ||L2|| S2.code}

▪ Let us turn this SDT(L-attributed) into an SDT that can operate with an LR
parse.
Bottom-Up Parsing of L-Attributed SDD's
 Example 3: Consider, SDT for if-else statement

S -> if ({ L1=new(); L2=new(); C.false=L2; C.true=L1;} C)


{S1.next=S.next;} S1 else {S2.next=S.next;} S2
{S.code=C.code || label || L1 || S1.code ||label ||L2|| S2.code}

▪ Let us turn this SDT(L-attributed) into an SDT that can operate with an LR
parse.

S -> if (M C) N S1 else P S2 {S.code=C.code||label||L1||S1.code||label||L2||S2.code}

M ->ϵ { L1=new(); L2=new(); C.false=L2; C.true=L1;}


N ->ϵ {S1.next=S.next;}
P ->ϵ {S2.next=S.next;}
Bottom-Up Parsing of L-Attributed SDD's
 Example 1: Consider, SDT for while-statement

S -> while ( M C) N S1 {S.code=label||L1||C.code||label||L2||S1.code;}


M -> ϵ {L1=new(); L2=new(); C.false=S.next; C.true=L2;}
N -> ϵ {S1.next=L1;}

 let us see where attributes are stored in Parser Stack.


1. Below the entire body of the while-production — that is, below while on the stack
— will be the inherited attribute S.next.
Bottom-Up Parsing of L-Attributed SDD's

2. Inherited attributes C.true and C.false will be just below the stack record for C.

The appearance of while on the input assures us that the while-production is the
only one that can be recognized, so we can be sure that M will appear
immediately below C on the stack, and M's record will hold the inherited attributes
of C.

3. Similarly, the inherited attribute S1.next must appear immediately below S1 on the
stack, so we may place that attribute in the record for N.
Bottom-Up Parsing of L-Attributed SDD's

4. The synthesized attribute C.code will appear in the record for C. As always
when we have a long string as an attribute value, we expect that in
practice a pointer to (an object representing) the string will appear in the
record, while the string itself is outside the stack.

5. Similarly, the synthesized attribute S1.code will appear in the record for S1.
Bottom-Up Parsing of L-Attributed SDD's

SDD to generate intermediate code for


Conditional Boolean expressions
Bottom-Up Parsing of L-Attributed SDD's
SDD to generate three-address intermediate code for Conditional Boolean expressions

Production Semantic Rules


C -> B1 || B2 Note: The function new generates new labels.
Where L1 is the label for the beginning of B2.code

C -> B1 && B2

C -> !B1
Bottom-Up Parsing of L-Attributed SDD's
SDD to generate three-address intermediate code for Conditional Boolean expressions

Production Semantic Rules


C -> B1 || B2 L1 = new() Note: The function new generates new labels.
Where L1 is the label for the beginning of B .code
2
B1.true = C.true
B1.false = L1
B2.true = C.true
B2.false = C.false
C.code = B1.code||label||L1||B2.code

C -> B1 && B2 L1 = new() Note: The function new generates new labels.
B1.true = L1 Where L1 is the label for the beginning of B .code
2

B1.false = C.false
B2.true = C.true
B2.false = C.false
C.code = B1.code||label||L1||B2.code

C -> !B1 B1.true = C.false


B1.false = C.true
C.code = B1.code
Bottom-Up Parsing of L-Attributed SDD's
SDD to generate three-address intermediate code for Conditional Boolean expressions

Production Semantic Rules


C -> E1 rel E2 C.code = E1.code||E2.code
||gen(‘if’ E1.addr rel.op E2.addr ‘goto’ C.true)
||gen(‘goto’ C.false)

Note: E.addr is a reference to the result of E, usually we get after evaluating E.


C -> true C.code = gen(‘goto’ C.true)

C -> false C.code = gen(‘goto’ C.false)

Note: function gen() constructs a three-address intermediate instruction, and it


also appends the instruction to the sequence of instructions generated so far.
Bottom-Up Parsing of L-Attributed SDD's

Bottom-Up Parsing of a while-statement for


intermediate code generation
Bottom-Up Parsing of L-Attributed SDD's
 Let us follow the bottom-up parsing process of a while-statement for
intermediate code generation.
▪ Suppose that a record holding S.next appears on top of the stack, and the next
input is the terminal while. Input: …. while ( i < 10 ) …..
LR parsing stack:

▪ We shift terminal while onto the stack, so the LR parser can shift "(" and determine
that its next step must be to reduce ϵ to M. The stack at this time is shown in Fig. 5.36.

Figure 5.36: LR parsing stack after reduction of ϵ to M


Bottom-Up Parsing of L-Attributed SDD's

 We presume that the next inputs are properly reduced to C.


The synthesized attribute C.code is therefore placed in the record for C.
This change to the stack is shown in Fig. 5.37, which also incorporates the
next several records that are later placed above C on the stack.

Input: …. while ( i < 10 ) …..


Bottom-Up Parsing of L-Attributed SDD's

 Continuing with the recognition of the while-statement, the parser should next find ")"
on the input, which it pushes onto the stack in a record of its own.

 At that point, the parser, will reduce ϵ to N. The single piece of data associated with
N is the inherited attribute S1.next.

o S1.next attribute needs to be in the record for N because that will be just below the
record for S1. The code that is executed to compute the value of S1.next is

S1.next = stack[top — 3].L1;


Bottom-Up Parsing of L-Attributed SDD's

 Next, the parser reduces some prefix of the remaining input to S, which we have
consistently referred to as S1 to distinguish it from the S at the head of the production.

 The value of S1.code is computed and appears in the stack record for S1. This step
takes us to the condition that is illustrated in Fig. 5.37.

Input: …. while ( i < 10 ) {…..}

Figure 5.37: Stack just before reduction of the while-production body to S


Bottom-Up Parsing of L-Attributed SDD's
 At this point, the parser will reduce everything from while to S1 to S. The code that is
executed during this reduction is:
tempCode = label || stack[top - 4].L1 || stack[top - 3].code ||
label || stack[top - 4].L2 || stack[top].code;
top = top – 6;
stack[top].code = tempCode;
 We construct the value of S.code in a variable tempCode. That code is the usual,
consisting of the two labels LI and L2, the code for C and the code for S1.

Figure 5.37: Stack just before reduction of the while-production body to S


Bottom-Up Parsing of L-Attributed SDD's
 The stack is popped, so S appears where while was. The value of the code for S is
placed in the code field of that record, where it can be interpreted as the
synthesized attribute S.code.

tempCode = label||stack[top - 4].L1||stack[top - 3].code||label||stack[top - 4].L2||


stack[top].code;
top = top – 6;
stack[top].code = tempCode;
Bottom-Up Parsing of L-Attributed SDD's
 Exercise 1: Implement the following SDT scheme for if-statement during LR
Parsing.

S -> if ({ L1=new(); C.false=S.next; C.true=L1;} C)


{S1.next=S.next;} S1 {S.code = C.code || label || L1 || S1.code}

Let us turn this SDT(L-attributed) into an SDT that can operate with an LR parse.

S -> if (M C) N S1 {S.code = C.code || label || L1 || S1.code}


M ->ϵ {L1=new(); C.true=L1; C.false=S.next;}
N ->ϵ {S1.next=S.next;}
Bottom-Up Parsing of L-Attributed SDD's
 Exercise 1: Implement the following SDT scheme for if-statement during LR Parsing.
top Input: if (a>b) print(a)

Parser stack Suppose that a record holding S.next appears


on top of the stack, and the next input is the
terminal if.
Shift terminals if and ( onto the stack one by
one.

Reduce ϵ to M and execute the action


{L1=new(); C.true=L1; C.false=S.next;}

L1=new();
C.true=L1
C.false=Stack[top-3].next;

We presume that the next inputs are


properly reduced to C.
The synthesized attribute C.code is therefore
placed in the record for C.
Bottom-Up Parsing of L-Attributed SDD's
 Exercise 1: Implement the following SDT scheme for if-statement during LR Parsing.
Input: if (a>b) print(a)

Shift ) on to the stack

Reduce ϵ to N and execute the action


{{S1.next=S.next;}}

S1.next=stack[top-6].next

We presume that the next inputs(i.e., if


body statements) are properly reduced
to S1. The attribute S1.code is therefore
placed in the record for S1.
Bottom-Up Parsing of L-Attributed SDD's
 Exercise 1: Implement the following SDT scheme for if-statement during LR Parsing.
Input: if (a>b) print(a)

We presume that the next inputs(i.e., if


body statements) are properly reduced
to S1. The attribute S1.code is therefore
placed in the record for S1.

Reduce if (M C) N S1 to S and execute


the action
{S.code = C.code || label || L1 ||S1.code}

tempcode= stack[top-3].code || label ||


stack[top-4].L1 || stack[top].code

Top=top-6
S.code = tempcode
Bottom-Up Parsing of L-Attributed SDD's
 Exercise 2:
Bottom-Up Parsing of L-Attributed SDD's
 Exercise 2:
Bottom-Up Parsing of L-Attributed SDD's
Symbol table
Name Type
1 x
 Exercise 2: Input: int x,y,z Token stream: <int> <id,1> <,> <id,2> <,> <id,3>
2 y
3 z
……
top

Input: int id,id,id


Parser stack
Shift int
Reduce int to T and execute the action
{T.type = integer}
T.type = int

Reduce ϵ to M and execute the action


{L.inh = T.type}
L.inh = stack[top-1].type

Reduce ϵ to N and execute the action


{L1.inh = L.inh}
L1.inh = stack[top-1].inh

Reduce ϵ to N and execute the action


{L1.inh = L.inh}
L1.inh = stack[top-1].inh
Bottom-Up Parsing of L-Attributed SDD's
Symbol table
Name Type
1 x int
 Exercise 2: Input: int x,y,z Token stream: <int> <id,1> <,> <id,2> <,> <id,3>
2 y
3 z
……

Input: int id,id,id

Shift id

Reduce id to L and execute the action


{addType(id.entry, L.inh)}
addType(1, stack[top-1].inh}

Shift ,

Shift id
Bottom-Up Parsing of L-Attributed SDD's
Symbol table
Name Type
1 x int
 Exercise 2: Input: int x,y,z Token stream: <int> <id,1> <,> <id,2> <,> <id,3>
2 y
3 z
……

Input: int id,id,id

Shift id

Reduce id to L and execute the action


{addType(id.entry, L.inh)}
addType(1, stack[top-1].inh}

Shift ,

Shift id
Bottom-Up Parsing of L-Attributed SDD's
Symbol table
Name Type
1 x int
 Exercise 2: Input: int x,y,z Token stream: <int> <id,1> <,> <id,2> <,> <id,3>
2 y int
3 z int
……

Input: int id,id,id

Reduce NL,id to L and execute the


action {addType(id.entry, L.inh)}
addType(2, stack[top-1].inh}

Shift terminals , and id onto the


stack one by one

Reduce NL,id to L and execute the


action {addType(id.entry, L.inh)}
addType(3, stack[top-1].inh}

Reduce TML to D
Bottom-Up Parsing of L-Attributed SDD's
 Exercise 3:
References

 Compilers–Principles, Techniques and Tools, Alfred V.


Aho, Monica S. Lam, Ravi Sethi, Jeffery D. Ullman, 2nd
Edition
Translation During Recursive-Descent Parsing

Translation (High-level code to Intermediate code)


during Recursive-Descent Parsing
Translation During Recursive-Descent Parsing

 A recursive-descent parser has a function A for each nonterminal A.


We can extend the parser into a translator as follows:

▪ The arguments of function A are the inherited attributes of


nonterminal A.

▪ The return-value of function A is the collection of synthesized


attributes of nonterminal A.

• Inherited: analogous to function arguments


• Synthesized: analogous to return values
Translation During Recursive-Descent Parsing

 In the body of function A, we need to both parse and handle


attributes:
1. Decide upon the production used to expand A.

2. Check that each terminal appears on the input when it is required. We shall
assume that no backtracking is needed, but the extension to recursive
descent parsing with backtracking can be done by restoring the input
position upon failure, as discussed in Section 4.4.1.

3. Preserve, in local variables, the values of all attributes needed to compute


inherited attributes for nonterminals in the body or synthesized attributes for
the head nonterminal.

4. Call functions corresponding to nonterminals in the body of the selected


production, providing them with the proper arguments. Since the underlying
SDD is L-attributed, we have already computed these attributes and stored
them in local variables.
Translation During Recursive-Descent Parsing
SDD and SDT for while-statements of example 5.19
Production Semantic Rules
S -> while (C) S1 L1=new();

L2=new();
S1.next=L1;
C.false=S.next;
C.true=L2;
S.code=label||L1||C.code||label||L2|| S1.code
Figure 5.27: SDD for while-statements

S -> while({ L1=new(); L2=new(); C.false=S.next; C.true=L2;} C)


{ S1.next=L1;} S1
{ S.code = label || L1 || C.code || label || L2 ||S1.code;}

Figure 5.28: SDT for while-statements


Translation During Recursive-Descent Parsing
On-The-Fly Code Generation

 The construction of long strings of code that are attribute values, as in


Example 5.20, is undesirable for several reasons, including the time it could
take to copy or move long strings.

 In common cases such as our running code generation example, we can


instead incrementally generate pieces of the code into an array or output
file by executing actions in an SDT.

Note: On-The-Fly Code Generation


1. Don’t use temporary variables to hold the generated intermediate code.
2. Print immediately the generated intermediate code to the console or to the file.
On-The-Fly Code Generation using RDP Parsing
On-The-Fly Code Generation

S -> while({ L1=new(); L2=new(); C.false=S.next; C.true=L2;} C)


{ S1.next=L1;} S1
{ S.code = label || L1 || C.code || label || L2 ||S1.code; }
Translation During Recursive-Descent Parsing

 Exercise: write an RDP procedure to translate if-else statement during


Recursive-Descent Parsing.
L-Attributed SDD's and LL Parsing

L-Attributed SDD's and LL Parsing for


Intermediate code generation
L-Attributed SDD's and LL Parsing
 An L-attributed SDD based on an LL-grammar is converted to an SDT
with actions embedded in the productions(SDT’s for L-Attributed
definitions).

 Perform the translation during LL parsing by extending the


parser stack to hold actions and certain data items needed for
attribute evaluation. Typically, the data items are copies of attributes.

 In addition to records representing terminals and nonterminals, the


parser stack will hold
▪ action-records representing actions to be executed and

▪ synthesize-records to hold the synthesized attributes for nonterminals.


L-Attributed SDD's and LL Parsing
 The following two principles are used to manage attributes on the
stack:
1. The inherited attributes of a nonterminal A are placed in the stack record that
represents that nonterminal.
The code to evaluate these attributes will usually be represented by an
action-record immediately above the stack record for A; in fact, the
conversion of L-attributed SDD's to SDT‘s ensures that the action-record will be
immediately above A.

2. The synthesized attributes for a nonterminal A are placed in a separate


synthesize-record that is immediately below the record for A on the stack.

Example:
L-Attributed SDD's and LL Parsing
Example 5.23:

 This example implements the SDT of Fig. 5.32, which generates code on the
fly for the while-production. This SDT does not have synthesized attributes,
except for dummy attributes that represent labels.
L-Attributed SDD's and LL Parsing
Example 5.23: (cont…)

 Figure 5.33(a) shows the situation as we are about to use the while-production
to expand S, presumably because the lookahead symbol on the input is while.

The record at the top of stack is for S, and it contains only the inherited attribute
S.next, which we suppose has the value x.

Since we are now parsing top-down, we show the stack top at the left,
according to our usual convention.

Figure 5.33: Expansion of S according to the while-statement production


L-Attributed SDD's and LL Parsing
Example 5.23 : (cont…)

 Figure 5.33(b) shows the situation immediately after we have expanded S.


There are action-records in front of the nonterminals C and S1,
corresponding to the actions in the underlying SDT of Fig. 5.32.
L-Attributed SDD's and LL Parsing
Example 5.23 : (cont…)
 The situation after matching while and ( with input and completing the first action
and popping its record off the stack is shown in Fig. 5.34.

 The values of inherited attributes in the record for C have been filled in properly, as
have the temporaries al1 and al2 in the second action record.

 At this point, C is expanded, and we presume that the code to implement its test
containing jumps to labels x and z, as appropriate, is generated.
L-Attributed SDD's and LL Parsing
Example 5.24:

 Now, let us consider the same while-statement, but with a translation that
produces the output S.code as a synthesized attribute.

 In order to follow the explanation, it is useful to bear in mind the following


inductive hypothesis, which we assume is followed for every nonterminal:

 Every nonterminal that has code associated with it leaves that code, as
a string, in the synthesize-record just below it on the stack.

 Assuming this statement is true, we shall handle the while-production so it


maintains this statement as an invariant.
L-Attributed SDD's and LL Parsing
Example 5.24: (cont…)

 Figure 5.35(a) shows the situation just before S is expanded using the
production for while-statements.

 At the top of the stack we see the record for S; it has a field for its inherited
attribute S.next. Immediately below that record is the synthesize-record for this
occurrence of S. The latter has a field for S.code, as all synthesize-records for S
must have.

 We also show it with some other fields for local storage and actions, since the
SDT for the while production in Fig. 5.28 is surely part of a larger SDT.
L-Attributed SDD's and LL Parsing
Example 5.24: (cont…)

 Let us examine what each record does when it becomes the top of stack.

 During the expansion of S, we assume that the inherited attribute S.next is


assigned directly to C.false, rather than being placed in the first action and
then copied into the record for C.
L-Attributed SDD's and LL Parsing
Example 5.24: (cont…)

 By our inductive hypothesis, S1 nonterminal is expanded, and the net effect is that its code is
correctly constructed and placed in the field for code in the synthesize-record for S1.

 Now, all the data fields of the synthesize-record for S1 have been filled in, so when it becomes the
top of stack, the action in that record can be executed.

 The action causes the labels and code from C.code and S1.code to be concatenated in the
proper order. The resulting string is placed in the record below; that is, in the synthesize-record for S.

 We have now correctly computed S.code, and when the synthesize-record for S becomes the top,
that code is available for placement in another record further down the stack, where it will
eventually be assembled into a larger string of code implementing a program element of which
this S is a part.
L-Attributed SDD's and LL Parsing
 Exercise: Convert the following SDD to SDT scheme and provide its
Implementation during LL Parsing for input string num1*num2 (num1.lexval=4,
num2.lexval=5)

Productions Semantic Rules

Convert the above SDD to SDT:


L-Attributed SDD's and LL Parsing
 Exercise: Convert the following SDD to SDT scheme and provide its
Implementation during LL Parsing for input string num1*num2 (num1.lexval=4,
num2.lexval=5)

Productions Semantic Rules

Convert the above SDD to SDT:

You might also like