CSC376 · TU past paper
Compiler Design and Construction 2081.1 question paper
The complete TU 2081.1 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 marksNumericalSyntax tree & DAG representationsHideAnswer
Discuss about Directed Acyclic Graph with an example. Represent the expression A = (B + C) - (D - E) using 3AC, Quadruple and Triple.[10]
--- A Directed Acyclic Graph (DAG) is a graph representation of an expression that, like a syntax tree, has: - Leaf nodes representing operands (identifiers/constants) - Interior nodes representing operators The key difference is that a ...
- 210 marksNumericalLR parsingHideAnswer
Create the LR(1) parsing table for following grammar.S→AAS \rightarrow AAS→AAA→0AA \rightarrow 0AA→0AA→εA \rightarrow \varepsilonA→ε[10]
LR(1) Parsing Table for the Grammar
Step 1: EXTRACT - Given Data
Grammar: $$S \to AA$$ $$A \to 0A$$ $$A \to \varepsilon$$
- Terminals: $${0, $}$$
- Non-terminals: ${S, A}$
- Marks: 10
Step 2: SOLVE
Augmented Grammar
$$ \begin{aligned} (0)\ & S' \to S\ (1)\ & S \to AA\ (2)\ & A \to 0A\ (3)\ & A \to \varepsilon \end{aligned} $$
Canonical LR(1) Item Sets
Closure rule: For $[B \to \alpha \bullet C\beta, a]$, add $[C \to \bullet \gamma, b]$ for each $b \in \text{FIRST}(\beta a)$.
$$I_0 = \text{Closure}([S' \to \bullet S, $])$$
- $$[S' \to \bullet S, $]$$
- $$[S \to \bullet AA, $]$$ (dot before $S$, FIRST($$$ $$) = $$$ $$)
- Dot before first $A$; lookahead $=$ FIRST$$(A$) = {0, $}$$ (since $A \Rightarrow \varepsilon$):
- $$[A \to \bullet 0A, 0/$]$$
- $$[A \to \bullet, 0/$]$$
Transitions: $S \to I_1$, $A \to I_2$, $0 \to I_3$
$I_1 = \text{goto}(I_0, S)$
- $$[S' \to S \bullet, $]$$ (Accept)
$I_2 = \text{goto}(I_0, A)$
- $$[S \to A \bullet A, $]$$
- Dot before second $A$; lookahead $=$ FIRST($$$ $$) $$= {$}$$:
- $$[A \to \bullet 0A, $]$$
- $$[A \to \bullet, $]$$
Transitions: $A \to I_4$, $0 \to I_5$
$I_3 = \text{goto}(I_0, 0)$
- $$[A \to 0 \bullet A, 0/$]$$
- Dot before $A$; lookahead $$= {0, $}$$:
- $$[A \to \bullet 0A, 0/$]$$
- $$[A \to \bullet, 0/$]$$
Transitions: $A \to I_6$, $0 \to I_3$ (self-loop, same item set)
$I_4 = \text{goto}(I_2, A)$
- $$[S \to AA \bullet, $]$$ (reduce by rule 1)
$I_5 = \text{goto}(I_2, 0)$
- $$[A \to 0 \bullet A, $]$$
- $$[A \to \bullet 0A, $]$$
- $$[A \to \bullet, $]$$
Transitions: $A \to I_7$, $0 \to I_5$ (self-loop)
$I_6 = \text{goto}(I_3, A)$
- $$[A \to 0A \bullet, 0/$]$$ (reduce by rule 2)
$I_7 = \text{goto}(I_5, A)$
- $$[A \to 0A \bullet, $]$$ (reduce by rule 2)
Reduce actions
- $I_0$: $$[A \to \bullet, 0/$]$$ → reduce by rule 3 on $0$ and $$$ $$
- $I_2$: $$[A \to \bullet, $]$$ → reduce by rule 3 on $$$ $$
- $I_3$: $$[A \to \bullet, 0/$]$$ → reduce by rule 3 on $0$ and $$$ $$
- $I_4$: reduce by rule 1 on $$$ $$
- $I_5$: $$[A \to \bullet, $]$$ → reduce by rule 3 on $$$ $$
- $I_6$: reduce by rule 2 on $0$ and $$$ $$
- $I_7$: reduce by rule 2 on $$$ $$
Notation: $sn$ = shift and go to state $n$; $rn$ = reduce by production $n$; $acc$ = accept; blank = error.
LR(1) Parsing Table
State Action 0Action $Goto SGoto A0 s3 / r3 r3 1 2 1 acc 2 s5 r3 4 3 s3 / r3 r3 6 4 r1 5 s5 r3 7 6 r2 r2 7 r2 Note on conflicts (States 0 and 3)
In states $I_0$ and $I_3$ the item $[A \to \bullet, 0]$ calls for reduce by rule 3 on input $0$, while the items $[A \to \bullet 0A, \dots]$ call for shift on $0$. This is a shift/reduce conflict on
0.Resolving in favor of shift (standard convention, and required for this grammar to accept strings like $00$), the entries $s3$ / $s5$ are used on input $0$. With this resolution the parser works correctly. Strictly, the grammar is not LR(1) because of this genuine shift/reduce conflict in states 0 and 3, so I have shown both actions in those cells.
Final Result
The canonical collection has 8 states ($I_0$ through $I_7$), and the completed LR(1) table is as shown above, with shift/reduce conflicts on input
0in states 0 and 3 (resolved by shift). - 310 marksNumericalBasic blocks and flow graphsHideAnswer
Explain the optimization techniques for code optimization. Convert the following program to basic block and control flow.
$M = A + B$
$N = C + D$
$\text{IF } (M > N) \rightarrow X = M - N;$
$\text{ELSE } E = M + N + X$
[10]
Program statements: (The repeated LaTeX text M=A+B, N=C+D etc. is just rendering duplication; the actual program has one statement each for M and N.) --- Code optimization transforms a program so it runs faster and/or uses less memory wi...
- 45 marksNumericalTop-down parsingHideAnswer
Compute the FIRST and FOLLOW of all the non-terminals in following grammar. S→ABS \rightarrow ABS→ABA→0A′∣1A′∣εA \rightarrow 0A' \mid 1A' \mid \varepsilonA→0A′∣1A′∣εA′→SSA′∣εA' \rightarrow SSA' \mid \varepsilonA′→SSA′∣εB→AS∣1B \rightarrow AS \mid 1B→AS∣1[5]
FIRST and FOLLOW Computation
Given data (Grammar)
$$S \rightarrow AB$$ $$A \rightarrow 0A' \mid 1A' \mid \varepsilon$$ $$A' \rightarrow SSA' \mid \varepsilon$$ $$B \rightarrow AS \mid 1$$
Non-terminals: $S, A, A', B$
Terminals: $0, 1$
Start symbol: $S$
Step 1: FIRST Sets
FIRST(A)
From $A \rightarrow 0A' \mid 1A' \mid \varepsilon$: $$FIRST(A) = {0, 1, \varepsilon}$$
FIRST(B)
From $B \rightarrow AS \mid 1$:
- $B \rightarrow 1$ gives $1$
- $B \rightarrow AS$: add $FIRST(A)\setminus{\varepsilon} = {0,1}$; since $\varepsilon \in FIRST(A)$, also add $FIRST(S)$.
Since $S$ cannot derive $\varepsilon$ (needs both $A$ and $B$ to vanish, but $B$ cannot), $FIRST(S)$ adds only ${0,1}$: $$FIRST(B) = {0, 1}$$
FIRST(S)
From $S \rightarrow AB$:
- Add $FIRST(A)\setminus{\varepsilon} = {0,1}$
- Since $\varepsilon \in FIRST(A)$, add $FIRST(B) = {0,1}$
- $\varepsilon$ not added (B cannot derive $\varepsilon$) $$FIRST(S) = {0, 1}$$
FIRST(A')
From $A' \rightarrow SSA' \mid \varepsilon$:
- $A' \rightarrow \varepsilon$ gives $\varepsilon$
- $A' \rightarrow SSA'$: add $FIRST(S) = {0,1}$ $$FIRST(A') = {0, 1, \varepsilon}$$
Summary
Non-terminal FIRST $S$ ${0, 1}$ $A$ ${0, 1, \varepsilon}$ $A'$ ${0, 1, \varepsilon}$ $B$ ${0, 1}$
Step 2: FOLLOW Sets
Rules: Start gets $$$ $$; for $X\to \alpha Y\beta$ add $FIRST(\beta)\setminus{\varepsilon}$ to $FOLLOW(Y)$; if $\varepsilon\in FIRST(\beta)$ or $\beta$ empty, add $FOLLOW(X)$ to $FOLLOW(Y)$.
FOLLOW(S)
- Start symbol: add $$$ $$
- $A' \rightarrow SSA'$:
- First $S$ followed by $SA'$: add $FIRST(S)\setminus{\varepsilon}={0,1}$
- Second $S$ followed by $A'$: add $FIRST(A')\setminus{\varepsilon}={0,1}$; since $\varepsilon\in FIRST(A')$, add $FOLLOW(A')$
- $B \rightarrow AS$: $S$ at end, add $FOLLOW(B)$
$$FOLLOW(S) = {$, 0, 1} \cup FOLLOW(A') \cup FOLLOW(B)$$
FOLLOW(A)
- $S \rightarrow AB$: $A$ followed by $B$, add $FIRST(B)\setminus{\varepsilon}={0,1}$; $\varepsilon\notin FIRST(B)$, no more.
- $B \rightarrow AS$: $A$ followed by $S$, add $FIRST(S)\setminus{\varepsilon}={0,1}$; $\varepsilon\notin FIRST(S)$, no more. $$FOLLOW(A) = {0, 1}$$
FOLLOW(A')
- $A \rightarrow 0A' \mid 1A'$: $A'$ at end, add $FOLLOW(A)={0,1}$
- $A' \rightarrow SSA'$: $A'$ at end, add $FOLLOW(A')$ (itself, no new info) $$FOLLOW(A') = {0, 1}$$
FOLLOW(B)
- $S \rightarrow AB$: $B$ at end, add $FOLLOW(S)$ $$FOLLOW(B) = FOLLOW(S)$$
Resolve FOLLOW(S) and FOLLOW(B)
$$FOLLOW(S) = {$, 0, 1} \cup FOLLOW(A') \cup FOLLOW(B)$$ $$= {$, 0, 1} \cup {0,1} \cup FOLLOW(B)$$
Since $FOLLOW(B) = FOLLOW(S)$, this is self-referential and adds nothing new: $$FOLLOW(S) = {$, 0, 1}$$ $$FOLLOW(B) = {$, 0, 1}$$
Final Answer
Non-terminal FIRST FOLLOW $S$ ${0, 1}$ $${$, 0, 1}$$ $A$ ${0, 1, \varepsilon}$ ${0, 1}$ $A'$ ${0, 1, \varepsilon}$ ${0, 1}$ $B$ ${0, 1}$ $${$, 0, 1}$$ - 55 marksHideAnswer
What are the operations performed in symbol table? Discuss about activation tree. [5]
Symbol Table Operations and Activation Tree
Part 1: Operations Performed in Symbol Table
A symbol table is a data structure used by compilers to store information about source-program constructs (identifiers, their types, storage locations, scope, etc.). The analysis phase collects this information and the synthesis phase uses it to generate target code.
Basic Operations on a Symbol Table
Operation Description allocate Allocates a new, empty symbol table free Removes all entries and frees the storage occupied by the symbol table insert Inserts a name (identifier) into the symbol table and returns a pointer to its entry lookup Searches for a name in the symbol table and returns a pointer to its entry (returns null if not found) set-attribute Associates an attribute (type, scope, location, etc.) with a given entry get-attribute Retrieves an attribute associated with a given entry Information Stored in Each Entry
Each entry in the symbol table typically contains:
- Name: Name of the identifier (stored directly or as a pointer to a string table)
- Type: Type of identifier (variable, label, procedure, etc.)
- Location/Offset: Position in storage where the identifier is stored
- Scope: Region of the program where the current definition is valid
- Other attributes: Array limits, record fields, parameter lists, return values, etc.
Part 2: Activation Tree
(Standard compiler design concept; notes are thin on this topic, so supplemented from standard CS knowledge)
Definition
An activation tree is a tree data structure used to represent the way control flows among procedures (functions) during the execution of a program. Each node in the tree represents an activation record (also called a stack frame) of a procedure call.
Key Points
- Each call to a procedure creates a new activation (node).
- The root of the tree represents the activation of the main program.
- If procedure
Acalls procedureB, then the node forBis a child of the node forA. - The execution of activations follows a last-in, first-out (LIFO) order, which is why a runtime stack can be used to implement it.
- Control enters a child node when a procedure is called and returns to the parent node when the procedure finishes.
Properties
- The lifetime of an activation of procedure
Aincludes the lifetime of all activations called byA. - Activations are non-overlapping or nested -- they never partially overlap.
- The path from the root to the currently executing node represents the current state of the runtime stack.
Example
Consider the following program:
main() { A(); B(); } A() { C(); } B() { } C() { }The activation tree is:
main / \ A B | CExplanation:
maincallsA, soAis the left child ofmain.AcallsC, soCis the child ofA.- After
A(andC) finish,maincallsB, soBis the right child ofmain.
Relation to Runtime Stack
At any point during execution, the stack contains exactly the activations on the path from the root to the currently executing node in the activation tree. For example:
- When
Cis executing: Stack =[main, A, C] - When
Bis executing: Stack =[main, B]
Summary
Feature Description Node One activation (call) of a procedure Root Activation of the main program Parent-Child Caller-Callee relationship Order LIFO (matches runtime stack behavior) Use Helps design and understand runtime memory management - 65 marksBasic concepts related to Compiler such asHideAnswer
What are the roles of macros and preprocessor? Discuss about one pass and multi pass compiler. [5]
Roles of Macros and Preprocessor; One Pass vs Multi-Pass Compiler
1. Roles of Macros and Preprocessor
A preprocessor is a tool that processes the source code before it is passed to the compiler. It is generally considered a part of the compiler system and produces input for the compiler proper.
Key Functions of a Preprocessor:
i) Macro Processing: A preprocessor allows a user to define macros, which are shorthands for longer constructs. When the preprocessor encounters a macro call, it replaces it with the corresponding expanded code before compilation begins.
- Example:
#define MAX 100replaces every occurrence ofMAXwith100.
ii) File Inclusion: The preprocessor handles header file inclusion by inserting the content of specified files into the program text.
- Example:
#include <stdio.h>inserts the standard I/O header file.
iii) Rational Preprocessor: These preprocessors augment older languages with more modern flow-of-control and data structuring facilities, making legacy code more manageable.
iv) Language Extensions: These preprocessors attempt to add capabilities to the language by building in macro-like constructs, effectively extending what the base language can express.
2. One Pass vs Multi-Pass Compiler
One Pass Compiler:
A compiler in which all phases are combined and executed in a single pass over the source program.
Multi-Pass Compiler:
A compiler in which different phases are grouped into multiple passes, each pass reading the output of the previous one.
Comparison Table:
Feature One Pass Compiler Multi-Pass Compiler Structure All phases combined into one single pass Different phases grouped into multiple passes Intermediate Representation Not created Created between passes Speed Faster Slightly slower Also Called Narrow compiler Wide compiler Memory Usage Requires less memory Requires more memory Example Pascal compiler C++ compiler
Summary:
- A one pass compiler is simpler and faster but less flexible, as it cannot easily handle forward references.
- A multi-pass compiler is more powerful and can perform better optimization since it has a complete view of the program through intermediate representations.
- Example:
- 75 marksType Checking and ConversionHideAnswer
Define explicit and implicit type conversion. Why do we need to check type of the system? Justify with an example. [5]
Type Conversion and Type Checking
Explicit Type Conversion
Explicit type conversion (also called type casting) is a conversion that is manually performed by the programmer by writing a specific instruction in the source code. The programmer explicitly specifies the target type to which a value should be converted.
Example:
int a = 10; float b = (float) a; // programmer explicitly casts int to float
Implicit Type Conversion
Implicit type conversion (also called type coercion) is a conversion that is automatically performed by the compiler without any explicit instruction from the programmer. The compiler applies conversion rules defined by the type system when operands of different types are used together.
Example:
int a = 5; float b = 3.2; float c = a + b; // compiler automatically converts 'a' to floatHere, the integer
ais automatically promoted tofloatbefore the addition is performed.
Why Do We Need to Check the Type of a System?
The collection of different data types and their associated rules to assign types to programming language constructs is known as type systems. A type checker implements a type system.
Type checking is necessary for the following reasons:
1. Ensures Type Correctness
Type checking verifies that operations are applied to compatible types. Without it, meaningless operations (such as adding a string to an integer) could go undetected.
2. Prevents Runtime Errors
Catching type mismatches at compile time avoids unexpected crashes or incorrect results at runtime.
3. Enforces Language Rules
Type systems define informal rules such as:
"If both operands of addition are of type integer, then the result is of type integer."
The type checker enforces these rules consistently across the program.
4. Supports Semantic Correctness
Even if a program is syntactically correct, it may be semantically wrong due to type mismatches. Type checking adds a layer of semantic validation.
Justification with an Example
Consider the following expression:
int x = 5; char ch = 'A'; int result = x + ch;Without type checking: The compiler would not know whether adding an
intand acharis valid, and the result type would be undefined or erroneous.With type checking: The type checker applies the rule:
Operand 1 Operand 2 Operation Result Type intchar+int(char is coerced to its ASCII integer value)The type checker detects that
charcan be implicitly converted toint(ASCII value of'A'= 65), so:result = 5 + 65 = 70This is a valid, well-typed expression. If instead we wrote:
int x = 5; char *str = "hello"; int result = x + str; // TYPE ERRORThe type checker would flag this as a type error because adding an integer and a string pointer violates the type rules, preventing a potential runtime crash.
Summary
Feature Explicit Conversion Implicit Conversion Initiated by Programmer Compiler Also known as Type casting Type coercion Risk Possible data loss if misused May cause unintended behavior Type checking is essential to ensure that every operation in a program is applied to operands of appropriate and compatible types, making programs safe, correct, and predictable.
- 85 marksAttribute TypesHideAnswer
Differentiate between synthesized and inherited attributes with example. [5]
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 attribute values ...
- 95 marksNumericalTop-down parsingHideAnswer
Construct the LL(1) parsing table for the following grammar. S→AS1∣CS \rightarrow AS1 \mid CS→AS1∣CA→0A \rightarrow 0A→0C→2C∣εC \rightarrow 2C \mid \varepsilonC→2C∣ε[5]
LL(1) Parsing Table Construction
Given Data
Grammar: $$S \rightarrow AS1 \mid C$$ $$A \rightarrow 0$$ $$C \rightarrow 2C \mid \varepsilon$$
Non-terminals: $S, A, C$; Terminals: $0, 1, 2$; End marker: $$$ $$.
Note: In $AS1$, the symbol $1$ is treated as a terminal.
Step 1: FIRST Sets
FIRST(A): $A \to 0 \Rightarrow$ FIRST(A) = ${0}$
FIRST(C): $C \to 2C$ gives $2$; $C \to \varepsilon$ gives $\varepsilon$ $$FIRST(C) = {2, \varepsilon}$$
FIRST(S):
- $S \to AS1$: FIRST(A) = ${0}$ (A is non-nullable), so add $0$
- $S \to C$: FIRST(C) = ${2, \varepsilon}$, add $2$ and $\varepsilon$ $$FIRST(S) = {0, 2, \varepsilon}$$
Step 2: FOLLOW Sets
Start: $$FOLLOW(S) \supseteq {$}$$
Production $S \to AS1$:
- After $A$ is $S$: $FOLLOW(A) \supseteq FIRST(S)\setminus{\varepsilon} = {0,2}$. Since $\varepsilon \in FIRST(S)$, continue to next symbol $1$: add ${1}$. So $FOLLOW(A) \supseteq {0,2,1}$.
- After inner $S$ is $1$: $FOLLOW(S) \supseteq {1}$
Production $S \to C$: $FOLLOW(C) \supseteq FOLLOW(S)$
Production $C \to 2C$: After $C$ is nothing: $FOLLOW(C) \supseteq FOLLOW(C)$ (no change)
Final: $$FOLLOW(S) = {1, $}$$ $$FOLLOW(A) = {0, 1, 2}$$ $$FOLLOW(C) = {1, $}$$
Step 3: PREDICT (SELECT) Sets
Production FIRST(RHS) ε in FIRST? FOLLOW(LHS) Predict Set $S \to AS1$ ${0}$ No - ${0}$ $S \to C$ ${2,\varepsilon}$ Yes $${1,$}$$ $${2, 1, $}$$ $A \to 0$ ${0}$ No - ${0}$ $C \to 2C$ ${2}$ No - ${2}$ $C \to \varepsilon$ ${\varepsilon}$ Yes $${1,$}$$ $${1, $}$$
Step 4: LL(1) Parsing Table
NT 0 1 2 $$$ $$ S $S \to AS1$ $S \to C$ $S \to C$ $S \to C$ A $A \to 0$ C $C \to \varepsilon$ $C \to 2C$ $C \to \varepsilon$
Verification
Each cell holds at most one production, so no conflict exists. The grammar is LL(1).
- 105 marksIntermediate code generation for DeclaratiHideAnswer
What are the advantages of intermediate code? How do you convert procedure call to 3AC? [5]
--- The key advantages of using intermediate code representation are: 1. Portability / Machine Independence - If a compiler translates source language directly to target machine language without intermediate code, then for each new machi...
- 115 marksLR parsingHideAnswer
What is a symbol table? Discuss the general structure of an LR parser. [5]
Symbol Table and General Structure of LR Parser
Part 1: Symbol Table
A symbol table is a data structure used by a compiler to store information about the identifiers (variables, functions, objects, etc.) encountered in the source program.
The lexical analyzer helps to identify tokens and enters them into the symbol table. It stores attributes such as:
- Name of the identifier
- Type (integer, float, etc.)
- Scope (local, global)
- Memory location / address
- Size
The symbol table is accessed and updated by almost all phases of the compiler (lexical analysis, syntax analysis, semantic analysis, code generation, etc.). It allows the compiler to quickly look up and verify identifier information during compilation.
Part 2: General Structure of an LR Parser
An LR parser is a bottom-up parser that uses right-most derivation in reverse. It is more powerful than top-down parsers and can handle a larger class of grammars.
Block Diagram of LR Parser
Input: a1 a2 ... an $ | +------+------+ | LR Parser | +------+------+ | +---------+---------+ | | Action Table Goto Table (Terminals + $) (Non-terminals) | | S(shift), R(reduce), Accept, Error | Stack [State | Symbol] Sm | Xm Sm-1 | Xm-1 ... S0Components of LR Parser
Component Description Input Buffer Contains the input string followed by $(end marker)Stack Stores states and grammar symbols (terminals or non-terminals) Action Table A 2D table indexed by [state, terminal]; gives shift, reduce, accept, or errorGoto Table A 2D table indexed by [state, non-terminal]; gives the next state after a reductionOutput A production rule representing a step in the derivation sequence Working of LR Parser
- The parser starts with an initial state
S0on the stack. - It reads the current input symbol
aand the top stateSm. - It consults the Action Table with
[Sm, a]:- Shift (S): Push the next state onto the stack and advance input.
- Reduce (R): Pop symbols from the stack according to the production rule and push the goto state.
- Accept: Parsing is successful.
- Error: Syntax error is reported.
- The Goto Table is used after a reduction to determine the new state.
Augmented Grammar
Before constructing the LR parsing table, the grammar is augmented. If
Gis a grammar with start symbolS, the augmented grammarG'adds a new start symbolS'with production:S' -> SThis ensures the parser has a definite accept state.
Types of LR Parsers
- SLR (Simple LR): Uses LR(0) items with FOLLOW sets
- CLR / LR(1): Uses LR(1) items with look-ahead symbols; more powerful
- LALR: Merges states of LR(1) to reduce table size
LR parsers are high power, use right-most derivation, and have a larger parsing table compared to top-down parsers.
- 125 marksNumericalLR parsingHideAnswer
Generate the LR(0) item sets for the following grammar. A→BBA \rightarrow BBA→BBB→bB∣aB \rightarrow bB \mid aB→bB∣a[5]
Grammar: - $A \to BB$ - $B \to bB \mid a$ Start symbol: $A$. Terminals: ${a, b}$. Non-terminals: ${A, B}$. - $(0)\ A' \to A$ - $(1)\ A \to BB$ - $(2)\ B \to bB$ - $(3)\ B \to a$ $I0 = \text{Closure}({A' \to \cdot A})$