0% found this document useful (0 votes)
18 views

Unit Iii QB

compiler design question bank

Uploaded by

Jeeva R
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
18 views

Unit Iii QB

compiler design question bank

Uploaded by

Jeeva R
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 20

Unit- III

Part- A
1. Write the semantic action for the production rule of E ->E1 OR ME2. (Nov/Dec
2020)
{X1.type=X2.type}
E1.type=E2.type
X1=E1;X2=E2

2. Translate the arithmetic expression x = (a + b)∗– c/d into quadruples and triples.
(Nov/Dec 2020)
t1=-c
t2=t1/d
t3=a+b
t4=t3*t2
x=t4
Quadruples
op arg1 arg2 Result
- c t1
/ t1 d t2
+ a b t3
* t3 t2 t4
= t4 x

Triples
op arg1 arg2
- c
/ (1) d
+ a b
* (3) (2)
= (4) x
= (6)

3. Define backpatching. (Nov/Dec 2020) (Nov/Dec 2023)


Backpatching is basically a process of fulfilling unspecified information. This
information is of labels. It basically uses the appropriate semantic actions during the
process of code generation. It may indicate the address of the Label in goto statements
while producing TACs for the given expressions.

4. List the three kinds of intermediate representation. (Nov/Dec 2018)


 Quadruples
 Triples
 Indirect triples

5. What are Inherited and Synthesized attributes? (Nov/Dec 2020)


An attribute is said to be Synthesized attribute if its parse tree node value is
determined by the attribute value at child nodes. An attribute is said to be Inherited
attribute if its parse tree node value is determined by the attribute value at parent and/or
siblings node.

6. What do you mean by Syntax directed translation? (April/May 2022)


Syntax-directed translation specifies the translation of a construct in terms of
Attributes associated with its syntactic components. Syntax-directed translation uses a
context free grammar to specify the syntactic structure of the input. It is an input- output
mapping.

7. Convert the following statement into three address codes. (April/May 2022)
x=a+(b*-c)+(d*-e)
Represent the three address code in triples.
t1=-c
t2=-e
t3=b*t1
t4=d*t2
t5=t3+t4
t6=a+t5
x=t6
op arg1 arg2
- c
- e
* b (1)
* d (2)
+ (1) (2)
+ a (5)
= x (6)

8. Give syntax directed translation for the following statement (Nov/Dec 2020)
Call sum(int a, int b).
S-> call id (Elist) — generate a param statement for each item on queue, causing these
statements to follows the statements evaluating the argument expressions { for each
item p on queue do emit (`param' P);
Emit(‘call’id.place)}
Elist-* Elist, E {append E. place to the end of the queue}
Elist --> E {initialize queue to contain only E. place}

9. Write syntax directed translation for declaration statement. (Nov/Dec 2020)


T->int T.type=int
T->float T.type=float

10. Differentiate implicit and explicit type conversions. (Nov/Dec 2019)


Implicit conversion is the conversion in which a derived class is converted into
a base class like int into a float type. Explicit conversion is the conversion that may
cause data loss. Explicit conversion converts the base class into the derived class.
11. What are the various ways to pass a parameter to a function? (April/May 2019)
1. Call by value
2. Copy and restore
3. Call by reference
4. Call by name

12. Write a grammar for flow control statement while-do. (April/May 2019)
S. BEGIN = newlabel;
E. TRUE = newlabel;
E. FALSE = S. NEXT;
S1. NEXT = S. BEGIN;
S. CODE = GEN(S. BEGIN '− ') | |
E. CODE | | GEN(E. TRUE '− ')| |
S1. CODE | | GEN('goto' S. BEGIN)

13. Write three address code sequence for the assignment Statement. (Nov/Dec 2021)
d := (a −b) + (a − c) + (a − c) .
t1=a-b
t2=a-c
t3=t2+t2
t4=t1+t3
d=t4

14. What are syntax directed translation schemes? (April/May 2023)


There can be two classes of syntax-directed translations S-attributed translation
and L-attributed translation.
An SDD is S-attributed if the attributes of the node are synthesized attributes.
To evaluate S-attributed SDD we can traverse the nodes of the parse tree in any bottom-
up order.

15. Determine the types and relative addresses for the identifiers in the following
sequence of declarations: (April/May 2023)
float x;
record{float x;float y;}p;
record{int tag;float x;float y;}q;

T->record `{` {Env.push(top); top=new Env();


Stack.push(offset); offset=0;}
D `}` {T.type=recod(top); T.width=offset;
top=Env.pop(); offset=Stack.pop();}

16. Write down syntax directed definition for simple desk calculator. (Nov/Dec 2016)
Production Semantic rules

L —> En L.val = E.val


E —> E1+ T E.val = E1.val+ T.val

E —> T E.val = T.val

T—> T1*F T.val = Ti.val x F.val

T —> F T.val = F.val

F —> (E) F.val = E.val

F —> digit F.val = digit.lexval

17. Mention the rules for type checking. (April/May 2017)


The first rule confirms that a variable has a given type if the context maps that
variable to that type.
The second rule manipulates the context: first, it typechecks the let-binding itself in
the original context Γ.

18. What do you mean by binding of names? (April/May 2017)


Name binding is the process of finding the declaration for each name that is
explicitly or implicitly used in a template.
The compiler may bind a name in the definition of a template, or it may bind a
name at the instantiation of a template.

19. Give examples for static check (May/June-2013)


1. Type checks
2. Flow-of-control checks
3. Uniqueness checks
4. Name –related checks

20. Define symbol table. (May/June-14)


The symbol table includes the name and value for each symbol in the source
program, together with flags to indicate error conditions.
Sometimes it may contain details about the data area. SYMTAB is usually
organized as a hash table for efficiency of insertion and retrieval.

21. What is meant by type checking? (Nov/Dec 2023)


Type checking is the process of verifying and enforcing the constraints of types
in a programming language. It ensures that the values used in a program match the
expected data types, as defined by the language's rules.
Type checking is crucial for preventing type errors, such as using a string where
a number is expected or performing arithmetic operations on incompatible types.
Part- B
1. Construct a Syntax-Directed Translation scheme that translates arithmetic
expressions from infix into postfix notation. Your solution should include the
context-free grammar, the semantic attributes for each of the grammar symbols
and semantic rules. Show the application of your scheme to the input string “3 ∗ 4
+ 5 ∗ 2”. (Nov/Dec 2020) (Nov/Dec 2018) (Nov/Dec 2021)
productions:

expr -> expr + term


| expr - term
| term
term -> term * factor
| term / factor
| factor
factor -> digit | (expr)
translation schemes:

expr -> {print("+")} expr + term


| {print("-")} expr - term
| term
term -> {print("*")} term * factor
| {print("/")} term / factor
| factor
factor -> digit {print(digit)}
| (expr)

2. Specify a type checker which can handle expressions, statements and functions.
(Nov/Dec 2020)
Specify a type checker for a simple language in which the type of each identifier must
be declared before the identifier is used. The type checker is a translation scheme that
synthesizes the type of each expression from the types of its sub expressions. The type
checker can handle arrays, pointers, statements and functions.
 A simple language
 Type checking of expression
 Type checking of statement
 Type checking of functions
A Simple Language
Consider the following grammar:
P→D;E
D → D ; D | id : T
T → char | integer | array [ num ] of T | ↑ T
E → literal | num | id | E mod E | E [ E ] | E

Translation scheme:
P→D;E
D→D;D
D → id : T { addtype (id.entry , T.type) }
T → char { T.type : = char }
T → integer { T.type : = integer }
T → ↑ T1 { T.type : = pointer(T1.type) }
T → array [ num ] of T1 { T.type : = array ( 1… num.val , T1.type) }

In the above language,


There are two basic types : char and integer ;
 type_error is used to signal errors;
 the prefix operator ↑ builds a pointer type. Example , ↑ integer leads to the
type expression pointer ( integer ).

Type checking of expressions


In the following rules, the attribute type for E gives the type expression assigned to
the expression
generated by E.
1. E → literal { E.type : = char }
E → num { E.type : = integer }
Here, constants represented by the tokens literal and num have type char and integer.

2. E → id { E.type : = lookup ( id.entry ) }


lookup ( e ) is used to fetch the type saved in the symbol table entry pointed to by e.

3. E → E1 mod E2 { E.type : = if E1. type = integer and


E2. type = integer then integer
else type_error }
The expression formed by applying the mod operator to two subexpressions of type
integer has type integer; otherwise, its type is type_error.

4. E → E1 [ E2 ] { E.type : = if E2.type = integer and


E1.type = array(s,t) then t
else type_error }
In an array reference E1 [ E2 ] , the index expression E2 must have type integer. The
result is the element type t obtained from the type array(s,t) of E1.

5. E → E1 ↑ { E.type : = if E1.type = pointer (t) then t


else type_error }
The postfix operator ↑ yields the object pointed to by its operand. The type of E ↑ is
the type t of the object pointed to by the pointer E.

Type checking of statements


Statements do not have values; hence the basic type void can be assigned to them. If
an error is detected within a statement, then type_error is assigned.

Translation scheme for checking the type of statements:


1. Assignment statement:
S → id : = E
{ S.type : = if id.type = E.type then void
else type_error }
2. Conditional statement:
S → if E then S1
{ S.type : = if E.type = boolean then S1.type
else type_error }
3. While statement:
S → while E do S1
{ S.type : = if E.type = boolean then S1.type
else type_error }
4. Sequence of statements:
S → S1 ; S2
{ S.type : = if S1.type = void and
S1.type = void then void
else type_error }

Type checking of functions


The rule for checking the type of a function application is :
E → E1 ( E2) { E.type : = if E2.type = s and
E1.type = s → t then t
else type_error }

3. What are the rules for type checking? Give an example. (Nov/Dec 2020) (April/May
2019
Type checking is the process of verifying and enforcing constraints of types in
values. A compiler must check that the source program should follow the syntactic and
semantic conventions of the source language and it should also check the type rules of
the language. It allows the programmer to limit what types may be used in certain
circumstances and assigns types to values. The type-checker determines whether these
values are used appropriately or not.
It checks the type of objects and reports a type error in the case of a violation,
and incorrect types are corrected. Whatever the compiler we use, while it is compiling
the program, it has to follow the type rules of the language. Every language has its own
set of type rules for the language. We know that the information about data types is
maintained and computed by the compiler.
The information about data types like INTEGER, FLOAT, CHARACTER, and
all the other data types is maintained and computed by the compiler. The compiler
contains modules, where the type checker is a module of a compiler and its task is type
checking.
Conversion
Conversion from one type to another type is known as implicit if it is to be
done automatically by the compiler. Implicit type conversions are also
called Coercion and coercion is limited in many languages.
Example: An integer may be converted to a real but real is not converted to an integer.
Conversion is said to be Explicit if the programmer writes something to do the
Conversion.
Tasks:
1. has to allow “Indexing is only on an array”
2. has to check the range of data types used
3. INTEGER (int) has a range of -32,768 to +32767
4. FLOAT has a range of 1.2E-38 to 3.4E+38.
Types of Type Checking:
There are two kinds of type checking:
1. Static Type Checking.
2. Dynamic Type Checking.
Static Type Checking:
Static type checking is defined as type checking performed at compile time. It
checks the type variables at compile-time, which means the type of the variable is
known at the compile time. It generally examines the program text during the
translation of the program. Using the type rules of a system, a compiler can infer from
the source text that a function (fun) will be applied to an operand (a) of the right type
each time the expression fun(a) is evaluated.
Examples of Static checks include:
 Type-checks: A compiler should report an error if an operator is applied to an
incompatible operand. For example, if an array variable and function variable are
added together.
 The flow of control checks: Statements that cause the flow of control to leave a
construct must have someplace to which to transfer the flow of control. For
example, a break statement in C causes control to leave the smallest enclosing
while, for, or switch statement, an error occurs if such an enclosing statement does
not exist.
 Uniqueness checks: There are situations in which an object must be defined only
once. For example, in Pascal an identifier must be declared uniquely, labels in a
case statement must be distinct, and else a statement in a scalar type may not be
represented.
 Name-related checks: Sometimes the same name may appear two or more times.
For example in Ada, a loop may have a name that appears at the beginning and end
of the construct. The compiler must check that the same name is used at both places.
The Benefits of Static Type Checking:
1. Runtime Error Protection.
2. It catches syntactic errors like spurious words or extra punctuation.
3. It catches wrong names like Math and Predefined Naming.
4. Detects incorrect argument types.
5. It catches the wrong number of arguments.
6. It catches wrong return types, like return “70”, from a function that’s declared to
return an int.
Dynamic Type Checking:
Dynamic Type Checking is defined as the type checking being done at run time.
In Dynamic Type Checking, types are associated with values, not variables.
Implementations of dynamically type-checked languages runtime objects are generally
associated with each other through a type tag, which is a reference to a type containing
its type information. Dynamic typing is more flexible. A static type system always
restricts what can be conveniently expressed. Dynamic typing results in more compact
programs since it is more flexible and does not require types to be spelled out.
Programming with a static type system often requires more design and implementation
effort.
The design of the type-checker depends on:
1. Syntactic Structure of language constructs.
2. The Expressions of languages.
3. The rules for assigning types to constructs (semantic rules).

The Position of the Type checker in the Compiler:


The token streams from the lexical analyzer are passed to the PARSER. The
PARSER will generate a syntax tree. When a program (source code) is converted into
a syntax tree, the type-checker plays a Crucial Role. So, by seeing the syntax tree, you
can tell whether each data type is handling the correct variable or not. The Type-
Checker will check and if any modifications are present, then it will modify. It produces
a syntax tree, and after that, INTERMEDIATE CODE Generation is done.

4. Discuss in detail about parameter passing methods. (Nov/Dec 2018) (April/May


2022) (April/May 2019) (Nov/Dec 2021)
There are four methods for parameter passing in compiler design. The four methods
are given below:

1. Call by Values
When using call by value, the compiler adds the r-value of the actual parameters
that were passed to the calling procedure to the called procedure's activation record.
Any modifications made to the formal parameters do not affect the actual parameters
because they include the values given by the calling procedure.

Program
#include <bits/stdc++.h>
using namespace std;
// Formal parameter
void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
}
int main() {
// Actual parameter
int x = 9;
int y = 12;
cout << "Values before swap: x= " << x << " y= " << y << endl;
swap(x, y);
cout << "Values after swap: x= " << x << " y= " << y << endl;
}

Output
Values before swap: x=9 y=12
Values after swap: x=9 y=12
Explanation
As we can see, even when we modify the contents of variables x and y defined
in the scope of the swap function, the modifications do not affect the variables'
definitions in the scope of the main. This is because main() will not be affected by
changes performed in the swap() because we call swap() by value, which will receive
separate memory for x and y.

2. Call by Reference
The formal and actual parameters in a call by reference relate to the same
memory address. The activation record of the called function receives a copy of the L-
value of the actual arguments. As a result, the address of the actual parameters is passed
to the called function.

If the actual parameters do not contain an L-value, they are evaluated in a new
temporary location, and the location's address is passed. Since changes are made at the
address, any modifications to the formal parameter are reflected in the actual
parameters.

Program
#include <stdio.h>
// Call by reference
// Formal parameter
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
int main() {
// Actual parameter
int x = 9;
int y = 12;
printf("Values before swap: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("Values after swap: x = %d, y = %d", x, y);
}

Output
Values before swap: x=9 y=12
Values after swap: x=12 y=9

Explanation
As we can see, instead of int x, int y, we used int *x, int y, and instead of giving
x,y, we gave &x,&y in the function call. Due to the usage of pointers as function
parameters, which return the original parameters' address rather than their value, this
practice is called by reference.

The variables' addresses are given using the & operator, and the memory
location to which the pointer is pointing is accessed using the * operator. The
modifications done in the swap() reflect in main(), as shown in the output above
because the variable function points to the same memory address as the original
arguments.

3. Call by Copy Restore


In contrast to call-by-reference, changes to actual parameters are made when
the called procedure completes. The actual parameter values are stored in the called
procedure's activation record during the function call. The manipulation of formal
parameters has no immediate impact on the actual parameters; however, the L-value of
the formal parameters is copied to the actual parameters when the called procedure
completes.
Program
#include <bits/stdc++.h>
using namespace std;
int a;
void func(int x) {
//a is still 10
x = 5;
//a is now 5
a = x;
}
//Function ends so the value of x is now stored in a and value of a is now 5
int main() {
a = 10;
// When this ends the value of a will be 5
func(a);
cout << "The value of a is: " << a; //prints 5
}

Output
The value of a is: 5
Explanation
Value is passed into the function in the example above, but until the function is
complete, it has no impact on the value of the passed-in variable. At that time, the
function variable's final value is saved in the passed-in variable.

4. Call by Name
A new form of preprocessor-like argument parsing mechanism is offered by
languages like Algol. Here, the procedure's body is called instead of the procedure's
name.

Program
#include <bits/stdc++.h>
using namespace std;
int a, b, c;
// Formal parameter
void swap(int a, int b) {
int t;
// These three swapping is done by compiler
t = a;
a = b;
b = t;
cout << a << " " << b << endl;
}
int main() {
// Actual parameter
int a = 9;
int b = 12;
cout << a << " " << b << endl;
//cout<<"Values before swap: x= "<<x<<" y= "<<y<<endl;
swap(a, b);
cout << a << " " << b;
//cout<<"Values after swap: x= "<<x<<" y= "<<y<<endl;
}

Output
9 12
12 9
9 12
Explanation
Here, the evaluation is done based on parameters. In all cases where formal
parameters are used in the technique, actual parameters are used instead of formal ones.

5. Suppose that we have a production A->BCD. Each of the four non terminals have two
attributes:s is a synthesized attribute, and i is an inherited attribute. For each of the sets
of rules below, tell whether (i) the rules are consistent with an S-attributed definition
(ii) the rules are consistent with an L-attributed definition (iii) whether the rules are
consistent with any evaluation order at all? (Nov/Dec 2018)
(1) A.s=B.i+C.i
(2) A.s=B.i+C.s and D.i=A.i+B.s
(3) A.s=B.s+D.s
(4) A.s=D.i
B.i=A.s+C.s
C.i=B.s
D.i=B.i+C.i

(1) This is L attributed SDD. Attributes of parent node can take values from their
children
(2) A cannot have inherited attribute. Since, there is nothing present on the LHS of A.
So this SDD is neither S attributed nor L attributed
(3) A's synthesized attribute is a function of synthesized attributes of its children. This
confirms to S attributed definition. Every S attributed SDD is also L attributed SDD
(4) In the rule B.i=A.s+C.s. Here B's inherited attributed is taking values from its right
sibling C. This violates L-attributed definition which says that inherited attributes are
limited to take values from its parents or left siblings only. Hence, this SDD is not L-
attributed.

6. Describe how SDD can be evaluated at the nodes of a parse tree using dependency
graphs. (Nov/Dec 2020)
Evaluation order for SDD includes how the SDD(Syntax Directed Definition)
is evaluated with the help of attributes, dependency graphs, semantic rules, and S and
L attributed definitions. SDD helps in the semantic analysis in the compiler so it’s
important to know about how SDDs are evaluated and their evaluation order. This
article provides detailed information about the SDD evaluation. It requires some basic
knowledge of grammar, production, parses tree, annotated parse tree, synthesized and
inherited attributes.

Terminologies:
 Parse Tree: A parse tree is a tree that represents the syntax of the production
hierarchically.
 Annotated Parse Tree: Annotated Parse tree contains the values and attributes at
each node.
 Synthesized Attributes: When the evaluation of any node’s attribute is based on
children.
 Inherited Attributes: When the evaluation of any node’s attribute is based on
children or parents.

Dependency Graphs:
A dependency graph provides information about the order of evaluation of
attributes with the help of edges. It is used to determine the order of evaluation of
attributes according to the semantic rules of the production. An edge from the first node
attribute to the second node attribute gives the information that first node attribute
evaluation is required for the evaluation of the second node attribute. Edges represent
the semantic rules of the corresponding production.

Dependency Graph Rules: A node in the dependency graph corresponds to the node
of the parse tree for each attribute. Edges (first node from the second node)of the
dependency graph represent that the attribute of the first node is evaluated before the
attribute of the second node.

Ordering the Evaluation of Attributes:


The dependency graph provides the evaluation order of attributes of the nodes of the
parse tree. An edge( i.e. first node to the second node) in the dependency graph
represents that the attribute of the second node is dependent on the attribute of the first
node for further evaluation. This order of evaluation gives a linear order
called topological order.

Production Table

S.No. Productions Semantic Rules

1. S⇢A&B S.val = A.syn + B.syn

A.syn = A1.syn * B.syn


2. A ⇢ A1 # B
A1.inh = A.syn

3. A1 ⇢ B A1.syn = B.syn

4. B ⇢ digit B.syn = digit.lexval


Annotated Parse Tree For 1#2&3

Dependency Graph For 1#2&3


Explanation of dependency graph:
Node number in the graph represents the order of the evaluation of the associated
attribute. Edges in the graph represent that the second value is dependent on the first
value.
Table-1 represents the attributes corresponding to each node.
Table-2 represents the semantic rules corresponding to each edge.
Table-1

Node Attribute

1 digit.lexval

2 digit.lexval

3 digit.lexval

4 B.syn

5 B.syn

6 B.syn

7 A1.syn

8 A.syn

9 A1.inh

10 S.val

S-Attributed Definitions:
S-attributed SDD can have only synthesized attributes. In this type of definitions
semantic rules are placed at the end of the production only. Its evaluation is based on
bottom up parsing.
Example: S ⇢ AB { S.x = f(A.x | B.x) }
L-Attributed Definitions:
L-attributed SDD can have both synthesized and inherited (restricted inherited
as attributes can only be taken from the parent or left siblings). In this type of definition,
semantics rules can be placed anywhere in the RHS of the production. Its evaluation is
based on inorder (topological sorting).
Example: S ⇢ AB {A.x = S.x + 2} or S ⇢ AB { B.x = f(A.x | B.x) } or S ⇢
AB { S.x = f(A.x | B.x) }
Note:
 Every S-attributed grammar is also L-attributed.
 For L-attributed evaluation in order of the annotated parse tree is used.
 For S-attributed reverse of the rightmost derivation is used.
Semantic Rules with controlled side-effects:
Side effects are the program fragment contained within semantic rules. These
side effects in SDD can be controlled in two ways: Permit incidental side effects and
constraint admissible evaluation orders to have the same translation as any admissible
order.

Table-2

Edge
Corresponding Semantic Rule
From To (From the production table)

1 4 B.syn = digit.lexval

2 5 B.syn = digit.lexval

3 6 B.syn = digit.lexval

4 7 A1.syn = B.syn

5 8 A.syn = A1.syn * B.syn

6 10 S.val = A.syn + B.syn

7 8 A.syn = A1.syn * B.syn

8 10 S.val = A.syn + B.syn

8 9 A1.inh = A.syn

7. Write the syntax directed translation and parse tree for the following code (April/May
2022)
S->id:=E
E->E1+E2
E->E1*E2
E->E1
E->(E1)
E->id
The translation scheme of above grammar is given below:

Production rule Semantic actions

S → id :=E {p = look_up(id.name);
If p ≠ nil then
Emit (p = E.place)
Else
Error;
}

E → E1 + E2 {E.place = newtemp();
Emit (E.place = E1.place '+' E2.place)
}

E → E1 * E2 {E.place = newtemp();
Emit (E.place = E1.place '*' E2.place)
}

E → (E1)/E->E1 {E.place = E1.place}

E → id {p = look_up(id.name);
If p ≠ nil then
Emit (p = E.place)
Else
Error;
}

8. What is a symbol table? What type of information is stored in it? Discuss on the use of
data structures (i) arrays (ii) linked lists (iii) Binary search trees for implementing
symbol table. (Nov/Dec 2019) (April/May 2023)
The symbol table is defined as the set of Name and Value pairs. Symbol Table
is an important data structure created and maintained by the compiler in order to keep
track of semantics of variables i.e. it stores information about the scope and binding
information about names, information about instances of various entities such as
variable and function names, classes, objects, etc.
Items stored in Symbol table:
 Variable names and constants
 Procedure and function names
 Literal constants and strings
 Compiler generated temporaries
 Labels in source languages
Information used by the compiler from Symbol table:
 Data type and name
 Declaring procedures
 Offset in storage
 If structure or record then, a pointer to structure table.
 For parameters, whether parameter passing by value or by reference
 Number and type of arguments passed to function
 Base Address
Implementation of Symbol table
1. Array
Single array or equivalently several arrays was used, to store names and their
associated information, new names are added to the list in the order in which they are
encountered. The position of the end of the array is marked by the pointer available,
pointing to where the next symbol-table entry will go. The search for a name proceeds
backwards from the end of the array to the beginning. When the name is located the
associated information can be found in the words following next.

2. Linked list
 This implementation is using a linked list. A link field is added to each record.
 Searching of names is done in order pointed by the link of the link field.
 A pointer “First” is maintained to point to the first record of the symbol table.
 Insertion is fast O(1), but lookup is slow for large tables – O(n) on average

3. Binary search trees


 Another approach to implementing a symbol table is to use a binary search tree
i.e. we add two link fields i.e. left and right child.
 All names are created as child of the root node that always follows the property
of the binary search tree.
 Insertion and lookup are O(log2 n) on average.

9. Explain in detail about Design of predictive translator.(Nov/Dec 23)


The following algorithm generalizes the construction of predictive parsers to
implement a translation scheme based on a grammar suitable for top-down parsing.
Algorithm : Construction of a predictive syntax-directed translator.
Input: A syntax-directed translation scheme with an underlying grammar
suitable for predictive parsing.
Output: Code for a syntax-directed translator.
Methud: The technique is a modification of the predictive-parser construction
1. For each nonterminal A, construct a function that has a formal parameter for
each inherited attribute of A and that returns the values of the synthesized
attributes of A
2. The code for nonterminal A decides what production to use based on the
current input symbol.
3. The code associated with each production does the following. We consider
the tokens, nonterminals, and actions on the right side of the production from
left to right.
(i). For token X with synthesized attribute x, save the value of x in the
variable declared for X.x. Then generate a call to match token X and
advance the input.
(ii). For nonterminal B, generate an assignment c := B ( b1, b2, …, bk) with
a function call on the right side, where b1, b2, …, bk are the variables
for the inherited attributes of B and c is the variable far the synthesized
attribute of B.
(iii). For an action, copy the code into the parser, replacing each reference
to an attribute by the variable for that attribute.

Example : The grammar in is LL( 1 ) and hence suitable for tap-down parsing
can be generated by predicting a suitable production rule.
E  E1 + T { E.nptr := mknode('+', Enptr, T.nptr) }
E  E1 - T { E.nptr := mkrtodr('-', El.nptr, T.nptr)}
E  T { E.nptr := T.nptr }
E  R { E.nptr := R.nptr } R  ε
T  id {T.nptr :=mkleaf(id, id.entry)}
T  num {T.nptr :=mkleaf(num, num.entry)}
Combine two of the E-productions to make the translator smaller. The new
productions use token op to represent + and -.
E  E1 op T { E.nptr := mknode('op', Enptr, T.nptr) }
E  T { E.nptr := T.nptr }
E  R { E.nptr := R.nptr } R  ε
T  id {T.nptr :=mkleaf(id, id.entry)}
T  num {T.nptr :=mkleaf(num, num.entry)}

10. Discuss the address code and its implementations with example. (Nov/Dec 23)
Three address code is implemented as records with address fields.
1. Quadruples
2. Triples
3. Indirect Triples
Quadruples
In quadruples representation, each instruction is splitted into the following 4 different
fields-
op, arg1, arg2, result
Here-
• The op field is used for storing the internal code of the operator.
• The arg1 and arg2 fields are used for storing the two operands used.
• The result field is used for storing the result of the expression.
Triples
In triples representation,
• References to the instructions are made.
• Temporary variables are not used.
Indirect Triples
 This representation is an enhancement over triples representation.
 It uses an additional instruction array to list the pointers to the triples in the
desired order.
 Thus, instead of position, pointers are used to store the results.
 It allows the optimizers to easily re-position the sub-expression for producing
the optimized code.
Example
a = b * – c + b* – c
Three Address Code
T1 = uminus c
T2 = b x T1
T3 = uminus c
T4 = b x T3
T5 = T2 + T4
a = T5

Quadruple Representation-
Location Op Arg1 Arg2 Result
(1) uminus c T1
(2) * b T1 T2
(3) uminus c T3
(4) x b T3 T4
(5) + T2 T4 T5
(6) = T5 a

Triple Representation-
Location Op Arg1 Arg2

(1) uminus c

(2) x b (1)

(3) uminus c

(4) x b (3)

(5) + (2) (4)

(6) = a (5)

Indirect Triples
Location Statement
1000 (1)
1001 (2)
1002 (3)
1003 (4)
1004 (5)
1005 (6)

You might also like