Compiler Design and ConstructionUnit 410 min read
Syntax-Directed Translation: SDDs, Attributes, and Code Generation
Unit 4 of Compiler Design and Construction covers syntax-directed definitions (SDDs), attribute grammars, and how compilers translate source code into intermediate representations (like 3AC) using parsing trees. Learn L/S-attributed definitions, semantic actions, and how to generate code for expressions, conditionals,
Core Concepts
1. Syntax-Directed Definitions (SDDs)
An SDD is a grammar augmented with semantic rules that define how to compute attributes (e.g., types, values, or intermediate code) during parsing. It consists of:
- A context-free grammar (CFG) defining syntax.
- Semantic rules tied to grammar productions, often written as:
Example:<non-terminal> → <production> { semantic action }E → E₁ + E₂ { $$ = E₁.val + E₂.val } // $$ = left-hand-side attribute
Why SDDs?
- Separation of concerns: Syntax (grammar) and semantics (actions) are distinct.
- Early error detection: Type checking, scope resolution, or code generation happens during parsing.
- Efficiency: Avoids separate passes for translation.
2. Attributes and Attribute Grammars
Attributes are properties of grammar symbols (terminals/non-terminals) that carry meaning (e.g., type, value, address). There are two types:
| Attribute Type | Definition | Example |
|---|---|---|
| Synthesized | Computed from children, passed upward. | E.val in E → E₁ + E₂ |
| Inherited | Passed downward from parent/ancestor. | E.type in if (E) then S (needs E.type to be bool) |
Example Grammar with Attributes:
E → id { $$ = id.lexeme } // Synthesized: $$ = id’s value
| E₁ + E₂ { $$ = E₁.val + E₂.val } // Synthesized
S → if (E) then S₁ else S₂ { } // Inherited: E must be boolean
In the Real World
eSewa Transaction Validation
- Idea Used: Type checking via SDDs
- How: When you pay a bill, eSewa’s compiler-like system checks if the
amount(a numeric attribute) matches theservice_type(e.g., electricity, phone). If the grammar rule forPaymentrequiresamount.type = numericandservice_type.type = string, a mismatched type (e.g., entering "abc" for amount) triggers an error before processing.
Daraz Order Processing Queue
- Idea Used: Inherited attributes for state tracking
- How: Daraz’s backend uses SDDs to track order status. The grammar rule for
Ordermight inheritstatus(e.g., "processing", "shipped") from parent nodes likePaymentorShipping. IfPayment.status = "failed", the inherited attribute forcesOrder.statusto "cancelled" without further checks.
Khalti Loan Interest Calculation
- Idea Used: Synthesized attributes for arithmetic
- How: When you apply for a loan, Khalti’s system computes
total_payable = principal + (principal * rate * time). This is a synthesized attribute in the grammar rule forLoan, where= E₁.val + (E₂.val * E₃.val * E₄.val).
3. Syntax Trees and Annotated Parse Trees
A syntax tree represents the hierarchical structure of a program. Annotated parse trees add semantic information (attributes) to nodes.
Example: Translate A = B + C * D into 3-address code (3AC) using SDDs.
Grammar:
E → E₁ + E₂ { t = newTemp(); emit(t = E₁.val + E₂.val); $$ = t }
| E₁ * E₂ { t = newTemp(); emit(t = E₁.val * E₂.val); $$ = t }
| id { $$ = id.lexeme }
Steps:
- Parse
A = B + C * D→ Tree:A = + / \ B * / \ C D - Annotate with semantic rules:
C * D→t1 = C * DB + t1→t2 = B + t1- Final:
A = t2
- 3AC Output:
t1 = C * D t2 = B + t1 A = t2
4. Three-Address Code (3AC) and Intermediate Representations
3AC is a low-level, easy-to-generate intermediate form where each instruction has at most one operator and three addresses (e.g., t1 = A + B). Other forms:
| Form | Example | Use Case |
|---|---|---|
| Triple | (+, A, B, t1) |
Simple arithmetic |
| Quadruple | (+, t1, C, D, t2) |
More readable than triples |
| DAG | Shared subexpressions | Optimizes repeated calculations |
Example: Convert A = (B + C) - (D - E) to Quadruple and DAG.
Quadruple Form:
1. t1 = B + C
2. t2 = D - E
3. A = t1 - t2
DAG Advantage: If (B + C) is reused, the DAG avoids recomputing it.
5. L-Attributed and S-Attributed Definitions
| Type | Attribute Flow | Example | Limitation |
|---|---|---|---|
| S-attributed | Only synthesized attributes. | E → E₁ + E₂ { = E₁.val + E₂.val } |
Cannot express inherited attributes. |
| L-attributed | Inherited attributes flow left-to-right. | S → if (E) S₁ else S₂ { E.type must be bool } |
No right-to-left inherited attributes. |
Example L-Attributed Grammar for if Statements:
S → if (E) S₁ else S₂ {
if (E.type != bool) error("Condition must be boolean");
E.type = bool; // Inherited to E
}
6. Backpatching for Control Flow
Backpatching is used to generate code for if, while, and goto by:
- Emitting jump instructions to placeholder labels.
- Filling labels later when control flow is resolved.
Example: Translate if (A > B) x = 1 else x = 0 to 3AC.
Grammar:
S → if (E) S₁ else S₂ {
nextquad = quad.length + 1;
emit(if E.val goto L1);
emit(x = 0; goto L2);
emit(L1: x = 1);
emit(L2: );
}
3AC Output:
1. if A > B goto 4
2. x = 0; goto 5
3. L1:
4. x = 1
5. L2:
Exam Tip
SDD Questions:
- Always annotate the parse tree with semantic rules. Show how attributes propagate.
- For type checking, explicitly state inherited/synthesized attributes and error conditions.
3AC/DAG Questions:
- Draw the syntax tree first, then derive 3AC step-by-step.
- For DAGs, highlight shared subexpressions and explain optimizations.
Backpatching:
- Show placeholder jumps and label filling in your trace.
- Example: For
while (E) S, emitgoto L1at the start, then backpatchS’s end toL1.
Common Pitfalls:
- Forgetting to initialize temporary variables in 3AC.
- Misplacing inherited attributes (e.g.,
E.typemust be checked beforeSis processed). - Not handling operator precedence in arithmetic expressions (use grammar rules that enforce precedence).
Worked Example: Full Translation Trace
Input: if (a < b && c > d || e == f) x = 1; else x = 0;
Grammar:
E → E₁ relop E₂ { $$ = E₁.val relop E₂.val }
| E₁ && E₂ { t = newTemp(); emit(t = E₁.val && E₂.val); $$ = t }
| E₁ || E₂ { t = newTemp(); emit(t = E₁.val || E₂.val); $$ = t }
S → if (E) S₁ else S₂ { backpatch(E, S₁, S₂) }
Steps:
- Parse
a < b && c > d || e == f:a < b→t1 = a < bt1 && c > d→t2 = t1 && (c > d)t2 || e == f→t3 = t2 || (e == f)
- Backpatch for
if:- Emit
if t3 goto L1 - Emit
x = 0; goto L2 - Emit
L1: x = 1 - Emit
L2:
- Emit
Final 3AC:
1. t1 = a < b
2. t4 = c > d
3. t2 = t1 && t4
4. t5 = e == f
5. t3 = t2 || t5
6. if t3 goto 10
7. x = 0; goto 11
8. L1:
9. x = 1
10. L2:
11.
Comparison Table: Intermediate Code Forms
| Feature | Triple | Quadruple | DAG |
|---|---|---|---|
| Format | (op, arg1, arg2, res) |
(op, arg1, arg2, res) |
Graph with nodes/edges |
| Example | (<, a, b, t1) |
< t1, a, b |
Shared a + b subgraph |
| Pros | Compact | Readable | Optimizes common subexpr. |
| Cons | Hard to debug | Slightly verbose | Complex to generate |
Key Algorithms
Algorithm: Backpatching for while Loops
Code Example (Pseudocode):
def backpatch_while(E, S):
nextquad = quad.length + 1
emit("goto L1") # Start of loop
emit(E) # Condition
emit("if false goto L2") # Exit if false
emit(S) # Body
emit("goto L1") # Loop back
emit("L2:") # End
Trace for while (x > 0) x = x - 1:
| Step | Quadruple Generated | Action |
|---|---|---|
| 1 | goto L1 |
Jump to start of loop |
| 2 | if x > 0 goto L2 |
Condition check |
| 3 | t1 = x - 1 |
Body: decrement x |
| 4 | x = t1 |
Update x |
| 5 | goto L1 |
Loop back |
| 6 | L2: |
Exit label |
Visual: Syntax Tree for A = B + C * D
graph TD
A["A ="] --> B["+"]
B --> C["B"]
B --> D["*"]
D --> E["C"]
D --> F["D"]Annotated with 3AC:
Visual: Stack During Expression Evaluation
For A = B + C * D (postfix evaluation):
Based on the TU BSc CSIT syllabus for Compiler Design and Construction (CSC365), unit 4.
Discussion
Loading…