CSC376 · TU past paper
Compiler Design and Construction 2081 question paper
The complete TU 2081 exam paper for Compiler Design and Construction (CSC376), all 12 questions with solved model answers written to the mark scheme.
Tap a question to open its answer.
- 110 marksNumericalLR parsingHideAnswer
Give an example of reduce-reduce conflict. Construct the SLR parsing table for the following grammar.E→(L)∣aE \rightarrow (L) \mid aE→(L)∣aL→L,E∣EL \rightarrow L, E \mid EL→L,E∣E[10]
Reduce-Reduce Conflict and SLR Parsing Table
Part 1: Reduce-Reduce Conflict Example
A reduce-reduce conflict occurs when, in a single LR state, two or more complete items (items with the dot at the far right) call for reduction by different productions, and a lookahead symbol is in the FOLLOW set of more than one of the corresponding left-hand nonterminals. The parser cannot decide which production to reduce by.
Example Grammar:
S -> A S -> B A -> c B -> cAfter reading
c, the state contains both complete items:A -> c . B -> c .Since both $A$ and $B$ can appear in the same context (via
S -> AandS -> B), the FOLLOW sets of $A$ and $B$ overlap (both contain$). On lookahead$, the parser cannot decide whether to reduceA -> corB -> c. This is a reduce-reduce conflict.
Part 2: SLR Parsing Table
Given Grammar (augmented and numbered):
$$ \begin{aligned} (0)\ & E' \rightarrow E \ (1)\ & E \rightarrow (L) \ (2)\ & E \rightarrow a \ (3)\ & L \rightarrow L, E \ (4)\ & L \rightarrow E \end{aligned} $$
FIRST and FOLLOW Sets
$$\text{FIRST}(E) = \text{FIRST}(L) = {\ (\ ,\ a\ }$$
- $$\text{FOLLOW}(E') = { $ }$$
- $$\text{FOLLOW}(E) = { $,\ ),\ ,\ }$$ (from $E'\to E$:
$; fromL -> L,E:,; andEinherits fromL -> EwhereLis followed by)and,) - $\text{FOLLOW}(L) = { ),\ ,\ }$ (from
E -> (L):); fromL -> L,E:,)
Canonical LR(0) Item Sets
I0:
E'->.E, E->.(L), E->.a→ on E→I1, on ( →I2, on a→I3I1:
E'->E.I2:
E->(.L), L->.L,E, L->.E, E->.(L), E->.a→ on L→I4, on E→I5, on ( →I2, on a→I3I3:
E->a.I4:
E->(L.), L->L.,E→ on ) →I6, on , →I7I5:
L->E.I6:
E->(L).I7:
L->L,.E, E->.(L), E->.a→ on E→I8, on ( →I2, on a→I3I8:
L->L,E.SLR Parsing Table
State ( a ) , $ E L 0 s2 s3 1 1 acc 2 s2 s3 5 4 3 r2 r2 r2 4 s6 s7 5 r4 r4 6 r1 r1 r1 7 s2 s3 8 8 r3 r3 Notes:
- Reductions by production (2)
E->aand (1)E->(L)appear on FOLLOW(E) ={ ), , , $ }. - Reductions by production (4)
L->Eand (3)L->L,Eappear on FOLLOW(L) ={ ), , }.
The table has no multiply-defined entries, so the grammar is SLR(1).
- 210 marksNumericalIntermediate Code GeneratorHideAnswer
What are the significances of intermediate code? Differentiate between DAG and Syntax tree. Represent the instruction A = B + C - D * E + G using quadruple and triple.[10]
Intermediate Code, DAG vs Syntax Tree, and Quadruple/Triple Representation
Given Data (Extracted)
- Task 1: Significances of intermediate code (conceptual)
- Task 2: Differentiate DAG vs Syntax tree (conceptual)
- Task 3: Expression to represent using quadruple and triple: $$A = B + C - D * E + G$$
Operator precedence assumption:
*binds tighter than+/-;+and-are left-associative and equal precedence, evaluated left to right. So the expression parses as: $$A = ((B + C) - (D * E)) + G$$
1. Significances of Intermediate Code (3 marks)
Intermediate code (IC) is a machine-independent representation produced between the analysis (front end) and synthesis (back end) phases of a compiler.
Significance Explanation Machine Independence IC is not tied to any target CPU, keeping the front end portable. Retargetability One front end can serve many target machines by swapping only the back-end code generator. Enables Optimization Machine-independent optimizations (common subexpression elimination, constant folding, dead-code removal) are applied conveniently on IC. Front-end Reuse for Many Languages Several source-language front ends can share a single back end if they emit the same IC. Modularity / Simpler Compiler Design Clean separation of phases eases development, debugging, and maintenance. Aids Error/Type Checking Type mismatches and semantic issues can be handled while generating IC.
2. Difference Between DAG and Syntax Tree (3 marks)
Feature Syntax Tree DAG (Directed Acyclic Graph) Definition Tree with operators at internal nodes and operands at leaves. Graph like a syntax tree but with common subexpressions shared as one node. Common subexpressions Repeated as separate subtrees. Shared using a single node. Node parents Each node has exactly one parent. A node may have multiple parents. Space usage More (duplication). Less (sharing). Redundancy detection Does not expose redundant computation. Explicitly reveals repeated computations. Typical use Semantic analysis, syntax-directed translation. Code optimization (common subexpression elimination). Example:
A + A * BSyntax Tree (A duplicated):
+ / \ A * / \ A BDAG (A shared):
+ / \ | * \ / \ A B
3. Quadruples and Triples for
A = B + C - D * E + G(4 marks)Three-Address Code
$$ \begin{aligned} t_1 &= B + C \ t_2 &= D * E \ t_3 &= t_1 - t_2 \ t_4 &= t_3 + G \ A &= t_4 \end{aligned} $$
Quadruple Representation:
(op, arg1, arg2, result)Index op arg1 arg2 result (0) +B C $t_1$ (1) *D E $t_2$ (2) -$t_1$ $t_2$ $t_3$ (3) +$t_3$ G $t_4$ (4) =$t_4$ - A Triple Representation:
(op, arg1, arg2)(result implied by position)Index op arg1 arg2 (0) +B C (1) *D E (2) -(0) (1) (3) +(2) G (4) =A (3) Quadruple vs Triple
Quadruple Triple Result Explicit named temporary Implicit (position index) Fields 4 3 Instruction reordering Easy Hard (position references break) - 310 marksNumericalBack patchingHideAnswer
Illustrate the concept of backpatching with an example. Convert the regular expression a(a + b)a# to DFA.[10]
Backpatching and RE to DFA Conversion
Part 1: Backpatching (5 marks)
Concept
Backpatching is a technique used during single-pass code generation for handling forward jumps whose target addresses are not yet known when the jump instruction is generated.
When we emit a conditional or unconditional jump, the destination label may still be undefined. Instead of making a second pass, we:
- Generate the jump with the target field left blank.
- Keep the instruction number in a list.
- When the target address becomes known, we go back and fill in all instructions in that list. This "going back to fill in" is backpatching.
Data Structures and Helper Functions
- truelist: jumps taken when a boolean is true.
- falselist: jumps taken when a boolean is false.
- nextlist: unconditional jumps to the statement following the current one.
Function Meaning makelist(i)creates a new list with only instruction imerge(p1,p2)concatenates two lists backpatch(p,i)inserts ias the target of every jump in listpExample:
if (a < b) then x = 1 else x = 2Code is emitted with blank targets:
Addr Emitted code List 100 if a < b goto ___truelist = {100} 101 goto ___falselist = {101} 102 x = 1(then-part) 103 goto ___nextlist = {103} 104 x = 2(else-part) 105 ... next statement Backpatching actions:
- then-part begins at 102 →
backpatch(truelist,102)gives100: if a<b goto 102 - else-part begins at 104 →
backpatch(falselist,104)gives101: goto 104 - exit is 105 →
backpatch(nextlist,105)gives103: goto 105
Final patched code:
100: if a < b goto 102 101: goto 104 102: x = 1 103: goto 105 104: x = 2 105: ...Backpatching therefore lets the compiler resolve forward addresses in a single pass.
Part 2: Convert
a(a + b)a#to DFA (Direct / Syntax-Tree Method)Step 1: Number the leaves
The augmented expression is $a \cdot (a+b) \cdot a \cdot #$. Numbering symbol positions:
$$a_1 \cdot (a_2 + b_3) \cdot a_4 \cdot #_5$$
Step 2: Syntax tree
. (root) / \ . #(5) / \ . a(4) / \ a(1) + / \ a(2) b(3)Step 3: nullable / firstpos / lastpos
No leaf is nullable, so every node here is non-nullable.
Node nullable firstpos lastpos 1 (a) F {1} {1} 2 (a) F {2} {2} 3 (b) F {3} {3} 2+3 F {2,3} {2,3} 1·(2+3) F {1} {2,3} 4 (a) F {4} {4} (…)·4 F {1} {4} 5 (#) F {5} {5} root F {1} {5} Step 4: followpos
Rule for concatenation $c_1 \cdot c_2$: for each $i \in lastpos(c_1)$, $followpos(i) \mathrel{+}= firstpos(c_2)$.
Concat node lastpos(left) firstpos(right) effect 1·(2+3) {1} {2,3} followpos(1)={2,3} (…)·4 {2,3} {4} followpos(2)={4}, followpos(3)={4} (…)·5 {4} {5} followpos(4)={5} followpos table:
Position Symbol followpos 1 a {2,3} 2 a {4} 3 b {4} 4 a {5} 5 # ∅ Step 5: Construct the DFA
- Start state $A = firstpos(\text{root}) = {1}$.
- The accepting states are those containing position 5 (the
#).
From $A={1}$ (position 1 is
a):- on
a: union of followpos of positions in $A$ with symbola= followpos(1) = {2,3} → state $B={2,3}$. - on
b: none.
From $B={2,3}$ (2 is
a, 3 isb):- on
a: followpos(2) = {4} → state $C={4}$. - on
b: followpos(3) = {4} → state $C={4}$.
From $C={4}$ (position 4 is
a):- on
a: followpos(4) = {5} → state $D={5}$. - on
b: none.
From $D={5}$ (position 5 is
#, contains #, so $D$ is the accepting state):- followpos(5) = ∅ → no outgoing transitions.
DFA Transition Table
State Positions on a on b →A {1} B - B {2,3} C C C {4} D - D* {5} - - DFA Diagram
a a / b a →(A) ------> (B) --------> (C) -------> ((D))Accepting state: $D={5}$. The DFA accepts strings matching $a(a,|,b)a$, i.e. exactly
aaaandaba, before the endmarker#. - 45 marksdifferent sub-phases within analysis and sHideAnswer
Explain different phases of compiler in brief. [5]
Phases of a Compiler
A compiler operates in phases, where each phase takes a program in one representation and produces output in another representation. There are two major phases of compilation:
1. Analysis Phase (Front End)
In the analysis part, an intermediate representation is created from the given source program. It consists of the following phases:
i) Lexical Analysis (Scanning)
- The source program is read left-to-right and grouped into tokens.
- Tokens are sequences of characters with a collective meaning (e.g., constants, operators, reserved words).
- Input: Source program
- Output: Stream of tokens
ii) Syntax Analysis (Parsing)
- The tokens produced by the lexical analyzer are grouped into grammatical phrases using the grammar of the programming language.
- A parse tree or syntax tree is produced to show the hierarchical structure of the token stream.
- Input: Stream of tokens
- Output: Parse tree / Syntax tree
iii) Semantic Analysis
- Checks whether the parse tree follows the rules of the language (e.g., type checking, scope resolution).
- Ensures that the program is semantically correct.
- Input: Parse tree
- Output: Annotated parse tree
iv) Intermediate Code Generation
- An intermediate representation (e.g., three-address code) of the source program is generated.
- This code is easy to produce and easy to translate into target code.
- Input: Annotated parse tree
- Output: Intermediate code
2. Synthesis Phase (Back End)
In the synthesis part, the equivalent target program is created from the intermediate representation. It consists of the following phases:
i) Code Optimization
- Improves the intermediate code so that the output program runs faster and takes less space.
- Removes unnecessary lines of code and rearranges statements to speed up execution.
- Example:
t2 = id3 * 3.0; id1 = id2 + t2;ii) Code Generation
- The final phase that translates the optimized intermediate code into target machine code (or assembly code).
- Memory locations are assigned to variables and intermediate results.
- Input: Optimized intermediate code
- Output: Target machine code
Supporting Component: Symbol Table
Throughout all phases, the symbol table is used. It is a data structure that holds information about source-program constructs (identifiers), such as:
- Type
- Position in storage
- Other relevant information
The analysis phase collects information into the symbol table, and the synthesis phase uses it to generate target code.
Summary Diagram
Source Program | Lexical Analysis | | | Analysis Syntax Analysis | Phase | | (Front End) Semantic Analysis | | Intermediate Code Gen | | | Code Optimization | Synthesis | | Phase Code Generation | (Back End) | Target Program - 55 marksInformation provided by Symbol TableHideAnswer
What types of information are stored in a symbol table? Discuss the activation record. [5]
A symbol table is a data structure used by compilers to hold information about source-program constructs. The information is collected incrementally by the analysis phases and used by the synthesis phases to generate target code. The fol...
- 65 marksNumericalBasic parsing techniquesHideAnswer
Compute the FIRST and FOLLOW of the non-terminals in the following grammar. S→(L)∣1S \rightarrow (L) \mid 1S→(L)∣1L→L;S∣εL \rightarrow L;S \mid \varepsilonL→L;S∣ε[5]
Grammar: $$S \rightarrow (L) \mid 1$$ $$L \rightarrow L,;,S \mid \varepsilon$$ - Non-terminals: $S$, $L$ - Terminals: $($, $)$, $1$, $;$ - Start symbol: $S$ --- FIRST(S): - $S \rightarrow (L)$ gives first symbol $($ - $S \rightarrow 1$...
- 75 marksDynamic programming code-generation algoriHideAnswer
Write the code generation algorithm for the instruction a = b op c. [5]
Code generation is the final phase of compilation that maps intermediate code (three-address instructions) to target machine instructions. For a three-address instruction of the form a = b op c, the code generator must decide: - Which re...
- 85 marksNumericalLR parsingHideAnswer
Define core item. Compute the LR(1) item sets for the following grammar. S→AAS \rightarrow AAS→AAA→aA∣bA \rightarrow aA \mid bA→aA∣b[5]
LR(1) Item Sets for S → AA, A → aA | b
Given Data
Grammar:
- $S \rightarrow AA$
- $A \rightarrow aA \mid b$
Definition of Core Item
The core of an LR(1) item $[A \rightarrow \alpha.\beta, a]$ is its first component $A \rightarrow \alpha.\beta$, i.e., the underlying LR(0) item with the look-ahead removed. Two LR(1) item sets have the same core if their sets of LR(0) items (ignoring look-aheads) are identical.
Step 1: Augmented Grammar
$$S' \rightarrow S,\quad S \rightarrow AA,\quad A \rightarrow aA,\quad A \rightarrow b$$
Step 2: FIRST Sets
$$\text{FIRST}(S) = {a,b},\qquad \text{FIRST}(A) = {a,b}$$
Step 3: Canonical Collection
I₀ = Closure([S' → .S, $]):
[S' → .S, $] [S → .AA, $] [A → .aA, a/b] [A → .b, a/b]I₁ = GOTO(I₀, S):
[S' → S., $] (accept)I₂ = GOTO(I₀, A):
[S → A.A, $] [A → .aA, $] [A → .b, $]I₃ = GOTO(I₀, a):
[A → a.A, a/b] [A → .aA, a/b] [A → .b, a/b]I₄ = GOTO(I₀, b):
[A → b., a/b] (reduce A → b)I₅ = GOTO(I₂, A):
[S → AA., $] (reduce S → AA)I₆ = GOTO(I₂, a):
[A → a.A, $] [A → .aA, $] [A → .b, $]I₇ = GOTO(I₂, b):
[A → b., $] (reduce A → b)I₈ = GOTO(I₃, A):
[A → aA., a/b] (reduce A → aA)- GOTO(I₃, a) = I₃ (same items, self-loop)
- GOTO(I₃, b) = I₄
I₉ = GOTO(I₆, A):
[A → aA., $] (reduce A → aA)- GOTO(I₆, a) = I₆ (self-loop)
- GOTO(I₆, b) = I₇
Summary of Transitions
State On S On A On a On b I₀ I₁ I₂ I₃ I₄ I₂ I₅ I₆ I₇ I₃ I₈ I₃ I₄ I₆ I₉ I₆ I₇ Total: 10 canonical LR(1) item sets (I₀ through I₉).
Note: Unlike LALR, states I₄/I₇ and I₈/I₉ have the same core but different look-aheads, so they remain distinct in the canonical LR(1) collection.
- 95 marksNumericalRun-time storage managementHideAnswer
How do you represent recursion in an activation tree? Generate the three-address code for the following instruction: [5]
STEP 1 - EXTRACT: Given Data
Part 1: Conceptual question about representing recursion in an activation tree. No numeric data.
Part 2: "Generate the three-address code for the following instruction."
Critical issue: The actual instruction/expression to be translated into three-address code is NOT provided in the question text. The statement ends with "the following instruction:" but no instruction, expression, or code fragment follows.
Therefore, the specific expression required for Part 2 is missing or unreadable, and three-address code cannot be generated for an expression that was not given. An expression such as
a = b * c + b * dcan only be treated as an illustration, not as the question's data.I will fully answer Part 1 (conceptual, no data needed) and, for Part 2, explain the method and demonstrate it on a clearly-labelled illustrative example (marked as assumed, since the actual instruction is missing).
STEP 2 - SOLVE
Part 1: Representing Recursion in an Activation Tree
An activation tree shows the flow of control among procedure activations during a program's execution.
Properties:
- Each node = one activation (one call) of a procedure.
- The root = the activation of
main. - Node for
Pis the parent of node forQif control flows fromPintoQ(i.e.,PcallsQ). - Left-to-right ordering reflects the temporal order in which activations begin.
Recursion representation: When a procedure calls itself, the activation tree contains several distinct nodes bearing the same procedure name, arranged as a chain (or subtree) descending from the calling node. Each recursive call is a separate activation with its own activation record (own parameters, locals, return address) pushed onto the control stack.
Example:
fact(n)computing $n!$$$ \text{fact}(4) \rightarrow \text{fact}(3) \rightarrow \text{fact}(2) \rightarrow \text{fact}(1) $$
fact(4) | fact(3) | fact(2) | fact(1)- Each node is a distinct activation even though the procedure name repeats.
- At any moment, the runtime stack holds exactly the activation records lying on the path from the root to the currently active node.
- On return,
fact(1)'s record is popped, control resumes infact(2), and so on.
Thus recursion = repeated nodes of the same procedure along a root-to-leaf path, each with its own activation record on the stack.
Part 2: Three-Address Code Generation
Missing data: The question says "Generate the three-address code for the following instruction:" but no instruction/expression was supplied in the given text. I cannot produce the intended answer without it. Below I state the method and demonstrate it on an assumed illustrative expression (clearly marked as not from the question).
Method (syntax-directed translation):
- $E \rightarrow id$:
E.place = id - $E \rightarrow E_1 * E_2$: emit
t = E1.place * E2.place - $E \rightarrow E_1 + E_2$: emit
t = E1.place + E2.place - $S \rightarrow id = E$: emit
id = E.place
Assumed example (illustrative only): $a = b * c + b * d$
$$ \begin{aligned} t_1 &= b * c \ t_2 &= b * d \ t_3 &= t_1 + t_2 \ a &= t_3 \end{aligned} $$
Quadruple form $(op, arg_1, arg_2, result)$:
Op Arg1 Arg2 Result *bct1*bdt2+t1t2t3=t3- aCaveat: If the actual exam instruction differs (e.g.,
x = (a + b) * (c + d)or a control statement), the code must be regenerated from that expression. The aboveb*c+b*dresult is not verifiable because the source instruction was absent from the given question. - 105 marksBasic optimization techniquesHideAnswer
What are the techniques for compiler optimization? Explain. [5]
Code optimization is the process of improving intermediate code so that the output program runs faster and takes less memory space. It removes unnecessary lines of code and arranges statements to speed up execution without wasting resour...
- 115 marksAttribute TypesHideAnswer
Describe the synthesized attribute and inherited attribute with an example. [5]
Definition: Attributes of a node that are derived from its children nodes are called synthesized attributes. - Information flows bottom-up in the parse tree (from children to parent). - A node's attribute value is computed from the attri...
- 125 marksTop-down parsingHideAnswer
What is a type expression? List the properties of LL(1) grammar. [5]
Type Expression and Properties of LL(1) Grammar
Part 1: Type Expression (2 marks)
Definition
The type of a language construct is denoted by a type expression. A type expression is either a basic type or is formed by applying an operator called a type constructor to other type expressions. The sets of basic types and constructors depend on the language being type-checked.
Type Constructors Include:
-
Basic Types
- Primitive types such as
int,float,char,bool, etc. - A special basic type
erroris used to signal type errors.
- Primitive types such as
-
Arrays
- If
Tis a type expression, thenarray(I, T)is a type expression denoting an array with elements of typeTand index setI. - Example:
array(0...99, int)
- If
-
Products
- If
T1andT2are type expressions, then their Cartesian productT1 x T2is a type expression. - Example:
int x int
- If
-
Pointers
- If
Tis a type expression, thenpointer(T)is a type expression denoting the type "pointer to an object of type T".
- If
-
Functions
- A function maps a domain type to a range type, expressed as
T1 -> T2.
- A function maps a domain type to a range type, expressed as
Part 2: Properties of LL(1) Grammar (3 marks)
Definition of LL(1) Grammar
LL(1) grammar is a class of context-free grammar that can be parsed by a non-recursive predictive (top-down) parser using a single lookahead symbol without backtracking. The name LL(1) stands for:
- L - Left to right scanning of input
- L - Leftmost derivation
- 1 - One lookahead symbol
Properties of LL(1) Grammar
A grammar G is said to be LL(1) if and only if the following conditions hold:
-
No Left Recursion
- The grammar must not contain any left-recursive productions.
- Example: A production of the form
A -> Aαis not allowed in LL(1) grammar.
-
No Ambiguity
- The grammar must be unambiguous. An ambiguous grammar cannot be LL(1) because it would lead to multiple entries in the parsing table for the same (non-terminal, terminal) pair.
-
Left Factoring
- For any non-terminal
Awith two or more productions, the productions must not have a common prefix. If they do, left factoring must be applied. - That is, for any two productions
A -> α | β,FIRST(α) ∩ FIRST(β) = ∅.
- For any non-terminal
-
FIRST and FOLLOW Condition
- For each non-terminal
Awith productionsA -> α | β:FIRST(α) ∩ FIRST(β) = ∅- If
α =>* ε, thenFIRST(β) ∩ FOLLOW(A) = ∅
- This ensures that the parsing table has at most one production per cell M[A, a].
- For each non-terminal
-
Deterministic Parsing Table
- The LL(1) parsing table M[A, a] must have at most one entry for every non-terminal
Aand terminala. Multiple entries indicate grammar conflicts and disqualify the grammar from being LL(1).
- The LL(1) parsing table M[A, a] must have at most one entry for every non-terminal
-
Single Lookahead is Sufficient
- Only one input symbol of lookahead is needed at each step to determine the correct production to apply, making parsing efficient and backtrack-free.
Summary: A type expression represents the type of a language construct using basic types and constructors like arrays, products, and functions. LL(1) grammar must be non-left-recursive, unambiguous, left-factored, and must produce a conflict-free parsing table using only one lookahead symbol.
-