Attribute Evaluation in Syntax Trees: Dependency Graphs, Evaluation Order, and an Example
Attribute evaluation in a syntax tree is the mechanism by which a compiler computes semantic information (types, values, symbol-table bindings, etc.) from the parse structure. In attribute grammar terminology, each node in a syntax tree carries attributes that are computed using semantic rules. There are two common kinds: inherited attribute and synthesized attribute.
A practical way to reason about “which semantic rules must run first” is to build a dependency graph: a graph whose edges represent “attribute B depends on attribute A”. If that dependency graph is acyclic, an evaluation order exists that guarantees every attribute is computed before it is needed. This is closely related to the well-known dependency graph approach used for attribute grammars.
Below, the process is presented in three layers:
- how to write semantic rules on a syntax tree,
- how to construct the dependency graph from attribute read/write relationships,
- how to choose an evaluation order that respects those dependencies (often implemented via a topological order).
Footnotes
-
Wikipedia — Attribute grammar (intro to inherited/synthesized attributes) - https://en.wikipedia.org/wiki/Attribute_grammar - Defines attribute grammars and inherited/synthesized attributes. ↩
-
Wikipedia — Syntax tree (parse tree concept used as carrier for attributes) - https://en.wikipedia.org/wiki/Syntax_tree - Provides background on syntax trees as parse representations. ↩
-
Wikipedia — Dependency graph (general concept of modeling dependencies) - https://en.wikipedia.org/wiki/Dependency_graph - General graph-theoretic framing for dependencies. ↩
Attribute Grammars (overview) — dependency/evaluation intuition
Key concepts and workflow
Syntax tree nodes are created by parsing. Then semantic rule bodies reference attributes of the current node and related nodes (children, parent, siblings via inherited flow). The compiler then:
- instantiates attributes at each node,
- determines which semantic rules can be evaluated,
- iteratively computes attribute values in a legal order.
The central correctness requirement is: if a semantic rule for attribute uses attribute in its expression, then must be computed before . The dependency graph makes that explicit. Topological order is a natural evaluation order when dependencies form a DAG. In many textbooks, the “acyclic dependency graph” condition is what makes evaluation feasible for general attribute grammars. Well-formedness is the name often used for such guarantees.
Footnotes
-
Wikipedia — Attribute grammar (intro to inherited/synthesized attributes) - https://en.wikipedia.org/wiki/Attribute_grammar - Defines attribute grammars and inherited/synthesized attributes. ↩
-
Wikipedia — Syntax tree (parse tree concept used as carrier for attributes) - https://en.wikipedia.org/wiki/Syntax_tree - Provides background on syntax trees as parse representations. ↩
-
Wikipedia — Dependency graph (general concept of modeling dependencies) - https://en.wikipedia.org/wiki/Dependency_graph - General graph-theoretic framing for dependencies. ↩
Pro Tip
When building the dependency graph, treat each attribute instance (e.g., ) as a separate node. Don’t just connect grammar nonterminals—connect the specific attribute occurrences.
Construction of dependency graphs from attribute rules
Assume a production of the form:
and semantic rules that assign attributes on the parent and/or children . For example, synthesized rules look like:
meaning: attribute is computed from attributes .
Dependency graph definition
To construct the dependency graph for a particular syntax tree:
- Create graph nodes for each attribute occurrence you will compute (e.g., at a specific tree node).
- For each semantic rule that assigns a target attribute from a set of source attributes , add directed edges:
- The result is a directed graph capturing all “must happen before” relationships.
If the graph contains a cycle, no evaluation order can satisfy all dependencies simultaneously—this is why acyclic dependency conditions are used in practice. A common approach is to compute a topological sorting order of the dependency graph nodes and then evaluate in that order.
Footnotes
-
Wikipedia — Attribute grammar (intro to inherited/synthesized attributes) - https://en.wikipedia.org/wiki/Attribute_grammar - Defines attribute grammars and inherited/synthesized attributes. ↩
-
Wikipedia — Syntax tree (parse tree concept used as carrier for attributes) - https://en.wikipedia.org/wiki/Syntax_tree - Provides background on syntax trees as parse representations. ↩
-
Wikipedia — Topological sorting (used to produce a valid evaluation order for DAGs) - https://en.wikipedia.org/wiki/Topological_sorting - States that DAGs admit topological orders, matching feasible attribute evaluation schedules. ↩
Evaluation order of attributes
Given the dependency graph is a DAG, an evaluation order exists. Two standard viewpoints:
1) Rule-triggering view (operational)
Evaluate any semantic rule whose dependencies are already satisfied. After evaluating, mark its target attribute as available; this may enable more rules.
This operational method is equivalent to repeatedly taking nodes with in-degree in the dependency graph (Kahn-style logic), conceptually producing a topological order.
2) Ordering view (declarative)
Compute a topological order of the dependency graph and evaluate semantic rules in that order. This is often easier to reason about formally.
Inherited vs synthesized implications
- Synthesized attribute: naturally tends to be computable in a bottom-up fashion if the rules only depend on children.
- Inherited attribute: tends to require top-down propagation (parent-to-child), but still can be scheduled safely if dependencies do not form cycles.
A key point: even with inherited attributes, evaluation is correct as long as the dependency graph remains acyclic and every attribute has a defined semantic rule.
Footnotes
-
Wikipedia — Attribute grammar (intro to inherited/synthesized attributes) - https://en.wikipedia.org/wiki/Attribute_grammar - Defines attribute grammars and inherited/synthesized attributes. ↩
-
Wikipedia — Syntax tree (parse tree concept used as carrier for attributes) - https://en.wikipedia.org/wiki/Syntax_tree - Provides background on syntax trees as parse representations. ↩
-
Wikipedia — Dependency graph (general concept of modeling dependencies) - https://en.wikipedia.org/wiki/Dependency_graph - General graph-theoretic framing for dependencies. ↩
Attribute evaluation with dependency graph (end-to-end)
- 1Step 1
Parse the input using the grammar to obtain a syntax tree with nodes for each nonterminal.
- 2Step 2
For each syntax-tree node, identify the attribute instances that will be computed (e.g., synthesized type/value attributes).
- 3Step 3
For each production, specify rules that compute target attributes from other attributes (possibly inherited or sibling/child-derived).
- 4Step 4
For each semantic rule target attribute and each source attribute in its expression, add edge .
- 5Step 5
If the dependency graph has a directed cycle, no valid evaluation order exists.
- 6Step 6
If acyclic, compute a topological order of the dependency graph nodes.
- 7Step 7
Traverse the topological order and execute semantic rules to compute each target attribute value.
- 8Step 8
Semantic results (types, values, addresses, etc.) are then used by later passes such as type checking and code generation.
Example: Type checking and dependency graph for arithmetic expressions
Consider a tiny expression language:
- expressions
- operators and on integer literals and identifiers We’ll compute:
- : synthesized type of an expression
- : (conceptually) inherited environment mapping identifiers to types
To keep the example focused, we’ll use a common pattern where types are synthesized from children for operators, while identifier lookup is “inherited-context-driven” (the environment is passed down).
Grammar + semantic rules (informal)
Productions (schematic):
Here, and return if both operands are (otherwise they signal a type error). This uses only child synthesized attributes, so the dependency graph for these operator nodes is bottom-up.
Input syntax tree
Take the input:
The parse tree is:
- root:
- ,
Dependency graph construction
Attribute nodes (attribute occurrences):
- :
- :
- :
- :
- :
Semantic-rule dependencies:
- : assigned directly.
- : assigned directly.
- : assigned directly.
- :
- :
Evaluation order
A valid topological evaluation order is:
- compute , , (independent leaves)
- compute from and
- compute from and
This mirrors the intuitive bottom-up flow for synthesized attributes. The dependency graph is what guarantees that any implementation won’t attempt to compute before its children’s types exist.
Footnotes
-
Wikipedia — Attribute grammar (intro to inherited/synthesized attributes) - https://en.wikipedia.org/wiki/Attribute_grammar - Defines attribute grammars and inherited/synthesized attributes. ↩
-
Wikipedia — Syntax tree (parse tree concept used as carrier for attributes) - https://en.wikipedia.org/wiki/Syntax_tree - Provides background on syntax trees as parse representations. ↩
-
Wikipedia — Topological sorting (used to produce a valid evaluation order for DAGs) - https://en.wikipedia.org/wiki/Topological_sorting - States that DAGs admit topological orders, matching feasible attribute evaluation schedules. ↩
Attribute evaluation schedule (example: 1 + 2 * 3)
One possible topological evaluation order respecting dependency edges.
Common pitfalls and edge cases
Attribute Evaluation Quick Recall
Knowledge Check
In an attribute grammar, a dependency graph edge most directly means: