Skip to main content

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

BasisCompilerInterpreter
Translation unitTranslates the whole source program into target code in one goTranslates and executes one statement at a time
OutputProduces an object/machine code file that can be run repeatedlyProduces no separate object file
Execution speedFast, because translation is done once beforehandSlow, because every statement is re-analysed each time it runs
Error reportingReports all errors after scanning the whole programStops at the first erroneous statement
MemoryNeeds more memory for the object codeNeeds less memory
ExamplesC, 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 a and b into the table with their types, and detect redeclaration of the same name in the same scope.
  • Semantic analysis looks up a and b to find their types. It sees that b + 2.5 is float and that it is assigned to an int, 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 a and b to generate instructions such as t1 = b + 2.5; a = t1, and the stored sizes to allocate storage.
  • Scope handling: in a nested block { int a; ... } the inner a hides the outer a. 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

BasisRecursive descent parserNon-recursive predictive parser
ImplementationOne (recursive) procedure for each non-terminalTable-driven; an explicit stack and a parsing table M[A,a]M[A,a]
StackUses the implicit run-time call stackUses an explicit stack managed by the parser
DecisionDecided by code written in each procedureDecided by looking up the table with (stack top, input symbol)
BacktrackingMay backtrack if not predictiveNever backtracks (LL(1))
GrammarLeft recursion must be removed, may be hand-coded for any grammarMust be LL(1) (no left recursion, left factored)
ModificationCode must change when the grammar changesOnly the table changes; the driver stays the same

FIRST and FOLLOW

Grammar:

  • E→TAE \to TA
  • A→+TA∣εA \to +TA \mid \varepsilon
  • T→FBT \to FB
  • B→∗FB∣εB \to *FB \mid \varepsilon
  • F→(E)∣idF \to (E) \mid id

FIRST sets:

  • FIRST(F)={(,id}FIRST(F) = \{ (, id \}
  • FIRST(T)=FIRST(F)={(,id}FIRST(T) = FIRST(F) = \{ (, id \}
  • FIRST(E)=FIRST(T)={(,id}FIRST(E) = FIRST(T) = \{ (, id \}
  • FIRST(A)={+,ε}FIRST(A) = \{ +, \varepsilon \}
  • FIRST(B)={∗,ε}FIRST(B) = \{ *, \varepsilon \}

FOLLOW sets:

  • FOLLOW(E)FOLLOW(E): EE is the start symbol, so USD∈FOLLOW(E)USD \in FOLLOW(E), and EE is followed by )) in F→(E)F \to (E). So FOLLOW(E)={USD,)}FOLLOW(E) = \{ USD, ) \}.
  • FOLLOW(A)FOLLOW(A): from E→TAE \to TA, FOLLOW(A)⊇FOLLOW(E)FOLLOW(A) \supseteq FOLLOW(E), and from A→+TAA \to +TA it adds nothing new. So FOLLOW(A)={USD,)}FOLLOW(A) = \{ USD, ) \}.
  • FOLLOW(T)FOLLOW(T): from E→TAE \to TA it gets FIRST(A)∖{ε}={+}FIRST(A) \setminus \{\varepsilon\} = \{+\}, and since AA is nullable also FOLLOW(E)FOLLOW(E) and FOLLOW(A)FOLLOW(A). So FOLLOW(T)={+,USD,)}FOLLOW(T) = \{ +, USD, ) \}.
  • FOLLOW(B)FOLLOW(B): from T→FBT \to FB, FOLLOW(B)=FOLLOW(T)={+,USD,)}FOLLOW(B) = FOLLOW(T) = \{ +, USD, ) \}.
  • FOLLOW(F)FOLLOW(F): from T→FBT \to FB it gets {∗}\{*\}, and since BB is nullable also FOLLOW(T)FOLLOW(T). So FOLLOW(F)={∗,+,USD,)}FOLLOW(F) = \{ *, +, USD, ) \}.
Non-terminalFIRSTFOLLOW
EE{(,id}\{(, id\}{USD,)}\{USD, )\}
AA{+,ε}\{+, \varepsilon\}{USD,)}\{USD, )\}
TT{(,id}\{(, id\}{+,USD,)}\{+, USD, )\}
BB{∗,ε}\{*, \varepsilon\}{+,USD,)}\{+, USD, )\}
FF{(,id}\{(, id\}{∗,+,USD,)}\{*, +, USD, )\}

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 S→ES \to E, and we add S′→SS' \to S):

  1. S→ES \to E
  2. E→E+TE \to E+T
  3. E→TE \to T
  4. T→T∗FT \to T*F
  5. T→FT \to F
  6. F→idF \to id

Canonical LR(0) collection

  • I0I_0: S′→.SS' \to .S, S→.ES \to .E, E→.E+TE \to .E+T, E→.TE \to .T, T→.T∗FT \to .T*F, T→.FT \to .F, F→.idF \to .id
  • I1=goto(I0,S)I_1 = goto(I_0,S): S′→S.S' \to S.
  • I2=goto(I0,E)I_2 = goto(I_0,E): S→E.S \to E., E→E.+TE \to E.+T
  • I3=goto(I0,T)I_3 = goto(I_0,T): E→T.E \to T., T→T.∗FT \to T.*F
  • I4=goto(I0,F)I_4 = goto(I_0,F): T→F.T \to F.
  • I5=goto(I0,id)I_5 = goto(I_0,id): F→id.F \to id.
  • I6=goto(I2,+)I_6 = goto(I_2,+): E→E+.TE \to E+.T, T→.T∗FT \to .T*F, T→.FT \to .F, F→.idF \to .id
  • I7=goto(I3,∗)I_7 = goto(I_3,*): T→T∗.FT \to T*.F, F→.idF \to .id
  • I8=goto(I6,T)I_8 = goto(I_6,T): E→E+T.E \to E+T., T→T.∗FT \to T.*F
  • I9=goto(I7,F)I_9 = goto(I_7,F): T→T∗F.T \to T*F.

Other transitions: goto(I6,F)=I4goto(I_6,F) = I_4, goto(I6,id)=I5goto(I_6,id) = I_5, goto(I7,id)=I5goto(I_7,id) = I_5, goto(I8,∗)=I7goto(I_8,*) = I_7.

FOLLOW sets

  • FOLLOW(S)={USD}FOLLOW(S) = \{USD\}
  • FOLLOW(E)={USD,+}FOLLOW(E) = \{USD, +\}
  • FOLLOW(T)={USD,+,∗}FOLLOW(T) = \{USD, +, *\}
  • FOLLOW(F)={USD,+,∗}FOLLOW(F) = \{USD, +, *\}

SLR table (s = shift, r = reduce, acc = accept)

Stateid+*$SETF
0s51234
1acc
2s6r1
3r3s7r3
4r5r5r5
5r6r6r6
6s584
7s59
8r2s7r2
9r4r4r4

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

BasisStatic type checkingDynamic type checking
WhenAt compile time, before executionAt run time, while the program executes
Type informationTypes are bound to variables by declarationsTypes are attached to values and checked when operations are performed
Errors foundBefore the program runsOnly when the faulty statement is actually executed
SpeedNo run-time overheadRun-time overhead for tag checks
Safety/flexibilitySafer but less flexibleMore flexible but errors may appear late
ExamplesC, Java, PascalPython, JavaScript, Lisp

Type checking using a syntax-directed definition

Each expression EE has a synthesized attribute E.type. Each statement SS 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.

ProductionSemantic rule
S→id=ES \to id = ES.type = if lookup(id.entry) == E.type then void else type_error
S→if E then S1S \to if\ E\ then\ S_1S.type = if E.type == boolean then S1.type else type_error
S→while E do S1S \to while\ E\ do\ S_1S.type = if E.type == boolean then S1.type else type_error
S→S1;S2S \to S_1 ; S_2S.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 if and while must be of type boolean; 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

x=y op zx = y \ op \ z

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:

FieldPurpose
Actual parametersArguments passed by the caller
Returned valueSpace 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 statusReturn address, program counter and registers of the caller
Local dataLocal variables of the procedure
TemporariesValues from expression evaluation

Calling sequence and return sequence

Work is divided between the caller and the callee.

Caller (at call time)

  1. Evaluates the actual parameters and places them (in the callee's frame or in registers).
  2. Stores the return address and the old stack top pointer (control link) in the callee's frame.
  3. Increments the stack pointer to create the new activation record, and jumps to the callee's code.

Callee (on entry)

  1. Saves the registers and other machine status.
  2. Initializes its local data and sets the frame pointer.
  3. Begins executing its body.

Callee (on return)

  1. Places the return value in the returned-value field (or a register).
  2. Restores the registers and the stack top pointer, removing its own frame.
  3. Branches to the return address.

Caller (after return)

  1. Retrieves the returned value from the callee's frame (or register).
  2. 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 (ε+0)∗10(\varepsilon + 0)^*10 using the syntax tree method

Since (ε+0)∗=0∗(\varepsilon + 0)^* = 0^*, the language is 0∗100^*10. We follow the direct method anyway.

Step 1: Augment and number the leaves

The augmented expression is (ε+0)∗⋅1⋅0⋅#(\varepsilon + 0)^* \cdot 1 \cdot 0 \cdot \#. The leaves are numbered: 00 is position 1, 11 is position 2, 00 is position 3 and #\# is position 4. The ε\varepsilon leaf has no position.

Step 2: nullable, firstpos, lastpos

  • (ε+0)(\varepsilon+0): nullable, firstpos ={1}=\{1\}, lastpos ={1}=\{1\}.
  • (ε+0)∗(\varepsilon+0)^*: nullable, firstpos ={1}=\{1\}, lastpos ={1}=\{1\}.
  • Whole concatenation: since the star part is nullable, firstpos(root) ={1}∪{2}={1,2}=\{1\} \cup \{2\} = \{1,2\}.

Step 3: followpos

PositionSymbolfollowpos
10{1,2}\{1, 2\} (star repeats to 1; next is 2)
21{3}\{3\}
30{4}\{4\}
4#∅\emptyset

Step 4: Construct the DFA states

  • Start state A=firstpos(root)={1,2}A = firstpos(root) = \{1,2\}.
  • AA on 0: positions labelled 0 in AA is {1}\{1\}, followpos ={1,2}=A=\{1,2\} = A.
  • AA on 1: position {2}\{2\}, followpos ={3}=B=\{3\} = B.
  • BB on 0: position {3}\{3\}, followpos ={4}=C=\{4\} = C.
  • BB on 1: none. CC has no outgoing transitions.
  • CC contains position 4 (#\#), so it is the accepting state.

Transition table

State01
→A\to AAABB
BBCC−-
∗C*C−-−-

(A missing transition goes to the dead state.) The DFA has three states: A→0AA \xrightarrow{0} A, A→1BA \xrightarrow{1} B, B→0CB \xrightarrow{0} C, with CC final. It accepts exactly the strings 0∗100^*10, for example 1010, 00100010. 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 (x+x)∗a(x+x)*a

Taking the grammar E→E+E∣E∗E∣(E)∣x∣aE \to E+E \mid E*E \mid (E) \mid x \mid a, with ∗* binding tighter than ++ (the parenthesis forces the sum first):

StepStackInputAction
1$(x+x)*a$shift
2$(x+x)*a$shift
3$(x+x)*a$reduce E→xE \to x
4$(E+x)*a$shift
5$(E+x)*a$shift
6$(E+x)*a$reduce E→xE \to x
7$(E+E)*a$reduce E→E+EE \to E+E
8$(E)*a$shift
9$(E)*a$reduce E→(E)E \to (E)
10$E*a$shift
11$E*a$shift
12$E*a$reduce E→aE \to a
13$E*E$reduce E→E∗EE \to E*E
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:

  1. S→aAaS \to aAa
  2. S→bAbS \to bAb
  3. S→baS \to ba

The non-terminal AA has no productions in the given grammar, so it never derives a string. The only string actually generated by the grammar is baba, but the table is still built by the standard procedure. Augment with S′→SS' \to S.

Canonical LR(0) collection

  • I0I_0: S′→.SS' \to .S, S→.aAaS \to .aAa, S→.bAbS \to .bAb, S→.baS \to .ba
  • I1=goto(I0,S)I_1 = goto(I_0,S): S′→S.S' \to S.
  • I2=goto(I0,a)I_2 = goto(I_0,a): S→a.AaS \to a.Aa
  • I3=goto(I0,b)I_3 = goto(I_0,b): S→b.AbS \to b.Ab, S→b.aS \to b.a
  • I4=goto(I2,A)I_4 = goto(I_2,A): S→aA.aS \to aA.a
  • I5=goto(I4,a)I_5 = goto(I_4,a): S→aAa.S \to aAa.
  • I6=goto(I3,A)I_6 = goto(I_3,A): S→bA.bS \to bA.b
  • I7=goto(I3,a)I_7 = goto(I_3,a): S→ba.S \to ba.
  • I8=goto(I6,b)I_8 = goto(I_6,b): S→bAb.S \to bAb.

FOLLOW(S)={USD}FOLLOW(S) = \{USD\}, so all reductions are placed under USDUSD.

SLR table

Stateab$SA
0s2s31
1acc
24
3s76
4s5
5r1
6s8
7r3
8r2

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 AA, so only the string baba is accepted (shift bb, shift aa to state 7, reduce by S→baS \to ba on USDUSD, 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 EE 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.

ProductionSemantic rule
E→idE \to idE.type = lookup(id.entry)
E→E1 op E2E \to E_1\ op\ E_2E.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→E1 relop E2E \to E_1\ relop\ E_2E.type = if E1.type == E2.type and E1.type is a basic type then boolean else type_error
E→E1[E2]E \to E_1[E_2]E.type = if E2.type == integer and E1.type == array(s, t) then t else type_error
E→E1↑E \to E_1 \uparrowE.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 tt.
  • For the dereference operator ↑\uparrow, the operand must be a pointer to type tt, and the result is tt.

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 a=b∗−c+b∗−ca = b * -c + b * -c.

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 x=y op zx = y\ op\ z 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):

oparg1arg2result
0uminusct1
1*bt1t2
2uminusct3
3*bt3t4
4+t2t4t5
5=t5a

Triples have three fields (op, arg1, arg2), and a result is referred to by the index of the instruction that computed it:

oparg1arg2
0uminusc
1*b(0)
2uminusc
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 ii and j=4ij = 4i) 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

PhaseTypical errorsHandling
Lexical analysisIllegal characters, malformed numbers, unterminated strings, over-long identifiersPanic mode: delete characters until a valid token is found; insert, replace or transpose characters
Syntax analysisMissing or misplaced symbols such as ; or unbalanced parenthesesPanic mode (skip tokens until a synchronizing token such as ; or }), phrase-level recovery (local correction), error productions, global correction
Semantic analysisType mismatch, undeclared variable, wrong number of argumentsReport the error and continue by assigning an error type to the offending expression so it is not reported again
Intermediate code generationOperand type incompatibilities, too-large constantsReport and continue with a safe default
Code optimizationPossible unreachable code, division by zero found by constant foldingWarnings are generated
Code generationTarget machine limits, such as out-of-range address or insufficient registersReport 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:

  1. Read the source characters and group them into lexemes, producing a stream of tokens (identifier, keyword, operator, constant, punctuation) for the parser.
  2. Remove white space (blank, tab, newline) and comments.
  3. Correlate error messages with the source line numbers.
  4. Enter identifiers into the symbol table, and expand macros if the language needs it.
  5. 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 (Q,Σ,δ,q0,F)(Q, \Sigma, \delta, q_0, F) where QQ is a finite set of states, Σ\Sigma the input alphabet, δ:Q×Σ→Q\delta: Q \times \Sigma \to Q the transition function (exactly one next state for each state and symbol), q0∈Qq_0 \in Q the start state and F⊆QF \subseteq Q the set of accepting states.

DFA for (a+b)∗a(a+b)(a+b)^*a(a+b)

The language is the set of strings over {a,b}\{a,b\} whose second-last symbol is aa.

NFA: q0→a,bq0q_0 \xrightarrow{a,b} q_0, q0→aq1q_0 \xrightarrow{a} q_1, q1→a,bq2q_1 \xrightarrow{a,b} q_2, with q2q_2 final.

Subset construction:

DFA stateNFA stateson aon b
AA (start){q0}\{q_0\}BBAA
BB{q0,q1}\{q_0,q_1\}CCDD
CC (final){q0,q1,q2}\{q_0,q_1,q_2\}CCDD
DD (final){q0,q2}\{q_0,q_2\}BBAA

Working: BB on aa gives {q0,q1}∪{q2}={q0,q1,q2}=C\{q_0,q_1\} \cup \{q_2\} = \{q_0,q_1,q_2\} = C; BB on bb gives {q0}∪{q2}=D\{q_0\} \cup \{q_2\} = D; CC on aa gives {q0,q1,q2}\{q_0,q_1,q_2\}; CC on bb gives {q0,q2}\{q_0,q_2\}; DD on aa gives {q0,q1}\{q_0,q_1\}; DD on bb gives {q0}\{q_0\}.

The states remember the last two symbols read, so all four are distinguishable and the DFA is minimal. Check: abab goes A→B→DA \to B \to D (accepted), baba goes A→A→BA \to A \to B (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)

BasisLR(0)LR(1)
LookaheadUses no lookahead symbol when deciding a reduceUses one lookahead symbol in each item
ItemsA→α.βA \to \alpha . \beta[A→α.β, a][A \to \alpha . \beta,\ a]
Reduce actionReduce in the whole row of the stateReduce only on the lookahead symbols of the item
PowerWeakest; many grammars have conflictsMost powerful of the canonical methods
Number of statesFewerMany more (the same core may appear with different lookaheads)
Table sizeSmallLarge

Canonical LR(1) table for S→AAS \to AA, A→aA∣bA \to aA \mid b

Augment: S′→SS' \to S. Productions: 1 S→AAS \to AA, 2 A→aAA \to aA, 3 A→bA \to b.

Item sets

  • I0I_0: S′→.S,USDS' \to .S, USD; S→.AA,USDS \to .AA, USD; A→.aA,a/bA \to .aA, a/b; A→.b,a/bA \to .b, a/b
  • I1=goto(I0,S)I_1 = goto(I_0,S): S′→S.,USDS' \to S., USD
  • I2=goto(I0,A)I_2 = goto(I_0,A): S→A.A,USDS \to A.A, USD; A→.aA,USDA \to .aA, USD; A→.b,USDA \to .b, USD
  • I3=goto(I0,a)I_3 = goto(I_0,a): A→a.A,a/bA \to a.A, a/b; A→.aA,a/bA \to .aA, a/b; A→.b,a/bA \to .b, a/b
  • I4=goto(I0,b)I_4 = goto(I_0,b): A→b.,a/bA \to b., a/b
  • I5=goto(I2,A)I_5 = goto(I_2,A): S→AA.,USDS \to AA., USD
  • I6=goto(I2,a)I_6 = goto(I_2,a): A→a.A,USDA \to a.A, USD; A→.aA,USDA \to .aA, USD; A→.b,USDA \to .b, USD
  • I7=goto(I2,b)I_7 = goto(I_2,b): A→b.,USDA \to b., USD
  • I8=goto(I3,A)I_8 = goto(I_3,A): A→aA.,a/bA \to aA., a/b
  • I9=goto(I6,A)I_9 = goto(I_6,A): A→aA.,USDA \to aA., USD

Also goto(I3,a)=I3goto(I_3,a) = I_3, goto(I3,b)=I4goto(I_3,b) = I_4, goto(I6,a)=I6goto(I_6,a) = I_6, goto(I6,b)=I7goto(I_6,b) = I_7.

Parsing table

Stateab$SA
0s3s412
1acc
2s6s75
3s3s48
4r3r3
5r1
6s6s79
7r3
8r2r2
9r2

The table has no conflicts, so the grammar is LR(1). (Merging states with equal cores, I3/I6I_3/I_6, I4/I7I_4/I_7, I8/I9I_8/I_9, 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 E→E1+E2E \to E_1 + E_2: if both operands are int the type is int; if one is float and the other int or float, the type is float; otherwise it is type_error.
  • For an assignment id=Eid = E: the type of id and of E must be compatible.
  • For an array reference E1[E2]E_1[E_2]: E1E_1 must be an array and E2E_2 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

BasisCompilerInterpreter
TranslationTranslates the entire program to target code at onceTranslates and executes the program statement by statement
Intermediate/object codeGenerates an object (machine) code fileDoes not produce an object file
Execution speedFaster, since execution uses the already translated codeSlower, as each statement is translated every time it is executed
Error detectionShows all errors after compiling the whole programStops at the first error found while executing
MemoryRequires more memory (object code)Requires less memory
Source needed at run timeNot needed once compiledNeeded on every run
DebuggingHarder, since errors are reported togetherEasier, as the error is located at the statement being executed
ExamplesC, C++, FortranPython, 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.

FieldMeaning
Name (lexeme)The identifier string, which acts as the search key
Kind/categoryVariable, constant, function, procedure, label, type name, parameter, array or record
Data typeint, float, char, array of..., pointer to..., record
Scope/block levelWhere the name is visible (global, local, block number)
Storage/memory addressOffset or address allocated at run time (relative to the activation record or static area)
SizeNumber of bytes required; for arrays the number and bounds of dimensions
ValueThe value for constants (and sometimes initial value)
Parameter informationFor procedures: number, types and passing mode (by value, reference) of parameters and the return type
Line numbersLines on which the name is declared and referenced (for the cross-reference listing)
PointersLink 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 AA such that A⇒+AαA \Rightarrow^+ A\alpha for some string α\alpha. If the recursion appears directly in a production, A→AαA \to A\alpha, 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

A→Aα1∣Aα2∣β1∣β2⟹A→β1A′∣β2A′,A′→α1A′∣α2A′∣εA \to A\alpha_1 \mid A\alpha_2 \mid \beta_1 \mid \beta_2 \quad\Longrightarrow\quad A \to \beta_1 A' \mid \beta_2 A',\qquad A' \to \alpha_1 A' \mid \alpha_2 A' \mid \varepsilon

Given grammar

  • S→SB∣CaS \to SB \mid Ca
  • B→Bb∣cB \to Bb \mid c
  • C→aB∣aC \to aB \mid a

For SS: α=B\alpha = B, β=Ca\beta = Ca:

  • S→CaS′S \to CaS'
  • S′→BS′∣εS' \to BS' \mid \varepsilon

For BB: α=b\alpha = b, β=c\beta = c:

  • B→cB′B \to cB'
  • B′→bB′∣εB' \to bB' \mid \varepsilon

For CC: C→aB∣aC \to aB \mid a has no left recursion (and does not derive a string starting with CC), so it is unchanged.

Resulting grammar

  • S→CaS′S \to CaS'
  • S′→BS′∣εS' \to BS' \mid \varepsilon
  • B→cB′B \to cB'
  • B′→bB′∣εB' \to bB' \mid \varepsilon
  • C→aB∣aC \to aB \mid a

It generates the same language and has no left recursion. (A left factoring of CC gives C→aC′C \to aC', C′→B∣εC' \to B \mid \varepsilon 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 (x−x)−(x/x)(x-x)-(x/x)

Grammar: E→E−E∣E/E∣(E)∣xE \to E-E \mid E/E \mid (E) \mid x, where // has higher precedence than −- and −- is left associative. Conflicts are resolved by these rules, and the parenthesized subexpressions are handled first.

StepStackInputAction
1$(x-x)-(x/x)$shift
2$(x-x)-(x/x)$shift
3$(x-x)-(x/x)$reduce E→xE \to x
4$(E-x)-(x/x)$shift
5$(E-x)-(x/x)$shift
6$(E-x)-(x/x)$reduce E→xE \to x
7$(E-E)-(x/x)$reduce E→E−EE \to E-E
8$(E)-(x/x)$shift
9$(E)-(x/x)$reduce E→(E)E \to (E)
10$E-(x/x)$shift
11$E-(x/x)$shift
12$E-(x/x)$shift
13$E-(x/x)$reduce E→xE \to x
14$E-(E/x)$shift
15$E-(E/x)$shift
16$E-(E/x)$reduce E→xE \to x
17$E-(E/E)$reduce E→E/EE \to E/E
18$E-(E)$shift
19$E-(E)$reduce E→(E)E \to (E)
20$E-E$reduce E→E−EE \to E-E
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):

ProductionSemantic rule
L→E nL \to E\ nprint(E.val)
E→E1+TE \to E_1 + TE.val = E1.val + T.val
E→TE \to TE.val = T.val
T→T1∗FT \to T_1 * FT.val = T1.val * F.val
T→FT \to FT.val = F.val
F→(E)F \to (E)F.val = E.val
F→digitF \to digitF.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):

ProductionSemantic rule
D→T LD \to T\ LL.in = T.type
T→intT \to intT.type = integer
T→realT \to realT.type = real
L→L1,idL \to L_1 , idL1.in = L.in; addtype(id.entry, L.in)
L→idL \to idaddtype(id.entry, L.in)

Here type of TT is synthesized, while in of LL is inherited from its left sibling TT 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

x=y op zx = y \ op \ z

where x,y,zx, y, z 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: a=−b∗(c+d)/ea = -b * (c + d) / e

With unary minus having the highest precedence, then * and / left to right (so the expression is ((−b)∗(c+d))/e((-b) * (c+d)) / e), the three address code is:

t1 = uminus b
t2 = c + d
t3 = t1 * t2
t4 = t3 / e
a  = t4

Quadruples

No.oparg1arg2result
(0)uminusbt1
(1)+cdt2
(2)*t1t2t3
(3)/t3et4
(4)=t4a

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.
BasisStaticStackHeap
Decided atcompile timerun time (LIFO)run time (any order)
Recursionnoyesnot applicable
Speedfastestfastslow
Typical useglobalslocals, parametersdynamic 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:

  1. Unreachable code: statements after a return or goto, or inside a branch whose condition is always false.
  2. 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.

BasisOne-pass compilerMulti-pass compiler
TraversalReads the source once and generates code directlyReads the source or its intermediate form several times
Intermediate formUsually noneUses one or more intermediate representations
Speed and memoryFast, small memorySlower, needs more memory
OptimizationVery limitedExtensive optimization is possible
Forward referencesHard to handle (declare before use)Easy to handle
Modularity and portabilityLowHigh, phases are separate, so the front end and back end can be reused
ExamplesPascal, early C compilersModern C, C++ and Java compilers

LL(1) parsing table

Grammar:

S→ABCD,A→a∣ε,B→b,C→0∣ε,D→d∣εS \to ABCD,\quad A \to a \mid \varepsilon,\quad B \to b,\quad C \to 0 \mid \varepsilon,\quad D \to d \mid \varepsilon

FIRST sets:

  • FIRST(A)={a,ε}FIRST(A)=\{a,\varepsilon\}
  • FIRST(B)={b}FIRST(B)=\{b\}
  • FIRST(C)={0,ε}FIRST(C)=\{0,\varepsilon\}
  • FIRST(D)={d,ε}FIRST(D)=\{d,\varepsilon\}
  • FIRST(S)={a,b}FIRST(S)=\{a,b\}, since AA is nullable and BB is not.

FOLLOW sets:

  • FOLLOW(S)={USD}FOLLOW(S)=\{USD\}
  • FOLLOW(A)=FIRST(B)={b}FOLLOW(A)=FIRST(B)=\{b\}
  • FOLLOW(B)={0,d,USD}FOLLOW(B)=\{0,d,USD\}, from FIRST(C)FIRST(C), then FIRST(D)FIRST(D), then FOLLOW(S)FOLLOW(S) because both are nullable.
  • FOLLOW(C)={d,USD}FOLLOW(C)=\{d,USD\}
  • FOLLOW(D)=FOLLOW(S)={USD}FOLLOW(D)=FOLLOW(S)=\{USD\}

Table entries. Entry M[X,t]M[X,t] takes X→αX\to\alpha for every t∈FIRST(α)t\in FIRST(\alpha). If α\alpha is nullable, it also takes X→αX\to\alpha for every t∈FOLLOW(X)t\in FOLLOW(X).

ab0d$
SS → ABCDS → ABCD
AA → aA → ε
BB → b
CC → 0C → εC → ε
DD → dD → ε

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: letter (letter∣digit)∗letter\,(letter\mid digit)^*
  • number: digit+digit^+
  • relational operator: <, <=, ==, >, >=

The specification is converted into a finite automaton (regular expression, then NFA, then DFA). The lexer then works as follows:

  1. It starts in the initial state at the first unread character (lexemeBegin).
  2. It moves a forward pointer through the input and follows the DFA transition for each character.
  3. It remembers the last accepting state seen (longest-match rule). When no transition is possible, it stops.
  4. It retracts the forward pointer to the last accepting position. The characters between lexemeBegin and forward form the lexeme.
  5. 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)
  • c takes the DFA from the start state to S1, which is accepting.
  • o, u, n, t, 1 keep it in S1.
  • The space has no transition, so the lexeme count1 is accepted.
  • The token returned is <id, pointer to the symbol-table entry for count1>.
  • Then = gives <assign_op>, 25 gives <num, 25> and ; gives <semicolon>.

Regular expression to DFA: a(a+b)(b+c)∗a#a(a+b)(b+c)^*a\#

The direct method (followpos) is used. Number the positions:

a1 (a2+b3) (b4+c5)∗ a6 #7a_1\,(a_2+b_3)\,(b_4+c_5)^*\,a_6\,\#_7

Nullable and firstpos: (b4+c5)∗(b_4+c_5)^* is nullable and the others are not. So firstpos(root)={1}firstpos(root)=\{1\}.

followpos:

Positionfollowpos
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 A={1}A=\{1\}.

  • AA on aa: positions with symbol aa are 1, so the state is followpos(1)={2,3}=Bfollowpos(1)=\{2,3\}=B.
  • BB on aa: position 2, giving {4,5,6}=C\{4,5,6\}=C. BB on bb: position 3, giving {4,5,6}=C\{4,5,6\}=C.
  • CC on aa: position 6, giving {7}=D\{7\}=D. CC on bb: position 4, giving {4,5,6}=C\{4,5,6\}=C. CC on cc: position 5, giving CC.
  • DD has no outgoing transitions. It contains #\#, so it is the accepting state.
Stateabc
A = {1}B––
B = {2,3}CC–
C = {4,5,6}DCC
D = {7} (final)–––

The DFA accepts strings of the form a (a∣b) (b∣c)∗ aa\,(a|b)\,(b|c)^*\,a.

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:

  1. S→AaAbS\to AaAb
  2. A→BbBaA\to BbBa
  3. A→εA\to\varepsilon
  4. B→εB\to\varepsilon

The grammar is augmented with S′→SS'\to S. Each item has a lookahead.

Canonical collection of LR(1) items

  • I0I_0: S′→⋅S, USDS'\to\cdot S,\ USD; S→⋅AaAb, USDS\to\cdot AaAb,\ USD; A→⋅BbBa, aA\to\cdot BbBa,\ a; A→⋅, aA\to\cdot,\ a; B→⋅, bB\to\cdot,\ b
  • I1=goto(I0,S)I_1=goto(I_0,S): S′→S⋅, USDS'\to S\cdot,\ USD
  • I2=goto(I0,A)I_2=goto(I_0,A): S→A⋅aAb, USDS\to A\cdot aAb,\ USD
  • I3=goto(I0,B)I_3=goto(I_0,B): A→B⋅bBa, aA\to B\cdot bBa,\ a
  • I4=goto(I2,a)I_4=goto(I_2,a): S→Aa⋅Ab, USDS\to Aa\cdot Ab,\ USD; A→⋅BbBa, bA\to\cdot BbBa,\ b; A→⋅, bA\to\cdot,\ b; B→⋅, bB\to\cdot,\ b
  • I5=goto(I3,b)I_5=goto(I_3,b): A→Bb⋅Ba, aA\to Bb\cdot Ba,\ a; B→⋅, aB\to\cdot,\ a
  • I6=goto(I5,B)I_6=goto(I_5,B): A→BbB⋅a, aA\to BbB\cdot a,\ a
  • I7=goto(I6,a)I_7=goto(I_6,a): A→BbBa⋅, aA\to BbBa\cdot,\ a
  • I8=goto(I4,A)I_8=goto(I_4,A): S→AaA⋅b, USDS\to AaA\cdot b,\ USD
  • I9=goto(I8,b)I_9=goto(I_8,b): S→AaAb⋅, USDS\to AaAb\cdot,\ USD
  • I10=goto(I4,B)I_{10}=goto(I_4,B): A→B⋅bBa, bA\to B\cdot bBa,\ b
  • I11=goto(I10,b)I_{11}=goto(I_{10},b): A→Bb⋅Ba, bA\to Bb\cdot Ba,\ b; B→⋅, aB\to\cdot,\ a
  • I12=goto(I11,B)I_{12}=goto(I_{11},B): A→BbB⋅a, bA\to BbB\cdot a,\ b
  • I13=goto(I12,a)I_{13}=goto(I_{12},a): A→BbBa⋅, bA\to BbBa\cdot,\ b

LR(1) parsing table

Stateab$SAB
0r3r4123
1acc
2s4
3s5
4r3 / r4810
5r46
6s7
7r2
8s9
9r1
10s11
11r412
12s13
13r2

State 4 contains two complete items, A→⋅A\to\cdot and B→⋅B\to\cdot, with the same lookahead bb. This is a reduce/reduce conflict in the entry (4, b). So the grammar is not LR(1). After the first aa, a single symbol bb cannot decide whether AA derives ε\varepsilon (the input is abab) or the start of A→BbBaA\to BbBa (the input is abababab). 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

x=y op zx = y\ \text{op}\ z

where x,y,zx, y, z 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 AA is the parent of the node for BB if control flows from activation AA to activation BB. 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 E1E_1 and E2E_2 have type int, then E1+E2E_1 + E_2 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

  1. Portability. One front end can be combined with many back ends (and many source languages with one back end), so m×nm\times n compilers need only m+nm+n components.
  2. Machine independence. The front end does not depend on the target machine's registers or instruction set.
  3. Easier optimization. Machine-independent optimizations (constant folding, common sub-expression elimination, loop optimization) are done on the intermediate code.
  4. Simpler design. The compiler is divided into analysis and synthesis parts, which are easier to build, test and maintain.
  5. Retargeting. Only the back end needs to be rewritten for a new machine.
  6. 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

ProductionSemantic rule
L→E nL \to E\,\mathbf{n}print(E.val)
E→E1+TE \to E_1 + TE.val = E₁.val + T.val
E→TE \to TE.val = T.val
T→T1∗FT \to T_1 * FT.val = T₁.val × F.val
T→FT \to FT.val = F.val
F→(E)F \to (E)F.val = E.val
F→digitF \to \mathbf{digit}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 1515 is computed from 3×53\times5, and then 15+4=1915+4=19 is the value of the root EE, 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:

  1. 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.
  2. 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

E→E1+T{ print(′+′) }E→TT→T1∗F{ print(′∗′) }T→FF→(E)F→id{ print(id.name) }\begin{aligned} E &\to E_1 + T \quad \{\,\text{print}('+')\,\}\\ E &\to T\\ T &\to T_1 * F \quad \{\,\text{print}('*')\,\}\\ T &\to F\\ F &\to (E)\\ F &\to \mathbf{id}\quad \{\,\text{print}(\mathbf{id}.name)\,\} \end{aligned}

For the input a + b * c:

  • a is reduced to FF, and a is printed.
  • b and c are printed as they are reduced.
  • * is printed after T → T * F, and then + is printed after E → 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)
STATEACTIONGOTO
int*+()$ET
1S5S423
2ACC
3S6R2R2
4S5S473
5S8R4R4R4
6S5S493
7S10
8S5S411
9R1R1
10R5R5R5
11R3R3R3

Answer

The productions are numbered: (1) E→T+EE\to T+E, (2) E→TE\to T, (3) T→int∗TT\to int*T, (4) T→intT\to int, (5) T→(E)T\to(E).

The input is int * ( int + int ) $. The stack holds states. The initial stack is 1.

StepStackInputAction
11int * ( int + int ) $S5 (shift int)
21 5* ( int + int ) $S8
31 5 8( int + int ) $S4
41 5 8 4int + int ) $S5
51 5 8 4 5+ int ) $R4 (T→int): pop 1, goto(4,T)=3
61 5 8 4 3+ int ) $S6
71 5 8 4 3 6int ) $S5
81 5 8 4 3 6 5) $R4: pop 1, goto(6,T)=3
91 5 8 4 3 6 3) $R2 (E→T): pop 1, goto(6,E)=9
101 5 8 4 3 6 9) $R1 (E→T+E): pop 3, goto(4,E)=7
111 5 8 4 7) $S10
121 5 8 4 7 10$R5 (T→(E)): pop 3, goto(8,T)=11
131 5 8 11$R3 (T→int*T): pop 3, goto(1,T)=3
141 3$R2 (E→T): pop 1, goto(1,E)=2
151 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).

  1. The first statement of the program is a leader.
  2. Any statement that is the target of a conditional or unconditional jump is a leader.
  3. 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

  1. 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.
  2. Target program: absolute machine code, relocatable code or assembly, and the target machine's instruction set (RISC or CISC).
  3. Instruction selection: choosing the best instruction sequence for each IR statement, using machine idioms such as INC.
  4. Register allocation and assignment: deciding which values stay in registers (which is NP-complete), and handling register pairs.
  5. Evaluation order: the order of computation changes the number of registers and instructions needed.
  6. 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:

S→aAbCD∣ε,A→SD∣ε,C→Sa,D→aBD∣εS\to aAbCD\mid\varepsilon,\quad A\to SD\mid\varepsilon,\quad C\to Sa,\quad D\to aBD\mid\varepsilon

BB 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 D→aBDD\to aBD always begins with aa.

FIRST sets

  • FIRST(S)={a,ε}FIRST(S)=\{a,\varepsilon\}, from aAbCDaAbCD and ε\varepsilon.
  • FIRST(D)={a,ε}FIRST(D)=\{a,\varepsilon\}, from aBDaBD and ε\varepsilon.
  • FIRST(A)FIRST(A): from SDSD we take FIRST(S)∖{ε}={a}FIRST(S)\setminus\{\varepsilon\}=\{a\}. Since SS is nullable, we add FIRST(D)={a,ε}FIRST(D)=\{a,\varepsilon\}. Together with A→εA\to\varepsilon, this gives {a,ε}\{a,\varepsilon\}.
  • FIRST(C)FIRST(C): from SaSa we take {a}\{a\}. Since SS is nullable, we add aa, which is already in the set. So FIRST(C)={a}FIRST(C)=\{a\}.

FOLLOW sets

  • FOLLOW(S)FOLLOW(S) contains USDUSD (start symbol).
    • From A→SDA\to SD: FIRST(D)∖{ε}={a}FIRST(D)\setminus\{\varepsilon\}=\{a\}. DD is nullable, so FOLLOW(A)FOLLOW(A) is also added.
    • From C→SaC\to Sa: {a}\{a\}.
  • FOLLOW(A)FOLLOW(A): from S→aAbCDS\to aAbCD we get {b}\{b\}.
  • Therefore FOLLOW(S)={a,b,USD}FOLLOW(S)=\{a,b,USD\}.
  • FOLLOW(C)FOLLOW(C): from S→aAbCDS\to aAbCD, FIRST(D)∖{ε}={a}FIRST(D)\setminus\{\varepsilon\}=\{a\}. DD is nullable, so FOLLOW(S)={a,b,USD}FOLLOW(S)=\{a,b,USD\} is added. So FOLLOW(C)={a,b,USD}FOLLOW(C)=\{a,b,USD\}.
  • FOLLOW(D)FOLLOW(D): from S→aAbCDS\to aAbCD we get FOLLOW(S)FOLLOW(S). From A→SDA\to SD we get FOLLOW(A)={b}FOLLOW(A)=\{b\}. From D→aBDD\to aBD we get FOLLOW(D)FOLLOW(D) itself. So FOLLOW(D)={a,b,USD}FOLLOW(D)=\{a,b,USD\}.
Non-terminalFIRSTFOLLOW
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:

S→A∣B,A→a,B→aS\to A\mid B,\qquad A\to a,\qquad B\to a

After shifting aa, the state contains A→a⋅A\to a\cdot and B→a⋅B\to a\cdot. Both can be reduced on lookahead USDUSD (since FOLLOW(A)=FOLLOW(B)={USD}FOLLOW(A)=FOLLOW(B)=\{USD\}), so the table entry has two reductions (r3/r4r3/r4).

SLR parsing table

Grammar (augmented), with numbered productions:

  1. E′→EE'\to E
  2. E→(L)E\to(L)
  3. E→aE\to a
  4. L→L,EL\to L,E
  5. L→EL\to E

FOLLOW sets:

  • FOLLOW(E)={USD, , )}FOLLOW(E)=\{USD,\ ,\ )\}
  • FOLLOW(L)={ ), ,}FOLLOW(L)=\{\ ),\ ,\}

Canonical LR(0) item sets:

  • I0I_0: E′→⋅EE'\to\cdot E; E→⋅(L)E\to\cdot(L); E→⋅aE\to\cdot a
  • I1=goto(I0,E)I_1=goto(I_0,E): E′→E⋅E'\to E\cdot
  • I2=goto(I0, ′(′)I_2=goto(I_0,\,'('): E→(⋅L)E\to(\cdot L); L→⋅L,EL\to\cdot L,E; L→⋅EL\to\cdot E; E→⋅(L)E\to\cdot(L); E→⋅aE\to\cdot a
  • I3=goto(I0,a)I_3=goto(I_0,a): E→a⋅E\to a\cdot
  • I4=goto(I2,L)I_4=goto(I_2,L): E→(L⋅)E\to(L\cdot); L→L⋅,EL\to L\cdot,E
  • I5=goto(I2,E)I_5=goto(I_2,E): L→E⋅L\to E\cdot
  • I6=goto(I4, ′)′)I_6=goto(I_4,\,')'): E→(L)⋅E\to(L)\cdot
  • I7=goto(I4, ′,′)I_7=goto(I_4,\,','): L→L,⋅EL\to L,\cdot E; E→⋅(L)E\to\cdot(L); E→⋅aE\to\cdot a
  • I8=goto(I7,E)I_8=goto(I_7,E): L→L,E⋅L\to L,E\cdot
  • goto(I2,′(′)=goto(I7,′(′)=I2goto(I_2,'(')=goto(I_7,'(')=I_2 and goto(I2,a)=goto(I7,a)=I3goto(I_2,a)=goto(I_7,a)=I_3.

Table:

State()a,$EL
0s2s31
1acc
2s2s354
3r2r2r2
4s6s7
5r4r4
6r1r1r1
7s2s38
8r3r3

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 mm languages and nn machines is combined with the others, so only m+nm+n components are needed instead of m×nm\times n 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 treeDAG (directed acyclic graph)
A common sub-expression appears as repeated sub-treesA common sub-expression is represented once and shared by several parents
Each node has exactly one parentA node can have more than one parent
Needs more nodes and memoryNeeds fewer nodes
Shows the structure of the sourceAlso shows the reuse of values, so it helps to find common sub-expressions and generate better code

For example, in a+a∗(b−c)+(b−c)∗da + a * (b - c) + (b - c) * d the sub-expression b−cb-c occurs twice. The tree has two copies of it, and the DAG has one node with two parents.

Quadruple and triple for A=B+C−D∗E+GA = B + C - D * E + G

By precedence and left-to-right associativity, the expression is (((B+C)−(D∗E))+G)(((B+C) - (D*E)) + G). The three-address code is:

t1 = B + C
t2 = D * E
t3 = t1 - t2
t4 = t3 + G
A  = t4

Quadruples (op, arg1, arg2, result):

#oparg1arg2result
0+BCt1
1*DEt2
2−t1t2t3
3+t3Gt4
4=t4A

Triples (op, arg1, arg2), where a result is referred to by the number of the triple that produced it:

#oparg1arg2
0+BC
1*DE
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 BB has two lists of incomplete jumps:

  • B.truelistB.truelist: jumps taken when BB is true;
  • B.falselistB.falselist: jumps taken when BB is false.

The operations are:

  • makelist(i) creates a list holding only instruction index ii;
  • merge(p1, p2) concatenates two lists;
  • backpatch(p, i) sets the target of every jump on list pp to ii.

A marker non-terminal MM records M.instr = nextinstr (the index of the next instruction to be generated).

Example: if (a < b || c < d) x = 1;

Rule for B→B1 ∣∣ M B2B\to B_1\ ||\ M\ B_2: backpatch(B1.falselist, M.instr); B.truelist = merge(B1.truelist, B2.truelist); B.falselist = B2.falselist.

  1. For a < b, we generate
    • 100: if a < b goto _
    • 101: goto _
    • so B1.truelist={100}B_1.truelist=\{100\} and B1.falselist={101}B_1.falselist=\{101\}.
  2. M.instr=102M.instr=102.
  3. For c < d, we generate
    • 102: if c < d goto _
    • 103: goto _
    • so B2.truelist={102}B_2.truelist=\{102\} and B2.falselist={103}B_2.falselist=\{103\}.
  4. Reducing B→B1∣∣MB2B\to B_1||MB_2, we backpatch {101}\{101\} with 102102, so 101: goto 102. Then B.truelist={100,102}B.truelist=\{100,102\} and B.falselist={103}B.falselist=\{103\}.
  5. For if (B) M2 S1, with M2.instr=104M_2.instr=104, we backpatch B.truelistB.truelist with 104104, so 100: ... goto 104 and 102: ... goto 104. The statement 104: x = 1 is generated.
  6. S.nextlist={103}S.nextlist=\{103\}. 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: a(a+b)a#a(a+b)a\#

Positions: a1 (a2+b3) a4 #5a_1\,(a_2+b_3)\,a_4\,\#_5. firstpos(root)={1}firstpos(root)=\{1\}.

Positionfollowpos
1{2, 3}
2{4}
3{4}
4{5}
5none

States:

  • A={1}A=\{1\}. AA on aa gives followpos(1)={2,3}=Bfollowpos(1)=\{2,3\}=B.
  • BB on aa (position 2) gives {4}=C\{4\}=C. BB on bb (position 3) gives {4}=C\{4\}=C.
  • CC on aa (position 4) gives {5}=D\{5\}=D.
  • DD contains #\#, so it is the accepting state.
Stateab
A = {1}B–
B = {2,3}CC
C = {4}D–
D = {5} (final)––

The DFA accepts aaaaaa and abaaba.

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:

FieldPurpose
Actual parametersArguments passed by the caller
Returned valueSpace 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 statusReturn address, program counter and saved registers
Local dataLocal variables of the procedure
TemporariesValues 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:

S→(L)∣1,L→LS∣∗SS\to(L)\mid 1,\qquad L\to LS\mid{*}S

FIRST sets

  • FIRST(S)={ ( , 1 }FIRST(S)=\{\,(\,,\ 1\,\}.
  • FIRST(L)FIRST(L): the production L→LSL\to LS is left-recursive and begins with FIRST(L)FIRST(L) itself. The production L→∗SL\to{*}S gives ∗*. So FIRST(L)={∗}FIRST(L)=\{*\}.

FOLLOW sets

  • FOLLOW(S)FOLLOW(S) contains USDUSD (start symbol).
    • From L→LSL\to LS: SS is at the end, so FOLLOW(L)⊆FOLLOW(S)FOLLOW(L)\subseteq FOLLOW(S).
    • From L→∗SL\to{*}S: SS is at the end, so FOLLOW(L)⊆FOLLOW(S)FOLLOW(L)\subseteq FOLLOW(S).
  • FOLLOW(L)FOLLOW(L):
    • From S→(L)S\to(L): { ) }\{\,)\,\}.
    • From L→LSL\to LS: FIRST(S)={ ( ,1}FIRST(S)=\{\,(\,,1\}.
  • So FOLLOW(L)={ ) , ( , 1}FOLLOW(L)=\{\,)\,,\ (\,,\ 1\}.
  • And FOLLOW(S)={USD}∪FOLLOW(L)={USD, ) , ( , 1}FOLLOW(S)=\{USD\}\cup FOLLOW(L)=\{USD,\ )\,,\ (\,,\ 1\}.
Non-terminalFIRSTFOLLOW
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 LL (normally a register) in which the result of the instruction II is to be held.

Algorithm for the three-address statement a=b op ca = b\ \text{op}\ c

  1. Call getreg to obtain a location LL for the result aa.

    • If bb is in a register that holds no other live value and bb 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).
  2. Consult the address descriptor of bb to find its current location b′b' (prefer a register). If bb is not in LL, generate MOV b', L, which loads the value of bb into LL.

  3. Find the current location c′c' of cc from its address descriptor (a register is preferred). Generate

    OP c', L

    so that LL now holds b op cb\ \text{op}\ c.

  4. Update the descriptors.

    • The address descriptor of aa becomes {L}\{L\} only, and the register descriptor of LL becomes {a}\{a\}.
    • LL is removed from the address descriptor of any other name.
  5. If the current values of bb and/or cc 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 a=ba = b: if bb is in a register, no instruction is generated and the register is simply added to the address descriptor of aa. 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 holds t (R0 = a − b).
  • Address descriptor of t: {R0}. R1 still holds b.

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 [A→α⋅β, a][A\to\alpha\cdot\beta,\ a] is its first component, the LR(0) item A→α⋅βA\to\alpha\cdot\beta (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 S→AA, A→aA∣bS\to AA,\ A\to aA\mid b

The grammar is augmented with S′→SS'\to S.

  • I0I_0: S′→⋅S, USDS'\to\cdot S,\ USD; S→⋅AA, USDS\to\cdot AA,\ USD; A→⋅aA, a/bA\to\cdot aA,\ a/b; A→⋅b, a/bA\to\cdot b,\ a/b
  • I1=goto(I0,S)I_1=goto(I_0,S): S′→S⋅, USDS'\to S\cdot,\ USD
  • I2=goto(I0,A)I_2=goto(I_0,A): S→A⋅A, USDS\to A\cdot A,\ USD; A→⋅aA, USDA\to\cdot aA,\ USD; A→⋅b, USDA\to\cdot b,\ USD
  • I3=goto(I0,a)I_3=goto(I_0,a): A→a⋅A, a/bA\to a\cdot A,\ a/b; A→⋅aA, a/bA\to\cdot aA,\ a/b; A→⋅b, a/bA\to\cdot b,\ a/b
  • I4=goto(I0,b)I_4=goto(I_0,b): A→b⋅, a/bA\to b\cdot,\ a/b
  • I5=goto(I2,A)I_5=goto(I_2,A): S→AA⋅, USDS\to AA\cdot,\ USD
  • I6=goto(I2,a)I_6=goto(I_2,a): A→a⋅A, USDA\to a\cdot A,\ USD; A→⋅aA, USDA\to\cdot aA,\ USD; A→⋅b, USDA\to\cdot b,\ USD
  • I7=goto(I2,b)I_7=goto(I_2,b): A→b⋅, USDA\to b\cdot,\ USD
  • I8=goto(I3,A)I_8=goto(I_3,A): A→aA⋅, a/bA\to aA\cdot,\ a/b
  • I9=goto(I6,A)I_9=goto(I_6,A): A→aA⋅, USDA\to aA\cdot,\ USD

The remaining transitions are goto(I3,a)=I3goto(I_3,a)=I_3, goto(I3,b)=I4goto(I_3,b)=I_4, goto(I6,a)=I6goto(I_6,a)=I_6 and goto(I6,b)=I7goto(I_6,b)=I_7.

Here the lookahead for the closure of S→⋅AA, USDS\to\cdot AA,\ USD is FIRST(AUSD)={a,b}FIRST(AUSD)=\{a,b\} for the first AA, and USDUSD for the second AA. This is why I3I_3 and I6I_6 have the same core but different lookaheads (a/ba/b and USDUSD); the pairs I3,I6I_3, I_6, I4,I7I_4, I_7 and I8,I9I_8, I_9 have the same cores. There are ten states, I0I_0 to I9I_9.

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, x+0x+0, x×1x\times1 and x×0x\times0 are replaced by xx, xx and 00.

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 AA 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):

ProductionSemantic rule
L→E nL \to E\ \mathbf{n}print(E.val)
E→E1+TE \to E_1 + TE.val = E1.val + T.val
E→TE \to TE.val = T.val
T→T1∗FT \to T_1 * FT.val = T1.val * F.val
T→FT \to FT.val = F.val
F→(E)F \to (E)F.val = E.val
F→digitF \to \mathbf{digit}F.val = digit.lexval

For 3 * 5 + 4 n the values F.val=3,5,4F.val=3,5,4 flow upward, giving T.val=15T.val=15 and E.val=19E.val=19.

(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):

ProductionSemantic rule
D→T LD \to T\ LL.in = T.type
T→intT \to \mathbf{int}T.type = integer
T→realT \to \mathbf{real}T.type = real
L→L1,idL \to L_1 , \mathbf{id}L1.in = L.in; addtype(id.entry, L.in)
L→idL \to \mathbf{id}addtype(id.entry, L.in)

Here L.in is inherited: the type obtained from TT is passed to LL 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), and type_error (signals an error).
  • Type names: a name given to a type expression is itself a type expression.
  • Constructors:
    • Array: if TT is a type expression, array(I,T)array(I, T) is a type expression, where II is the index range. Example: int A[10] has type array(0..9,integer)array(0..9, integer).
    • Product: T1×T2T_1 \times T_2 is a type expression (used for argument lists and tuples).
    • Record: record((f1×T1)×(f2×T2))record((f_1 \times T_1) \times (f_2 \times T_2)) for fields and their types.
    • Pointer: pointer(T)pointer(T) is the type "pointer to an object of type TT".
    • Function: D→RD \to R is the type of a function from domain type DD to range type RR. Example: int f(int, char) has type integer×char→integerinteger \times char \to integer.

(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:

  1. The grammar is not ambiguous.
  2. The grammar has no left recursion.
  3. The grammar is left factored (no two alternatives of a non-terminal start with a common prefix).
  4. For every non-terminal AA with productions A→α∣βA \to \alpha \mid \beta:
    • FIRST(α)∩FIRST(β)=∅FIRST(\alpha) \cap FIRST(\beta) = \emptyset;
    • at most one of α,β\alpha,\beta can derive ε\varepsilon;
    • if β⇒∗ε\beta \Rightarrow^* \varepsilon, then FIRST(α)∩FOLLOW(A)=∅FIRST(\alpha) \cap FOLLOW(A) = \emptyset.
  5. Each entry of the predictive parsing table M[A,a]M[A,a] 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 ε\varepsilon 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 rr is augmented as (r)#(r)\#, 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 rr could not be told apart from an ordinary leaf, so the final state would be unrecognizable.
  • It makes every position of rr (including the last ones) have a non-empty followposfollowpos set, so the method works uniformly.

(c) DFA for (a+b)∗a(a+b)(a+b)^*a(a+b)

Augmented expression: (a+b)∗ a (a+b) #(a+b)^*\,a\,(a+b)\,\#. Number the leaves: a1,b2,a3,a4,b5,#6a_1, b_2, a_3, a_4, b_5, \#_6.

Nullable: only the starred part is nullable.

firstpos(root)={1,2,3}firstpos(root) = \{1,2,3\}

followpos:

Positionfollowpos
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):

  • A={1,2,3}A = \{1,2,3\} (start)
    • on aa: followpos(1)∪followpos(3)={1,2,3,4,5}=Bfollowpos(1)\cup followpos(3)=\{1,2,3,4,5\}=B
    • on bb: followpos(2)={1,2,3}=Afollowpos(2)=\{1,2,3\}=A
  • B={1,2,3,4,5}B = \{1,2,3,4,5\}
    • on aa: followpos(1)∪followpos(3)∪followpos(4)={1,2,3,4,5,6}=Cfollowpos(1)\cup followpos(3)\cup followpos(4)=\{1,2,3,4,5,6\}=C
    • on bb: followpos(2)∪followpos(5)={1,2,3,6}=Dfollowpos(2)\cup followpos(5)=\{1,2,3,6\}=D
  • C={1,2,3,4,5,6}C = \{1,2,3,4,5,6\} (contains 6, final)
    • on aa: {1,2,3,4,5,6}=C\{1,2,3,4,5,6\}=C
    • on bb: {1,2,3,6}=D\{1,2,3,6\}=D
  • D={1,2,3,6}D = \{1,2,3,6\} (final)
    • on aa: followpos(1)∪followpos(3)={1,2,3,4,5}=Bfollowpos(1)\cup followpos(3)=\{1,2,3,4,5\}=B
    • on bb: followpos(2)={1,2,3}=Afollowpos(2)=\{1,2,3\}=A

Transition table:

Stateab
→ABA
BCD
*CCD
*DBA

The DFA has 4 states, with CC and DD accepting. It accepts exactly the strings over {a,b}\{a,b\} whose second-last symbol is aa.

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.

  1. The first statement of the program is a leader.
  2. Any statement that is the target of a conditional or unconditional jump is a leader.
  3. 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 B1B_1 to block B2B_2 if B2B_2 can immediately follow B1B_1 in some execution, that is:

  • there is a jump (conditional or unconditional) from the end of B1B_1 to the first statement of B2B_2; or
  • B2B_2 immediately follows B1B_1 in the program order and B1B_1 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

  1. Semantic preservation: the optimization must not change the meaning (output) of the program.
  2. Worthwhile improvement: it should improve the common case, such as inner loops, by a measurable amount on average.
  3. Effort versus benefit: the compilation time and complexity of the optimization must be reasonable and worth the gain.
  4. 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.
  5. 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

  1. It cannot handle left-recursive grammars (it would loop forever), so left recursion must be eliminated first.
  2. The grammar must be left factored; otherwise, backtracking is needed, which is slow and can take exponential time.
  3. 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.
  4. The code must be rewritten whenever the grammar changes, since there is one procedure per non-terminal.
  5. 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):

expr→expr+term{ print(′+′) }expr→expr−term{ print(′−′) }expr→termterm→0 {print(′0′)}∣1 {print(′1′)}∣⋯∣9 {print(′9′)}\begin{aligned} expr &\to expr + term \quad \{\ print('+')\ \}\\ expr &\to expr - term \quad \{\ print('-')\ \}\\ expr &\to term\\ term &\to 0\ \{print('0')\} \mid 1\ \{print('1')\} \mid \dots \mid 9\ \{print('9')\} \end{aligned}

The same translation as a syntax-directed definition with a synthesized attribute t (the postfix string):

ProductionSemantic rule
expr→expr1+termexpr \to expr_1 + termexpr.t = expr1.t || term.t || '+'
expr→expr1−termexpr \to expr_1 - termexpr.t = expr1.t || term.t || '-'
expr→termexpr \to termexpr.t = term.t
term→0…9term \to 0 \ldots 9term.t = 'digit'

(|| means string concatenation.)

(c) Annotated parse tree for 9 – 5 + 2

Since ++ and −- are left associative, the input is parsed as (9−5)+2(9-5)+2.

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: term.t=term.t= "9", "5", "2"; the inner expr.t=expr.t = "9" ∥∥\|\| "5" ∥∥\|\| "-" = "95-"; the root expr.t=expr.t= "95-" ∥∥\|\| "2" ∥∥\|\| "+" = "95-2+".

With the translation scheme, the actions fire in the order of a depth-first traversal and print 9 5 − 2 +9\ 5\ -\ 2\ +, 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, ((A∗B)+C)−2.5((A*B)+C)-2.5.

3. Semantic analysis (type checking): A∗BA*B and (A∗B)+C(A*B)+C 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 id is letter (letter∣digit)∗letter\,(letter\mid digit)^*.
  • Lexeme: the actual sequence of characters in the source program that matches the pattern of a token. Example: in count = 10, count is a lexeme of the token id and 10 is a lexeme of the token number.
TokenSample lexemesPattern
idsum, x1letter followed by letters/digits
number3.14, 10numeric constant
ififthe 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:

  1. 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 S versus if E then S else S.
  2. Reduce/reduce conflict: the parser can reduce by two or more different productions and cannot decide which. Example: A→αA \to \alpha and B→αB \to \alpha with the same handle α\alpha 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 O(1)O(1) (add at the end) but lookup is a linear search, O(n)O(n).
  • 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, O(log⁡n)O(\log n) for an array.
  • Insertion costs O(n)O(n) 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 O(log⁡n)O(\log n), but a skewed tree degrades to O(n)O(n) 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 O(1)O(1), 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 tit_i) for the given statements:

Statement 1: A = B + C + D, evaluated as (B+C)+D(B + C) + D.

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:

  1. Obtains a stream of tokens from the lexical analyzer on demand.
  2. Verifies that the token stream can be generated by the grammar of the source language, that is, checks syntax.
  3. Builds the parse tree (or syntax tree) that is passed to later phases (semantic analysis and intermediate code generation).
  4. Detects and reports syntax errors with clear messages (and recovers, e.g. by panic mode, so that parsing continues).
  5. 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 S→SS+∣SS∗∣aS \to SS+ \mid SS* \mid a

Each production puts the operator after its two operands, so the strings are in postfix notation. Starting from aa as the operand:

  • aa is generated by S→aS \to a;
  • if xx and yy are strings of the language, then xy+xy+ and xy∗xy* are also strings of the language.

Examples: aa+aa+, aa∗aa*, aa+a∗aa+a*, aaa∗+aaa*+.

Hence the language is the set of all postfix expressions with operands aa and the binary operators ++ and ∗*, that is, correctly formed postfix expressions over {a,+,∗}\{a,+,*\}. For example, aa+a∗aa+a* is (a+a)∗a(a+a)*a and aaa∗+aaa*+ is a+(a∗a)a+(a*a).

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: S→aSa∣bSb∣dS \to aSa \mid bSb \mid d.

Augment with S′→SS' \to S. The canonical collection of LR(0) item sets is built using closure and goto.

I0I_0 = closure(S′→⋅SS' \to \cdot S):

  • S′→⋅SS' \to \cdot S
  • S→⋅aSaS \to \cdot aSa
  • S→⋅bSbS \to \cdot bSb
  • S→⋅dS \to \cdot d

I1I_1 = goto(I0I_0, SS): S′→S⋅S' \to S\cdot

I2I_2 = goto(I0I_0, aa):

  • S→a⋅SaS \to a\cdot Sa
  • S→⋅aSaS \to \cdot aSa
  • S→⋅bSbS \to \cdot bSb
  • S→⋅dS \to \cdot d

I3I_3 = goto(I0I_0, bb):

  • S→b⋅SbS \to b\cdot Sb
  • S→⋅aSaS \to \cdot aSa
  • S→⋅bSbS \to \cdot bSb
  • S→⋅dS \to \cdot d

I4I_4 = goto(I0I_0, dd): S→d⋅S \to d\cdot

I5I_5 = goto(I2I_2, SS): S→aS⋅aS \to aS\cdot a

I6I_6 = goto(I3I_3, SS): S→bS⋅bS \to bS\cdot b

I7I_7 = goto(I5I_5, aa): S→aSa⋅S \to aSa\cdot

I8I_8 = goto(I6I_6, bb): S→bSb⋅S \to bSb\cdot

Other transitions: goto(I2I_2, aa) = goto(I3I_3, aa) = I2I_2; goto(I2I_2, bb) = goto(I3I_3, bb) = I3I_3; goto(I2I_2, dd) = goto(I3I_3, dd) = I4I_4.

So the collection is {I0,I1,…,I8}\{I_0, I_1, \dots, I_8\} (9 item sets). I1I_1 is the accepting state (on $); I4,I7,I8I_4, I_7, I_8 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 AA has a derivation A⇒+AαA \Rightarrow^+ A\alpha. Top-down parsers loop forever on it. For immediate left recursion

A→Aα∣βA \to A\alpha \mid \beta

it is removed by:

A→βA′,A′→αA′∣εA \to \beta A', \qquad A' \to \alpha A' \mid \varepsilon

Example: E→E+T∣TE \to E + T \mid T becomes E→TE′E \to TE', E′→+TE′∣εE' \to +TE' \mid \varepsilon.

(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:

A→αβ1∣αβ2⟹A→αA′,  A′→β1∣β2A \to \alpha\beta_1 \mid \alpha\beta_2 \quad\Longrightarrow\quad A \to \alpha A',\ \ A' \to \beta_1 \mid \beta_2

Example: S→iEtS∣iEtSeS∣aS \to iEtS \mid iEtSeS \mid a becomes S→iEtSS′∣aS \to iEtSS' \mid a, S′→eS∣εS' \to eS \mid \varepsilon.

(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. int to float). For x + i with float x and int i, the compiler emits t1 = inttofloat(i) then t2 = 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: E.val=E1.val+T.valE.val = E_1.val + T.val.
  • 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: L.in=T.typeL.in = T.type in a declaration.

(b) FOLLOW sets

Grammar:

  • S→(L)∣bS \to (L) \mid b
  • L→SL′L \to SL'
  • L′→,SL′∣εL' \to ,SL' \mid \varepsilon

FIRST sets (needed): FIRST(S)={(,b}FIRST(S)=\{(, b\}, FIRST(L)={(,b}FIRST(L)=\{(, b\}, FIRST(L′)={, ε}FIRST(L')=\{,\ \varepsilon\}.

Rules applied:

  1. FOLLOW(S)FOLLOW(S) contains $ because SS is the start symbol.
  2. S→(L)S \to (L): ) follows LL, so )∈FOLLOW(L)) \in FOLLOW(L).
  3. L→SL′L \to SL': FIRST(L′)∖{ε}={,}FIRST(L') \setminus \{\varepsilon\} = \{,\} is in FOLLOW(S)FOLLOW(S). Since L′L' is nullable, FOLLOW(L)⊆FOLLOW(S)FOLLOW(L)\subseteq FOLLOW(S) is also added, and FOLLOW(L)⊆FOLLOW(L′)FOLLOW(L) \subseteq FOLLOW(L').
  4. L′→,SL′L' \to ,SL': FIRST(L′)∖{ε}={,}FIRST(L')\setminus\{\varepsilon\}=\{,\} is in FOLLOW(S)FOLLOW(S), and since L′L' is nullable FOLLOW(L′)⊆FOLLOW(S)FOLLOW(L') \subseteq FOLLOW(S).

Result:

FOLLOW(S)={ USD, ), , }FOLLOW(S) = \{\ USD,\ ),\ ,\ \}

FOLLOW(L)={ ) }FOLLOW(L) = \{\ )\ \}

FOLLOW(L′)={ ) }FOLLOW(L') = \{\ )\ \}

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:

  1. 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.
  2. Ensure type safety, so memory is not interpreted incorrectly (reliability and security).
  3. Decide which operator or version of an overloaded function to use and insert required type conversions (coercions).
  4. Provide the information (size and layout of types) needed for code generation and storage allocation.

(b) DAG versus syntax tree

BasisSyntax treeDAG (directed acyclic graph)
StructureA tree; every node has one parentA graph in which a node can have more than one parent
Common subexpressionsRepeated subexpressions appear as separate copiesIdentical subexpressions are represented once and shared
SizeLargerSmaller, since nodes are shared
Example a+a∗(b−c)+(b−c)∗da+a*(b-c)+(b-c)*db-c appears twiceb-c has a single node with two parents
UseRepresenting structure of the source programOptimization: detects common subexpressions and generates better code
CreationCreated by a new node for each constructBefore 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.