PRINCIPLES OF COMPILER DESIGN UNIT IV SYNTAX DIRECTED TRANSLATION AND TYPE CHECKING

Similar documents
Syntax-Directed Translation

Syntax-Directed Translation

Abstract Syntax Tree

Compilers. 5. Attributed Grammars. Laszlo Böszörmenyi Compilers Attributed Grammars - 1

UNIT-3. (if we were doing an infix to postfix translator) Figure: conceptual view of syntax directed translation.

Syntax-Directed Translation. Concepts Introduced in Chapter 5. Syntax-Directed Definitions

[Syntax Directed Translation] Bikash Balami

NARESHKUMAR.R, AP\CSE, MAHALAKSHMI ENGINEERING COLLEGE, TRICHY Page 1

Intermediate Code Generation

UNIT IV INTERMEDIATE CODE GENERATION

Syntax-Directed Translation Part I

Syntax-Directed Translation

Principles of Programming Languages

Chapter 6 Intermediate Code Generation

PART 4 - SYNTAX DIRECTED TRANSLATION. F. Wotawa TU Graz) Compiler Construction Summer term / 309

Syntax-Directed Translation Part II

Syntax-Directed Translation. Introduction

Semantic analysis and intermediate representations. Which methods / formalisms are used in the various phases during the analysis?

Time : 1 Hour Max Marks : 30

Syntax Directed Translation

Principles of Programming Languages

ΕΠΛ323 - Θεωρία και Πρακτική Μεταγλωττιστών. Lecture 8a Syntax-directed Transla1on Elias Athanasopoulos

Syntax-Directed Translation

Concepts Introduced in Chapter 6

A Simple Syntax-Directed Translator

Syntax-Directed Translation. CS Compiler Design. SDD and SDT scheme. Example: SDD vs SDT scheme infix to postfix trans

intermediate-code Generation

Intermediate Code Generation

Principle of Complier Design Prof. Y. N. Srikant Department of Computer Science and Automation Indian Institute of Science, Bangalore

Concepts Introduced in Chapter 6

1 Lexical Considerations

Syntax-directed translation. Context-sensitive analysis. What context-sensitive questions might the compiler ask?

COP5621 Exam 3 - Spring 2005

PSD3A Principles of Compiler Design Unit : I-V. PSD3A- Principles of Compiler Design

COMP-421 Compiler Design. Presented by Dr Ioanna Dionysiou

PART 4 - SYNTAX DIRECTED TRANSLATION. F. Wotawa TU Graz) Compiler Construction Summer term / 264

Semantic Analysis computes additional information related to the meaning of the program once the syntactic structure is known.

Intermediate-Code Generat ion

More On Syntax Directed Translation

Computer Science Department Carlos III University of Madrid Leganés (Spain) David Griol Barres

Intermediate Code Generation

2.2 Syntax Definition

THEORY OF COMPILATION

CS606- compiler instruction Solved MCQS From Midterm Papers

Chapter 4 :: Semantic Analysis

Question Bank. 10CS63:Compiler Design

PRINCIPLES OF COMPILER DESIGN

Compiler Principle and Technology. Prof. Dongming LU April 15th, 2019

Context-Free Grammar. Concepts Introduced in Chapter 2. Parse Trees. Example Grammar and Derivation

Syntax Directed Translation

Context-sensitive analysis. Semantic Processing. Alternatives for semantic processing. Context-sensitive analysis

Semantic Analysis Attribute Grammars

Intermediate Code Generation

Lexical Considerations

Syntax-Directed Translation

Evaluation of Semantic Actions in Predictive Non- Recursive Parsing

Formal Languages and Compilers Lecture X Intermediate Code Generation

Static Checking and Intermediate Code Generation Pat Morin COMP 3002

PRINCIPLES OF COMPILER DESIGN UNIT I INTRODUCTION TO COMPILERS

Lexical Considerations

Computer Science Department Carlos III University of Madrid Leganés (Spain) David Griol Barres

Semantic Analysis. CSE 307 Principles of Programming Languages Stony Brook University

Compiler Theory. (Semantic Analysis and Run-Time Environments)

DEPARTMENT OF COMPUTER SCIENCE & ENGINEERING Subject Name: CS2352 Principles of Compiler Design Year/Sem : III/VI

Type Checking. Chapter 6, Section 6.3, 6.5

VIVA QUESTIONS WITH ANSWERS

Prof. Mohamed Hamada Software Engineering Lab. The University of Aizu Japan

CSE443 Compilers. Dr. Carl Alphonce 343 Davis Hall

5. Syntax-Directed Definitions & Type Analysis

Semantic Analysis. Role of Semantic Analysis

Section A. A grammar that produces more than one parse tree for some sentences is said to be ambiguous.

The analysis part breaks up the source program into constituent pieces and creates an intermediate representation of the source program.

CSc 453 Intermediate Code Generation

1. Explain the input buffer scheme for scanning the source program. How the use of sentinels can improve its performance? Describe in detail.

LECTURE NOTES ON COMPILER DESIGN P a g e 2

Lecture 14 Sections Mon, Mar 2, 2009

CHAPTER 4 FUNCTIONS. 4.1 Introduction

Dixita Kagathara Page 1

SEMANTIC ANALYSIS TYPES AND DECLARATIONS

Module 27 Switch-case statements and Run-time storage management

Gujarat Technological University Sankalchand Patel College of Engineering, Visnagar B.E. Semester VII (CE) July-Nov Compiler Design (170701)

About the Tutorial. Audience. Prerequisites. Copyright & Disclaimer. Compiler Design

Syntactic Directed Translation

CS2210: Compiler Construction. Code Generation

A programming language requires two major definitions A simple one pass compiler

Principles of Programming Languages COMP251: Syntax and Grammars

CSC 467 Lecture 13-14: Semantic Analysis

Chapter 4. Action Routines

Compiler Design Aug 1996

Compiler Theory. (Intermediate Code Generation Abstract S yntax + 3 Address Code)

Alternatives for semantic processing

Semantic Analysis and Intermediate Code Generation

Intermediate Code Generation

Formal Languages and Compilers Lecture IX Semantic Analysis: Type Chec. Type Checking & Symbol Table

Final Term Papers 2013

Acknowledgement. CS Compiler Design. Intermediate representations. Intermediate representations. Semantic Analysis - IR Generation

Intermediate Representa.on

Intermediate representation

CS1622. Semantic Analysis. The Compiler So Far. Lecture 15 Semantic Analysis. How to build symbol tables How to use them to find

Examples of attributes: values of evaluated subtrees, type information, source file coordinates,

Transcription:

PRINCIPLES OF COMPILER DESIGN UNIT IV SYNTAX DIRECTED TRANSLATION AND TYPE CHECKING Objectives Explain about syntax directed translation Explain how to write the three address code. Explain how to handle the Boolean expression Explain the concept of back patching. 4.1 Syntax-Directed Definitions A syntax directed definition is a generalization of a context free grammar in which each grammar symbol has an associated set of attributes, partitioned into two subsets called the synthesized and inherited attributes of that grammar symbol. An attribute can represent anything we choose: a string, a number, a type, a memory location, or whatever. The value of an attribute at a parse tree node is defined by a semantic rule associated with a production used at that node. The value of a synthesized attribute at a node is computed from the values of attributes at the children of that node in the parse tree; the value of an inherited attribute is computed from the values of attributes at the siblings and parent of that node. Semantic rules set up dependencies between attributes that will be represented by a graph. From the dependency graph, we derive an evaluation order for the semantic rules. Evaluation of the semantic rules defines the values of the attributes at the nodes in parse tree for the input string. A parse tree showing the values of attributes at each node is called an annotated parse tree. The process of computing the attributes at the nodes is called annotating or decorating the parse tree. (i) Form of a Syntax Directed Definition In a syntax directed definition, each grammar production A a has associated with it a set of semantic rules of the form b:= f(c 1, c 2,..,c k ), where f is a function, and either 1. b is a synthesized attribute of A and c 1, c 2,..,c k are attributes belonging to the grammar symbols of the production, or, 2. b is an inherited attribute of one of the grammar symbols on the right side of the production, and c 1, c 2,..,c k are attributes belonging to the grammar symbols of the production. Principles of Compiler Design Unit4 1

In either case, we say that the attribute b depends on attributes c 1, c 2,..,c k. An attribute grammar is a syntax directed definition in which the functions in semantic rules cannot have side effects. (ii) Synthesized Attributes A syntax directed definition that uses synthesized attributes exclusively is said to be an S-attributed definition. A parse tree for an S-attributed definition can always be annotated by evaluating the semantic rules for the attributes at each node bottom up, from the leaves to the root. EXAMPLE: The figure contains an annotated parse tree for the input 3*5+4n. The output printed at the root of the tree, is the value of E.val at the first child of the root. To see how attribute values are computed, consider the leftmost, bottommost interior node, which corresponds to the use of the production F-> digit. The corresponding semantic rule, F.val:=digit.lexval, defines the attribute F.val at that node to have the value 3 because the value of digit.lexval at the child of this node is 3. Similarily, at the parent of this F node, the attribute T.val has a value 3. Now consider the node for the production T->T*F. The value of the attribute T.val at this node is defined by PRODUCTION SEMANTIC RULE T->T1*F T.val:=T1.val x F.val When we apply he semantic rule at this node, T1.val has a value 3 from the left child and F.val the value 5 from the right child. Thus, T.val acquires the value 15 at this node. The rule associated with production for the starting nonterminal L->E n prints the value of the expression generated by E. Principles of Compiler Design Unit4 2

(iii) Inherited Attributes An inherited attribute is one whose value at a node in a parse tree is defined in terms of attributes at the parent and/or siblings of that node. Inherited attributes are convenient for expressing the dependence of a programming language construct on the context in which it appears. For example, we can use an inherited attribute to keep track of whether an identifier appears on the left or the right side of an assignment in order to decide whether the address or the value of the identifier is needed. Although it is always possible to rewrite a syntax directed definitions with inherited attributes. EXAMPLE:A declaration generated by the nonterminal D in the syntax directed definition in figure below, consists of the keyword int or real, followed by a list of identifiers. The nonterminal T has the synthesized attribute type, whose value is determined by the keyword in the declaration. The semantic rule L.in :=T.type, associated with production D->TL, sets inherited attribute L.in to the type in the declaration. The rules then pass this type down the parse tree using the inherited attribute L.in. Rules associated with the productions for L call procedure addtype to add the type of each identifier to its entry in the symbol table(pointed to by attribute entry). Syntax Directed definition with inherited attribute L.in PRODUCTION SEMANTIC RULES D TL L.in :=T.type T int T.type := integer T real T.type := real L L 1,id L 1.in := L.in addtype(id.entry,l.in) L id addtype(id.entry,l.in) The figure shows an annotated parse tree for the sentence real id1, id2, id3. The value of L.in at the three L nodes gives the type of identifiers id1, id2, and id3. These values are determined by computing the value of the attribute T.type at the left child of the root and then evaluate L.in top-down at the three L nodes in the right subtree of the root. At each L node we also call the procedure addtype to insert into the symbol table the fact that the identifier at the right child of this node has type real. Principles of Compiler Design Unit4 3

Parse tree with inherited attributed in at each node labeled L (iv) Dependency Graphs If an attribute b at a node in a parse tree depends on an attribute c, then the semantic rule for b at that node must be evaluated after the semantic rule that defines c. The interdependencies among the inherited and synthesized attributes at the nodes in a parse tree can be depicted by a directed graph called a dependency graph. Before constructing a dependency graph for a parse tree, we put each semantic rule in the form b:= f(c 1, c 2,..,c k ), by introducing a dummy synthesized attribute b for each semantic rule that consists of a procedure call. The graph has a node for each attribute and an edge to the node for b from the node for c if attribute b depends on attribute c. The dependency graph for a given parse tree is constructed as follows. for each node n in the parse tree do for each attribute a of the grammar symbol at n do construct a node in the dependency graph for a; for each node n in the parse tree do for each semantic rule b:= f(c 1, c 2,..,c k ) associated with the production used at n do for i:=1 to k do construct an edge from the node for c i to the node for b; Principles of Compiler Design Unit4 4

For example, suppose A.a:= f(x.x,y.y) is a semantic rule for the production A XY. This rule defines a synthesized attribute A.a that depends on the attributes X.x and Y.y. If this productions used in the parse tree, then there will be three nodes A.a, X.x and Y.y in the dependency graph with an edge to A.a from X.x since A.a depends on X.x, and an edge to A.a from Y.y since A.a also depends on Y.y. If the production A XY has a semantic rule X.i :=g(a.a,y.y) associated with it, then there will be an edge to X.i from A.a and also an edge to X.i from Y.y, since X.i depends on both A.a and Y.y. (v) Evaluation Order A topological sort of a directed acyclic graph is any ordering m 1, m 2,.m k of the node of the graph such that edges go from nodes earlier in the ordering to later nodes; that is, if m i m j is an edge from m i to m j, then mi appears before m j in the ordering. Any topological sort of a dependency graph gives a valid order in which the semantic rules associated with the nodes in a parse tree can be evaluated. That is, in the topological sort, the dependent attributes c 1, c 2,..,c k in a semantic rule b:= f(c 1, c 2,..,c k ) are available at a node before f is evaluated. The translation specified by a syntax directed definition can be made precise as follows. The underlying grammar is used to construct a parse tree for the input. The dependency graph is constructed as discussed above. From a topological sort of the dependency graph, we obtain an evaluation order for the semantic rules. Evaluation of the semantic rules in this order yields the translation of the input string. Several methods have been proposed for evaluating semantic rules: 1. Parse tree methods. At compile time, these methods obtain an evaluation order from a topological sort of the dependency graph constructed from the parse tree for each input. These methods will fail to find an evaluation order only if the dependency graph for the particular parse tree under construction has a cycle. 2. Rule based methods. At compiler construction time, the semantic rules associated with productions are analyzed, either by hand, or by specialized tool. For each production, the order in which the attributes associated with that production are evaluated is predetermined at compiler construction time. 3. Oblivious methods. An evaluation order is chosen without considering the semantic rules. For example, if translation takes place during parsing, then the order of evaluation is forced by the Principles of Compiler Design Unit4 5

parsing method, independent of the semantic rules. An oblivious evaluation order restricts the class of syntax directed definitions that can be implemented. Rule-based and oblivious methods need not explicitly construct the dependency graph at compile time, so they can be more efficient in their use of compile time and space A syntax directed definition is said to be circular if the dependency graph for some parse tree generated by its grammar has a cycle. 4.2 Construction of Syntax Trees A syntax tree is a condensed form of parse tree useful for representing language constructs. They are created with the help of syntax-directed definitions. The use of syntax trees as an intermediate form helps to dissociate translation from parsing. Translation routines that are invoked during parsing must operate under two kinds of restrictions. First, a grammar that is suited for parsing may not reflect the natural hierarchical structure of the constructs in the language. Second, the parsing method constrains the order in which nodes in a parse tree are considered. This order may not match the order in which information about a construct becomes available. In a syntax tree, operators and keywords do not appear as leaves, but rather are associated with the interior node that would be the parent of those leaves in the parse tree. Another simplification found in syntax trees is that chains of single productions may be collapsed. Syntax-directed translation can be based on syntax trees as well as on parse trees. The approach is the same in each case; we attach attributes to the nodes as in a parse tree. Principles of Compiler Design Unit4 6

Constructing Syntax Trees for Expressions The construction of a syntax tree for an expression is similar to the translation of the expression into postfix form.we construct subtrees for the subexpressions by creating a node for each operator and operand.the children of an operator node are the roots of the nodes representing the subexpresions constituting the operands of that operator. Each node in a syntax tree can be implemented as a record with several fields.in the node for an operator,one field identifies the operator and the remaining fields contain pointers to the nodes for the operands.the operator is often called the label of the node.when used for translation,the nodes in a syntax tree may have additional fields to hold the values of attributes attached to the node.usually there are a number of functions defined to create the nodes of syntax trees.each function returns a pointer to a newly created node. Consider for example the expression a - 4 + c.here,we make use of the following functions to create the nodes of syntax trees for expressions with binary operators. a) mknode(op, left, right) creates an operator node with label op and two fields containing pointers to left and right. b) mkleaf(id, entry) creates a identifier node with label id and a field containing entry,a pointer to the symbol-table entry for the identifier. c) mkleaf(num, val) creates a number node with label num and a field containing val,the value of the number. The following sequence of function calls creates the syntax tree for the expression a - 4 + c.in this sequence, p1, p2, p3, p4, p5 are pointers to nodes, and entrya and entyrc are pointers to the symboltable entries for identifiers a and c,respectively. i) p1 := mkleaf(id, entrya); ii) p2 := mkleaf(num, 4); iii) p3 := mknode(' - ', p1, p2); iv) p4 := mkleaf(id, entryc); v) p5 := mknode('+', p3, p4); Principles of Compiler Design Unit4 7

The tree is constructed bottom up.the function calls mkleaf(id,entrya) and mkleaf(num, 4) construct the leaves for a and 4;the pointers to these nodes are saved using p1 and p2.the call mknode (' - ', p1, p2 ) then constructs the interior node with the leaves for a and 4 as children.after two mor steps, p5 is left pointing to the root. A Syntax-Directed Definition for Constructing Syntax Trees Figure 4 contains an S-attributed definition for constructing a syntax tree for an expression containing the operators + and -.It uses the underlying productions of the grammar to schedule the calls of the functions mknode and mkleaf to construct the tree.the synthesized attribute nptr for E and T keeps track of the pointers returned by the function calls. An annotated parse tree depicting the construction of a syntax tree for the expression a - 4 + c is shown in Figure 5.The parse tree is shown dotted.the parse-tree nodes labeled by the nonterminals E and T use the synthesized attribute nptr to hold a pointer to the syntax-tree node for the expression represented by the nonterminal. Principles of Compiler Design Unit4 8

The semantic rules associated with the productions T ---> id and T ---> num define attribute T.nptr to bea pointer to a new leaf for an identifier and a number,respectively.attributes id.entry and num.val are the lexical values assumed to be returned by the analyzer with the tokens id and num. In Fig 2,when an expression E is a single term,corresponding to a use of the production E ---> T,the attribute E.nptr gets the value of T.nptr.When the semantic rule E.nptr := mknode(' - ', E1.nptr, T.nptr) associated with the production E ---> E1 - T is invoked,previous rules have set E1.nptr and T.nptr to be pointers to the leaves for a and 4,respectively. Directed Acyclic Graphs for Expressions A directed acyclic graph (dag) for an expression identifies the common subexpressions in the expression.like a syntax tree,a dag has a node for every subexpression of the expression;an interior node represents an operator and its children represent its operands.the difference is that a node in a dag representing a common subexpression has more than one "parent;" in a syntax tree,comon subexpression would be represented as a duplicated subtree. Figure 6 shows a dag for the expression a + a * ( b - c ) + ( b - c ) * d. Principles of Compiler Design Unit4 9

The leaf for a has two parents because a is common to the two subexpressions a and a * ( b - c ).Likewise,both occurrences of the common subexpression b - c are represented by the same node,which also has two parents. The syntax-directed definition of Figure 4 will construct a dag instead of a syntax tree if we modify the operations for constructing nodes.a dag is obtained if the function constructing a node first checks to see whether an identical node already exists.for example,before constructing a new node with label op and fields with pointers to left and right,mknode( op, left, right ) can check whether such a node has already been constructed.if so,mknode( op, left, right ) can return a pointer to the previously constructed node.the leaf-constructing functions mkleaf can behave similarly. The sequence of instructions for constructing the dag in Figure 6 is listed as below.the functions defined constructs the dag provided mknode and mkleaf create new nodes only when necessary,returning pointers to existing nodes with the correct label and children whenever possible. (1) p1 := mkleaf(id,a); (2) p2 := mkleaf(id,a); (3) p3 := mkleaf(id,b); (4) p4 := mkleaf(id,c); (5) p5 := mknode(' - ',p3,p4); (6) p6 := mknode(' * ',p2,p5); (7) p7 := mknode(' + ',p1,p6); (8) p8 := mkleaf(id,b); (9) p9 := mkleaf(id,c); (10) p10 := mknode(' - ',p8,p9); (11) p11 := mkleaf(id,d); (12) p12 := mknode(' * ',p10,p11); (13) p13 := mknode(' + ',p7,p12); When the call mkleaf(id,a) is repeated on line 2,the node constructed by the previous call mkleaf(id,a) is returned,so p1=p2.similarly,the nodes returned on lines 8 and 9 are the same as those Principles of Compiler Design Unit4 10

returned on lines 3 and 4,respectively.Hence,the node returned on line 10 must be the same one constructed by the call of mknode on line 5. In many applications,nodes are implemented as records stored in an array,as in Figure 7.In the figure,each record has a label field that determines the nature of the node.we can refer to a node by its index in the array.the integer index of a node is often called value number.for example, using value numbers,we can say node 3 has label +,its left child is node 1,and its right child is node 2.The following algorithm can be used to create nodes for a dag representation of an expression. Algorithm: Value-number method for constructing a node in a dag. Suppose that nodes are stored in an array and that each node is referred to by its value number.let the signature of an operator node be a triple<op, l, r> consisting of its label op,left child l,and right child r. Input. Label op, node l,and node r. Output. A node with signature <op, l, r>. Method. Serach the array for a node m with label op, left child l,and right child r. If there is such a node,return m ; otherwise,create a a new node n with label op, left child l, right child r,and return n. An obivious way to detrmine if node m is already in the array is to keep all previously created nodes on a list and to check each node on the list to see if it has the desired signature.the search for m can be made more efficient by using k lists,called buckets,and using a hashing function h to determine which bucket to search. Principles of Compiler Design Unit4 11

The hash function h computes the number of a bucket from the value of op, l,and r.it will always return the same bucket number,given the same arguments.if m is not in the bucket h( op, l, r), then a new node n is created and added to this bucket,so subsequent searches will find it there.several signatures may hash into the same bucket number,but in practice we expect each bucket to contain a small number of nodes. Each bucket can be implemented as a link as shown in Figure 8.Each cell in a linked list represents a node.the bucket headers,consisting of pointers to the first cell in a list,are stored in an array.the bucket number returned by h( op, l, r ) is an index into this array of bucket headers. This algorithm can be adapted to apply to nodes that are not allocated sequentially from an array.in many compilers,nodes are allocated as they are needed,to avoid preallocating an array that may hold too many nodes most of the time and not enough nodes some of the time.in this case,we cannot assume that nodes are in sequential storage,so we have to use pointers to refer to nodes.if the hash function can be made to compute the bucket number from label and pointers to children,then we can number the nodes in any way and use this number as the value number of the node. 4.3 Bottom-Up Evaluation of S-Attributed Definitions A syntax directed definition that uses synthesized attributes exclusively is said to be an S- attributed definition. A parse tree for an S-attributed definition can always be annotated by evaluating the semantic rules for the attributes at each node bottom up, from the leaves to the root. The dependency graph approach works in general to find an evaluation order for a given parse tree, but the ordering may not permit evaluation during parsing. However, there are certain classes of Principles of Compiler Design Unit4 12

attribute grammars for which the evaluation can be done during parsing. These are S-attributed grammars and L-attributed grammars. An S-attributed grammar is one in which all the attributes are synthesized. In this case, we can use one of the following evaluation strategies: 1.top-down. In a recursive descent parser, consider the recognition procedure for a certain non terminal N, with production N --> A 1 A 2... A n. Assuming that the attributes of A 1 A 2,...,A n are evaluated when those symbols are recognized, the attributes of N can be computed at the end of the recognition routine for N. 2.bottom-up. Consider an LR parser applying the reduction N --> A 1 A 2... A n. It can pop the symbols A 1 A 2,...,A n from the parsing stack, then evaluate the attributes of N from the attributes of A 1 A 2,..., and A n, and then push N onto the parsing stack. In this way, the attributes of every symbol are evaluated before the symbol is pushed onto the stack Now we will see how the attributes are evaluated during the bottom up parsing in detail. Recall that an S-attributed syntax-directed definition contains only synthesized attributes. Thus, in some production, we can compute attributes for with attributes for. This makes S-attributed definitions very easily computed in conjunction with a bottom-up parser. Synthesized attributes can be evaluated by a bottom up parser as the input string is being parsed. The parser can keep the values of the synthesized attributes associated with the grammar symbols on its stack. whenever a reduction is made, the values of the new synthesized attributes are computed from the attributes appearing on the stack for the grammar symbols on the right side of the reducing production. attributes. Now lets see how the parser stack can be extended to hold the values of these synthesized Synthesized attributes on the parser stack A bottom up parser uses a stack to hold information about he subtrees that have been parsed. 1. add additional fields to the stack to hold the attribute values. 2. each child node pushes its synthesized attributes on the stack. Principles of Compiler Design Unit4 13

3. when reducing, parents pops them, uses them to evaluate its own attributes and pushes them on the stack. let the stack be implemented by a pair of arrays state and val. Each state entry is a pointer to LR(1)parsing table. If the ith state symbol is A, hen val[i] will hold the value of the attribute associated with parse tree n ode corresponding to this A. Table: Parser stack with attributes. State Val X Y X.x Y.x top Z Z.x The current top of the stack is indicated by the pointer top. Suppose that we have a production Aà XYZ,we can compute the attributes of A denoted by A.x by accessing the val[top],val[top-1] and val[top-2]. Let the semantic rule associated with the production AàXYZ be A.a:=f(X.x,Y.y,Z.z) Before XYZ is reduced to A. the value of the attribute Z.z is int val[top],that of Y.y in va;[top-1], and that of X.x in val[top-2]. If a symbol has no attribute, then the corresponding entry in the val array is undefined. After the reduction,top is decremented by 2,the state covering A is put in state[top](i.e,where X was), and the value of the synthesized attribute A.a is put in val[top]. Consider the following grammar along with its code fragments. Là E n EàE + T print(val[top]) val[ntop] :=val[top-2] + val[top] EàT Eà T*F val[ntop] :=val[top-2] * val[top] Principles of Compiler Design Unit4 14

TàF Fà(E) val[ntop] := val[top-1] Fà digit Let the input string be 3*5+4n,we assume that lexical analyzer supplies the value of the attribute digit.lexval,which is the numeric constant of each token representing a digit. when a parser shifts a digit on to the stack, the token digit is placed in state[top] and its attributes is placed in val[top]. What we do during parsing is that,we modify the parser to execute the code fragments just before making the appropriate reduction.we associate attribute evalution with reduction because each reduction determines the production to be applied.code fragments are obtained from the semantic rules by replacing each attribute by a position in the val array. When a production with r symbols on the right side is reduced, the value of ntop is set to top-r-1.after each code fragment is executed, the top is set to ntop. Table show the sequence of moves made by the parser on input 3*5+4n. The contents of the state and val fields of the parsing stack are shown for each move. In the first mve,the parser shifts the state corresponding to the token digit onto the stack.(the state is represnted by 3 and the value 3 is in the val field).on the second move, the parser reduces by the production Fà digit and implements the semantic rule F.val:=digit.lexval. on the third move the parser Principles of Compiler Design Unit4 15

reduces by TàF.no code fragment is associated with this production,so the val array is left unchanged.during each reduction the top ofthe val stack contains the attribute value associated with the left side of the reducing production. This is how during the bottom up parsing the evaluation of the S-attribute definitions is done. 4.4 L-Attributed Definitions These are definitions that define rules for determining inherited attributes in which each inherited attribute depends only on The attributes of the symbols to the left of it in the production The inherited attributes of the non-terminal on the left-side of the production Every L-attributed definition is L-attributed, as the rules stated above apply only to inherited attributes. L-attributed definitions are said to be L-attributed as information flows from left to right in the syntax tree. Order of Evaluation The L-attributed definitions can be evaluated by a depth first traversal of the tree. The procedure can be outlined as below. dfvisit(node n) { for each child m of n, from left to right evaluate inherited attributes of m dfvisit(m) } evaluate synthesized attributes of n Translation Scheme A translation scheme is a context free grammar in which there are attributes associated with the grammar symbols and semantic actions enclosed within braces are inserted with the right hand sides of productions. They are useful for specifying translations during parsing. A translation scheme must be designed carefully if we have both inherited and synthesized attributes. The rules that must be followed by a translation scheme involving both synthesized and inherited attributes, the following rules have to be followed. Principles of Compiler Design Unit4 16

An inherited attribute for a symbol on the right hand side of a production must be computed in an action before that symbol. An action must not refer to a synthesized attribute of a symbol to the right of the action. A synthesized attribute for the non-terminal on the left can only be computed after all attributes it references have been computed. The action for computing such attributes can usually be placed at the end of the right side of the production. It is always possible to start with an L-attributed definition and construct a translation scheme that satisfies all the above requirements. Given an L-attributed grammar in which no inherited attributes depend on synthesized attributes, a recursive-descent parser can evaluate all attributes by turning inherited attributes into parameters and synthesized attributes into return values. L-attributed definitions in Top-Down translation A major issue in this case is left recursion. For top-down predictive parsing, we can assume that an action be executed at the time that a symbol in the same position would be expanded. First of all, left recursion is to be eliminated from the translation scheme. Translation Scheme with left recursion A->A 1 Y {A.a=g(A 1.a,Y.y)} A->X { A.a=f(X.x)} Translation scheme with left recursion eliminated A->X {R.i=f(X.x)} R {A.a=R.s} R->Y { R 1.i=g(R.i,Y.y)} R 1 {R.s= R 1.s} R-> e {R.s=R.i} A translation scheme should be eliminated to left-recursion as per the above algorithm (just as leftrecursion is eliminated from context free grammars when they are to be subjected to predictive topdown translator creation) and the translation scheme can be implemented in a very straightforward manner. A top-down translation nicely adapts to top-down and left-right flow of information. L-attributed definitions in Bottom-up translation Bottom-up translation lends itself naturally to passage of information up the tree rather than down the tree. So some modifications have to be made to the translation schemes based on various issues. Principles of Compiler Design Unit4 17

(i) Moving attribute rules from the middle to the end of productions This requires addition of new non-terminals called marker non-terminals and additional productions. The additional productions introduced are of the form M->e for each marker non-terminal M. A-> B {print( A );} C A->BMC M-> {print( A );} The rule in the left column can be replaced by the set of rules in the right column. This evidently does not make any change at all. (ii) Inheriting attributes on the parser stack If the translation scheme inherits attributes by copy rules, the value can just be inherited from the stack. A bottom-up parser reduces the right side of a A->XY, by removing X and Y from the top of the stack and replacing them by A. Suppose X has a synthesized attribute X.s, which is kept along with X in the stack, this can be inherited directly by Y if the copy rule is of the form Y.i=X.s etc. But this works if and only if we can tell where a synthesized attribute value is is on the stack. i.e., the grammar allows the attribute value to be predicted. Example: Case where the position on parser stack cannot be predicted. S->aAC {C.i=A.s;} S->bABC {C.i=A.s;} C->c {C.s=g(C.i);} In this case, when reduction by C->c occurs, C.i is either top-1 or top-2 of the stack. This cannot be predicted as there may or may not be an intervening B between C and A. Systematic introduction of markers, can make it possible to evaluate L-attributed definitions during LR parsing. Since there is only one production for each marker, a grammar, a grammar remains LL(1) when markers are added. (iii)replacement of inherited by synthesized attributes Principles of Compiler Design Unit4 18

It is sometimes possible to avoid the use of inherited attributes by changing the grammar. For example, a declaration in Pascal can consist of a list of identifiers followed by a type e.g., a,b:integer. A grammar for such declarations may include productions of the form. Examples Grammar for which synthesized attributes alone will not work: D->L:T T->integer char L->L,id id Modifcation to remove the need for inherited attributes: D->id L L->,id L :T T-> integer char 1. Left-Recursion Elimination Example Grammar with left recursion: E->E1+T {E.val=E1.val+T.val;} E->E1-T {E.val=E1.val-T.val;} E->T {E.val=T.val;} T->(E) {T.val=E.val;} T->num {T.val=num.val;} Equivalent grammar without left-recursion: E-> T {R.iT.val;} R {e.val=r.s;} R->+T {R1.i=R.i+T.val;} R1 {R.s=R1.s;} R->-T {R1.i=R.i-T.val;} R1 {R.s=R1.s;} R->e {R.s=R.i;} T->(E) {T.val=E.val;} T->num {T.val=num.val;} Principles of Compiler Design Unit4 19

2. Moving embedded actions to ends of productions Example Grammat with embedded actions: E->TR R-> +T{print( + );} R -T {print( - );} R e T-> num {print(num.val);} E-> TR R-> +TMR -TNR e T->num {print(num.val);} M->e {print( + );} N->e {print( - );} 3. Inheriting attributes from the parser stack Example D->TL; T->int {val[ntop]=integer;} T->real {val[ntop]=real;} L->L,id {addtype(val[top],val[top-3]);} L->id {addtype(val[top],val[top-1]);} 4. Replacing inherited by synthesized attributes Example Grammar where the id-list cannot be associated with a type using synthesized attributes alone: D->L:T T->integer char L->L, id id Equivalent grammar where the id-list is associated with a type using synthesized attributes alone: D-> id L L->,id L1 {L.s=L1.s;} L-> :T {L.s=T.s;} Principles of Compiler Design Unit4 20

T-> integer {T.s=integer;} T-> char {T.s=char;} 4.5 Top down Translation For each non-terminal A, construct a function that Has a formal parameter for each inherited attribute of A Returns the values of the synthesized attributes of A The code associated with each production does the following Save the s-attribute of each token X into a variable X.x Generate an assignment B.s=parseB(B.i1,B.i2,..,B.ik) for each non-terminal B, where B.i1,,B.ik are values for the L-attributes of B and B.s is a variable to store s-attributes of B. variables Copy the code for each action, replacing references to attributes by the corresponding void parsed() { Type t = parset(); } parsel(t); } Type parset { switch (currenttoken()) { case INT: return TYPE_INT; case REAL: return TYPE_REAL; } } void parsel(type in) { SymEntry e = parseid(); AddType(e, in); if (currenttoken() == COMMA) { parseterminal(comma); parsel(in) } } 4.6 Bottom-Up Evaluation of Inherited Attributes Works exceptionally well with L-attributed Definitions (with corresponding translation schemes) Nevertheless we must make sure that the underlying grammar is suitable for predictive parsing. Grammar has no left-recursion and it is left-factored. Principles of Compiler Design Unit4 21

We also discussed bottom up translation For S-Directed Definitions. Simple Idea: Use additional stack space to store attribute values for Non-terminals. During Reduce Actions update attributes in stack accordingly. Removing Embedding Action from Translation Schemes 1. In the bottom-up translation method, all translation actions being at the right end of the production. 2. In the predictive-parsing method, we need to embedded actions at various places within the right side. E T R R + T { print( + ) } R - T { print( - ) } R T num { print(num.val) } Parsing Example. Attempt B-U parsing over real p, q, r Inherited Attributes and Bottom Up Translation Consider the translation scheme: PRODUCTION SEMANTIC RULE D T L L.in = T.type T int T.type = integer T real T.type = real Principles of Compiler Design Unit4 22

L L1, id L1.in = L.in addtype(id.entry, L.in) L id addtype(id.entry, L.in) Bottom Up Translation with Inherited Attributes Try to predict the location in the stack that you can recover the value of the inherited attribute you need. PRODUCTION SEMANTIC RULE D T L T int val[ntop] = integer T real val[ntop] = real L L1, id addtype(id.entry, val[top-3]) L id addtype(id.entry, val[top-1]) Simulating the Evaluation of Inherited Attributes Reaching into the parser stack for an attribute value works only if the grammar allows the position of the attribute value to the predicted. 1. C inherits the synthesized attribute A.s by a copy rule. 2. There may or may not be a B between A and C. 3. When a reduction by C c is performed, the value of C.i is either in val[top-1] or in val[top- 2]. Principles of Compiler Design Unit4 23

4.7 Forms of Intermediate code Intermediate representations span the gap between the source and target languages: _ closer to target language; _ (more or less) machine independent; _ allows many optimizations to be done in a machine-independent way. _ Implementable via syntax directed translation, so can be folded into the parsing process. Benefits 1. Retargeting is facilitated 2. Machine independent Code Optimization can be applied. Intermediate language can be many different languages, and the designer of the compiler decides this intermediate language. syntax trees can be used as an intermediate language. postfix notation can be used as an intermediate language. three-address code (Quadraples) can be used as an intermediate language we will use quadraples to discuss intermediate code generation Principles of Compiler Design Unit4 24

quadraples are close to machine instructions, but they are not actual machine instructions. Types of Intermediate Languages (i) High Level Representations (e.g., syntax trees): _ closer to the source language _ easy to generate from an input program _ code optimizations may not be straightforward. (ii) Low Level Representations (e.g., 3-address code, RTL): _ closer to the target machine; _ easier for optimizations, final code generation; 1. Syntax Trees A syntax tree shows the structure of a program by abstracting away irrelevant details from a parse tree. _ Each node represents a computation to be performed; _ The children of the node represents what that computation is performed on. Syntax trees decouple parsing from subsequent processing. Syntax Trees: Example 2. Three-Address Code In three-address code, there is at most one operator on the right side of an instruction; that is, no builtup arithmetic expressions are permitted. Thus a source-language expression like x+y*z might be translated into the sequence of three-address instructions where tl and tz are compiler-generated temporary names. Example : Three-address code is a linearized representation of a syntax tree or a DAG in which explicit names correspond to the interior nodes of the graph. Principles of Compiler Design Unit4 25

Here is a list of the common three-address instruction forms: Types 1. Quadruples The description of three-address instructions specifies the components of each type of instruction, but it does not specify the representation of these instructions in a data structure. In a compiler, these instructions can be implemented as objects or as records with fields for the operator and the operands. Three such representations are called "quadruples," Principles of Compiler Design Unit4 26

2. Triples A triple has only three fields, which we call op, arg,, and arg2. A triple representation would refer to position (0). Parenthesized numbers represent pointers into the triple structure itself. Hence, the DAG and triple representations of expressions are equivalent. The equivalence ends with expressions, since syntax-tree variants and three-address code represent control flow quite differently. 3. Indirect Triples 4.8 Type Expressions Types have structure, which we shall represent using type expressions: a type expression is either a basic type or is formed by applying an operator called a type constructor to a type expression. The sets of basic types and constructors depend on the language to be checked. Translation of Expressions The rest of this chapter explores issues that arise during the translation of expressions and statements. We begin in this section with the translation of expressions into three-address code. An expression with more than one operator, like a + b * c, will translate into instructions with at most one operator per instruction. An array reference A[i][ j ]w ill expand into a sequence of three-address instructions that calculate an address for the reference. Principles of Compiler Design Unit4 27

Operations Within Expressions The syntax-directed definition in Fig. builds up the three-address code for an assignment statement S using attribute code for S and attributes addr and code for an expression E. Attributes S.code and E.code denote the three-address code for S and E, respectively. Attribute E.addr denotes the address that will Incremental Translation In the incremental approach, gen not only constructs a three-address instruction, it appends the instruction to the sequence of instructions generated so far. The sequence may either be retained in memory for further processing, or it may be output incrementally. Principles of Compiler Design Unit4 28

Addressing Array Elements Array elements can be accessed quickly if they are stored in a block of consecutive locations. In C and Java, array elements are numbered O, 1,..., n - 1, for an array with n elements. If the width of each array element is w, then the ith element of array A begins in location Translation of Array References Let nonterminal L generate an array name followed by a sequence of index expressions: Principles of Compiler Design Unit4 29

Summary Pick an intermediate representation: An intermediate representation is typically some combination of a graphical notation and three-address code. As in syntax trees, a node in a graphical notation represents a construct; the children of a node represent its subconstructs. Three address code takes its name from instructions of the form x = y op z, with at most one operator per instruction. There are additional instructions for control flow. Translate expressions: Expressions with built-up operations can be unwound into a sequence of individual operations by attaching actions to each production of the form E -+ El op E2. The action either creates a node for E with the nodes for El and E2 as children, or it generates a three-address instruction that applies op to the addresses for El and E2 and puts the result into a new temporary name, which becomes the address for E. Check types: The type of an expression El op Ez is determined by the operator op and the types of El and Ez. A coercion is an implicit type conversion, such as from integer to float. Intermediate code contains explicit type conversions to ensure an exact match between operand types and the types expected by an operator. Flatten arrays: For quick access, array elements are stored in consecutive locations. Arrays of arrays are flattened so they can be treated as a one. Principles of Compiler Design Unit4 30

Key Terms >> DAG >> Three address code >> Quadruples >> Triples Key Term Quiz 1. A ------------------- not only represents expressions more succinctly, it gives the compiler important clues regarding the generation of efficient code to evaluate the expressions. 2. -------------------- is a linearized representation of a syntax tree or a DAG in which explicit names correspond to the interior nodes of the graph. 3. ------------------ is one that represents the expression with op, arg1, agr2 and result. 4. --------------- is one that represents the expression with op, arg1, agr2. Multiple Choice Questions 1. Which expression that corresponds to the following DAG? a. a+a*(b-c)+(b-c)*d b. (a+a*(b-c))+b-c*d c. a+a*(b-c)+b-c*d d. a+a*b-c+(b-c)*d 2. Which one of the following, the triples represents for the expression? a. op1 op op2 b. op, arg1, arg2, result c. op, agr1, arg2 d. op, arg, result 3. Which one of the following, the quadruples represents for the expression? a. op1 op op2 b. op, arg1, arg2, result c. op, agr1, arg2 d. op, arg, result 4. Which one of the following, the three address code represents for the expression? a. op1 op op2 b. op, arg1, arg2, result c. op, agr1, arg2 d. op, arg, result 4.9 Boolean Expressions Boolean expressions are composed of the boolean operators applied to elements that are boolean variables or relational expressions. Relational expressions are of the form El re1 E2, where El and E2 are arithmetic expressions. In this section, we consider boolean expressions generated by the following grammar: Principles of Compiler Design Unit4 31

We use the attribute rel.op to indicate which of the six comparison operators <, <=, =,! =, >, or >= is represented by rel. As is customary, we assume that I I and && are left-associative, and that I I has lowest precedence, then &&, then!. Given the expression B1 I I B2, if we determine that B1 is true, then we can conclude that the entire expression is true without having to evaluate B2. Similarly, given B1&&B2, if B1 is false, then the entire expression is false. The semantic definition of the programming language determines whether all parts of a boolean expression must be evaluated. If the language definition permits (or requires) portions of a boolean expression to go unevaluated, then the compiler can optimize the evaluation of boolean expressions by computing only enough of an expression to determine its value. Thus, in an expression such as B1 I I B2, neither B1 nor B2 is necessarily evaluated fully. If either B1 or B2 is an expression with side effects (e.g., it contains a function that changes a global variable), then an unexpected answer may be obtained. Flow-of-Control Statements We now consider the translation of boolean expressions into three-address code in the context of statements such as those generated by the following grammar: Principles of Compiler Design Unit4 32

Control-Flow Translation of Boolean Expressions A Boolean expression B is translated into three-address instructions that evaluate B using creates labels only when they are needed. Alternatively, unnecessary labels can be eliminated during a subsequent optimization phase. conditional and unconditional jumps to one of two labels: B.true if B is true, and B.fa1se if B is false. Principles of Compiler Design Unit4 33

4.10 Back patching A key problem when generating code for boolean expressions and flow-of-control statements is that of matching a jump instruction with the target of the jump. For example, the translation of the boolean expression B in if ( B ) S contains a jump, for when B is false, to the instruction following the code for S. In a one-pass translation, B must be translated before S is examined. Backpatching, in which lists of jumps are passed as synthesized attributes. Specifically, when a jump is generated, the target of the jump is temporarily left unspecified. Each such jump is put on a list of jumps whose labels are to be filled in when the proper label can be determined. All of the jumps on a list have the same target label. 1. One-Pass Code Generation Using Backpatching Backpatching can be used to generate code for boolean expressions and flowof- control statements in one pass. The translations we generate will be of the same form as those in Section 6.6, except for how we manage labels. Synthesized attributes truelist and falselist of nonterminal B are used to manage labels in jumping code for boolean expressions. In particular, B.truelist will be a list of jump or conditional jump instructions into which we must insert the label to which control goes if B is true. B.falselist likewise is the list of instructions that eventually get the label to which control goes when B is false. As code is generated for B, jumps to the true and false exits are left incomplete, with the label Principles of Compiler Design Unit4 34

field unfilled. These incomplete jumps are placed on lists pointed to by B.truelist and B.falselist, as appropriate. Similarly, a statement S has a synthesized attribute S.nextlist, denoting a list of jumps to the instruction immediately following the code for S. For specificity, we generate instructions into an instruction array, and labels will be indices into this array. To manipulate lists of jumps, we use three functions: 1. makelist(i) creates a new list containing only i, an index into the array of instructions; makelist returns a pointer to the newly created list. 2. merge(pl, p2) concatenates the lists pointed to by pl and p2, and returns a pointer to the concatenated list. 3. backpatch(p, i) inserts i as the target label for each of the instructions on the list pointed to by p. 2. Backpatching for Boolean Expressions A translation scheme suitable for generating code for Boolean expressions during bottom-up parsing. A marker nonterminal M in the grammar causes a semantic action to pick up, at appropriate times, the index of the next instruction to be generated. The grammar is as follows: Principles of Compiler Design Unit4 35

Flow-of-Control Statements Consider statements generated by the following grammar: Principles of Compiler Design Unit4 36

Break-, Continue-, and Goto-Statements The most elementary programming language construct for changing the flow of control in a program is the goto-statement. In C, a statement like goto L sends control to the statement labeled L - there must be precisely one statement with label L in this scope. Goto-statements can be implemented by maintaining a list of unfilled jumps for each label and then backpatching the target when it is known. 4.11 Type conversion 1. Expression x+i where x is real and i is integer. 2. Since representation of integers and reals is different within a computer, and different machine instructions are used for operations on integers and reals, the compiler have to convert one of the operands of +. 3. The language definition specifies what conversions are necessary. 4. Postfix notation for x+i, might be a. x i inttoreal real+ 5. Conversion from one type to another is said to be implicit if it is to be done automatically by the computer. 6. Implicit type conversions are called coercions. 7. Conversion is said to be explicit if the programmer must write something to cause the conversion. 8. All conversions is Ada are explicit. 9. Explicit conversions look just like function applications to a type checker, so they present no new problems. 10. Implicit conversion of constants can usually be done at compile time, often with a great improvement to the running time of the object program. Type checking rules for Coercions from integer to real E num { E.type= integer } E num.num { E.type= real } E id { E.type=lookup(id.entry) } E E1 op E2 { E.type = if E1.type=integer and E2.type=integer then integer else if E1.type=integer and E2.type=real then real else if E1.type=real and E2.type=integer then real else if E1.type=real and E2.type=real Principles of Compiler Design Unit4 37

then real else type-error } Summary Generate jumping code for boolean expressions: In short-circuit or jumping code, the value of a boolean expression is implicit in the position reached in the code. Jumping code is useful because a boolean expression B is typically used for control flow, as in if (B) S. Boolean values can be computed by jumping to t = true or t = false, as appropriate, where t is a temporary name. Using labels for jumps, a boolean expression can be translated by inheriting labels corresponding to its true and false exits. The constants true and false translate into a jump to the true and false exits, respectively. Implement statements using control Bow: Statements can be translated by inheriting a label next, where next marks the first instruction after the code for this statement. The conditional S - + if (B) S1 can be translated by attaching a new label marking the beginning of the code for S1 and passing the new label and S.next for the true and false exits, respectively, of B. Backpatching: Backpatching is a technique for generating code for boolean expressions and statements in one pass. The idea is to maintain lists of incomplete jumps, where all the jump instructions on a list have the same target. When the target becomes known, all the instructions on its list are completed by filling in the target. Key Terms >> back patching >> assignment >> type conversions Key Term Quiz 1. --------------------- is a technique for generating code for boolean expressions and statements in one pass. Review Questions Two Mark Questions 1. Write the properties of intermediate language. 2. Why itbis necessary to generate intermediate code instead of generating target program itself? 3. Define quadruples, triples, three address code. 4. What is back patching? 5. List out three functions that are used to manipulate list of labels in back patching. 6. Write the syntax for three-address code statement, and mention its properties. 7. What is the intermediate code representation for the expression a or b and not c? 8. What are the various methods of implementing three address statements? Principles of Compiler Design Unit4 38