I. Lexical Analysis
Input Buffering
-
Purpose: To efficiently read the source program character by character, minimizing I/O operations.
-
Implementation: Uses two buffers (
lexemeBegin,forwardpointers) of sizeN. Whenforwardreaches end of buffer, the buffer is reloaded. -
Sentinels: A special character (EOF) is placed at the end of each buffer to avoid checking boundary conditions on every character read.
-
Scheme:
lexemeBeginmarks start of current token,forwardscans ahead. After token recognition,lexemeBeginis advanced toforward.
Token Recognition
-
Identifiers vs Keywords:
-
Pattern: Identifiers typically match
[a-zA-Z_][a-zA-Z0-9_]*. -
Process: Lexical analyzer first checks if the scanned string matches any keyword in a fixed table. If not, it's classified as an identifier.
-
Example:
int→ keyword;myVar123→ identifier.
-
-
Lexical Patterns & Finite Automata: Each token pattern is represented by a Regular Expression and converted to a Deterministic Finite Automaton (DFA) for efficient recognition.
Regular Expressions
-
Formal Representation: Concise notation for token patterns (e.g.,
digit -> [0-9],id -> letter (letter|digit)*). -
Example (Exam Focus): Regular expression for strings with odd number of
aand odd number ofb:
$$ (aa^*bb^* + bb^*aa^*)(aa^* + bb^*)^* $$
* `aa*bb*` = odd `a`s followed by odd `b`s.
* `bb*aa*` = odd `b`s followed by odd `a`s.
* `(aa* + bb*)*` = any number (even/zero) of additional odd `a` or odd `b` blocks.
Lexical Errors
-
Detection: Character not belonging to any token pattern (e.g.,
@in C). -
Handling: Report error, discard offending character, and resume scanning.
Lex Tool
-
Purpose: Generates lexical analyzers from regular expression specifications.
-
Simple Program (Identifier Recognition):
%{ #include <stdio.h> %} %% [a-zA-Z_][a-zA-Z0-9_]* { printf("Identifier: %s\n", yytext); } [ \t\n]+ ; /* ignore whitespace */ . { printf("Invalid char: %c\n", *yytext); } %% int main() { yylex(); return 0; }
[!TIP] Exam Tip: For regex problems, break into cases (odd a/odd b, odd b/odd a) and handle interleaving with Kleene star.
II. Syntax Analysis (Parsing)
Parsing Techniques
| Top-Down | Bottom-Up |
|---|---|
| Starts from start symbol, derives input string. | Starts from input string, reduces to start symbol. |
| Predictive Parsing (no backtracking) | LR Parsing (shift-reduce) |
| Fails on left-recursive grammars. | Handles wide class of grammars. |
LR Parsing Variants (Detailed Comparison)
| Parser | Construction | Power | Table Size |
|---|---|---|---|
| LR(1) | Uses 1 lookahead. States from canonical LR(1) items. | Most powerful (all deterministic CFGs). | Largest |
| LALR(1) | Merges LR(1) states with same core (ignoring lookahead). | Slightly less than LR(1) (some conflicts). | Smaller than LR(1) |
| SLR(1) | Uses FOLLOW(A) for reductions of A -> β. |
Weakest (may have conflicts LALR resolves). | Smallest |
[!TIP] Common Pitfall: SLR uses FOLLOW(
A) for all reductions ofA, while LALR/LR(1) use precise lookaheads. SLR may have spurious conflicts.
SLR Parsing Table Construction (Example Steps)
-
Augment Grammar: Add
S' -> S. -
Build LR(0) Items: Compute closure and goto.
-
Construct ACTION/GOTO:
-
Shift: On
a(terminal), goto statej→ACTION[i, a] = shift j. -
Reduce: For item
A -> β.in statei, for eachain FOLLOW(A),ACTION[i, a] = reduce A->β. -
Accept: On
$$\displaystyle ` for `S' -> S.` → `ACTION[i, $$] = accept. -
Goto:
GOTO[i, A] = jfor itemA -> β.Aγ.
-
-
Check for Conflicts: Multiple actions for same cell → grammar not SLR(1).
Syntax Errors & Recovery
-
Detection: Parser encounters no valid action (blank entry in table).
-
Recovery Strategies:
-
Panic Mode: Skip tokens until synchronizing token (e.g.,
;,}) found. -
Phrase Level: Insert/delete tokens to make parser progress.
-
Error Productions: Augment grammar with common error patterns.
-
Predictive Parsing
-
Table (
M[A, a]): For non-terminalAand terminala, entry is productionA -> αifais in FIRST(α), orA -> εifεin FIRST(α) andain FOLLOW(A). -
Construction: Compute FIRST and FOLLOW sets. Fill table accordingly.
-
Requirement: Grammar must be non-left-recursive and non-ambiguous.
III. Semantic Analysis
Type Systems
-
Type Checking: Verifies operator-operand type compatibility.
- Example:
int + float→ convertinttofloat(type coercion).
- Example:
-
Type Conversion:
-
Implicit (Coercion): Done by compiler (e.g.,
inttofloat). -
Explicit (Cast): Programmer-specified (e.g.,
(float)i).
-
Syntax-Directed Translation
-
Attributes: Values associated with grammar symbols.
-
Synthesized: Computed from children (bottom-up).
-
Inherited: Passed from parent/siblings (top-down).
-
-
L-attributed Definitions: A restricted form where inherited attributes can only come from left siblings and parent. Allows top-down evaluation (used in predictive parsers).
Intermediate Code Generation: Three-Address Code (TAC)
-
Form:
x = y op z(at most 3 addresses per instruction). -
Example:
switch p+q { case 1: x=x+1; case 2: y=y+2; case 3: z=z+3; default: c=c-1; }t1 = p + q if t1 == 1 goto L1 if t1 == 2 goto L2 if t1 == 3 goto L3 goto L4 L1: x = x + 1 L2: y = y + 2 L3: z = z + 3 L4: c = c - 1Note: Fall-through behavior implies cases are not mutually exclusive jumps unless
breakis present.
Intermediate Representations: Quadruples vs Triples
| Quadruple | Triple |
|---|---|
4 fields: (op, arg1, arg2, result) |
3 fields: (op, arg1, arg2); result is implicit index. |
result is a temporary variable name. |
result is position in triple array. |
| Easier for optimization (redefinitions visible). | More compact; harder to reorder. |
Example: t1 = a + b → (+, a, b, t1) |
Example: (+, a, b) stored at index (1) |
Name Resolution: Function Overloading
-
Resolution Time: Compile-time (static binding).
-
Process: Based on function signature (name + parameter types). Compiler selects the most specific matching function.
-
Example:
print(int)vsprint(string)→ call resolved by argument types.
IV. Intermediate Code Representation & Optimization
Directed Acyclic Graphs (DAGs)
-
Definition: A DAG for an expression is a graph where:
-
Leaf nodes are identifiers/constants.
-
Interior nodes are operators.
-
Common subexpressions are represented by a single node.
-
-
Construction (Exam Focus): Build from expression using hash table to detect identical subexpressions (same operator, same children).
-
Example 1 (DEC 2024):
a + a*(b-c) + (b-c)*dStep 1: t1 = b - c Step 2: t2 = a * t1 Step 3: t3 = t1 * d Step 4: a + t2 + t3 DAG: Node `-` (b,c) shared by `*` (a, -) and `*` (-, d). -
Application: Common Subexpression Elimination (CSE). If node exists, reuse it instead of recomputing.
Basic Blocks
-
Definition: A sequence of consecutive statements with:
-
Single entry (first statement executed).
-
Single exit (last statement causes jump).
-
-
Formation: Partition code by leaders (first statement, target of jump, statement after jump). Each leader starts a new block.
-
Control Flow Graph (CFG): Nodes = basic blocks; edges = possible flow of control.
Optimization Techniques
-
Constant Folding: Evaluate constant expressions at compile time.
x = 3 * 4 + 5→x = 17.
-
Loop Optimization:
-
Invariant Code Motion: Move loop-invariant calculations outside.
for(i=0; i<n; i++) { x = y*z + a[i]; // y*z is invariant } // Optimized: t = y*z; for(i=0; i<n; i++) x = t + a[i]; -
Strength Reduction: Replace expensive op with cheaper one (e.g.,
x*8→x<<3). -
Loop Unrolling: Duplicate loop body to reduce jump overhead.
-
-
Peephole Optimization: Local optimization on small instruction window (2-5 instructions).
-
Examples:
-
Redundant load elimination:
t1 = a; t2 = t1→t2 = a. -
Algebraic simplifications:
x = x*1→ delete. -
Jump-to-jump:
goto L1; L1: goto L2→goto L2.
-
-
V. Runtime Environment & Storage Management
Activation Records (AR)
-
Definition: The data structure created at procedure call to manage execution of that call.
-
Components (Typical Layout):
[Actual Parameters] [Return Address] [Control Link (Access Link)] [Saved Machine Registers] [Local Variables] [Temporaries] -
Example: For
proc(x, y)called frommain, AR contains values forx,y, return address tomain, pointer tomain's AR (control link), andproc's locals.
Storage Allocation Strategies
| Stack Allocation | Heap Allocation |
|---|---|
| LIFO order (procedure calls/returns). | Dynamic, arbitrary order (malloc/free). |
| Size known at compile time (for statically scoped languages). | Size unknown until runtime. |
| Fast (pointer adjustment). | Slower (garbage collection/management). |
| No fragmentation. | Fragmentation possible (internal/external). |
| Used for local variables, ARs. | Used for dynamic data structures (linked lists, objects). |
Procedure Calls
-
Call Mechanism:
-
Caller evaluates actual parameters.
-
Caller pushes return address, old FP, and possibly parameters onto stack.
-
Control transfers to callee.
-
Callee sets up its AR (FP points to old FP).
-
-
Return Mechanism:
-
Callee places return value (if any) in designated location.
-
Callee restores caller's registers, pops AR, jumps to return address.
-
-
Parameter Passing Methods:
-
Pass-by-Value: Copy actual value.
-
Pass-by-Reference: Copy address (callee can modify actual).
-
Pass-by-Value-Result (Copy-in/Copy-out): Copy in at call, copy out at return.
-
Advanced Runtime Features
-
Polymorphic Functions (Runtime Handling):
-
Definition: Function that behaves differently based on runtime type of arguments.
-
Runtime Handling: Requires dynamic dispatch (e.g., virtual function tables in C++). Compiler generates code to look up correct function address at runtime.
-
-
Function Overloading (Compile-time Resolution):
- Resolved during compilation based on static types of arguments. No runtime overhead.
VI. Symbol Table Management
Purpose & Scope
-
Purpose: Central repository for information about identifiers (name, type, scope, address, etc.). Used in all phases (lexical to code generation).
-
Scope: Supports nested scopes (functions within functions) and separate namespaces (labels, variables).
Data Structures
| Structure | Description | Pros | Cons |
|---|---|---|---|
| Linear List | Unordered array of entries. | Simple. | Slow lookup (O(n)). |
| Hash Table | Hash function on name → bucket; bucket managed as list. | Fast average lookup (O(1)). | Collision handling needed. |
| Binary Search Tree | Sorted tree (e.g., std::map). |
Fast (O(log n)), maintains order. | More complex than hash. |
| Scope Tree | Each scope is a symbol table (hash/BST); parent pointer for nested scopes. | Natural for nested scopes. | Slightly more overhead. |
- Organization for Scopes: Stack of hash tables (one per scope). On entering scope, push new table; on exit, pop. Lookup searches from top of stack downward.
VII. Error Handling
Error Detection Phases
| Phase | Errors Detected |
|---|---|
| Lexical | Invalid characters, unterminated comments. |
| Syntax | Missing ;, mismatched {}, incorrect expression structure. |
| Semantic | Type mismatch, undeclared variable, incompatible operand. |
Error Reporting & Recovery
-
Reporting: Should be precise (line/column), understandable, and non-intrusive.
-
Recovery Strategies:
-
Panic Mode: Discard tokens until synchronizing token (e.g.,
;,}). Simple, prevents infinite loops. -
Phrase Level: Insert/delete single token to correct error (e.g., missing
;). Requires local correction. -
Error Productions: Add grammar rules like
stmt -> error ;to catch and recover from common errors. -
Global Correction: Minimum number of insertions/deletions to make program valid (expensive, rarely used).
-
[!TIP] Exam Focus: Distinguish lexical (invalid chars) vs syntactic (structure) vs semantic (meaning) errors with examples.
VIII. Compiler Phases: Integrated Example
Source Code: a = (x/y) * (y + x - z)
| Phase | Output / Action |
|---|---|
| 1. Lexical Analysis | Tokens: id(a), =, (, id(x), /, id(y), ), *, (, id(y), +, id(x), -, id(z), ) |
| 2. Syntax Analysis | Parse Tree / AST: =(a, *(/(x,y), +(y, -(x,z))) |
| 3. Semantic Analysis | Type checking: Assume a,x,y,z are int → x/y is int (integer division). Annotated AST with types. |
| 4. Intermediate Code (TAC) | ``` |
t1 = x / y
t2 = x - z
t3 = y + t2
t4 = t1 * t3
a = t4
``` |
| 5. Optimization (DAG/CSE) | DAG: t2 = x - z is unique. No common subexpressions. Constant folding not applicable. |
| 6. Code Generation | Target assembly (e.g., x86): ```
mov eax, x
cdq
idiv y ; eax = x/y
mov ebx, eax
mov eax, x
sub eax, z
add eax, y
imul eax, ebx
mov a, eax
``` |
[!TIP] Exam Question Pattern: "Show output generated by each phase" → List tokens, show parse tree/AST, show TAC, mention optimizations applied, show target code snippet.
Summary of High-Frequency Exam Topics (from DEC 2024 & JUN 2025):
-
DAG Construction (2+ questions) – Practice with expressions containing common subexpressions like
(b-c). -
Loop Optimization (2+ questions) – Know invariant code motion, strength reduction with clear examples.
-
SLR/LALR/LR Comparison & Tables (2+ questions) – Be able to construct SLR table for small grammar.
-
Activation Records & Storage Allocation – Compare stack vs heap, draw AR diagram.
-
Three-Address Code (TAC) – Generate for
switch,if-then-else, loops. Know quadruple/triple formats. -
Backpatching – For boolean expressions and
if/while(fill in jump addresses). -
Basic Blocks – Identify from code, draw CFG.
-
Constant Folding & Peephole – Apply to small instruction sequences.
-
Predictive Parsing – Construct table for given grammar (eliminate left recursion first).
-
Lexical Analysis – Write Lex program for identifiers, explain sentinel buffering.