Chapter 1
Compiler Design and Construction
TU BSc CSIT past exam questions
Past questions and answers
64 questions set from this chapter, 1 of them more than once. Most repeated first.
- Asked 2 times
- 2075
- 2076
Discuss about the different factors affecting target code generation.
Answer
Factors affecting target code generation
The code generator takes the intermediate representation (and symbol table) as input and produces target code. The code must be correct and of high quality, and the generator itself should be efficient. The main design issues are as follows.
1. Input to the code generator: the intermediate representation (postfix, three address code, syntax trees, DAGs) with the symbol table information. The input is assumed to be free of errors, with type conversions already inserted.
2. Target program: the output may be absolute machine code (fast, fixed memory locations), relocatable machine code (separate compilation and linking, but with extra overhead), or assembly code (easier to generate and debug, but needs an assembler).
3. Memory management: names in the intermediate code must be mapped to run-time addresses using the symbol table; labels are converted to instruction addresses, and static or stack allocation is handled.
4. Instruction selection: the instruction set of the target machine decides the quality of the code. A statement can be translated in many ways, so the generator must choose efficient instructions (for example INC a instead of LD R0,a; ADD R0,#1; ST a,R0), considering the instruction speed and addressing modes.
5. Register allocation: register operations are faster than memory operations, so choosing which values to keep in registers (allocation) and which register to use (assignment) is important. The problem is made harder by instruction constraints, such as registers reserved for some operations.
6. Evaluation order: the order in which computations are performed affects the number of registers and temporaries needed. Finding the best order is NP-complete, so heuristics are used.
7. Approach to code generation: the generator should be correct, maintainable, testable and flexible.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2075
Difference between compiler and interpreter. “Symbol table is necessary component of compiler”. Justify this statement with examples.
Answer
Difference between compiler and interpreter
| Basis | Compiler | Interpreter |
|---|---|---|
| Translation unit | Translates the whole source program into target code in one go | Translates and executes one statement at a time |
| Output | Produces an object/machine code file that can be run repeatedly | Produces no separate object file |
| Execution speed | Fast, because translation is done once beforehand | Slow, because every statement is re-analysed each time it runs |
| Error reporting | Reports all errors after scanning the whole program | Stops at the first erroneous statement |
| Memory | Needs more memory for the object code | Needs less memory |
| Examples | C, C++, Java (to bytecode) | Python, BASIC, JavaScript engines |
Symbol table is a necessary component of a compiler
The symbol table is a data structure (usually a hash table) that stores information about every identifier in the source program: its name, type, scope, size, memory address/offset, and for procedures the number and types of parameters and the return type. It is built mainly during lexical and syntax analysis and consulted by almost every later phase, so a compiler cannot work properly without it.
Justification with the statement int a; float b; a = b + 2.5;
- Lexical/syntax analysis enter
aandbinto the table with their types, and detect redeclaration of the same name in the same scope. - Semantic analysis looks up
aandbto find their types. It sees thatb + 2.5isfloatand that it is assigned to anint, so a type-conversion or a type-mismatch error is generated. An undeclared identifier is also detected only because the lookup fails. - Intermediate and code generation use the stored address/offset of
aandbto generate instructions such ast1 = b + 2.5; a = t1, and the stored sizes to allocate storage. - Scope handling: in a nested block
{ int a; ... }the innerahides the outera. Separate tables or a stack of tables per scope resolve each use to the correct declaration. - Optimization uses attributes such as whether a variable is constant or used.
Without the symbol table the compiler could not do type checking, scope resolution, storage allocation or consistent code generation, so it is an essential component.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2075
Differentiate between recursive descent and non-recursive predictive parsing method. Find first and follow of all the non-terminals in the following grammar.
E→TA; A→ +TA|ε ; T→FB; B→*FB|ε ; F→(E)|id
Answer
Recursive descent vs non-recursive predictive parsing
| Basis | Recursive descent parser | Non-recursive predictive parser |
|---|---|---|
| Implementation | One (recursive) procedure for each non-terminal | Table-driven; an explicit stack and a parsing table |
| Stack | Uses the implicit run-time call stack | Uses an explicit stack managed by the parser |
| Decision | Decided by code written in each procedure | Decided by looking up the table with (stack top, input symbol) |
| Backtracking | May backtrack if not predictive | Never backtracks (LL(1)) |
| Grammar | Left recursion must be removed, may be hand-coded for any grammar | Must be LL(1) (no left recursion, left factored) |
| Modification | Code must change when the grammar changes | Only the table changes; the driver stays the same |
FIRST and FOLLOW
Grammar:
FIRST sets:
FOLLOW sets:
- : is the start symbol, so , and is followed by in . So .
- : from , , and from it adds nothing new. So .
- : from it gets , and since is nullable also and . So .
- : from , .
- : from it gets , and since is nullable also . So .
| Non-terminal | FIRST | FOLLOW |
|---|---|---|
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2075
Construct SLR parse table for the following grammar
S→E
E→E+T|T
T→T*F|F
F→id
Answer
SLR parse table
Number the productions (the grammar is already augmented by the start production , and we add ):
Canonical LR(0) collection
- : , , , , , ,
- :
- : ,
- : ,
- :
- :
- : , , ,
- : ,
- : ,
- :
Other transitions: , , , .
FOLLOW sets
SLR table (s = shift, r = reduce, acc = accept)
| State | id | + | * | $ | S | E | T | F |
|---|---|---|---|---|---|---|---|---|
| 0 | s5 | 1 | 2 | 3 | 4 | |||
| 1 | acc | |||||||
| 2 | s6 | r1 | ||||||
| 3 | r3 | s7 | r3 | |||||
| 4 | r5 | r5 | r5 | |||||
| 5 | r6 | r6 | r6 | |||||
| 6 | s5 | 8 | 4 | |||||
| 7 | s5 | 9 | ||||||
| 8 | r2 | s7 | r2 | |||||
| 9 | r4 | r4 | r4 |
The reduce entries are placed only under the terminals in the FOLLOW set of the left-hand side. There are no multiply-defined entries, so the grammar is SLR(1).
Answer checked by Claude Sonnet · Oct 21, 2026
- 2075
Differentiate between static and dynamic type checking. How can we carry out type checking for the following expression using syntax-directed defination?
S→ id = E
S→ if E then S1
S→ while E do S1
S→ S1; S2
Answer
Static vs dynamic type checking
| Basis | Static type checking | Dynamic type checking |
|---|---|---|
| When | At compile time, before execution | At run time, while the program executes |
| Type information | Types are bound to variables by declarations | Types are attached to values and checked when operations are performed |
| Errors found | Before the program runs | Only when the faulty statement is actually executed |
| Speed | No run-time overhead | Run-time overhead for tag checks |
| Safety/flexibility | Safer but less flexible | More flexible but errors may appear late |
| Examples | C, Java, Pascal | Python, JavaScript, Lisp |
Type checking using a syntax-directed definition
Each expression has a synthesized attribute E.type. Each statement has the attribute S.type, which is void if the statement is type correct and type_error otherwise. The function lookup(id.entry) returns the type of the identifier from the symbol table.
| Production | Semantic rule |
|---|---|
S.type = if lookup(id.entry) == E.type then void else type_error | |
S.type = if E.type == boolean then S1.type else type_error | |
S.type = if E.type == boolean then S1.type else type_error | |
S.type = if S1.type == void and S2.type == void then void else type_error |
Explanation:
- In the assignment, the type of the identifier on the left must match the type of the expression on the right.
- The condition of
ifandwhilemust be of typeboolean; the statement then takes the type of its body, so an error inside the body propagates upward. - A sequence of statements is correct only if both statements are correct.
For example, while x do y = 5 with x declared as int gives E.type = int, which is not boolean, so S.type = type_error.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2075
Define three address codes. write three address codes for
S → do m = n-p while a <= b
Answer
Three address code
Three address code is an intermediate representation in which each instruction has at most three addresses (two operands and one result) and at most one operator on the right-hand side, of the general form
Complex expressions are broken into a sequence of such simple instructions using compiler-generated temporaries, and control flow is expressed with labels and jumps (goto, if ... goto). It is easy to optimize and to translate into target code. It is implemented as quadruples, triples or indirect triples.
Three address code for do m = n - p while a <= b
The body is executed first, then the condition is tested, and control returns to the start of the body while the condition is true.
L1: t1 = n - p
m = t1
if a <= b goto L1
L2: ...
The code at L2 is the next statement after the loop. When a <= b is false the jump is not taken and control falls through to it.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2075
Define code optimization. Discuss about any three code optimization techniques with example.
Answer
Code optimization
Code optimization is the compiler phase (or set of transformations) that improves the intermediate or target code so that the program runs faster and/or uses less memory, without changing its meaning (output). Optimizations must be safe, must preserve the semantics, and should be worth the effort.
Three techniques
1. Constant folding (and propagation): expressions whose operands are constants are evaluated at compile time.
Before: x = 2 * 3.14; y = x + 1;
After: x = 6.28; y = 7.28; (after propagating x)
2. Common subexpression elimination: if an expression has already been computed and its operands have not changed, reuse the earlier value instead of recomputing.
Before: t1 = b * c After: t1 = b * c
t2 = a + t1 t2 = a + t1
t3 = b * c t4 = t1 + d
t4 = t3 + d
3. Dead code elimination: removal of code that is never executed or whose result is never used.
Before: x = y + z; x = 5; print(x);
After: x = 5; print(x); (first assignment is dead)
Other techniques include strength reduction (replace x*2 by x+x or x<<1), loop-invariant code motion, induction variable elimination, copy propagation and peephole optimization.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2075
What is activation record? Discuss the different activities performed by caller and callee during procedure call and return.
Answer
Activation record
When a procedure is called, the run-time system creates an activation record (stack frame) on the run-time stack to hold the information needed for that single execution (activation) of the procedure. It is removed when the procedure returns. The typical fields are:
| Field | Purpose |
|---|---|
| Actual parameters | Arguments passed by the caller |
| Returned value | Space for the value returned to the caller |
| Control link (dynamic link) | Pointer to the caller's activation record |
| Access link (static link) | Pointer to the record that holds non-local data |
| Saved machine status | Return address, program counter and registers of the caller |
| Local data | Local variables of the procedure |
| Temporaries | Values from expression evaluation |
Calling sequence and return sequence
Work is divided between the caller and the callee.
Caller (at call time)
- Evaluates the actual parameters and places them (in the callee's frame or in registers).
- Stores the return address and the old stack top pointer (control link) in the callee's frame.
- Increments the stack pointer to create the new activation record, and jumps to the callee's code.
Callee (on entry)
- Saves the registers and other machine status.
- Initializes its local data and sets the frame pointer.
- Begins executing its body.
Callee (on return)
- Places the return value in the returned-value field (or a register).
- Restores the registers and the stack top pointer, removing its own frame.
- Branches to the return address.
Caller (after return)
- Retrieves the returned value from the callee's frame (or register).
- Continues execution with the next instruction, using the restored environment.
This division allows the number of arguments to be variable, since the caller knows what it passed while the callee knows how much local space it needs.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2076
Explain briefly about different phases involved in compiler, with a block diagram
Answer
Phases of a compiler
A compiler translates a source program into target code in a number of phases. Each phase takes the output of the previous phase, while the symbol table manager and the error handler interact with all phases.
Source program
|
+---------v---------+
| Lexical analyzer | -> tokens
+---------+---------+
|
+---------v---------+
| Syntax analyzer | -> parse tree
+---------+---------+
| +----------------+
+---------v---------+ | Symbol table |
| Semantic analyzer | <--------> | manager |
+---------+---------+ +----------------+
| +----------------+
+---------v---------+ <--------> | Error handler |
| Intermediate code | +----------------+
| generator |
+---------+---------+
|
+---------v---------+
| Code optimizer |
+---------+---------+
|
+---------v---------+
| Code generator |
+---------+---------+
|
Target program
1. Lexical analysis (scanning): reads the characters of the source program, removes white space and comments, and groups characters into tokens (identifiers, keywords, operators, constants). For a = b + 10 the tokens are id(a), =, id(b), +, num(10).
2. Syntax analysis (parsing): checks whether the sequence of tokens follows the grammar of the language and builds a parse (syntax) tree. Syntax errors such as a missing ; are reported here.
3. Semantic analysis: uses the syntax tree and the symbol table to check the meaning: type checking, type conversion, declaration before use, and scope rules.
4. Intermediate code generation: produces a machine-independent representation, for example three address code: t1 = b + 10; a = t1.
5. Code optimization: improves the intermediate code to make it faster or smaller, for example by constant folding, removing common subexpressions and dead code, and loop optimizations.
6. Code generation: translates the optimized intermediate code to the target machine code or assembly, choosing instructions, allocating registers and memory.
Symbol table management keeps the attributes of identifiers (type, scope, address). Error handling detects, reports and recovers from errors in each phase. The first three phases are often called the analysis (front end) part, and the remaining ones the synthesis (back end) part.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2076
Given a regular expression (ε + 0)*10. Construct the DFA recognizing the pattern described by this regular expression using syntax
tree based reduction.
Answer
DFA for using the syntax tree method
Since , the language is . We follow the direct method anyway.
Step 1: Augment and number the leaves
The augmented expression is . The leaves are numbered: is position 1, is position 2, is position 3 and is position 4. The leaf has no position.
Step 2: nullable, firstpos, lastpos
- : nullable, firstpos , lastpos .
- : nullable, firstpos , lastpos .
- Whole concatenation: since the star part is nullable, firstpos(root) .
Step 3: followpos
| Position | Symbol | followpos |
|---|---|---|
| 1 | 0 | (star repeats to 1; next is 2) |
| 2 | 1 | |
| 3 | 0 | |
| 4 | # |
Step 4: Construct the DFA states
- Start state .
- on 0: positions labelled 0 in is , followpos .
- on 1: position , followpos .
- on 0: position , followpos .
- on 1: none. has no outgoing transitions.
- contains position 4 (), so it is the accepting state.
Transition table
| State | 0 | 1 |
|---|---|---|
(A missing transition goes to the dead state.) The DFA has three states: , , , with final. It accepts exactly the strings , for example , . No two states are equivalent, so the DFA is already minimal.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2076
What is shift reduce parsing techniques? Show shift reduce parsing action for the string (x+x)*a, given the grammar
Answer
Shift reduce parsing
Shift reduce parsing is a bottom-up parsing technique that constructs the parse tree from the leaves toward the root, reducing the input string to the start symbol. It uses a stack and an input buffer, with the following actions:
- Shift: move the next input symbol onto the top of the stack.
- Reduce: if the top of the stack is a handle (the right side of a production), replace it by the left-hand side non-terminal.
- Accept: the stack holds only the start symbol and the input is empty (
$). - Error: call the error recovery routine.
Parsing of
Taking the grammar , with binding tighter than (the parenthesis forces the sum first):
| Step | Stack | Input | Action |
|---|---|---|---|
| 1 | $ | (x+x)*a$ | shift |
| 2 | $( | x+x)*a$ | shift |
| 3 | $(x | +x)*a$ | reduce |
| 4 | $(E | +x)*a$ | shift |
| 5 | $(E+ | x)*a$ | shift |
| 6 | $(E+x | )*a$ | reduce |
| 7 | $(E+E | )*a$ | reduce |
| 8 | $(E | )*a$ | shift |
| 9 | $(E) | *a$ | reduce |
| 10 | $E | *a$ | shift |
| 11 | $E* | a$ | shift |
| 12 | $E*a | $ | reduce |
| 13 | $E*E | $ | reduce |
| 14 | $E | $ | accept |
The string is successfully parsed.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2076
Construct SLR parsing table for the following grammar.
S -> aAa | bAb | ba
Answer
SLR parsing table
Productions:
The non-terminal has no productions in the given grammar, so it never derives a string. The only string actually generated by the grammar is , but the table is still built by the standard procedure. Augment with .
Canonical LR(0) collection
- : , , ,
- :
- :
- : ,
- :
- :
- :
- :
- :
, so all reductions are placed under .
SLR table
| State | a | b | $ | S | A |
|---|---|---|---|---|---|
| 0 | s2 | s3 | 1 | ||
| 1 | acc | ||||
| 2 | 4 | ||||
| 3 | s7 | 6 | |||
| 4 | s5 | ||||
| 5 | r1 | ||||
| 6 | s8 | ||||
| 7 | r3 | ||||
| 8 | r2 |
There are no conflicts, so the grammar is SLR(1). States 2, 4 and 6 can never be reached in a parse because there is no way to reduce to , so only the string is accepted (shift , shift to state 7, reduce by on , goto state 1, accept).
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2076
Write Syntax Directed Definition to carry out type checking for the following expression.
E -> id | E1 op E2 | E1 relop E2 | E1[E2] | E1 ↑
Answer
Type checking using a syntax-directed definition
Each expression has a synthesized attribute E.type, whose value is a type expression such as integer, real, boolean, array(s,t), pointer(t) or type_error. The function lookup(id.entry) returns the type stored in the symbol table.
| Production | Semantic rule |
|---|---|
E.type = lookup(id.entry) | |
E.type = if E1.type == integer and E2.type == integer then integer else if (E1.type == integer or real) and (E2.type == integer or real) then real else type_error | |
E.type = if E1.type == E2.type and E1.type is a basic type then boolean else type_error | |
E.type = if E2.type == integer and E1.type == array(s, t) then t else type_error | |
E.type = if E1.type == pointer(t) then t else type_error |
Explanation:
- For an identifier, the type is taken from the symbol table.
- For an arithmetic operator, two integers give an integer; if either operand is real (the other being numeric) the result is real, with the integer operand converted. Anything else is an error.
- For a relational operator, both operands must have the same basic type and the result is boolean.
- For array indexing, the index must be an integer and the indexed expression must be an array; the result is the element type .
- For the dereference operator , the operand must be a pointer to type , and the result is .
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2076
Explain with example about different methods of intermediate code representation.
Answer
Intermediate code representation
Intermediate code is a machine-independent form of the program generated between the front end and the back end. It makes the compiler portable and allows optimization. The main representations are listed below, using the example .
1. Syntax tree: a condensed parse tree in which the operators are the interior nodes and the operands are the children.
=
/ \
a +
/ \
* *
/ \ / \
b uminus b uminus
| |
c c
A DAG is similar, but identical sub-expressions are represented by a single shared node (here b * uminus c appears once), which avoids recomputation.
2. Postfix notation: the operator follows its operands, so no parentheses are needed: a b c uminus * b c uminus * + =.
3. Three address code: instructions of the form using temporaries:
t1 = uminus c
t2 = b * t1
t3 = uminus c
t4 = b * t3
t5 = t2 + t4
a = t5
It is stored as one of three forms.
Quadruples have four fields (op, arg1, arg2, result):
| op | arg1 | arg2 | result | |
|---|---|---|---|---|
| 0 | uminus | c | t1 | |
| 1 | * | b | t1 | t2 |
| 2 | uminus | c | t3 | |
| 3 | * | b | t3 | t4 |
| 4 | + | t2 | t4 | t5 |
| 5 | = | t5 | a |
Triples have three fields (op, arg1, arg2), and a result is referred to by the index of the instruction that computed it:
| op | arg1 | arg2 | |
|---|---|---|---|
| 0 | uminus | c | |
| 1 | * | b | (0) |
| 2 | uminus | c | |
| 3 | * | b | (2) |
| 4 | + | (1) | (3) |
| 5 | = | a | (4) |
Indirect triples keep a separate list of pointers to triples, so statements can be reordered during optimization without changing the references.
Quadruples are easy to optimize (temporaries are explicit) but use more space; triples save space but moving statements is hard.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2076
What is the purpose of code optimization? Explain different types of loop optimization techniques with example.
Answer
Purpose of code optimization
Code optimization transforms the intermediate code so that the final program runs faster and/or takes less memory, without changing its meaning. Since programs spend most of their time in loops, loop optimization gives the greatest improvement.
Loop optimization techniques
1. Code motion (loop-invariant code motion): a computation whose value does not change inside the loop is moved outside it, so it is done once.
Before: After:
while (i <= limit - 2) { t = limit - 2;
... while (i <= t) {
} ...
}
2. Induction variable elimination: variables that change by a fixed amount each iteration (such as and ) are related; redundant ones are removed.
Before: After:
for (i = 0; i < n; i++) for (j = 0; j < 4*n; j += 4)
a[4*i] = 0; a[j] = 0;
3. Strength reduction: replace an expensive operation in the loop by a cheaper one, for example multiplication by repeated addition.
Before: for (i=1;i<=n;i++) x = i * 5;
After: t = 0; for (i=1;i<=n;i++) { t = t + 5; x = t; }
4. Loop unrolling: the body is replicated several times to reduce the number of iterations and the loop-control overhead.
Before: for (i=0;i<100;i++) a[i]=0;
After: for (i=0;i<100;i+=2) { a[i]=0; a[i+1]=0; }
5. Loop jamming (fusion): two loops with the same limits are merged into one loop.
Before: for(i=0;i<n;i++) a[i]=0; for(i=0;i<n;i++) b[i]=1;
After: for(i=0;i<n;i++) { a[i]=0; b[i]=1; }
6. Loop invariant/dead code removal and loop fission (splitting) are other variants.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2076
Discuss the importance of error handler in compiler. How is it manipulated in the different phases of compilation?
Answer
Importance of the error handler
Programmers make errors, so a compiler must not simply stop at the first error. The error handler is the component of the compiler that detects errors, reports them, and recovers so that compilation continues and further errors can be found. Its importance:
- It reports errors clearly with the line number and a meaningful message, helping the programmer to correct the program.
- It recovers from each error so that the rest of the program can be checked in a single run.
- It prevents the compiler from crashing or looping on erroneous input, and avoids producing misleading cascaded errors.
- It adds little overhead to the processing of correct programs.
Handling errors in the different phases
| Phase | Typical errors | Handling |
|---|---|---|
| Lexical analysis | Illegal characters, malformed numbers, unterminated strings, over-long identifiers | Panic mode: delete characters until a valid token is found; insert, replace or transpose characters |
| Syntax analysis | Missing or misplaced symbols such as ; or unbalanced parentheses | Panic mode (skip tokens until a synchronizing token such as ; or }), phrase-level recovery (local correction), error productions, global correction |
| Semantic analysis | Type mismatch, undeclared variable, wrong number of arguments | Report the error and continue by assigning an error type to the offending expression so it is not reported again |
| Intermediate code generation | Operand type incompatibilities, too-large constants | Report and continue with a safe default |
| Code optimization | Possible unreachable code, division by zero found by constant folding | Warnings are generated |
| Code generation | Target machine limits, such as out-of-range address or insufficient registers | Report an error or spill to memory |
Recovery strategies in brief
- Panic mode: discard input symbols until a synchronizing token is seen. It is simple and cannot loop forever.
- Phrase-level: perform a local correction (e.g. replace a comma by a semicolon).
- Error productions: extend the grammar with productions for common errors.
- Global correction: find the minimum changes that make the program correct (expensive, mostly theoretical).
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
What are the task performed in lexical analysis. Define DFA. Given regular expression:
(a+b)*a(a+b)
Answer
Tasks performed in lexical analysis
The lexical analyzer (scanner) is the first phase of the compiler. Its tasks are:
- Read the source characters and group them into lexemes, producing a stream of tokens (identifier, keyword, operator, constant, punctuation) for the parser.
- Remove white space (blank, tab, newline) and comments.
- Correlate error messages with the source line numbers.
- Enter identifiers into the symbol table, and expand macros if the language needs it.
- Report lexical errors such as illegal characters or malformed numbers.
Token specification is done with regular expressions, and recognition is done with finite automata.
DFA
A deterministic finite automaton is a 5-tuple where is a finite set of states, the input alphabet, the transition function (exactly one next state for each state and symbol), the start state and the set of accepting states.
DFA for
The language is the set of strings over whose second-last symbol is .
NFA: , , , with final.
Subset construction:
| DFA state | NFA states | on a | on b |
|---|---|---|---|
| (start) | |||
| (final) | |||
| (final) |
Working: on gives ; on gives ; on gives ; on gives ; on gives ; on gives .
The states remember the last two symbols read, so all four are distinguishable and the DFA is minimal. Check: goes (accepted), goes (rejected).
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
Difference between LR(0) and LR(1) algorithm. Construct LR(1) parse table for s->AA ,A->aA/b
Answer
Difference between LR(0) and LR(1)
| Basis | LR(0) | LR(1) |
|---|---|---|
| Lookahead | Uses no lookahead symbol when deciding a reduce | Uses one lookahead symbol in each item |
| Items | ||
| Reduce action | Reduce in the whole row of the state | Reduce only on the lookahead symbols of the item |
| Power | Weakest; many grammars have conflicts | Most powerful of the canonical methods |
| Number of states | Fewer | Many more (the same core may appear with different lookaheads) |
| Table size | Small | Large |
Canonical LR(1) table for ,
Augment: . Productions: 1 , 2 , 3 .
Item sets
- : ; ; ;
- :
- : ; ;
- : ; ;
- :
- :
- : ; ;
- :
- :
- :
Also , , , .
Parsing table
| State | a | b | $ | S | A |
|---|---|---|---|---|---|
| 0 | s3 | s4 | 1 | 2 | |
| 1 | acc | ||||
| 2 | s6 | s7 | 5 | ||
| 3 | s3 | s4 | 8 | ||
| 4 | r3 | r3 | |||
| 5 | r1 | ||||
| 6 | s6 | s7 | 9 | ||
| 7 | r3 | ||||
| 8 | r2 | r2 | |||
| 9 | r2 |
The table has no conflicts, so the grammar is LR(1). (Merging states with equal cores, , , , would give the 7-state LALR table.)
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
Type checking is the process of verifying that the types of expressions and variables used in a program are consistent and adhere to languages type system rules. The primary goal of type checking is to identify and prevent type-related errors before the program is executed.
Answer
Type checking
Type checking is the semantic-analysis task of verifying that every operator and function is applied to operands of the correct types according to the language's type system, so that type-related errors are found before the program is run.
Type system
A type system consists of the type expressions (basic types such as int, float, boolean, char, and constructed types such as arrays, records, pointers and functions), together with the rules for assigning types to the constructs of the language. A compiler assigns a type expression to every identifier and sub-expression, and a type checker implements the rules.
Kinds of checking
- Static checking is done at compile time (C, Java). It finds errors early with no run-time cost.
- Dynamic checking is done at run time (Python, JavaScript). It is more flexible but adds overhead, and errors appear late.
Type conversion and coercion
If the operand types differ, the type checker may insert an implicit conversion (coercion), for example int to float in 2 + 3.5, or report a type error when no conversion is allowed, as when a string is added to an int in a strictly typed language. An explicit conversion is called a cast.
Example of rules
- For : if both operands are
intthe type isint; if one isfloatand the otherintorfloat, the type isfloat; otherwise it istype_error. - For an assignment : the type of
idand ofEmust be compatible. - For an array reference : must be an array and an
int. - For a function call: the number and types of the actual arguments must match the formal parameters.
Example of a type error: int x; x = "abc"; is rejected at compile time because a string cannot be assigned to an int.
Implementation
Type checking is implemented with a syntax-directed definition in which every expression has a synthesized attribute type, with symbol table lookups giving the types of identifiers. Type equivalence is decided by structural or name equivalence.
The primary goal is to prevent type-related errors, such as invalid operations, wrong function arguments and incompatible assignments, before the program runs, which makes programs safer and more reliable.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
Difference between compiler and interpreter.
Answer
Difference between compiler and interpreter
| Basis | Compiler | Interpreter |
|---|---|---|
| Translation | Translates the entire program to target code at once | Translates and executes the program statement by statement |
| Intermediate/object code | Generates an object (machine) code file | Does not produce an object file |
| Execution speed | Faster, since execution uses the already translated code | Slower, as each statement is translated every time it is executed |
| Error detection | Shows all errors after compiling the whole program | Stops at the first error found while executing |
| Memory | Requires more memory (object code) | Requires less memory |
| Source needed at run time | Not needed once compiled | Needed on every run |
| Debugging | Harder, since errors are reported together | Easier, as the error is located at the statement being executed |
| Examples | C, C++, Fortran | Python, Ruby, early BASIC |
Some systems, such as Java, use both: the source is compiled to bytecode, which is then interpreted (or JIT-compiled) by a virtual machine.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
What are the typical entries made in symbol table? Explain.
Answer
Symbol table entries
The symbol table is a data structure used by the compiler to store information about the names (identifiers) used in the source program. It is created during lexical and syntax analysis and used in all later phases. Each entry typically contains the following information.
| Field | Meaning |
|---|---|
| Name (lexeme) | The identifier string, which acts as the search key |
| Kind/category | Variable, constant, function, procedure, label, type name, parameter, array or record |
| Data type | int, float, char, array of..., pointer to..., record |
| Scope/block level | Where the name is visible (global, local, block number) |
| Storage/memory address | Offset or address allocated at run time (relative to the activation record or static area) |
| Size | Number of bytes required; for arrays the number and bounds of dimensions |
| Value | The value for constants (and sometimes initial value) |
| Parameter information | For procedures: number, types and passing mode (by value, reference) of parameters and the return type |
| Line numbers | Lines on which the name is declared and referenced (for the cross-reference listing) |
| Pointers | Link to the next entry in the hash chain or the enclosing scope |
Example: for float x[10]; declared inside function main, the entry is (x, array variable, array(10,float), local to main, offset 0, size 40 bytes).
The compiler performs two main operations: insert a name at the declaration, and lookup when the name is used. A hash table is generally used so that both are fast.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
Define Left recursive grammar. Remove left recursion from the following grammar.
S→SB | Ca
B→Bb | c
C→aB | a
Answer
Left recursive grammar
A grammar is left recursive if there is a non-terminal such that for some string . If the recursion appears directly in a production, , it is immediate left recursion. Top-down parsers (recursive descent, LL(1)) can loop forever on such grammars, so left recursion must be removed.
Removal rule
Given grammar
For : , :
For : , :
For : has no left recursion (and does not derive a string starting with ), so it is unchanged.
Resulting grammar
It generates the same language and has no left recursion. (A left factoring of gives , if an LL(1) grammar is needed.)
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
What are the disadvantages of shift reduce parsin perform shift reduce parsing of string
w=(x-x)-(x/x) for grammar
E=E-E/ E/E / (E) / x
Answer
Disadvantages of shift reduce parsing
- It is hard to write by hand; the parser is usually generated by tools (Yacc/Bison).
- Conflicts arise: a shift/reduce conflict (the parser cannot decide whether to shift or to reduce) and a reduce/reduce conflict (more than one production can be reduced). Ambiguous grammars, such as the one in this question, give conflicts that must be resolved with precedence and associativity rules.
- Not every context-free grammar can be handled; the handle must be found correctly on the stack.
- Error reporting and recovery are difficult because the parser has lost the context of what it was building.
- The parsing tables (for LR parsers) are large.
Shift reduce parsing of
Grammar: , where has higher precedence than and is left associative. Conflicts are resolved by these rules, and the parenthesized subexpressions are handled first.
| Step | Stack | Input | Action |
|---|---|---|---|
| 1 | $ | (x-x)-(x/x)$ | shift |
| 2 | $( | x-x)-(x/x)$ | shift |
| 3 | $(x | -x)-(x/x)$ | reduce |
| 4 | $(E | -x)-(x/x)$ | shift |
| 5 | $(E- | x)-(x/x)$ | shift |
| 6 | $(E-x | )-(x/x)$ | reduce |
| 7 | $(E-E | )-(x/x)$ | reduce |
| 8 | $(E | )-(x/x)$ | shift |
| 9 | $(E) | -(x/x)$ | reduce |
| 10 | $E | -(x/x)$ | shift |
| 11 | $E- | (x/x)$ | shift |
| 12 | $E-( | x/x)$ | shift |
| 13 | $E-(x | /x)$ | reduce |
| 14 | $E-(E | /x)$ | shift |
| 15 | $E-(E/ | x)$ | shift |
| 16 | $E-(E/x | )$ | reduce |
| 17 | $E-(E/E | )$ | reduce |
| 18 | $E-(E | )$ | shift |
| 19 | $E-(E) | $ | reduce |
| 20 | $E-E | $ | reduce |
| 21 | $E | $ | accept |
The string is accepted.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
Define attribute grammar with example of inherited and synthesized attributes
Answer
Attribute grammar
An attribute grammar is a context-free grammar in which each grammar symbol has a set of attributes (values such as type, value or code), and each production is associated with semantic rules that define how the attribute values are computed. It is a formal way to describe the semantics of a language and is the basis of syntax-directed translation. The attributes are of two kinds.
Synthesized attribute: the value of an attribute at a node is computed from the attributes of its children (and possibly of the node itself). Information flows bottom-up. A grammar with only synthesized attributes is called S-attributed.
Example (desk calculator):
| Production | Semantic rule |
|---|---|
print(E.val) | |
E.val = E1.val + T.val | |
E.val = T.val | |
T.val = T1.val * F.val | |
T.val = F.val | |
F.val = E.val | |
F.val = digit.lexval |
For input 3*5+4, the synthesized attribute val flows from the leaves up, giving E.val = 19.
Inherited attribute: the value of an attribute at a node is computed from the attributes of its parent and/or siblings. Information flows top-down or sideways.
Example (declaration int a, b, c):
| Production | Semantic rule |
|---|---|
L.in = T.type | |
T.type = integer | |
T.type = real | |
L1.in = L.in; addtype(id.entry, L.in) | |
addtype(id.entry, L.in) |
Here type of is synthesized, while in of is inherited from its left sibling and passes down the list so that every identifier gets the type integer.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
Define three address code. Write down Quadruples for a=-b*(c+d)/e
Answer
Three address code
Three address code is an intermediate representation in which every instruction has at most three addresses, and at most one operator on the right side, of the form
where are names, constants or compiler-generated temporaries. Complex expressions are broken into a sequence of such simple instructions. It is easy to optimize and to convert into target code, and is stored as quadruples, triples or indirect triples.
Expression:
With unary minus having the highest precedence, then * and / left to right (so the expression is ), the three address code is:
t1 = uminus b
t2 = c + d
t3 = t1 * t2
t4 = t3 / e
a = t4
Quadruples
| No. | op | arg1 | arg2 | result |
|---|---|---|---|---|
| (0) | uminus | b | t1 | |
| (1) | + | c | d | t2 |
| (2) | * | t1 | t2 | t3 |
| (3) | / | t3 | e | t4 |
| (4) | = | t4 | a |
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
List out the different types of runtime storage management techniques.
Answer
Runtime storage management decides where the data of a running program lives. The target program's run-time memory is divided into a code area, a static area, a stack and a heap. Three allocation strategies are used.
1. Static allocation
- Storage is fixed at compile time, and the size and address of every name are known before execution.
- It is used for global variables, constants and static variables.
- It does not allow recursion, because each procedure has only one activation record, and it does not allow dynamically sized data.
- Advantage: it is fast and has no run-time overhead.
2. Stack allocation
- Storage is organised as a stack of activation records. A record is pushed when a procedure is called and popped when it returns.
- Local variables, parameters, the return address and the saved registers live in the record.
- It supports recursion and reuses memory automatically. Local values are lost after the return.
- It cannot hold data that must outlive the call that created it.
3. Heap allocation
- Memory is allocated and freed at run time in any order (
malloc/free,new/delete). - It is used for dynamic data structures such as linked lists and trees, and for data that must outlive the procedure that created it.
- It needs a free-space manager, which can cause fragmentation, and it needs garbage collection or explicit deallocation to reclaim storage. It is slower than the stack.
| Basis | Static | Stack | Heap |
|---|---|---|---|
| Decided at | compile time | run time (LIFO) | run time (any order) |
| Recursion | no | yes | not applicable |
| Speed | fastest | fast | slow |
| Typical use | globals | locals, parameters | dynamic structures |
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
What are the advantages of code optimization. Define Dead-code elimination.
Answer
Advantages of code optimization
- The program runs faster, because redundant and expensive operations are removed or replaced.
- The program uses less memory (smaller code and fewer temporaries).
- It consumes less power, which matters on embedded and mobile devices.
- It lets programmers write clear, high-level code, because the compiler removes the inefficiency.
- It reduces the cost of loops and function calls, which are where programs spend most of their time.
- The program becomes more portable, since the optimizer can produce efficient code for each target machine.
- It does not change the meaning of the program. Only the efficiency changes.
Dead-code elimination
Dead code is code that can never be executed, or code that computes a value that is never used afterwards. Dead-code elimination removes such statements. This makes the program smaller and faster without changing its output.
There are two kinds:
- Unreachable code: statements after a
returnorgoto, or inside a branch whose condition is always false. - Dead (useless) assignments: a variable is assigned but not live afterwards, meaning its value is never used before it is redefined.
Example:
int f(int a) {
int x = a * 5; // x never used: dead store
int debug = 0;
if (debug) { // condition always false
print(a); // unreachable
}
return a + 1;
}
After the optimization:
int f(int a) {
return a + 1;
}
Dead code often appears after other optimizations such as constant propagation and copy propagation, so the compiler usually runs dead-code elimination repeatedly.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2078
Factors affecting (target code generator) code generator/code generator design issues
Answer
The code generator takes the intermediate representation (IR) and the symbol table as input and produces the target program. The output must be correct and of high quality, meaning it must run efficiently and use little memory. Generating optimal code is undecidable in general, so heuristics are used. The main design issues are listed below.
1. Input to the code generator The input is the IR (three-address code, quadruples, DAG, postfix) together with symbol-table information. The IR is assumed to be free of syntactic and semantic errors, with types already checked and conversions inserted. The level of the IR (high or low) affects the quality of the generated code.
2. Target program The output may be absolute machine code, relocatable machine code or assembly language.
- Absolute code can be executed immediately, but it is placed at a fixed memory location.
- Relocatable code allows separate compilation and linking, but it needs a linker and loader.
- Assembly code makes generation and debugging easier, but it needs an extra assembly step.
The target instruction set (RISC or CISC, addressing modes) also shapes the generated code.
3. Instruction selection
Each IR statement must be mapped to target instructions. The choice depends on the uniformity and completeness of the instruction set, the instruction speeds and machine idioms. For example, a = a + 1 can use INC a instead of LD, ADD, ST. A naive statement-by-statement translation gives redundant loads and stores.
4. Register allocation and assignment Registers are much faster than memory but there are few of them. Deciding which values stay in registers is register allocation. Deciding which register holds a value is register assignment. Finding the optimal assignment is NP-complete. Some machines also require register pairs, for example for multiplication and division.
5. Evaluation order The order in which computations are performed affects the number of registers and instructions needed. Some orders need fewer temporaries than others. Choosing the best order is also NP-complete, so a simple order is usually used.
6. Memory management Names in the IR must be mapped to run-time addresses. Static, stack and heap storage determine how a name is addressed (absolute, frame offset, pointer), and labels in three-address code must be turned into instruction addresses.
7. Approach to code generation The generator should be correct, easy to implement, test and maintain. Correctness is the most important property.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 10 marks
Differentiate between one-pass and multi-pass compiler. Construct the LL(1) parsing table for the following grammar.
S → ABCD
A → a | ε
B → b
C → 0 | ε
D → d | ε
Answer
Difference between one-pass and multi-pass compiler
A pass is one complete traversal of the source program or its intermediate form.
| Basis | One-pass compiler | Multi-pass compiler |
|---|---|---|
| Traversal | Reads the source once and generates code directly | Reads the source or its intermediate form several times |
| Intermediate form | Usually none | Uses one or more intermediate representations |
| Speed and memory | Fast, small memory | Slower, needs more memory |
| Optimization | Very limited | Extensive optimization is possible |
| Forward references | Hard to handle (declare before use) | Easy to handle |
| Modularity and portability | Low | High, phases are separate, so the front end and back end can be reused |
| Examples | Pascal, early C compilers | Modern C, C++ and Java compilers |
LL(1) parsing table
Grammar:
FIRST sets:
- , since is nullable and is not.
FOLLOW sets:
- , from , then , then because both are nullable.
Table entries. Entry takes for every . If is nullable, it also takes for every .
| a | b | 0 | d | $ | |
|---|---|---|---|---|---|
| S | S → ABCD | S → ABCD | |||
| A | A → a | A → ε | |||
| B | B → b | ||||
| C | C → 0 | C → ε | C → ε | ||
| D | D → d | D → ε |
No cell contains more than one production, so the grammar is LL(1).
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 10 marks
How does Lexical Analyzer recognize a token? Give an example to make it clear. Convert the regular expression a(a+b)(b+c)^*a# to DFA.
Answer
How a lexical analyzer recognizes a token
Each token class is described by a regular expression, for example:
- identifier:
- number:
- relational operator:
<,<=,==,>,>=
The specification is converted into a finite automaton (regular expression, then NFA, then DFA). The lexer then works as follows:
- It starts in the initial state at the first unread character (
lexemeBegin). - It moves a
forwardpointer through the input and follows the DFA transition for each character. - It remembers the last accepting state seen (longest-match rule). When no transition is possible, it stops.
- It retracts the
forwardpointer to the last accepting position. The characters betweenlexemeBeginandforwardform the lexeme. - It returns the token (token name, attribute value), for example by looking up the identifier in the symbol table or checking the keyword table. Whitespace and comments are skipped.
Example: for the input count1 = 25; and the identifier DFA
start --letter--> (S1) --letter/digit--> (S1)
ctakes the DFA from the start state to S1, which is accepting.o, u, n, t, 1keep it in S1.- The space has no transition, so the lexeme
count1is accepted. - The token returned is
<id, pointer to the symbol-table entry for count1>. - Then
=gives<assign_op>,25gives<num, 25>and;gives<semicolon>.
Regular expression to DFA:
The direct method (followpos) is used. Number the positions:
Nullable and firstpos: is nullable and the others are not. So .
followpos:
| Position | followpos |
|---|---|
| 1 (a) | {2, 3} |
| 2 (a) | {4, 5, 6} |
| 3 (b) | {4, 5, 6} |
| 4 (b) | {4, 5, 6} |
| 5 (c) | {4, 5, 6} |
| 6 (a) | {7} |
| 7 (#) | none |
Subset construction. Start with .
- on : positions with symbol are 1, so the state is .
- on : position 2, giving . on : position 3, giving .
- on : position 6, giving . on : position 4, giving . on : position 5, giving .
- has no outgoing transitions. It contains , so it is the accepting state.
| State | a | b | c |
|---|---|---|---|
| A = {1} | B | – | – |
| B = {2,3} | C | C | – |
| C = {4,5,6} | D | C | C |
| D = {7} (final) | – | – | – |
The DFA accepts strings of the form .
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 10 marks
Construct the LR(1) parsing table for the following grammar.
S → AaAb
A → BbBa
A → ε
B → ε
Answer
The grammar is numbered:
The grammar is augmented with . Each item has a lookahead.
Canonical collection of LR(1) items
- : ; ; ; ;
- :
- :
- :
- : ; ; ;
- : ;
- :
- :
- :
- :
- :
- : ;
- :
- :
LR(1) parsing table
| State | a | b | $ | S | A | B |
|---|---|---|---|---|---|---|
| 0 | r3 | r4 | 1 | 2 | 3 | |
| 1 | acc | |||||
| 2 | s4 | |||||
| 3 | s5 | |||||
| 4 | r3 / r4 | 8 | 10 | |||
| 5 | r4 | 6 | ||||
| 6 | s7 | |||||
| 7 | r2 | |||||
| 8 | s9 | |||||
| 9 | r1 | |||||
| 10 | s11 | |||||
| 11 | r4 | 12 | ||||
| 12 | s13 | |||||
| 13 | r2 |
State 4 contains two complete items, and , with the same lookahead . This is a reduce/reduce conflict in the entry (4, b). So the grammar is not LR(1). After the first , a single symbol cannot decide whether derives (the input is ) or the start of (the input is ). Two symbols of lookahead are needed. All other entries are conflict-free.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 5 marks
Why code optimization is needed? Describe any two techniques for loop optimization.
Answer
Why code optimization is needed
The code produced by the front end (and by the code generator) is often correct but inefficient. Optimization transforms the code, without changing its meaning, so that it runs faster, uses less memory or consumes less power. It is needed because:
- programs spend most of their time in loops, where small savings are multiplied many times;
- the source is written for readability, and the compiler can remove the resulting redundancy;
- the intermediate code contains many temporaries and redundant computations.
Two loop optimization techniques
1. Code motion (loop-invariant computation). An expression whose value does not change inside the loop is moved out, before the loop.
Before:
while (i <= limit - 2) {
x = y * z; // y, z not changed in loop
i = i + 1;
}
After:
t = limit - 2;
x = y * z; // hoisted (keeps the loop semantics if the loop executes)
while (i <= t) {
i = i + 1;
}
limit - 2 is computed once rather than on every iteration.
2. Strength reduction and induction-variable elimination. An expensive operation is replaced by a cheaper one. Induction variables are variables that change by a constant amount in every iteration.
Before:
for (i = 0; i < n; i++)
a[i] = i * 4; // multiplication in every iteration
After:
t = 0;
for (i = 0; i < n; i++) {
a[i] = t;
t = t + 4; // addition replaces multiplication
}
Multiplication is replaced by addition, which is cheaper. The multiplied value is kept in a temporary that is updated by a constant increment each time.
Other loop optimizations are loop unrolling and loop jamming (fusion).
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 5 marks
What is three-address code? How high level code is converted to three address code? Illustrate with an example.
Answer
Three-address code (TAC)
Three-address code is an intermediate representation in which each instruction has at most three operands (two sources and one destination) and at most one operator other than assignment. The general form is
where are names, constants or compiler-generated temporaries. A complex expression is broken into a sequence of such simple instructions. Typical statements are:
- assignment:
x = y op z,x = op y,x = y - jumps:
goto L,if x relop y goto L - indexed and pointer:
x = a[i],a[i] = x,x = *p - procedure calls:
param x,call p, n,return y
TAC is machine-independent and easy to optimize and translate. It can be stored as quadruples, triples or indirect triples.
Converting high-level code to TAC
The syntax tree is traversed. Each interior node gets a temporary that holds its value and an instruction that computes it. Control-flow statements are translated using labels and jumps.
Example: a = b * -c + b * -c
t1 = minus c
t2 = b * t1
t3 = minus c
t4 = b * t3
t5 = t2 + t4
a = t5
Example with control flow: while (a < b) a = a + 1;
L1: if a < b goto L2
goto L3
L2: t1 = a + 1
a = t1
goto L1
L3:
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 5 marks
What is activation tree? Define type checking system with examples.
Answer
Activation tree
An activation tree depicts the way control enters and leaves procedure activations during the execution of a program.
- Each node represents one activation of a procedure.
- The root is the activation of
main. - The node for is the parent of the node for if control flows from activation to activation . The children of a node are shown left to right in the order of the calls.
- The execution is a depth-first traversal. The control stack holds the path from the root to the current activation.
Example: main calls f() and g(), and f calls h().
main
├── f
│ └── h
└── g
For a recursive procedure (fact(3)), every recursive call is a separate node (fact(3) to fact(2) to fact(1)).
Type checking
Type checking verifies that each operation is applied to operands of the correct type according to the type rules of the language. It is part of semantic analysis. A type system consists of type expressions and rules that assign a type to each expression. Checking may be static (at compile time) or dynamic (at run time).
A rule: if and have type int, then has type int. If one operand is float, the int is converted to float by coercion and the result is float.
Examples:
int a; float b;
a = a + 1; // OK: int + int
b = a + 2.5; // OK: a coerced to float
char *s = a * 3.0; // Error: type mismatch (float cannot be assigned to a pointer)
int arr[5];
arr["x"] = 1; // Error: array index must be an integer
A function call f(x, y) is correct only if the number and types of the arguments match the parameter types of f. Type errors are reported at compile time.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 5 marks
What are the advantages of intermediate code? What types of information are provided by symbol table?
Answer
Advantages of intermediate code
- Portability. One front end can be combined with many back ends (and many source languages with one back end), so compilers need only components.
- Machine independence. The front end does not depend on the target machine's registers or instruction set.
- Easier optimization. Machine-independent optimizations (constant folding, common sub-expression elimination, loop optimization) are done on the intermediate code.
- Simpler design. The compiler is divided into analysis and synthesis parts, which are easier to build, test and maintain.
- Retargeting. Only the back end needs to be rewritten for a new machine.
- Easy code generation. Three-address code is close to assembly, so the code generator can translate it simply.
Information provided by the symbol table
The symbol table is a data structure that stores information about each identifier in the source program. It is used by every phase of the compiler. It provides:
- the name (lexeme) of the identifier;
- its type (int, float, array, record, function) and the type of its elements for arrays;
- its kind (variable, constant, procedure, parameter, label);
- its scope and nesting level;
- its storage information (memory address or offset, size in bytes);
- for procedures, the number and types of parameters, the return type and the passing mode (by value or by reference);
- the line numbers where it is declared and used, for error messages;
- for arrays, the dimensions and bounds;
- the values of constants.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 5 marks
What is annotated parse tree? Define S-attributed grammar with an example.
Answer
Annotated parse tree
An annotated parse tree is a parse tree in which every node has its attribute values attached, that is, the values computed by the semantic rules of a syntax-directed definition. It is also called a decorated parse tree. Evaluating the attributes at all nodes of the tree gives the meaning of the input string.
S-attributed grammar
An S-attributed definition is a syntax-directed definition that uses only synthesized attributes. The attribute of a node is computed from the attributes of its children. Such attributes can be evaluated by a single bottom-up (post-order) traversal, so they fit naturally with LR parsers: the value is computed when a reduction takes place.
Example: a desk calculator
| Production | Semantic rule |
|---|---|
| print(E.val) | |
| E.val = E₁.val + T.val | |
| E.val = T.val | |
| T.val = T₁.val × F.val | |
| T.val = F.val | |
| F.val = E.val | |
| F.val = digit.lexval |
The annotated parse tree for 3 * 5 + 4 n:
L
└── E.val = 19
├── E.val = 15
│ └── T.val = 15
│ ├── T.val = 3
│ │ └── F.val = 3 (digit 3)
│ ├── *
│ └── F.val = 5 (digit 5)
├── +
└── T.val = 4
└── F.val = 4 (digit 4)
The value is computed from , and then is the value of the root , which is printed.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 5 marks
Describe about syntax directed translation with an example.
Answer
Syntax-directed translation (SDT)
Syntax-directed translation attaches semantic information to a context-free grammar, so that translation is driven by the syntax of the input. It is done in two ways:
- Syntax-directed definition (SDD): a grammar in which every symbol has attributes and every production has semantic rules that compute them. It does not fix the order of evaluation.
- Synthesized attribute: computed from the attributes of the children.
- Inherited attribute: computed from the attributes of the parent and siblings.
- Syntax-directed translation scheme: a grammar with program fragments (semantic actions) written within the right sides of productions. The position of an action fixes when it executes.
During parsing, the parse tree is built (or implied), attribute values are calculated by the rules, and the result is the translation (for example, intermediate code, a value, a type or a postfix string). SDT is used for expression evaluation, type checking, building syntax trees and generating intermediate code.
Example: translating an infix expression to postfix
For the input a + b * c:
ais reduced to , andais printed.bandcare printed as they are reduced.*is printed afterT → T * F, and then+is printed afterE → E + T.- The output is
a b c * +, which is the postfix form.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 5 marks
Given the following grammar with SLR parsing table, test whether the string “int * (int + int)” will be accepted or rejected.
E -> T + E……….(1)
E -> T……….(2)
T -> int*T……….(3)
T -> int……….(4)
T -> (E)……….(5)
STATE ACTION GOTO int * + ( ) $ E T 1 S5 S4 2 3 2 ACC 3 S6 R2 R2 4 S5 S4 7 3 5 S8 R4 R4 R4 6 S5 S4 9 3 7 S10 8 S5 S4 11 9 R1 R1 10 R5 R5 R5 11 R3 R3 R3
Answer
The productions are numbered: (1) , (2) , (3) , (4) , (5) .
The input is int * ( int + int ) $. The stack holds states. The initial stack is 1.
| Step | Stack | Input | Action |
|---|---|---|---|
| 1 | 1 | int * ( int + int ) $ | S5 (shift int) |
| 2 | 1 5 | * ( int + int ) $ | S8 |
| 3 | 1 5 8 | ( int + int ) $ | S4 |
| 4 | 1 5 8 4 | int + int ) $ | S5 |
| 5 | 1 5 8 4 5 | + int ) $ | R4 (T→int): pop 1, goto(4,T)=3 |
| 6 | 1 5 8 4 3 | + int ) $ | S6 |
| 7 | 1 5 8 4 3 6 | int ) $ | S5 |
| 8 | 1 5 8 4 3 6 5 | ) $ | R4: pop 1, goto(6,T)=3 |
| 9 | 1 5 8 4 3 6 3 | ) $ | R2 (E→T): pop 1, goto(6,E)=9 |
| 10 | 1 5 8 4 3 6 9 | ) $ | R1 (E→T+E): pop 3, goto(4,E)=7 |
| 11 | 1 5 8 4 7 | ) $ | S10 |
| 12 | 1 5 8 4 7 10 | $ | R5 (T→(E)): pop 3, goto(8,T)=11 |
| 13 | 1 5 8 11 | $ | R3 (T→int*T): pop 3, goto(1,T)=3 |
| 14 | 1 3 | $ | R2 (E→T): pop 1, goto(1,E)=2 |
| 15 | 1 2 | $ | ACC |
The parser reaches the accept action with the input fully consumed, so the string int * (int + int) is accepted.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 5 marks
How do you recognize basic block? Discuss about the factors that affect code generator.
Answer
Basic block
A basic block is a maximal sequence of consecutive three-address statements in which control enters only at the first statement and leaves only at the last. There is no jump into the middle and no branch out except at the end.
Algorithm to recognise basic blocks
Step 1: find the leaders (first statements of blocks).
- The first statement of the program is a leader.
- Any statement that is the target of a conditional or unconditional jump is a leader.
- Any statement that immediately follows a conditional or unconditional jump is a leader.
Step 2: form the blocks. Each block consists of a leader and all statements up to, but not including, the next leader (or the end of the program).
Example:
1: i = 1
2: j = 1
3: t1 = 10 * i
4: t2 = t1 + j
5: a[t2] = 0
6: j = j + 1
7: if j <= 10 goto 3
8: i = i + 1
9: if i <= 10 goto 2
Leaders: 1 (first), 2 (target of 9), 3 (target of 7), 8 (follows jump 7). Statement 10 would follow 9 if it existed. Blocks: B1 = {1}, B2 = {2}, B3 = {3 to 7}, B4 = {8, 9}.
Factors that affect the code generator
- Input IR: its form (three-address code, DAG, postfix) and level, and the information in the symbol table. The IR is assumed to be error-free.
- Target program: absolute machine code, relocatable code or assembly, and the target machine's instruction set (RISC or CISC).
- Instruction selection: choosing the best instruction sequence for each IR statement, using machine idioms such as
INC. - Register allocation and assignment: deciding which values stay in registers (which is NP-complete), and handling register pairs.
- Evaluation order: the order of computation changes the number of registers and instructions needed.
- Memory management: mapping names to run-time addresses and labels to instruction addresses.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2080 · 5 marks
Find the FIRST and FOLLOW of all the non terminals in following grammar.
S → aAbCD | ε
A → SD | ε
C → Sa
D → aBD | ε
Answer
Grammar:
has no production of its own. It is treated as a symbol that does not affect the FIRST and FOLLOW sets of the other non-terminals, because always begins with .
FIRST sets
- , from and .
- , from and .
- : from we take . Since is nullable, we add . Together with , this gives .
- : from we take . Since is nullable, we add , which is already in the set. So .
FOLLOW sets
- contains (start symbol).
- From : . is nullable, so is also added.
- From : .
- : from we get .
- Therefore .
- : from , . is nullable, so is added. So .
- : from we get . From we get . From we get itself. So .
| Non-terminal | FIRST | FOLLOW |
|---|---|---|
| S | {a, ε} | {a, b, $} |
| A | {a, ε} | {b} |
| C | {a} | {a, b, $} |
| D | {a, ε} | {a, b, $} |
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 1 MCQ
Loading…
- 2081 · 10 marks
Give an example of reduce-reduce conflict. Construct the SLR parsing table for the following grammar.
E→ (L) | a
L → L, E | E
Answer
Reduce-reduce conflict
A reduce-reduce conflict occurs when an LR state contains two complete items (two different productions that can be reduced) for the same lookahead, so the parser cannot decide which production to reduce by.
Example:
After shifting , the state contains and . Both can be reduced on lookahead (since ), so the table entry has two reductions ().
SLR parsing table
Grammar (augmented), with numbered productions:
FOLLOW sets:
Canonical LR(0) item sets:
- : ; ;
- :
- : ; ; ; ;
- :
- : ;
- :
- :
- : ; ;
- :
- and .
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 |
There are no multiply-defined entries, so the grammar is SLR(1).
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 1 MCQ
Loading…
- 2081 · 10 marks
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.
Answer
Significance of intermediate code
- It separates the front end from the back end. Each of the languages and machines is combined with the others, so only components are needed instead of compilers.
- It is machine-independent, which makes the compiler portable and easy to retarget.
- It permits machine-independent optimization.
- It is simpler than the source and easier to translate into target code than the syntax tree.
- It simplifies the compiler design by dividing it into manageable modules.
DAG versus syntax tree
| Syntax tree | DAG (directed acyclic graph) |
|---|---|
| A common sub-expression appears as repeated sub-trees | A common sub-expression is represented once and shared by several parents |
| Each node has exactly one parent | A node can have more than one parent |
| Needs more nodes and memory | Needs fewer nodes |
| Shows the structure of the source | Also shows the reuse of values, so it helps to find common sub-expressions and generate better code |
For example, in the sub-expression occurs twice. The tree has two copies of it, and the DAG has one node with two parents.
Quadruple and triple for
By precedence and left-to-right associativity, the expression is . The three-address code is:
t1 = B + C
t2 = D * E
t3 = t1 - t2
t4 = t3 + G
A = t4
Quadruples (op, arg1, arg2, result):
| # | op | arg1 | arg2 | result |
|---|---|---|---|---|
| 0 | + | B | C | t1 |
| 1 | * | D | E | t2 |
| 2 | − | t1 | t2 | t3 |
| 3 | + | t3 | G | t4 |
| 4 | = | t4 | A |
Triples (op, arg1, arg2), where a result is referred to by the number of the triple that produced it:
| # | op | arg1 | arg2 |
|---|---|---|---|
| 0 | + | B | C |
| 1 | * | D | E |
| 2 | − | (0) | (1) |
| 3 | + | (2) | G |
| 4 | = | A | (3) |
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2081 · 10 marks
Illustrate the concept of backpatching with an example. Convert the regular expression a(a + b)a# to DFA.
Answer
Backpatching
In a one-pass translation of Boolean expressions and control-flow statements, a jump is generated before the address of its target is known (the target is a later statement). Backpatching leaves such a jump with its target blank and fills in the target later, when it becomes known.
Each Boolean expression has two lists of incomplete jumps:
- : jumps taken when is true;
- : jumps taken when is false.
The operations are:
makelist(i)creates a list holding only instruction index ;merge(p1, p2)concatenates two lists;backpatch(p, i)sets the target of every jump on list to .
A marker non-terminal records M.instr = nextinstr (the index of the next instruction to be generated).
Example: if (a < b || c < d) x = 1;
Rule for : backpatch(B1.falselist, M.instr); B.truelist = merge(B1.truelist, B2.truelist); B.falselist = B2.falselist.
- For
a < b, we generate100: if a < b goto _101: goto _- so and .
- .
- For
c < d, we generate102: if c < d goto _103: goto _- so and .
- Reducing , we backpatch with , so
101: goto 102. Then and . - For
if (B) M2 S1, with , we backpatch with , so100: ... goto 104and102: ... goto 104. The statement104: x = 1is generated. - . When the next statement starts at 105, 103 is backpatched with 105.
Final code:
100: if a < b goto 104
101: goto 102
102: if c < d goto 104
103: goto 105
104: x = 1
105:
Regular expression to DFA:
Positions: . .
| Position | followpos |
|---|---|
| 1 | {2, 3} |
| 2 | {4} |
| 3 | {4} |
| 4 | {5} |
| 5 | none |
States:
- . on gives .
- on (position 2) gives . on (position 3) gives .
- on (position 4) gives .
- contains , so it is the accepting state.
| State | a | b |
|---|---|---|
| A = {1} | B | – |
| B = {2,3} | C | C |
| C = {4} | D | – |
| D = {5} (final) | – | – |
The DFA accepts and .
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2081
Explain different phases of compiler in brief.
Answer
A compiler translates a source program into a target program in several phases. Each phase takes one representation of the program as input and produces another. The first four phases are the analysis (front end) part and the last two are the synthesis (back end) part. The symbol table and the error handler are used by all phases.
Source -> Lexical -> Syntax -> Semantic -> Intermediate -> Code -> Code -> Target
analysis analysis analysis code gen. optimizer generator
(both share symbol table & error handler)
1. Lexical analysis (scanner)
It reads the character stream and groups characters into lexemes. It produces tokens such as identifier, keyword, operator, constant and punctuation. It removes white space and comments, and reports illegal tokens. For example, position = initial + rate * 60 becomes id1 = id2 + id3 * 60.
2. Syntax analysis (parser)
It checks the token stream against the grammar of the language and builds a parse tree or syntax tree. It reports syntax errors such as a missing ; or unbalanced parentheses.
3. Semantic analysis
It checks meaning that the grammar cannot express: type checking, declaration before use, argument counts and types. It inserts type conversions, for example inttofloat(60), and produces an annotated syntax tree.
4. Intermediate code generation It translates the tree into a machine-independent intermediate form such as three-address code:
t1 = inttofloat(60)
t2 = id3 * t1
t3 = id2 + t2
id1 = t3
5. Code optimization It improves the intermediate code so that the final program is faster or smaller, for example by constant folding, removing common sub-expressions, loop optimization and dead-code elimination.
6. Code generation It maps the optimized intermediate code to target machine code or assembly. It selects instructions, allocates registers and assigns memory locations:
LDF R2, id3
MULF R2, R2, #60.0
LDF R1, id2
ADDF R1, R1, R2
STF id1, R1
Symbol table manager: it stores information about identifiers (type, scope, storage) for all phases.
Error handler: it detects, reports and recovers from errors found in any phase.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2081
What types of information are stored in a symbol table? Discuss the activation record.
Answer
Information stored in a symbol table
The symbol table stores one entry per identifier, and it is used by all the phases of the compiler. The entry contains:
- the name (lexeme) of the identifier;
- its type (int, float, array, record, pointer, function);
- its kind (variable, constant, parameter, procedure, label);
- its scope and nesting level;
- its storage details (size, memory address or offset);
- for arrays, the dimensions and bounds;
- for procedures, the number and types of parameters, the passing mode and the return type;
- for constants, the value;
- the line numbers of declaration and use (for error reports).
Operations are insert, lookup, and entering or leaving a scope. It is commonly implemented as a hash table.
Activation record
An activation record (stack frame) is a block of memory that holds the information needed for a single execution of a procedure. A record is pushed on the run-time stack at a call and popped at the return. The fields, in the usual order, are:
| Field | Purpose |
|---|---|
| Actual parameters | Arguments passed by the caller |
| Returned value | Space for the value returned to the caller |
| Control link (dynamic link) | Points to the activation record of the caller |
| Access link (static link) | Points to the record that holds non-local data (for nested procedures) |
| Saved machine status | Return address, program counter and saved registers |
| Local data | Local variables of the procedure |
| Temporaries | Values from evaluating expressions |
The caller evaluates the actual parameters and pushes them. The callee stores the saved status, then allocates the local data. On return, the callee restores the registers and the stack pointer, and control returns to the saved return address. The stack of activation records is what makes recursion possible, since each call has its own set of variables.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2081
Compute the FIRST and FOLLOW of the non-terminals in the following grammar:
S → (L) ∣ 1
L → LS ∣∗S
Answer
Grammar:
FIRST sets
- .
- : the production is left-recursive and begins with itself. The production gives . So .
FOLLOW sets
- contains (start symbol).
- From : is at the end, so .
- From : is at the end, so .
- :
- From : .
- From : .
- So .
- And .
| Non-terminal | FIRST | FOLLOW |
|---|---|---|
| S | { ( , 1 } | { $, ), (, 1 } |
| L | { * } | { ), (, 1 } |
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 1 MCQ
Loading…
- 2081
Write the code generation algorithm for the instruction a = b op c.
Answer
The code generator uses two descriptors:
- the register descriptor, which records what each register currently holds;
- the address descriptor, which records where the current value of each name can be found (register, memory location or both).
The function getreg(I) returns the location (normally a register) in which the result of the instruction is to be held.
Algorithm for the three-address statement
-
Call
getregto obtain a location for the result .- If is in a register that holds no other live value and is not used later, that register may be chosen.
- Otherwise, use an empty register if one exists.
- Otherwise, pick an occupied register, store its value to memory (
ST) if it is not already there, and use that register (spill).
-
Consult the address descriptor of to find its current location (prefer a register). If is not in , generate
MOV b', L, which loads the value of into . -
Find the current location of from its address descriptor (a register is preferred). Generate
OP c', Lso that now holds .
-
Update the descriptors.
- The address descriptor of becomes only, and the register descriptor of becomes .
- is removed from the address descriptor of any other name.
-
If the current values of and/or are in registers, and there is no further use of them (they are not live, from the next-use information), delete those registers from the address descriptors and mark them as free in the register descriptors.
For the copy statement : if is in a register, no instruction is generated and the register is simply added to the address descriptor of . Otherwise MOV b, R is generated. At the end of a basic block, all live variables whose values are only in registers are stored in memory with ST.
Example: t = a - b with a in R0 and b in R1 where a is not used again:
- Generate
SUB R1, R0, and R0 now holdst(R0 = a − b). - Address descriptor of
t: {R0}. R1 still holdsb.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2081
Define core item. Compute the LR(1) item sets for the following grammar:
S → AA
A → aA ∣ b
Answer
Core of an item
The core of an LR(1) item is its first component, the LR(0) item (the production with a dot, without the lookahead). The core of a set of LR(1) items is the set of cores of its items. Two states with the same core but different lookaheads can be merged in the LALR method.
LR(1) item sets for
The grammar is augmented with .
- : ; ; ;
- :
- : ; ;
- : ; ;
- :
- :
- : ; ;
- :
- :
- :
The remaining transitions are , , and .
Here the lookahead for the closure of is for the first , and for the second . This is why and have the same core but different lookaheads ( and ); the pairs , and have the same cores. There are ten states, to .
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2081
How do you represent recursion in an activation tree? Generate the three-address code for the following instruction:
n = (a + b) * (c - d);
for(i = 0; i < n; i++) {
for(j = 0; j < n; j++) {
x = n + i + j;
}
}
Answer
Recursion in an activation tree
In an activation tree each call of a procedure is a separate node. When a procedure calls itself, the new activation becomes a child of the activation that made the call, so a chain of nodes with the same procedure name is formed. The depth of this chain equals the recursion depth, and the control stack holds the path from the root to the current node.
For fact(n) = n * fact(n-1) with fact(1) = 1, calling fact(3) gives
main
└── fact(3)
└── fact(2)
└── fact(1)
For a function with two recursive calls, such as fib(3) = fib(2) + fib(1), the tree branches:
fib(3)
├── fib(2)
│ ├── fib(1)
│ └── fib(0)
└── fib(1)
The nodes are visited depth-first and left to right. A node is created when the call begins and finished when it returns.
Three-address code
n = (a + b) * (c - d);
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
x = n + i + j;
}
}
1: t1 = a + b
2: t2 = c - d
3: n = t1 * t2
4: i = 0
5: if i >= n goto 17
6: j = 0
7: if j >= n goto 14
8: t3 = n + i
9: t4 = t3 + j
10: x = t4
11: t5 = j + 1
12: j = t5
13: goto 7
14: t6 = i + 1
15: i = t6
16: goto 5
17: (next statement)
Statements 7 to 13 are the inner loop and statements 5 to 16 are the outer loop. The loop test jumps to 14 when the inner loop ends, so i++ is executed and the outer test is repeated, and it jumps to 17 when the outer loop ends.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2081
What are the techniques for compiler optimization? Explain.
Answer
Compiler optimization transforms code, without changing its meaning, to make it faster or smaller. The main techniques are the following.
1. Constant folding. Expressions whose operands are constants are evaluated at compile time.
x = 3 * 4 + 2 becomes x = 14.
2. Constant and copy propagation. A variable known to hold a constant (or a copy of another variable) is replaced by that value.
a = 5; b = a + 2 becomes b = 5 + 2, which then folds to b = 7.
3. Common sub-expression elimination. An expression already computed, whose operands have not changed, is reused.
t1 = a + b; ...; t2 = a + b becomes t2 = t1.
4. Dead-code elimination. Statements that are unreachable or whose results are never used are removed.
5. Strength reduction. An expensive operation is replaced by a cheaper one.
x * 2 becomes x << 1 (or x + x), and x * 8 becomes x << 3.
6. Loop optimizations.
- Code motion: loop-invariant computations are moved out of the loop.
- Induction-variable elimination and strength reduction: a multiplication by the loop counter is replaced by an addition.
- Loop unrolling: the loop body is replicated to reduce the branch overhead.
- Loop jamming (fusion): two loops over the same range are merged into one.
7. Peephole optimization. A small window of target instructions is examined and replaced by a shorter or faster sequence. This includes removing redundant loads and stores (ST R, a; LD a, R), removing jumps to jumps, and algebraic simplification (x + 0, x * 1).
8. Function inlining. A call is replaced by the body of the function, which removes the call overhead.
9. Algebraic simplification. For example, , and are replaced by , and .
Optimizations are classified as machine-independent (applied to the intermediate code) and machine-dependent (applied to the target code, such as register allocation and peephole). Each must preserve the program's observable behaviour.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2081
Describe the synthesized attribute and inherited attribute with an example.
Answer
Syntax-directed definition (SDD) attaches attributes to grammar symbols and semantic rules to productions. Attributes are of two kinds.
(a) Synthesized attribute
An attribute of a non-terminal at a parse-tree node is synthesized if its value is computed only from the attribute values of the node's children (and possibly the node itself). Information flows bottom-up. A terminal's synthesized attributes (such as lexval) come from the lexical analyzer. An SDD that uses only synthesized attributes is called S-attributed, and it can be evaluated during bottom-up (LR) parsing.
Example (desk calculator):
| Production | Semantic rule |
|---|---|
print(E.val) | |
E.val = E1.val + T.val | |
E.val = T.val | |
T.val = T1.val * F.val | |
T.val = F.val | |
F.val = E.val | |
F.val = digit.lexval |
For 3 * 5 + 4 n the values flow upward, giving and .
(b) Inherited attribute
An attribute of a node is inherited if its value is computed from the attribute values of its parent and/or siblings. Information flows top-down or sideways. Inherited attributes are convenient for passing context, such as the type in a declaration.
Example (declaration int a, b, c):
| Production | Semantic rule |
|---|---|
L.in = T.type | |
T.type = integer | |
T.type = real | |
L1.in = L.in; addtype(id.entry, L.in) | |
addtype(id.entry, L.in) |
Here L.in is inherited: the type obtained from is passed to and then down to each identifier, so a, b and c all get type integer.
Difference in brief: a synthesized attribute depends on children (bottom-up); an inherited attribute depends on parent or left siblings (top-down). Inherited attributes need an order of evaluation that follows the dependency graph (an L-attributed definition allows evaluation in one left-to-right depth-first pass).
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2081
What is a type expression? List the properties of LL(1) grammar.
Answer
(a) Type expression
A type expression is a notation that denotes the type of a language construct. It is either a basic type or is formed by applying a type constructor to other type expressions. The compiler uses type expressions in type checking and in calculating storage.
- Basic types:
integer,real,char,boolean,void(no value), andtype_error(signals an error). - Type names: a name given to a type expression is itself a type expression.
- Constructors:
- Array: if is a type expression, is a type expression, where is the index range. Example:
int A[10]has type . - Product: is a type expression (used for argument lists and tuples).
- Record: for fields and their types.
- Pointer: is the type "pointer to an object of type ".
- Function: is the type of a function from domain type to range type . Example:
int f(int, char)has type .
- Array: if is a type expression, is a type expression, where is the index range. Example:
(b) Properties of an LL(1) grammar
A grammar is LL(1) if it can be parsed by a predictive parser that scans the input Left to right, builds a Leftmost derivation and uses 1 symbol of lookahead. The properties are:
- The grammar is not ambiguous.
- The grammar has no left recursion.
- The grammar is left factored (no two alternatives of a non-terminal start with a common prefix).
- For every non-terminal with productions :
- ;
- at most one of can derive ;
- if , then .
- Each entry of the predictive parsing table holds at most one production (no multiply-defined entries).
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2082 · 10 marks
Define important state. Why do we need augmented regular expression? Convert the regular expression “(a + b)*a(a + b)” to DFA.
Answer
(a) Important state
While constructing a DFA directly from a regular expression, the positions (leaves) of the syntax tree are numbered. A DFA state is a set of such positions. A position is important if it corresponds to a symbol of the alphabet (a leaf labelled with an input symbol or the end-marker #), as opposed to a position labelled or an operator node. An important state is a set of positions that contains at least one important position, and only important positions are kept because they alone determine the transitions. The final states are those that contain the position of the end-marker #.
(b) Need of augmented regular expression
The regular expression is augmented as , where # is a unique end-marker that is not in the alphabet.
- It gives the accepting state a distinct position, so a state is accepting exactly when it contains the position of
#. - Without it, the rightmost leaf of could not be told apart from an ordinary leaf, so the final state would be unrecognizable.
- It makes every position of (including the last ones) have a non-empty set, so the method works uniformly.
(c) DFA for
Augmented expression: . Number the leaves: .
Nullable: only the starred part is nullable.
followpos:
| Position | followpos |
|---|---|
| 1 | {1,2,3} |
| 2 | {1,2,3} |
| 3 | {4,5} |
| 4 | {6} |
| 5 | {6} |
Subset construction (positions of a: 1,3,4; positions of b: 2,5):
- (start)
- on :
- on :
-
- on :
- on :
- (contains 6, final)
- on :
- on :
- (final)
- on :
- on :
Transition table:
| State | a | b |
|---|---|---|
| →A | B | A |
| B | C | D |
| *C | C | D |
| *D | B | A |
The DFA has 4 states, with and accepting. It accepts exactly the strings over whose second-last symbol is .
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2082 · 10 marks
How do you create a control flow graph from a basic block? Explain the need and criteria for code optimization.
Answer
(a) Control flow graph from basic blocks
A basic block is a maximal sequence of consecutive three-address statements with a single entry (the first statement) and a single exit (the last), with no jumps into or out of the middle.
Step 1: find leaders.
- The first statement of the program is a leader.
- Any statement that is the target of a conditional or unconditional jump is a leader.
- Any statement that immediately follows a jump is a leader.
Step 2: form blocks. Each leader and all statements up to (not including) the next leader form one basic block.
Step 3: build the graph. The blocks are the nodes of the flow graph. There is a directed edge from block to block if can immediately follow in some execution, that is:
- there is a jump (conditional or unconditional) from the end of to the first statement of ; or
- immediately follows in the program order and does not end in an unconditional jump.
An extra entry node (and an exit node) are often added. The result is the control flow graph (CFG), which is used for loop detection and global optimization.
(b) Need for code optimization
The code produced by the front end and straightforward code generation is often slow and large. Optimization transforms it, without changing the meaning, to:
- reduce execution time and so improve program speed;
- reduce memory usage (code and data size);
- reduce power consumption, important for embedded and mobile devices;
- let programmers write clear, readable source code while the compiler produces efficient target code.
(c) Criteria for code optimization
- Semantic preservation: the optimization must not change the meaning (output) of the program.
- Worthwhile improvement: it should improve the common case, such as inner loops, by a measurable amount on average.
- Effort versus benefit: the compilation time and complexity of the optimization must be reasonable and worth the gain.
- Machine independence where possible: transformations such as constant folding, common subexpression elimination, dead-code elimination, strength reduction and loop-invariant code motion apply to intermediate code regardless of the target machine.
- Correctness and safety: the transformation must be provably valid under the data-flow information available.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2082 · 10 marks
List some limitations of Recursive Descent Parsing. Construct a syntax directed translation scheme that translates an arithmetic expression from infix notation into postfix notation. Also create an annotated parse tree for the input 9 – 5 + 2.
Answer
(a) Limitations of recursive descent parsing
- It cannot handle left-recursive grammars (it would loop forever), so left recursion must be eliminated first.
- The grammar must be left factored; otherwise, backtracking is needed, which is slow and can take exponential time.
- It is a top-down method, so it cannot parse all context-free grammars; it handles only LL-type grammars, which is a smaller class than LR grammars.
- The code must be rewritten whenever the grammar changes, since there is one procedure per non-terminal.
- Deep recursion uses the run-time stack, which may overflow on large inputs; error reporting and recovery are harder, and semantic actions are awkward with backtracking.
(b) Syntax-directed translation scheme: infix to postfix
Grammar after the actions are embedded (left-associative, with the semantic actions at the end of each production body):
The same translation as a syntax-directed definition with a synthesized attribute t (the postfix string):
| Production | Semantic rule |
|---|---|
expr.t = expr1.t || term.t || '+' | |
expr.t = expr1.t || term.t || '-' | |
expr.t = term.t | |
term.t = 'digit' |
(|| means string concatenation.)
(c) Annotated parse tree for 9 – 5 + 2
Since and are left associative, the input is parsed as .
expr.t = "95-2+"
├── expr.t = "95-"
│ ├── expr.t = "9"
│ │ └── term.t = "9"
│ │ └── 9
│ ├── -
│ └── term.t = "5"
│ └── 5
├── +
└── term.t = "2"
└── 2
Evaluating bottom-up: "9", "5", "2"; the inner "9" "5" "-" = "95-"; the root "95-" "2" "+" = "95-2+".
With the translation scheme, the actions fire in the order of a depth-first traversal and print , the postfix form.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2082 · 5 marks
Simulate the process of execution in different phases of a compiler for the statement “A * B + C – 2.5”.
Answer
Statement: A * B + C - 2.5 (assume A, B, C are declared int).
1. Lexical analysis (produces tokens):
<id,1> <*> <id,2> <+> <id,3> <-> <real,2.5>
The symbol table holds A (1), B (2), C (3), each of type int.
2. Syntax analysis (builds the parse/syntax tree using the precedence of * over + and -, with left associativity):
-
/ \
+ 2.5
/ \
* C
/ \
A B
That is, .
3. Semantic analysis (type checking): and are int, but 2.5 is real, so the compiler inserts a conversion:
-
/ \
inttofloat 2.5
|
+
/ \
* C
/ \
A B
4. Intermediate code generation (three-address code):
t1 = A * B
t2 = t1 + C
t3 = inttofloat(t2)
t4 = t3 - 2.5
5. Code optimization: no common subexpression or dead code exists; the code is already minimal (the constant 2.5 is already a literal), so it stays as it is.
6. Code generation (target assembly):
LD R1, A
LD R2, B
MUL R1, R1, R2
LD R2, C
ADD R1, R1, R2
ITOF R1, R1
SUBF R1, R1, #2.5
ST t4, R1
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2082 · 5 marks
Define tokens, pattern and lexemes. Mention the conflicts in shift-reduce parsing.
Answer
(a) Token, pattern and lexeme
- Token: a pair consisting of a token name and an optional attribute value; it is the unit passed from the lexical analyzer to the parser. Examples:
id,number,if,relop. - Pattern: a description of the form that the lexemes of a token may take, usually written as a regular expression. Example: the pattern for
idis . - Lexeme: the actual sequence of characters in the source program that matches the pattern of a token. Example: in
count = 10,countis a lexeme of the tokenidand10is a lexeme of the tokennumber.
| Token | Sample lexemes | Pattern |
|---|---|---|
| id | sum, x1 | letter followed by letters/digits |
| number | 3.14, 10 | numeric constant |
| if | if | the characters i, f |
(b) Conflicts in shift-reduce parsing
A bottom-up parser may reach a configuration in which it cannot decide the next action using the stack contents and the lookahead. This happens with ambiguous or non-LR(k) grammars, and there are two kinds of conflict:
- Shift/reduce conflict: the parser cannot decide whether to shift the next input symbol or to reduce by a production. Classic example: the dangling-else, in
if E then Sversusif E then S else S. - Reduce/reduce conflict: the parser can reduce by two or more different productions and cannot decide which. Example: and with the same handle and the same lookahead.
Both are reflected as multiply-defined entries in the parsing table. They are resolved by rewriting the grammar, using precedence/associativity rules, or using a stronger parser such as LALR/CLR.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2082 · 5 marks
Explain the types of data structures used in a symbol table.
Answer
A symbol table stores information about identifiers (name, type, scope, memory location, and so on). It must support fast insertion and lookup. The commonly used data structures are as follows.
1. Linear list (array or linked list)
- Records are stored one after another; a pointer marks the end of the table.
- Insertion is (add at the end) but lookup is a linear search, .
- Simple and uses minimal space, but slow for large programs.
2. Ordered (sorted) list
- Entries are kept sorted by name, so lookup can use binary search, for an array.
- Insertion costs because of shifting to keep the order.
3. Binary search tree
- Each node holds a symbol; the left subtree holds smaller names and the right subtree larger names.
- Average lookup and insertion are , but a skewed tree degrades to unless it is balanced (AVL/red-black).
4. Hash table
- A hash function maps the name to a bucket index; collisions are resolved by chaining or open addressing.
- Average lookup and insertion are , so this is the most popular and efficient choice in compilers. Its drawback is the extra space and the need for a good hash function.
5. Scoped tables (stack of tables)
- For block-structured languages, a separate table is kept per scope and the tables are chained (stack). A search starts in the innermost scope and proceeds outward; the table is dropped when the scope closes.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2082 · 5 marks
Convert the following expression to 3AC (Three Address Code):
A = B + C + D
E = D – A * C
MUL(A, E)
Answer
Three-address code (temporaries ) for the given statements:
Statement 1: A = B + C + D, evaluated as .
t1 = B + C
t2 = t1 + D
A = t2
Statement 2: E = D – A * C, where * has higher precedence.
t3 = A * C
t4 = D - t3
E = t4
Statement 3: MUL(A, E), a procedure/function call with two arguments.
param A
param E
t5 = call MUL, 2
Complete code:
(1) t1 = B + C
(2) t2 = t1 + D
(3) A = t2
(4) t3 = A * C
(5) t4 = D - t3
(6) E = t4
(7) param A
(8) param E
(9) t5 = call MUL, 2
Each statement has at most one operator on the right-hand side and at most three addresses, as three-address code requires.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2082 · 5 marks
Discuss the role of parser. Describe the language generated by the grammar S → SS+ | SS* | a.
Answer
(a) Role of the parser
The parser is the second phase of the compiler. It:
- Obtains a stream of tokens from the lexical analyzer on demand.
- Verifies that the token stream can be generated by the grammar of the source language, that is, checks syntax.
- Builds the parse tree (or syntax tree) that is passed to later phases (semantic analysis and intermediate code generation).
- Detects and reports syntax errors with clear messages (and recovers, e.g. by panic mode, so that parsing continues).
- Collects information into the symbol table and may carry out semantic actions (syntax-directed translation).
Parsers are either top-down (LL, recursive descent) or bottom-up (LR, shift-reduce).
(b) Language of
Each production puts the operator after its two operands, so the strings are in postfix notation. Starting from as the operand:
- is generated by ;
- if and are strings of the language, then and are also strings of the language.
Examples: , , , .
Hence the language is the set of all postfix expressions with operands and the binary operators and , that is, correctly formed postfix expressions over . For example, is and is .
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2082 · 5 marks
Compute the LR(0) item set for the grammar, S → aSa | bSb | d.
Answer
Grammar: .
Augment with . The canonical collection of LR(0) item sets is built using closure and goto.
= closure():
= goto(, ):
= goto(, ):
= goto(, ):
= goto(, ):
= goto(, ):
= goto(, ):
= goto(, ):
= goto(, ):
Other transitions: goto(, ) = goto(, ) = ; goto(, ) = goto(, ) = ; goto(, ) = goto(, ) = .
So the collection is (9 item sets). is the accepting state (on $); are the reduce states.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2082 · 5 marks
What do you understand by left-recursion and left-factoring? Discuss about type conversion.
Answer
(a) Left recursion
A grammar is left recursive if some non-terminal has a derivation . Top-down parsers loop forever on it. For immediate left recursion
it is removed by:
Example: becomes , .
(b) Left factoring
When two or more alternatives of a non-terminal begin with the same prefix, a predictive parser cannot choose between them with one lookahead. Left factoring defers the decision:
Example: becomes , .
(c) Type conversion
When operands of an operation have different types, the compiler converts one to the other.
- Implicit conversion (coercion): done automatically by the compiler, normally by widening (e.g.
inttofloat). Forx + iwithfloat xandint i, the compiler emitst1 = inttofloat(i)thent2 = x + t1. - Explicit conversion (cast): written by the programmer, e.g.
(int) 3.7.
The widening hierarchy is char → int → long → float → double. Narrowing (e.g. float to int) may lose information and usually needs an explicit cast. The type checker finds the common type using the hierarchy and inserts the conversion operations in the intermediate code.
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
- 2082 · 5 marks
Define inherited and synthesized attribute. Compute the FOLLOW set for the following grammar:
S → (L) | b, L → SL’, L’ → ,SL’ | ε
Answer
(a) Attributes
- Synthesized attribute: the value of an attribute at a node is computed from the attributes of its children (information flows bottom-up). Example: .
- Inherited attribute: the value of an attribute at a node is computed from the attributes of its parent and/or siblings (information flows top-down or sideways). Example: in a declaration.
(b) FOLLOW sets
Grammar:
FIRST sets (needed): , , .
Rules applied:
- contains
$because is the start symbol. - :
)follows , so . - : is in . Since is nullable, is also added, and .
- : is in , and since is nullable .
Result:
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 1 MCQ
Loading…
- 2082 · 5 marks
Why do we need to check the type? Distinguish between DAG and syntax tree.
Answer
(a) Need for type checking
Type checking verifies that each operation is applied to operands of compatible types. It is needed to:
- Detect errors early at compile time, such as adding a string to an integer or calling a function with the wrong arguments, instead of failing at run time.
- Ensure type safety, so memory is not interpreted incorrectly (reliability and security).
- Decide which operator or version of an overloaded function to use and insert required type conversions (coercions).
- Provide the information (size and layout of types) needed for code generation and storage allocation.
(b) DAG versus syntax tree
| Basis | Syntax tree | DAG (directed acyclic graph) |
|---|---|---|
| Structure | A tree; every node has one parent | A graph in which a node can have more than one parent |
| Common subexpressions | Repeated subexpressions appear as separate copies | Identical subexpressions are represented once and shared |
| Size | Larger | Smaller, since nodes are shared |
| Example | b-c appears twice | b-c has a single node with two parents |
| Use | Representing structure of the source program | Optimization: detects common subexpressions and generates better code |
| Creation | Created by a new node for each construct | Before creating a node, the compiler checks whether an identical node exists (value numbering) |
Answer checked by Claude Sonnet · Oct 21, 2026
Test yourself · 2 MCQs
Loading…
Questions from TU BSc CSIT 2075 Compiler Design and Construction (TU IOST board exam, 2075), TU BSc CSIT 2076 Compiler Design and Construction (TU IOST board exam, 2076), TU BSc CSIT 2078 Compiler Design and Construction (TU IOST board exam, 2078), TU BSc CSIT 2080 Compiler Design and Construction (TU IOST board exam, 2080), TU BSc CSIT 2081 Compiler Design and Construction (TU IOST board exam, 2081), TU BSc CSIT 2082 Compiler Design and Construction (new syllabus) (TU IOST board exam, 2082), TU BSc CSIT 2076 E-Commerce (TU IOST board exam, 2076), TU BSc CSIT 2078 E-Commerce (TU IOST board exam, 2078), TU BSc CSIT 2079 E-Commerce (TU IOST board exam, 2079), TU BSc CSIT 2080 E-Commerce (TU IOST board exam, 2080), TU BSc CSIT 2081 E-Commerce (TU IOST board exam, 2081), TU BSc CSIT 2076 E-Governance (TU IOST board exam, 2076), TU BSc CSIT 2078 E-Governance (TU IOST board exam, 2078), TU BSc CSIT 2079 E-Governance (TU IOST board exam, 2079), TU BSc CSIT 2080 E-Governance (TU IOST board exam, 2080), TU BSc CSIT 2081 E-Governance (TU IOST board exam, 2081), TU BSc CSIT 2076 NET Centric Computing (TU IOST board exam, 2076), TU BSc CSIT 2078 NET Centric Computing (TU IOST board exam, 2078), TU BSc CSIT 2079 NET Centric Computing (TU IOST board exam, 2079), TU BSc CSIT 2080 NET Centric Computing (TU IOST board exam, 2080), TU BSc CSIT 2081 NET Centric Computing (TU IOST board exam, 2081), TU BSc CSIT 2082 NET Centric Computing (TU IOST board exam, 2082), TU BSc CSIT 2076 Software Engineering (TU IOST board exam, 2076), TU BSc CSIT 2077 Software Engineering (TU IOST board exam, 2077), TU BSc CSIT 2079 Software Engineering (TU IOST board exam, 2079), TU BSc CSIT 2080 Software Engineering (TU IOST board exam, 2080), TU BSc CSIT 2081 Software Engineering (TU IOST board exam, 2081), TU BSc CSIT 2082 Software Engineering (TU IOST board exam, 2082), TU BSc CSIT 2074 Technical Writing (TU IOST board exam, 2074), TU BSc CSIT 2075 Technical Writing (TU IOST board exam, 2075), TU BSc CSIT 2078 Technical Writing (TU IOST board exam, 2078), TU BSc CSIT 2079 Technical Writing (TU IOST board exam, 2079), TU BSc CSIT 2080 Technical Writing (TU IOST board exam, 2080), TU BSc CSIT 2081 Technical Writing (TU IOST board exam, 2081) and TU BSc CSIT 2082 Technical Writing (TU IOST board exam, 2082). Answers are written for this site; check them against your class notes.