⚡ TechVani: AI, Technology, Apps, Coding & Digital World
TechVaniAI • Technology • Apps • Coding • Digital World
HomeAI & TechAppsCodingFree AI ToolsStudent TechSecurityAboutContactPrivacy
TECHVANI // LEARN. BUILD. TEST.

Learn the tech. Build the idea.

Clear study notes, tested lab files and practical guides for coding, AI and digital safety.

Explore Latest →
šŸ¤– AI & TechAI news, tools & guides šŸ“± Apps & ToolsUseful apps & websites šŸ’» CodingProgramming & web development šŸŽ“ Student TechStudy & career technology šŸ” Cyber SecuritySafety & awareness

Latest Posts

Circuit Notebook theme

Exam Notes

šŸ“š BTech CSE Exam Notes

Find Semester 1-8 subjects in one place. Choose a semester and open the notes you need. This is a general study guide, not an official syllabus.

Open Exam Notes →🧪 Open Lab Files →

Exam Numericals & Study Series

Practice CPU scheduling and explore OS and DBMS topics in separate, easy-to-follow parts. New topics will appear in the hub as they are published.

Open Numericals Hub →

Start here and stay in touch

New to TechVani? Start with our free AI tools guide for students. It covers which tools to try and what to check before relying on an answer.

For step-by-step videos, visit SiteSudhar on YouTube.

Join TechVani on WhatsApp

Get new AI tools and coding guides in your WhatsApp Updates tab.

Browse Topics

Popular Posts

TechVani

Compiler Design | Semester 6 Complete Core Notes

Semester 6 - Page 2: Compiler Design. 25 numbered topics, highlighted rules, worked token/grammar/IR examples, answered exam questions, diagrams, comparison tables and three complete Python demos. General common-core guide, not an official syllabus or a full compiler.

Run each Python block separately: Python 3, standard library only. The lexer, integer-expression parser and FIRST/FOLLOW calculator are small teaching programs with explicit input grammars. No variables, unary operators, division, type checker, optimizer or native code generation are implemented by the parser. Python integers in this demo do not establish machine overflow semantics.

Topic map

  1. Compiler, interpreter and JIT
  2. Compiler phases and toolchain
  3. Tokens, lexemes and source locations
  4. Regular expressions and finite automata
  5. Lexical edge cases and comments
  6. CFGs, derivations and ambiguity
  7. Precedence, associativity and AST
  8. Left recursion and left factoring
  9. FIRST, FOLLOW and nullable symbols
  10. LL(1) tables and predictive choices
  11. Recursive-descent parsing
  12. Bottom-up parsing and handles
  13. LR items, states and conflicts
  14. Syntax errors and recovery
  15. Symbol tables and scope
  16. Type checking and coercion
  17. Syntax-directed translation and attributes
  18. IR and three-address code
  19. Control flow, basic blocks and backpatching
  20. Runtime storage and calling conventions
  21. Data-flow analysis and liveness
  22. Optimization and preservation
  23. Code generation and register allocation
  24. Compiler testing and differential checks
  25. Exam pipeline and limitations

1. Compiler, interpreter and JIT

A compiler translates representations under language semantics; an interpreter executes a representation; a JIT compiles during execution. Real systems can combine these roles.

Worked example: Source can become IR, bytecode or machine code; translation does not always produce a standalone native executable.
Exam question: Does compilation mean a program has no runtime checks?
Answer: No. Dynamic values and language rules can require checks during execution.

2. Compiler phases and toolchain

Lexing, parsing, semantic analysis, IR, optimization and code generation have different jobs. Assembly/linking/loading are related toolchain stages, not identical to parsing.

Worked example: x=2+3*4 must preserve precedence and type/side-effect rules throughout translation.
Exam question: Does a parser alone establish a well-typed program?
Answer: No. Semantic analysis is a separate responsibility.
Characters -> tokens -> syntax tree
 -> semantic facts -> IR
 -> justified optimization
 -> target code
Each phase checks different rules. Source/target behavior must remain compatible.

3. Tokens, lexemes and source locations

A token has a kind and often value/span; a lexeme is the matched source text. Source positions support diagnostics and tooling.

Worked example: sum 12 is one identifier under the usual letter-then-letter/digit rule, not sum followed by 12.
Exam question: Is every keyword always an identifier token?
Answer: Not necessarily. Lexer rules/priority can classify reserved words separately.

4. Regular expressions and finite automata

Regular patterns describe many lexical classes and can be implemented with finite automata. Longest match and rule priority resolve overlaps under stated rules.

Worked example: An identifier and keyword pattern can both match if; longest match alone cannot distinguish equal-length matches.
Exam question: Does regex cover every nested language structure naturally?
Answer: No. General balanced nesting usually belongs to grammar/parsing machinery.

5. Lexical edge cases and comments

Whitespace/comments may be skipped unless semantically meaningful. Strings, escapes, encodings and malformed literals need deliberate rules and diagnostics.

Worked example: Our toy lexer accepts ASCII identifiers/integers and a small operator set only; an at-sign is an error, not silently ignored.
Exam question: Why reject unmatched characters instead of skipping them?
Answer: Skipping can change the program meaning and hide source mistakes.

Toy lexer with exact unmatched-character errors

import re
pattern = re.compile(r"(?P<SPACE>\s+)|(?P<ID>[A-Za-z_][A-Za-z_0-9]*)|(?P<NUM>[0-9]+)|(?P<OP>[=+*();-])")
def tokens(text):
    answer, position = [], 0
    while position < len(text):
        match = pattern.match(text, position)
        if match is None: raise ValueError("invalid character at " + str(position))
        if match.lastgroup != "SPACE": answer.append((match.lastgroup, match.group()))
        position = match.end()
    return answer
print(tokens("sum12 = 3 + 4;"))
try: tokens("x@2")
except ValueError as error: print(error)

Checked output:

[('ID', 'sum12'), ('OP', '='), ('NUM', '3'), ('OP', '+'), ('NUM', '4'), ('OP', ';')]
invalid character at 1

6. CFGs, derivations and ambiguity

A context-free grammar defines terminals, nonterminals, productions and start symbol. Ambiguity means multiple parse trees for one token string.

Worked example: E -> E+E or E*E or number is ambiguous without further structure/rules for 2+3*4.
Exam question: Does every ambiguous grammar imply the language cannot be parsed?
Answer: No. The grammar or disambiguation strategy may be changed.

7. Precedence, associativity and AST

Precedence chooses which operators bind tighter; associativity resolves grouping among equal precedence. ASTs preserve meaningful structure while omitting incidental parse details.

Worked example: 2+3*4 has addition root with a multiplication subtree;8-3-1 under left associativity is 4, not 6.
Exam question: Is a parse tree always identical to an AST?
Answer: No. AST construction deliberately removes grammar scaffolding.
      +
    /   \
   2     *
        / \
       3   4
Value = 2 + (3 x 4) = 14
AST structure records multiplication precedence.

8. Left recursion and left factoring

Basic predictive recursive descent cannot consume input through direct left recursion. Factoring separates shared prefixes, but these changes do not guarantee every grammar becomes LL(1).

Worked example: E -> E+T or T can become E -> T Eprime with Eprime -> +T Eprime or epsilon.
Exam question: Does removing left recursion alone remove all parsing conflicts?
Answer: No. FIRST/FOLLOW choices and ambiguity still matter.

9. FIRST, FOLLOW and nullable symbols

FIRST identifies possible starting terminals; FOLLOW identifies terminals after a nonterminal plus end marker for the start. Nullable derivations require fixed-point propagation.

Worked example: S -> A b, A -> a or epsilon gives FIRST(S)={a, b}, FOLLOW(A)={b}; the complete tiny demo computes this.
Exam question: Is epsilon a member of a standard FOLLOW set?
Answer: No. FOLLOW contains terminals/end marker, not an empty string.

Fixed-point FIRST/FOLLOW for the stated tiny grammar

grammar = {"S": [["A", "b"]], "A": [["a"], []]}
first = {n: set() for n in grammar}
nullable = set()
def sequence(symbols):
    result = set()
    for symbol in symbols:
        if symbol not in grammar: return result | {symbol}, False
        result |= first[symbol]
        if symbol not in nullable: return result, False
    return result, True
changed = True
while changed:
    changed = False
    for left, rules in grammar.items():
        for rule in rules:
            values, empty = sequence(rule)
            before = (len(first[left]), len(nullable))
            first[left] |= values
            if empty: nullable.add(left)
            changed |= before != (len(first[left]), len(nullable))
follow = {n: set() for n in grammar}; follow["S"].add("$")
changed = True
while changed:
    changed = False
    for left, rules in grammar.items():
        for rule in rules:
            for i, symbol in enumerate(rule):
                if symbol not in grammar: continue
                values, empty = sequence(rule[i+1:]); before = len(follow[symbol])
                follow[symbol] |= values
                if empty: follow[symbol] |= follow[left]
                changed |= before != len(follow[symbol])
for symbol in sorted(grammar):
    print(symbol, "FIRST:", sorted(first[symbol]), "nullable:", symbol in nullable, "FOLLOW:", sorted(follow[symbol]))

Checked output:

A FIRST: ['a'] nullable: True FOLLOW: ['b']
S FIRST: ['a', 'b'] nullable: False FOLLOW: ['$']

10. LL(1) tables and predictive choices

An LL(1) table uses one lookahead with FIRST and nullable/FOLLOW rules. Multiple productions in one cell indicate a conflict under that grammar.

Worked example: For A -> a or epsilon in S -> A b, choose a on lookahead a and empty on b.
Exam question: Does a missing table entry mean accept the input?
Answer: No. It generally indicates a syntax error under that parse state.

11. Recursive-descent parsing

Recursive-descent functions mirror grammar responsibilities and consume tokens. Explicit precedence levels and full-input checks avoid accidental partial acceptance.

Worked example: The demo evaluates integer arithmetic with addition, subtraction, multiplication and parentheses, then requires EOF.
Exam question: Why check trailing tokens after parsing an expression?
Answer: Otherwise a valid prefix could hide an invalid suffix.

Full-input arithmetic recursive descent

import re

def evaluate(text):
    parts = re.findall(r"[0-9]+|[()+*-]|\S", text) + ["EOF"]
    position = 0
    def factor():
        nonlocal position
        token = parts[position]
        if token.isascii() and token.isdigit():
            position += 1; return int(token)
        if token == "(":
            position += 1; value = expression()
            if parts[position] != ")": raise ValueError("expected closing parenthesis")
            position += 1; return value
        raise ValueError("expected integer or parenthesis")
    def term():
        nonlocal position
        value = factor()
        while parts[position] == "*":
            position += 1; value *= factor()
        return value
    def expression():
        nonlocal position
        value = term()
        while parts[position] in ("+", "-"):
            operator = parts[position]; position += 1; right = term()
            value = value+right if operator == "+" else value-right
        return value
    result = expression()
    if parts[position] != "EOF": raise ValueError("trailing token")
    return result
for text in ["2+3*4", "(2+3)*4", "8-3-1", "2+", "2 3"]:
    try: print(text, "=", evaluate(text))
    except ValueError as error: print(text, "error:", error)

Checked output:

2+3*4 = 14
(2+3)*4 = 20
8-3-1 = 4
2+ error: expected integer or parenthesis
2 3 error: trailing token

12. Bottom-up parsing and handles

Shift-reduce parsing builds toward the start symbol by recognizing reduction handles. A stack and tables coordinate shifts, reductions and acceptance.

Worked example: A parser shifts a number then reduces it to a factor according to the grammar/state, not arbitrary textual substitution.
Exam question: Can a reduction be chosen solely because its right-hand text appears somewhere?
Answer: No. State/lookahead and viable-prefix context matter.

13. LR items, states and conflicts

LR items describe position in productions; closure/goto construct parser states. SLR uses FOLLOW-based reductions; LR/LALR variants differ in lookahead/state handling.

Worked example: Shift/reduce or reduce/reduce conflicts reveal competing actions; precedence declarations resolve selected intended conflicts, not every faulty grammar.
Exam question: Are LL and LR table entries interchangeable?
Answer: No. Their construction and stack meaning differ.

14. Syntax errors and recovery

Diagnostics identify location, offending token and expected construct. Recovery can synchronize at delimiters; one error may trigger later cascades.

Worked example: 2+ causes an expected-operand error; our toy parser stops rather than pretending it recovered a full program.
Exam question: Does every later diagnostic represent an independent defect?
Answer: No. Earlier missing delimiters can cause cascading errors.

15. Symbol tables and scope

A symbol table records names, declarations, types and related information. Resolution follows language scope rules; nearest visible binding may shadow an outer declaration.

Worked example: An inner x can shadow outer x; leaving scope restores the outer binding instead of deleting all knowledge of x.
Exam question: Does tokenizing a name prove it was declared?
Answer: No. Declaration/name resolution belongs to semantic analysis.

16. Type checking and coercion

Type rules determine allowed operations, assignments and calls. Static and dynamic checks have different timing; implicit conversions depend on the language.

Worked example: int n assigned a string may parse but fail type checking; a runtime array index can still need bounds checking.
Exam question: Can syntactically valid code be semantically invalid?
Answer: Yes. Names, types and contextual rules can fail after parsing.

17. Syntax-directed translation and attributes

Attributes attach computed information to grammar structure; semantic actions can construct ASTs or values. Synthesized/inherited dependencies must be evaluated in a valid order.

Worked example: A number node synthesizes its value; an expression node combines child values according to its operator.
Exam question: Can semantic actions ignore side effects/order rules?
Answer: No. Translation must respect the language's defined behavior.

18. IR and three-address code

IR makes operations and control flow explicit and separates front-end concerns from target details. Three-address code often uses temporaries for elementary operations.

Worked example: a=b+c*d becomes t 1=c*d, t 2=b+t 1, a=t 2. Original resource links include fuller IR labs.
Exam question: Does one IR guarantee every source language has identical semantics?
Answer: No. The IR must represent relevant source behaviors correctly.
Source: a = b + c * d
IR:     t1 = c * d
        t2 = b + t1
        a = t2
Teaching three-address form makes intermediate operations explicit.

19. Control flow, basic blocks and backpatching

A basic block is a straight-line unit with suitable entry/exit boundaries. CFG edges represent possible execution; backpatching fills deferred jump targets in some translation schemes.

Worked example: An if/else introduces branch targets and joins; its AST is not the same graph as its execution CFG.
Exam question: Does a CFG describe only the happy path?
Answer: No. It should model relevant possible control transfers.

20. Runtime storage and calling conventions

Activation records, parameters, returns, locals and saved state depend on language/runtime and calling convention. Heap allocation and stack-like call behavior serve different lifetimes.

Worked example: Recursive calls need distinct invocation state; returned data must not refer unsafely to expired storage under the language model.
Exam question: Can every local variable be assumed to have one fixed physical stack location?
Answer: No. Optimization/runtime representation can change placement.

21. Data-flow analysis and liveness

Data-flow analyses propagate facts over CFGs to a fixed point. Liveness concerns future reads before overwrite; reaching definitions tracks potential originating assignments.

Worked example: x=5 then x=7 then read x makes the first pure assignment dead, but calls/volatile accesses may preserve effects.
Exam question: Is a currently unused value always safe to eliminate?
Answer: No. Side effects, exceptions and language observability matter.

22. Optimization and preservation

Constant folding, propagation, common-subexpression elimination, loop transformations and dead-code removal need semantic justification. Overflow and floating-point rules can restrict algebra.

Worked example: 2+3*4 can fold to 14 for our unbounded-integer toy semantics; removing f() is unsafe if its effects matter.
Exam question: Does mathematically equal arithmetic imply every machine-language rewrite is valid?
Answer: No. Overflow, rounding, traps and evaluation order can differ.

23. Code generation and register allocation

The back end selects instructions, orders control flow and assigns registers/spills under target constraints. Correct target behavior matters beyond fewer instructions.

Worked example: An expression temporary can occupy a register or spill slot depending on live ranges and available resources.
Exam question: Can registers hold every live value in a large program?
Answer: No. Spilling/memory or other allocation choices may be required.

24. Compiler testing and differential checks

Test valid/invalid syntax, precedence, name/type errors and optimization behavior. Compare independent implementations on bounded domains where suitable.

Worked example: Run 2+3*4, parentheses and malformed suffixes; public code/output must match, not just a claimed compiler build.
Exam question: Does compiling the compiler prove every translation is correct?
Answer: No. Translation behavior and target execution need tests.

25. Exam pipeline and limitations

Trace characters to tokens, AST, semantic facts, IR and target; distinguish FIRST/FOLLOW, AST/CFG and compile/runtime checks. State assumptions before transformations.

Worked example: For a+b*c show tokens, multiplication subtree and temporary operations, then identify declaration/type checks.
Exam question: What completes an exam answer beyond phase names?
Answer: A worked trace with rules, diagnostics and behavior-preservation limits.

Phase diagnostics

PhaseInput/outputExample error
LexerCharacters -> tokensUnmatched character
ParserTokens -> syntaxMissing operand/delimiter
SemanticSyntax + declarations -> checked factsUndeclared name/type mismatch
OptimizationIR -> equivalent IRUnsafe removal of observable effect
Back endIR -> targetIncorrect calling/register behavior

LL and LR distinctions

AspectLL / predictiveLR / shift-reduce
DirectionTop-down derivationBottom-up reduction
Typical decisionsFIRST/FOLLOW lookaheadState/items/table
Left recursionBasic recursive descent needs removalMany left-recursive grammars supported
ConflictCompeting predictive cellShift/reduce or reduce/reduce

Tiny grammar sets

SymbolFIRST terminals / nullableFOLLOW
A -> a or empty{a}; nullable{b}
S -> A b{a,b}; not nullable{$}
ConventionsEmpty tracked separately in codeFOLLOW never contains epsilon

Revision and implementation checklist

Trace tokens, precedence AST, scope/type facts and IR; calculate nullable/FIRST/FOLLOW; distinguish AST from control flow. Check malformed and trailing input. State side effects, overflow and floating-point constraints before claiming a rewrite is valid.

Resources and sources

Compiler teaching and LLVM front-end tutorials support these concepts. All diagrams and small Python examples here are original, not a production compiler implementation.

Comments