Compiler Design

105601
Back to Compiler Design

Module 1: Introduction, Lexical Analysis

  1. Q1a. Consider the statement: int total = 25; Which of the following represents a lexeme? (i) int (ii) 25 (iii) total (iv) All of the Above20262m

    Module 1: Introduction, Lexical Analysis

    Consider the statement: int total = 25; Which of the following represents a lexeme?
    (i) int
    (ii) 25
    (iii) total
    (iv) All of the Above

    View this question on its own page →
    Worked Solution

    Answer: (iv) All of the Above

    Explanation

    A lexeme is the actual sequence of characters in the source program that matches the pattern of a token. The scanner groups the characters of int total = 25; into these lexemes:

    Lexeme Token class
    int keyword
    total identifier
    = assignment operator
    25 integer constant
    ; separator

    So int, total and 25 are all lexemes.

    (iv) All of the Above\boxed{\text{(iv) All of the Above}}

  2. Q1b. The regular expression (a \mid b)^* represents: (i) Strings containing only a (ii) Strings containing only b (iii) All strings over \{a, b\}, including \varepsilon (iv) Strings beginning with ab20262m

    Module 1: Introduction, Lexical Analysis

    The regular expression (a∣b)∗(a \mid b)^* represents:
    (i) Strings containing only aa
    (ii) Strings containing only bb
    (iii) All strings over {a,b}\{a, b\}, including ε\varepsilon
    (iv) Strings beginning with abab

    View this question on its own page →
    Worked Solution

    Answer: (iii) All strings over {a,b}\{a, b\}, including ε\varepsilon

    Explanation

    • (a∣b)(a \mid b) matches exactly one symbol: either aa or bb.
    • The Kleene star ∗^* allows zero or more repetitions of that choice.

    So the language is {ε,a,b,aa,ab,ba,bb,aaa,… }\{\varepsilon, a, b, aa, ab, ba, bb, aaa, \dots\}, i.e. every string over the alphabet {a,b}\{a,b\}, with ε\varepsilon (zero repetitions) included.

    Why the other options fail:

    • (i), (ii): they allow only one of the two symbols.
    • (iv): it forces the string to start with abab, but (a∣b)∗(a\mid b)^* also accepts bb, ε\varepsilon, etc.

    (iii)\boxed{\text{(iii)}}

  3. Q1j. Which statement is true regarding DFA and NFA? (i) NFA is strictly more powerful than DFA (ii) DFA and NFA recognize the same class of languages (iii) DFA is strictly more powerful than NFA (iv) Every NFA requires more states than every equivalent DFA20262m

    Module 1: Introduction, Lexical Analysis

    Which statement is true regarding DFA and NFA?
    (i) NFA is strictly more powerful than DFA
    (ii) DFA and NFA recognize the same class of languages
    (iii) DFA is strictly more powerful than NFA
    (iv) Every NFA requires more states than every equivalent DFA

    View this question on its own page →
    Worked Solution

    Answer: (ii) DFA and NFA recognize the same class of languages

    Explanation

    Both DFAs and NFAs recognize exactly the regular languages.

    • Every DFA is already an NFA (a special case).
    • Every NFA can be converted to an equivalent DFA by the subset construction.

    So neither is more powerful, which rules out (i) and (iii). An NFA is often smaller than the equivalent DFA: an NFA with nn states may need up to 2n2^n DFA states. That is a size difference only, and option (iv) is false because many NFAs need no more states than their DFAs (a DFA is itself an NFA).

    (ii)\boxed{\text{(ii)}}

  4. Q2a. Explain the working of each phase of compiler in detail with an example.20237m

    Module 1: Introduction, Lexical Analysis

    Explain the working of each phase of compiler in detail with an example.

    View this question on its own page →
    Worked Solution

    The six phases of a compiler (with an example)

    A compiler translates a high-level program into target (machine) code in several phases. They are divided into the front end (source-language dependent) and back end (machine dependent).

    1. Lexical Analysis (Scanner)

      • Reads the raw character stream and groups it into meaningful tokens (identifiers, keywords, operators, literals).
      • Output: a stream of tokens ⟨type, attribute⟩\langle \text{type, attribute}\rangle.
      • Uses a symbol table to record identifiers.
      • Example: for position = initial + rate * 60, tokens are: ident(position), =, ident(initial), +, ident(rate), *, num(60).
    2. Syntax Analysis (Parsing)

      • Groups tokens into a parse tree / syntax tree according to the grammar.
      • Detects syntactic errors (e.g. x = + * y).
      • Output: a parse tree (for position = initial + rate*60 we get precedence built in: rate*60 is computed first).
    3. Semantic Analysis

      • Checks the parse tree for semantic correctness: type checking, name/declaration matching, etc.
      • For types: if position, initial, rate are float and 10 is int, it may insert implicit conversion int→real.
      • Output: annotated syntax tree.
    4. Intermediate Code Generation

      • Produces a machine-independent intermediate representation (e.g. three-address code).
      • Example for the above assignment:
        t1 = int_to_float(10)
        t2 = rate * t1
        t3 = initial + t2
        position = t3
        
    5. Code Optimization

      • Improves the intermediate code, e.g. reduces t1 = int_to_float(10) because 10 is a constant that can be folded and converted once. After optimization:
        t2 = rate * 10.0
        position = initial + t2
        
    6. Code Generation (Target Code Generation)

      • Maps optimized intermediate code onto target machine's assembly instructions, e.g.:
        MOVF R2, #10.0      (or LDF)
        MULF R2, R1
        ADDF R2, R0
        MOVF position, R2
        

    Symbol Table is a central data structure maintained throughout all phases, storing names, types, scope, and addresses.

    Order: Lexical → Syntax → Semantic → Intermediate Code → Optimization → Code Generation\boxed{\text{Order: Lexical → Syntax → Semantic → Intermediate Code → Optimization → Code Generation}}

  5. Q2a. Explain the different phases of compilation with a suitable example. Show the input and output produced at each phase.20267m

    Module 1: Introduction, Lexical Analysis

    Explain the different phases of compilation with a suitable example. Show the input and output produced at each phase.

    View this question on its own page →
    Worked Solution

    Phases of a compiler

    A compiler translates a source program into target code in a sequence of phases. Two components work with every phase: the symbol table (stores names and attributes) and the error handler (reports errors with line numbers).

    MermaidDiagram
    Rendering diagram…

    Running example: position = initial + rate * 60 where position, initial, rate are float.

    Phase Task Output for the example
    1. Lexical analysis Scans characters, groups them into lexemes and produces tokens; enters identifiers into the symbol table; removes whitespace and comments <id,1> <=> <id,2> <+> <id,3> <*> <num,60>
    2. Syntax analysis Checks token order against the grammar and builds a parse/syntax tree Syntax tree (diagram below)
    3. Semantic analysis Type checking, declaration checks, type conversions (coercion) 60 is int, rate is float, so insert inttofloat(60)
    4. Intermediate code generation Produces machine-independent code, usually three-address code t1 = inttofloat(60)
    t2 = id3 * t1
    t3 = id2 + t2
    id1 = t3
    5. Code optimization Improves speed/size: constant folding, copy propagation, dead-code removal t1 = id3 * 60.0
    id1 = id2 + t1
    6. Code generation Selects instructions, allocates registers, emits target code LDF R2, id3
    MULF R2, R2, #60.0
    LDF R1, id2
    ADDF R1, R1, R2
    STF id1, R1

    Input and output summary

    • Lexical analyzer: character stream to token stream.
    • Syntax analyzer: tokens to syntax tree.
    • Semantic analyzer: syntax tree to type-checked (annotated) tree.
    • Intermediate code generator: annotated tree to three-address code.
    • Optimizer: intermediate code to better intermediate code.
    • Code generator: optimized intermediate code to target code.

    The first three phases form the analysis (front end); the last three form the synthesis (back end).

    Syntax tree (phase 2)

    MermaidDiagram
    Rendering diagram…

    Tree after semantic analysis (phase 3): the integer constant is converted to float.

    MermaidDiagram
    Rendering diagram…
  6. Q2b. Construct a DFA over \{0, 1\} that accepts all strings containing an even number of 0s and an odd number of 1s. Verify the constructed DFA using suitable accepted and rejected input strings.20267m

    Module 1: Introduction, Lexical Analysis

    Construct a DFA over {0,1}\{0, 1\} that accepts all strings containing an even number of 00s and an odd number of 11s. Verify the constructed DFA using suitable accepted and rejected input strings.

    View this question on its own page →
    Worked Solution

    DFA for: even number of 0s and odd number of 1s over {0,1}\{0,1\}

    Idea: track two parities, the parity of the number of 0s and the parity of the number of 1s seen so far. That gives 2×2=42 \times 2 = 4 states.

    State #0s #1s
    AA (start) even even
    BB odd even
    CC (final) even odd
    DD odd odd

    Reading a 0 flips the parity of 0s; reading a 1 flips the parity of 1s. Accepting state: CC (even 0s, odd 1s).

    Transition table

    State on 0 on 1
    →A\to A BB CC
    BB AA DD
    ∗C*C DD AA
    DD CC BB

    Transition diagram

    MermaidDiagram
    Rendering diagram…

    Formally M=({A,B,C,D},{0,1},δ,A,{C})M = (\{A,B,C,D\}, \{0,1\}, \delta, A, \{C\}).

    Verification

    Input Path Final state 0s / 1s Result
    1 A→CA \to C CC 0 / 1 Accepted
    001 A→B→A→CA \to B \to A \to C CC 2 / 1 Accepted
    10011 A→C→D→C→A→CA \to C \to D \to C \to A \to C CC 2 / 3 Accepted
    01 A→B→DA \to B \to D DD 1 / 1 Rejected
    0110 A→B→D→B→AA \to B \to D \to B \to A AA 2 / 2 Rejected
    11 A→C→AA \to C \to A AA 0 / 2 Rejected
    ε\varepsilon AA AA 0 / 0 Rejected

    Every accepted string has an even count of 0s and an odd count of 1s, and every rejected one violates at least one condition, so the DFA is correct.

  7. Q3a. For a lexical analyzer, design regular expressions to recognize the following token classes: (i) Identifiers beginning with a letter and containing letters and digits thereafter. (ii) Unsigned integer constants. (iii) Relational operators <, <=, >, >=, ==, and !=.20267m

    Module 1: Introduction, Lexical Analysis

    For a lexical analyzer, design regular expressions to recognize the following token classes:
    (i) Identifiers beginning with a letter and containing letters and digits thereafter.
    (ii) Unsigned integer constants.
    (iii) Relational operators <,<=,>,>=,==,<, <=, >, >=, ==, and !=!=.

    View this question on its own page →
    Worked Solution

    Regular expressions for the token classes

    Let

    letter=[A-Za-z](often also _),digit=[0-9]\text{letter} = [A\text{-}Za\text{-}z] \quad (\text{often also } \_), \qquad \text{digit} = [0\text{-}9]

    (i) Identifiers (start with a letter, then letters or digits)

    id=letter (letter∣digit)∗\text{id} = \text{letter}\,(\text{letter} \mid \text{digit})^*

    Accepted: total, x1, rate2023. Rejected: 2abc (starts with a digit).

    (ii) Unsigned integer constants

    num=digit digit∗  =  digit+\text{num} = \text{digit}\,\text{digit}^* \;=\; \text{digit}^+

    Accepted: 0, 25, 1000. Rejected: -5 (sign), 3.5 (not an integer).

    (iii) Relational operators

    relop=  <  ∣  <=  ∣  >  ∣  >=  ∣  ==  ∣  !=\text{relop} = \; < \;\mid\; <= \;\mid\; > \;\mid\; >= \;\mid\; == \;\mid\; !=

    Factored form: relop=(<∣>) (=)?  ∣  (=∣!) =\text{relop} = (< \mid >)\,(=)? \;\mid\; (= \mid !)\,=

    The scanner always takes the longest match, so <= is read as one token, not < followed by =. A single = is the assignment operator, not a relational operator.

    Recognizer for relop (transition diagram)

    MermaidDiagram
    Rendering diagram…

    Final states: LT (<), GT (>), LE (<=), GE (>=), EQEQ (==), NE (!=). The states EQ1 and NOT are not final because = alone (assignment) and ! alone are not relational operators here.

  8. Q9. Write short notes on any two of the following: (a) LEX and YACC (b) Peephole Optimization (c) Symbol Table (d) Predictive parser202314m

    Module 1: Introduction, Lexical Analysis

    Write short notes on any two of the following:
    (a) LEX and YACC
    (b) Peephole Optimization
    (c) Symbol Table
    (d) Predictive parser

    View this question on its own page →
    Worked Solution

    Short notes (any two)


    (a) LEX and YACC

    • LEX is the lexical-analyzer generator. It takes a specification of token patterns written with regular expressions and produces a C function (e.g. yylex) that scans the input and returns tokens.

      • Structure of a LEX program: declaration %% translation rules %% auxiliary functions.
      • Each token rule: pattern → action (action = C code, e.g. return token_id;).
      • Powers: longest-match rule; a symbol table kept for identifiers/text. Used with YACC. LEX produces the scanner that feeds tokens to the parser.
    • YACC ("Yet Another Compiler-Compiler") is the parser generator, producing a LALR(1) (or YACC's default SLR/LALR) parser (function yyparse) from a CFG grammar in BNF with embedded semantic actions.

      • Structure: %{ declarations %} %% productions { semantic action } %% subroutines.
      • Uses the tokens lexed by LEX; each grammar rule may have a semantic value and user code; supports precedence and associativity declarations (%left, %right); optionally builds an AST via actions.
      • Together: LEX gives tokens to YACC, YACC builds the parse tree and schedules the semantic translation.

    (b) Peephole Optimization

    A simple but effective local, post-optimization technique performed on the target (assembly/machine) code over a small window ("peephole") of adjacent instructions.

    Techniques in a peephole optimizer:

    1. Redundant instruction elimination (dead code): e.g. MOV R0, R1; MOV R0, R1 — delete the second.
    2. Flow-of-control optimizations: e.g. jumps to jumps (JMP L; ... L: JMP L1) — replace with direct jump.
    3. Algebraic simplifications: x + 0 → x; x*1 → x; x/2` → shift etc.
    4. Reduction in strength: replace expensive ops like 2 * x with x + x and x^2.
    5. Use of machine idioms: e.g. X = X + 0 removed, X+1 replaced by INC/INC and X<<n for X * 2^n.
    6. Constant folding inside window.

    Advantage: very cheap, catches a lot; often run repeatedly.


    (c) Symbol Table

    • A compile-time data structure that maps identifiers (names) to their attributes: name, type, category (variable, function, record/array), address (stack/relative/global offset), scope & lifetime, parameter list, and kind (static, local).
    • Operations: insert (add new name), lookup/access (find entry), modify; the table is consulted/modified during every phase — lexical (insert), syntax/semantic (lookup/check type), code gen (get address).
    • Organization:
      • Lists / hash tables; open (separate chaining) or closed addressing for speed.
      • Scoping: block structure ⇒ re-entrant by saved-bottom markers; often a stack of scopes with a new.. pointer (scope stack).
      • Multiple table organizations: (1) flat global, (2) per-scope linked lists, (3) tree/structural block table (often implemented as stack-based with block index markers).
    • Lookup in nested scopes follows lexical chain (innermost outward). Implementations use hash table for names with scope table.
    • Applications: optimize code-gen (address known), type checking, overloading, and reporting.

    (d) Predictive parser

    A predictive parser (or LL(1) table parser) is a non-backtracking top-down parser. It uses a parsing table M[A][a], the input's next symbol a, and a stack of grammar symbols to decide deterministically ("predict") which production to apply without backtracking.

    • Table-driven: stack holds nonterminals (and $); at each step, if top of stack is terminal, match with input; if it's a nonterminal, use entry M[top][a] to choose the production for A.
    • Table is built from FIRST and FOLLOW sets: M[A][a] = A→α if a∈FIRST(α), or (if α nullable) if a∈FOLLOW(A).
    • A given grammar can be predictive only when it is LL(1): no multiple entries (no conflict) in any table cell and left-recursion removed + factorized.
    • Also called recursive-descent parser when implemented with recursive functions, each nonterminal being one function that calls other via the table (though classic "predictive parser" = table-driven with no backtracking).
    • Algorithm: repeat — look at top X and next input a:
      • if X is a, advance both;
      • if X is nonterminal and M[X][a] = X→Y1..Yk, pop X push Yk…Y1 (reverse feedback);
      • if M entry empty → error.
      • Accept when stack = ‘andinput=‘` and input = `.

    Characteristics: no backtracking ⇒ linear time (O(n) for LL(1)); works for all LL(1) grammars; fastest top-down method; the classic example is predicting grammar E → T E', etc.