Compiler DesignUnit 37 min read
Syntax Analysis: Parsing, Grammars, and Abstract Syntax Trees
Unit 3 of Compiler Design explores how compilers analyze the syntactic structure of source code using grammars, parsing techniques, and abstract syntax trees (ASTs). This note covers context-free grammars (CFGs), parsing algorithms (top-down and bottom-up), and the role of ASTs in semantic analysis, with visual traces
What is Syntax Analysis?
Syntax analysis (or parsing) is the second phase of compilation (after lexical analysis) that checks whether a program’s tokens follow the grammar rules of the language and constructs a parse tree or abstract syntax tree (AST). It ensures the program’s structure is valid before semantic analysis.
Key Definitions
- Grammar: A set of production rules defining valid sentences (programs) in a language.
- Parse Tree: A tree structure showing how tokens derive from grammar rules.
- Abstract Syntax Tree (AST): A simplified parse tree that omits non-semantic details (e.g., parentheses in arithmetic expressions).
- Ambiguity: When a grammar can generate multiple parse trees for the same input (e.g.,
if-elseprecedence).
Context-Free Grammars (CFGs)
A CFG is a 4-tuple , where:
- : Non-terminal symbols (e.g.,
E,T). - : Terminal symbols (e.g.,
+,id,(). - : Production rules (e.g.,
E → E + T). - : Start symbol (e.g.,
Program).
Example: Arithmetic Expressions
graph TD E["E"] --> T["T"] E -->|"'+'"| E2["E"] E2 --> T2["T"] T -->|"'id'"| id1["id"] T2 -->|"'id'"| id2["id"]
CFG parse tree for E → T + T (left-recursive rule) Grammar Rules:
E → E + T | T
T → id | ( E )
Input: id + id
Parse Tree:
E
/ \
E +
/ \ \
id + T
/ \
id id
In the Real World
eSewa App (Nepal):
- Uses CFGs internally to validate user inputs (e.g., transaction syntax like
pay <amount> <phone>). The parser checks if the command follows the grammar before processing. - Example: The rule
Transaction → "pay" Amount Phoneensurespay 500 9800000000is valid, butpayto 500 9800000000is rejected.
- Uses CFGs internally to validate user inputs (e.g., transaction syntax like
Khalti’s API Requests:
- Khalti’s backend parses JSON/XML requests using ASTs to extract fields like
amount,merchant_id, andsignature. Ambiguity in grammar could lead to incorrect transaction routing (e.g.,{"amount": 100, "currency": "NPR"}vs.{"currency": 100, "amount": "NPR"}).
- Khalti’s backend parses JSON/XML requests using ASTs to extract fields like
NTC’s Traffic Route Planning (Nepal):
- Syntax analysis ensures road network grammars (e.g.,
Route → Start → Junction → End) are followed when parsing GPS data. A misparsed route (e.g., missing→) could send a bus to a dead end.
- Syntax analysis ensures road network grammars (e.g.,
Parsing Techniques
Parsing converts input tokens into a parse tree using two main approaches:
1. Top-Down Parsing (LL Parsers)
- Starts with the start symbol and expands non-terminals.
- Uses recursive descent or predictive parsing.
- Requires left-recursive grammars to be eliminated (e.g.,
A → Aα | β→A → βA').
Example: Eliminating Left Recursion Original:
E → E + T | T
After elimination:
E → T E'
E' → + T E' | ε
Mermaid Flowchart: Top-Down Parsing for id + id
Code Example (Recursive Descent in C):
void E() {
T();
E_prime();
}
void E_prime() {
if (token == '+') {
match('+');
T();
E_prime();
}
}
void T() {
if (token == 'id') match('id');
else if (token == '(') { match('('); E(); match(')'); }
}
Trace for id + id:
| Step | Action | Stack/Input |
|---|---|---|
| 1 | Call E() |
E | id + id $ |
| 2 | Call T() → match id |
T | + id $ |
| 3 | Call E_prime() → match + |
E' | id $ |
| 4 | Call T() → match id |
T | $ |
| 5 | E_prime() → ε |
Accept |
2. Bottom-Up Parsing (LR Parsers)
- Starts with the input string and reduces it using grammar rules.
- Uses shift-reduce or LR(0)/SLR/LALR/LR(1) parsers.
- Handles right-recursive grammars naturally.
Example: LR(0) Parsing for id * id
Grammar:
E → E * T | T
T → id
Input: id * id $
LR(0) States:
State 0: {E → •E * T, E → •T, T → •id}
State 1: {E → E • * T, E → •T, T → •id}
State 2: {E → E * •T, T → •id}
State 3: {E → E * T •, E → •T, T → •id}
State 4: {E → T •, E → •T, T → •id}
State 5: {E → T •}
Trace:
| State | Stack | Input | Action |
|---|---|---|---|
| 0 | [0] | id * id $ | Shift 1 |
| 1 | [0,1] | * id $ | Shift 2 |
| 2 | [0,1,2] | id $ | Shift 3 |
| 3 | [0,1,2,3] | $ | Reduce T→id |
| 4 | [0,1,4] | $ | Reduce E→T |
| 5 | [0,5] | $ | Reduce E→E*T |
| 6 | [6] | $ | Accept |
Abstract Syntax Trees (ASTs)
An AST represents the logical structure of code, ignoring syntax noise (e.g., parentheses, semicolons). It’s used for:
- Semantic analysis (type checking).
- Code optimization.
- Code generation.
Example: AST for a = b + c * d
Conversion from Parse Tree to AST:
- Remove redundant nodes (e.g.,
+in(a + b)). - Flatten common subtrees (e.g.,
*before+ina + b * c).
Handling Ambiguity
Ambiguity occurs when multiple parse trees are valid. Solutions:
- Grammar Restrictions:
- Prefer left-recursive or right-recursive rules.
- Example: Use
E → T E'instead ofE → E + Tto avoid ambiguity ina + b + c.
- Precedence Rules:
*has higher precedence than+, soa + b * cparses asa + (b * c).
- Disambiguating Symbols:
- Use
begin/endfor blocks (e.g.,if (cond) begin ... end).
- Use
Ambiguous Grammar Example:
E → E + E | E * E | id
Input: a + b * c
Possible Parse Trees:
(a + b) * ca + (b * c)
Solution: Add precedence rules or rewrite as:
E → T E'
E' → + T E' | ε
T → F T'
T' → * F T' | ε
F → id | ( E )
Comparison: Top-Down vs. Bottom-Up Parsing
| Feature | Top-Down (LL) | Bottom-Up (LR) |
|---|---|---|
| Direction | Start symbol → Input | Input → Start symbol |
| Grammar Requirement | Left-recursive free | Right-recursive friendly |
| Error Detection | Early (leftmost derivation) | Late (full reduction) |
| Example Algorithms | Recursive Descent, LL(1) | SLR, LR(0), LALR(1) |
| Use Case | Simple grammars (e.g., C) | Complex grammars (e.g., Java) |
Exam Tip
Grammar Questions:
- Always derive the parse tree for given inputs.
- Show all steps of leftmost/rightmost derivations.
- Example: For
if (x > 0) y = 1;, draw the parse tree using the grammar rules.
Parsing Algorithms:
- Trace stack/input for LR(0) or recursive calls for LL(1).
- Highlight shift/reduce/accept actions in LR parsing.
ASTs:
- Focus on logical structure (ignore syntax).
- Example: Convert
(a + b) * cto*with children+(childrena,b) andc.
Ambiguity:
- Identify ambiguous grammars and propose fixes (e.g., precedence rules).
- Example: For
a = b = c, clarify as(a = b) = cora = (b = c).
Real-World Links:
- Relate parsing to API validation (e.g., Khalti’s JSON grammar) or compiler tools (e.g., GCC’s Yacc/Bison).
Based on the PU BE Computer (PU) syllabus for Compiler Design (CMP360), unit 3.
Discussion
Loading…