CD Notes
Understand dependency graphs for attribute evaluation: node ordering, cycle detection, topological sort algorithms, evaluation schemes, practical implementations in modern compilers with visual examples.
Introduction
A dependency graph is a directed acyclic graph (DAG) that represents attribute dependencies in a parse tree. It shows which attributes must be computed before others, determining the evaluation order for attribute grammar rules.
Purpose
Graph Construction
Algorithm: Building Dependency Graphs
Example: Simple Expression Grammar
Grammar
E → E₁ + T {E.val = E₁.val + T.val}
E → T {E.val = T.val}
T → F {T.val = F.val}
F → num {F.val = num.value}
Parse: 5 + 3
Parse tree
E (val=?)
/ \
E₁ T (val=3)
/ |
T F
| |
5 3
Dependency nodes
F₁.val, T₁.val, T₂.val, E₁.val, E.val, num₁.val, num₂.val
Dependency edges
num₁ → F₁ (num1.value → F1.val)
F₁ → T₁ (F1.val → T1.val)
num₂ → F₂
F₂ → T₂
T₁ → E₁ (T1.val → E1.val)
E₁ → E (E1.val → E.val, T2.val → E.val)
T₂ → E
Evaluation Order Computation
Topological Sort Algorithm
Example Topological Sort
Dependency Graph
A → B (A depends on B)
A → C
B → D
C → D
In-degrees
D: 0
B: 1
C: 1
A: 2
Execution
1. In-degree 0: [D]
Process D: decrements B (0), C (0)
Queue: [B, C]
2. Process B: decrements A (1)
Queue: [C]
3. Process C: decrements A (0)
Queue: [A]
4. Process A
Order: D, B, C, A (or D, C, B, A)
Cycle Detection
Algorithm: Detecting Circular Dependencies
Algorithm: HasCycle(Graph):
visited = set()
rec_stack = set() // Recursion stack
for each node:
if DFS(node, visited, rec_stack):
return true // Cycle found
return false
DFS(node, visited, rec_stack):
visited.add(node)
rec_stack.add(node)
for each neighbor of node:
if neighbor not in visited:
if DFS(neighbor, visited, rec_stack):
return true
elif neighbor in rec_stack:
return true // Back edge found (cycle)
rec_stack.remove(node)
return falseExample: Circular Dependency
Grammar (INVALID)
A.x := B.y
B.y := C.z
C.z := A.x
Dependency graph
A.x → B.y → C.z → A.x
Cycle: A.x → B.y → C.z → A.x
Result: No evaluation order exists ✗
Practical Implementation
Example 1: Multi-Pass Evaluation Strategy
If dependency graph suggests multiple "layers"
1. Input attributes (from tokens)
2. Layer 1 attributes (depend on inputs)
3. Layer 2 attributes (depend on Layer 1)
4. ...
Process in layers (breadth-first)
Pass 1: Compute all Layer 1 attributes
Pass 2: Compute all Layer 2 attributes
...
Example 2: On-The-Fly Attribute Computation
Comparing Evaluation Strategies
Strategy 1: Top-Down (Demand-Driven)
Advantages
- Compute attributes on-demand
- Advantages: Lazy evaluation, only compute needed attributes
- Example:
- evaluate(E.val):
- need E₁.val and T.val
- compute E₁.val (recursive)
- compute T.val (recursive)
- return E₁.val + T.val
Disadvantages
Strategy 2: Bottom-Up (Supply-Driven)
Advantages
- Compute all attributes bottom-up post-order
- Advantages: Efficient one-pass
- Example:
- post_order_traversal(tree):
- for each child:
- post_order_traversal(child)
- compute_all_synthesized_attributes(node)
Disadvantages
Strategy 3: Topological Order
Advantages
- Compute attributes in order given by topological sort
- Advantages: Respect dependencies, efficient
- Example:
- order = topological_sort(dependency_graph)
- for each attribute in order:
- compute(attribute)
Disadvantages
Attributes and Parse Trees
Full Dependency Graph Example
Parse tree with attributes
E.val
/ \
E.val T.val
/ \ / |
E₁.val T.val F.val
/ | |
T.val T.val id.val
Dependencies
T.val depends on F.val (via T → F rule)
E.val depends on E₁.val, T.val (via E → E+T rule)
Evaluation order
1. id.val (leaf value)
2. F.val (F → id)
3. T.val (T → F)
4. E₁.val
5. E.val (E → E+T)
Error Handling
Handling Undefined Attributes
Quick Revision Notes
- Dependency graph: DAG showing attribute computation dependencies
- Node: Each attribute instance in parse tree
- Edge: From dependency to dependent attribute
- Acyclic: Necessary for well-formed attribute grammar
- Topological sort: Valid evaluation order
- Circular dependency: Indicates grammar malformation
- On-demand evaluation: Compute attributes when needed (memoize)
- Batch evaluation: Compute in layers or post-order
Interview Q&A
Q1: When would you actually construct an explicit dependency graph?
A: During attribute grammar compiler design or when analyzing attribute flow. For implementation, you typically use implicit dependency ordering (top-down recursion or post-order traversal) without constructing the graph explicitly.
Q2: How do you detect circular dependencies before parsing?
A: Many attribute grammar compilers do static analysis on the grammar rules before parsing any input. If the grammar rules allow circular dependencies, parsing will fail. Good compilers reject such grammars at compile-time.
Q3: Can you have different evaluation orders for the same grammar?
A: Yes! Any topological order of the dependency graph is valid. Multiple different orders may correctly evaluate attributes. However, some orders are more efficient (fewer redundant computations) than others.
Q4: What's the relationship between attribute dependencies and left-recursion?
A: Left recursion can create unusual dependencies. For example, A → Aα might have A.syn depending on its own value. This can still be acyclic if properly structured (like attribute computation over left-associated trees).
Q5: How do memoization and dependency graphs interact?
A: Memoization prevents redundant computation. If you evaluate on-demand and memoize results, you effectively follow dependencies lazily. The dependency graph tells you which orders are valid; memoization ensures efficiency.
Q6: Why is topological sort stable under grammar transformations?
A: If you restructure a grammar (eliminate left recursion, factor prefixes), you change the parse tree structure but not the underlying attribute relationships. The dependency graph should remain equivalent, maintaining evaluation correctness.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Dependency Graphs: Visualizing Attribute Computation Order in Semantic Analysis.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Compiler Design topic.
Search Terms
compiler-design, compiler design, compiler, design, syntax, directed, translation, dependency
Related Compiler Design Topics