Section 7 · Revision
Compiler Design formula sheet
One-page revision of scanning, FIRST/FOLLOW and LL/LR parsing, attribute grammars, runtime storage, three-address code and data flow analysis.
Lexical analysis
- Token = class (
id, num); lexeme = matched text; pattern = regex for the lexemes.
- Pipeline: regex → NFA (Thompson) → DFA (subset construction, up to 2n states) → minimal DFA → table-driven scanner.
- Thompson counts: symbol =2; N(r∣s)=N(r)+N(s)+2; N(rs)=N(r)+N(s)−1; N(r∗)=N(r)+2.
(a|b)*abb gives 11.
- Longest match first, then the pattern listed first:
ifx is an identifier, if the keyword.
- Keywords: preload into the symbol table, scan with the identifier pattern.
- Sentinel
eof per buffer half: one test per character instead of two.
- Lexical errors: illegal character, unterminated string or comment. Missing
; is syntactic; undeclared name is semantic.
- Token counting in C: comments and whitespace give 0; a string literal is 1;
>=, ++, -> are 1 each; a+++b is a ++ + b; /* opens a comment that ends at the first */.
Parsing
FIRST and FOLLOW
| Rule |
Effect |
| X→Y1Y2… |
add FIRST(Y1)−{ε}; go on to Y2 only if Y1⇒∗ε |
| all Yi derive ε |
ε∈FIRST(X) |
| start symbol |
$∈FOLLOW(S) |
| A→αBβ |
FIRST(β)−{ε}⊆FOLLOW(B) |
| A→αB, or β⇒∗ε |
FOLLOW(A)⊆FOLLOW(B) |
LL(1)
- Table: put A→α in M[A,a] for each a∈FIRST(α); if ε∈FIRST(α), also in M[A,b] for each b∈FOLLOW(A) (including $).
- LL(1) iff no cell has two productions. For A→α∣β: FIRST(α)∩FIRST(β)=∅, at most one derives ε, and if β⇒∗ε then FIRST(α)∩FOLLOW(A)=∅.
- Never LL(1): left recursive, ambiguous, or not left factored.
- Left recursion: A→Aα∣β becomes A→βA′, A′→αA′∣ε. Indirect: order nonterminals, substitute earlier ones, then remove immediate recursion.
- Left factoring: A→αβ1∣αβ2 becomes A→αA′, A′→β1∣β2. Dangling else still conflicts.
LR family
| Fact |
Value |
| Grammar classes |
LR(0) ⊂ SLR(1) ⊂ LALR(1) ⊂ CLR(1) |
| State counts |
LR(0) = SLR = LALR ≤ CLR |
| LR(0) items of A→X1…Xk |
k+1 |
| Every LL(1) grammar is |
LR(1), but not always LALR(1) |
| Ambiguous grammar |
never LL(k) or LR(k) |
| LR(1) languages |
exactly the deterministic CFLs |
| Parser |
Reduce A→α⋅ on |
Conflict appears when |
| LR(0) |
every terminal |
complete item plus any other item |
| SLR(1) |
FOLLOW(A) |
shift on a∈FOLLOW(A), or FOLLOW sets overlap |
| CLR(1) |
item lookahead |
lookaheads collide in one state |
| LALR(1) |
union of merged lookaheads |
merging can add reduce-reduce, never shift-reduce |
- Bottom-up = reverse rightmost derivation; top-down = leftmost.
- Handle: right side of the last production of the rightmost derivation. Viable prefixes = paths in the LR(0) DFA (a regular set).
- On a valid input: shifts = tokens; reductions = interior parse-tree nodes.
- Operator grammar: no ε-production, no adjacent nonterminals. +⋖∗, ∗⋗+, left associative +⋗+, right associative ↑⋖↑, id⋗ any operator.
- Yacc: later
%left/%right lines bind tighter; shift-reduce defaults to shift; reduce-reduce to the earlier production.
Syntax-directed translation
| Kind |
Attributes |
Evaluation |
| S-attributed |
synthesized only |
postorder; during LR parsing on a value stack |
| L-attributed |
inherited from parent's inherited attrs and LEFT siblings only |
one left-to-right depth-first pass; LL parsing, or LR with markers |
- Every S-attributed SDD is L-attributed. A dependency cycle means no evaluation order.
- In A→XBY, B's inherited attribute may use A's inherited attributes and X, never Y.
- Marker M→ε replaces an embedded action for bottom-up parsing.
- Value stack: reducing A→X1…Xr, Xi is at top−r+i.
- End-of-production actions print in postorder (infix to postfix). Embedded actions run as leaves in a left-to-right depth-first walk.
- Left recursion groups left, right recursion groups right; lower in the grammar binds tighter.
- Syntax tree nodes = operators + operand occurrences; a DAG shares repeats.
Runtime environments
| Activation record field |
Purpose |
| Parameters, return value |
communication with caller |
| Control (dynamic) link |
caller's record |
| Access (static) link |
lexically enclosing procedure's record |
| Saved status |
return address, registers |
| Locals, temporaries |
callee data |
- Non-local at nesting depth dv from depth d: follow d−dv access links.
- Caller at depth np calls procedure at depth nx: if nx=np+1, access link = caller; if nx≤np, follow np−nx+1 links from the caller.
- Display:
display[d] points to the latest record at depth d; constant-time access.
- Static allocation cannot support recursion; returning a local's address gives a dangling pointer; closures that outlive the call need the heap.
- Static scope: nearest enclosing declaration in the text. Dynamic scope: most recent active binding.
| Passing |
Behaviour |
| Value |
copy in; callee cannot change actual |
| Reference |
address passed; aliasing (p(a, a)) visible |
| Value-result |
copy in, copy back on return (order of copy-back matters) |
| Name |
argument re-evaluated at every use (swap(i, a[i]) fails) |
| Need |
evaluated at first use, then cached |
fib with base n≤1: C(n)=1+C(n−1)+C(n−2)=2Fn+1−1 calls (F1=F2=1; 15 for n=5); stack depth n.
| Form |
Fields |
Note |
| Quadruple |
op, arg1, arg2, result |
easy to move; needs temporaries in symbol table |
| Triple |
op, arg1, arg2 |
result named by position; moving breaks references |
| Indirect triple |
triples plus a list of pointers |
reorder by permuting the list |
- Row-major
A[n1][n2], width w, lower bounds l1,l2: base+((i−l1)n2+(j−l2))w. Column-major: base+((j−l2)n1+(i−l1))w.
- Leaders: first instruction, every jump target, every instruction right after a jump. Block = leader up to the next leader.
- Flow graph edges: jump target, and fall-through unless the block ends in an unconditional jump or
return.
- d dominates n: every path from entry to n passes d. Back edge n→h with h dominating n. Natural loop =h plus nodes that reach n without passing h.
| Backpatching rule |
Actions |
B -> B1 or M B2 |
patch B1.falselist to M; true = merge(B1.t, B2.t); false =B2.f |
B -> B1 and M B2 |
patch B1.truelist to M; true =B2.t; false = merge(B1.f, B2.f) |
B -> not B1 |
swap truelist and falselist |
x relop y |
emit if x relop y goto _ and goto _; lists {n}, {n+1} |
while M1 B do M2 S1 |
patch S1.next to M1, B.t to M2; next =B.f; emit goto M1 |
- SSA: each name assigned once; ϕ at the iterated dominance frontier of the blocks that assign the variable; ϕ picks the value by incoming edge.
Local optimisation and data flow analysis
| Analysis |
Direction |
Meet |
Equation |
Initial |
| Reaching definitions |
forward |
∪ |
OUT=gen∪(IN−kill) |
∅ |
| Available expressions |
forward |
∩ |
OUT=e_gen∪(IN−e_kill) |
entry ∅, others all |
| Live variables |
backward |
∪ |
IN=use∪(OUT−def) |
∅ |
| Very busy expressions |
backward |
∩ |
IN=use∪(OUT−kill) |
exit ∅, others all |
| Constant propagation |
forward |
lattice meet |
evaluate statement |
UNDEF |
- Constant lattice: UNDEF ∧v=v; c∧c=c; c1∧c2= NAC for c1=c2; NAC ∧v= NAC.
- Liveness → dead code, registers (minimum without spills = most values live at once); available expressions → global CSE; reaching definitions → constant propagation, use-def chains.
| Optimisation |
Scope |
| Constant folding, algebraic simplification, local CSE (DAG), copy propagation, dead code in a block |
local |
| Global CSE, global constant propagation |
global (data flow) |
| Loop-invariant code motion, strength reduction, induction variable elimination, unrolling |
loop |
| Redundant load/store, jump-to-jump, unreachable code |
peephole |
- Hoisting
x = y op z needs: operands invariant; block dominates all exits (or x dead after the loop); x assigned once in the loop; uses of x in the loop reached only by it.
- Strength reduction:
t = 4 * i becomes t = t + 4 after i = i + 1, with t set before the loop.
- Copies left by copy propagation are removed by a separate dead code pass; iterate until nothing changes.