Compiler DesignUnit 67 min read
Syntax-Directed Translation: Semantic Rules, Attribute Grammars & Code Generation
Unit 6 of Compiler Design explores how compilers generate intermediate or target code by embedding semantic actions in grammar rules, using attribute grammars, and implementing translation schemes (L-attributed, S-attributed). It covers translation of arithmetic expressions, declarations, and control structures with st
Core Concepts
What is Syntax-Directed Translation?
Syntax-directed translation is the process of generating intermediate or target code while parsing the source program, guided by the grammar rules. Unlike pure parsing (which only checks syntax), translation attaches meaning to each grammar rule via semantic actions (code snippets) embedded in the grammar.
Key Idea: Grammar rules define both syntax and translation simultaneously.
1. Attribute Grammars: The Formalism Behind Translation
An attribute grammar extends a context-free grammar (CFG) by:
- Adding attributes to non-terminals (e.g.,
E.valfor expression value). - Defining semantic rules to compute attributes based on syntax.
Types of Attributes
| Type | Definition | Example |
|---|---|---|
| Synthesized | Computed from children, passed up. | E.val = E1.val + E2.val |
| Inherited | Passed down from parent to child. | T.type = E.type (for type checking) |
| L-attributed | Inherited attrs only flow left-to-right. | Used in top-down parsing. |
| S-attributed | Inherited attrs flow in any direction. | Used in bottom-up parsing. |
Example: Arithmetic Expression Translation
Grammar Rule (with semantic actions):
E → T E'
E' → + T { print("+") } E' | ε
T → F T'
T' → * F { print("*") } T' | ε
F → ( E ) | id
Semantic Actions:
+and*are printed as operators.- Parentheses are handled by recursion.
Trace for id1 + id2 * id3:
- Parse
id1→F.val = id1. - See
+→ print+, computeid1 + T.val. - Parse
id2 * id3→T.val = id2 * id3. - Final expression:
id1 + id2 * id3.
MERMAID FLOWCHART:
flowchart TD
A["Start: E → T E'"] --> B["Parse T: id1 → F.val = id1"]
B --> C["Parse E': + → print('+')"]
C --> D["Parse T: id2 * id3 → T.val = id2 * id3"]
D --> E["Final: id1 + id2 * id3"]2. Translation Schemes
L-Attributed Definitions
An attribute grammar is L-attributed if:
- All synthesized attributes are computed from left-to-right.
- Inherited attributes flow only left-to-right.
Why L-attributed?
- Works naturally with top-down parsers (e.g., recursive descent).
- Simplifies implementation (no backtracking for inherited attrs).
Example: Variable Declaration Translation
Grammar Rule:
D → type id { emit("var ", id, ": ", type) }
Trace for int x;:
type = "int",id = "x".- Emit:
var x: int.
MERMAID FLOWCHART:
flowchart TD
A["D → type id"] --> B["type = int"]
B --> C["id = x"]
C --> D["emit('var x: int')"]3. Handling Control Structures
If-Else Translation
Grammar Rule:
S → if ( E ) S1 else S2 { emit("if (", E, ") { ", S1, " } else { ", S2, " }") }
Trace for if (x > 0) S1 else S2:
- Parse
E: x > 0→E.val = "x > 0". - Emit:
if (x > 0) { S1 } else { S2 }
4. Error Handling in Translation
Recovery Strategies
| Error | Recoction Strategy | Example |
|---|---|---|
Missing ; |
Insert ; and continue. |
int x → treated as int x; |
| Mismatched parentheses | Skip to matching ) or ). |
if (x > 0 → treated as if (x > 0) {} |
| Undeclared variable | Replace with 0 or error message. |
y = x + z → y = x + 0 (if z missing) |
MERMAID FLOWCHART:
flowchart TD
A["Parse error detected"] --> B["Check error type"]
B --> C["Missing ';'?"]
C -->|"Yes"| D["Insert ';'"]
C -->|"No"| E["Mismatched paren?"]
E -->|"Yes"| F["Skip to match"]
E -->|"No"| G["Undeclared var?"]
G -->|"Yes"| H["Replace with 0"]In the Real World
Khalti (Nepal):
- Idea Used: Syntax-directed translation for transaction validation.
- How: When you enter
Khalti.transfer("user1", 500), the compiler translates this into intermediate code that checks:- Account balance (
user1.balance >= 500). - Transaction rules (e.g., no negative amounts).
- The semantic action emits SQL like
UPDATE accounts SET balance = balance - 500 WHERE id = "user1".
- Account balance (
Daraz Order Processing:
- Idea Used: Attribute grammars for order validation.
- How: An order like
order.addItem("Laptop", 50000, 1)is parsed and translated into:- Check stock (
stock["Laptop"] >= 1). - Calculate total (
total += 50000). - Emit database update:
UPDATE inventory SET stock = stock - 1 WHERE product = "Laptop".
- Check stock (
Ncell Billing System:
- Idea Used: L-attributed grammars for call duration calculation.
- How: A call log like
call("01234567", 180)is translated to:duration = 180 seconds.cost = duration * rate.- Emit billing record:
INSERT INTO bills (phone, duration, cost) VALUES ("01234567", 180, 300).
WORKED EXAMPLE: NTC Traffic Route Optimization
Assume NTC’s traffic system uses a grammar to parse routes like:
route.addStop("Kathmandu", "Lalitpur", "Bhaktapur").
The compiler translates this into:
- Syntax Check: Ensure
addStophas 3 args. - Semantic Action:
stops = ["Kathmandu", "Lalitpur", "Bhaktapur"] emit("UPDATE routes SET path = ", stops, " WHERE id = current_route") - Output:
UPDATE routes SET path = ["Kathmandu", "Lalitpur", "Bhaktapur"] WHERE id = 101
5. Code Generation vs. Intermediate Code
| Aspect | Intermediate Code (e.g., 3-address) | Target Code (e.g., x86) |
|---|---|---|
| Purpose | Machine-independent, portable. | Machine-specific, executable. |
| Example | t1 = x + y |
mov eax, [x]; add eax, [y]; mov [t1], eax |
| Compiler Phase | After parsing, before optimization. | Final phase. |
| Tools | LLVM IR, Java bytecode. | GCC, NASM. |
Exam Tip
What to Expect in TU/PU Exams:
Grammar Translation:
- Given a grammar with semantic actions, write the output for a sample input (e.g.,
a + b * c). - Common Pitfall: Forgetting operator precedence in arithmetic expressions.
- Given a grammar with semantic actions, write the output for a sample input (e.g.,
Attribute Grammar Questions:
- Identify synthesized vs. inherited attributes in a given grammar.
- Example Question:
"For the grammar
E → T E', whereE'.opis inherited, classify the attributes."
Error Handling:
- Trace recovery for missing tokens (e.g.,
;or)). - Example:
"How would you recover from
if (x > 0(missing))?" Answer: Skip to the next)or treat asif (x > 0) {}.
- Trace recovery for missing tokens (e.g.,
Code Generation:
- Convert a simple statement (e.g.,
while (x > 0) x = x - 1) into intermediate/target code. - Tip: Use stack machines (e.g.,
PUSH x; PUSH 1; SUB) for intermediate code.
- Convert a simple statement (e.g.,
Comparison Tables:
- Compare L-attributed vs. S-attributed grammars or intermediate vs. target code.
- Memorize: L-attributed = left-to-right inherited attrs; S-attributed = any direction.
Final Checklist for Full Marks:
- Show attribute flow in diagrams (L-attributed arrows).
- Trace semantic actions step-by-step for given inputs.
- Link real-world examples (Khalti, Daraz) to grammar rules.
- Handle edge cases (missing tokens, undeclared vars).
- Differentiate intermediate vs. target code clearly.
Based on the PU BE Computer (PU) syllabus for Compiler Design (CMP360), unit 6.
Discussion
Loading…