Attribute Evaluation in Syntax Trees: Dependency Graphs, Evaluation Order, and an Example

Attribute Evaluation in Syntax Trees: Dependency Graphs, Evaluation Order, and an Example

Verified Sources
Nov 4, 2026

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:

  1. how to write semantic rules on a syntax tree,
  2. how to construct the dependency graph from attribute read/write relationships,
  3. how to choose an evaluation order that respects those dependencies (often implemented via a topological order).

3

Footnotes

  1. Wikipedia — Attribute grammar (intro to inherited/synthesized attributes) - https://en.wikipedia.org/wiki/Attribute_grammar - Defines attribute grammars and inherited/synthesized attributes. ↩

  2. 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. ↩

  3. 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 aa uses attribute bb in its expression, then bb must be computed before aa. 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.

3

Footnotes

  1. Wikipedia — Attribute grammar (intro to inherited/synthesized attributes) - https://en.wikipedia.org/wiki/Attribute_grammar - Defines attribute grammars and inherited/synthesized attributes. ↩

  2. 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. ↩

  3. 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., Enode.typeE_{node}.type) 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:

A→X1 X2 … XkA \rightarrow X_1\,X_2\,\dots\,X_k

and semantic rules that assign attributes on the parent AA and/or children XiX_i. For example, synthesized rules look like:

b←f(a1,a2,… )b \leftarrow f(a_1, a_2, \dots)

meaning: attribute bb is computed from attributes a1,a2,…a_1,a_2,\dots.

Dependency graph definition

To construct the dependency graph for a particular syntax tree:

  1. Create graph nodes for each attribute occurrence you will compute (e.g., E.typeE.\text{type} at a specific tree node).
  2. For each semantic rule that assigns a target attribute tt from a set of source attributes {si}\{s_i\}, add directed edges:
si→ts_i \rightarrow t
  1. 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.

3

Footnotes

  1. Wikipedia — Attribute grammar (intro to inherited/synthesized attributes) - https://en.wikipedia.org/wiki/Attribute_grammar - Defines attribute grammars and inherited/synthesized attributes. ↩

  2. 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. ↩

  3. 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 00 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.

3

Footnotes

  1. Wikipedia — Attribute grammar (intro to inherited/synthesized attributes) - https://en.wikipedia.org/wiki/Attribute_grammar - Defines attribute grammars and inherited/synthesized attributes. ↩

  2. 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. ↩

  3. 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)

  1. 1
    Step 1

    Parse the input using the grammar to obtain a syntax tree with nodes for each nonterminal.

  2. 2
    Step 2

    For each syntax-tree node, identify the attribute instances that will be computed (e.g., synthesized type/value attributes).

  3. 3
    Step 3

    For each production, specify rules that compute target attributes from other attributes (possibly inherited or sibling/child-derived).

  4. 4
    Step 4

    For each semantic rule target attribute tt and each source attribute ss in its expression, add edge s→ts \rightarrow t.

  5. 5
    Step 5

    If the dependency graph has a directed cycle, no valid evaluation order exists.

  6. 6
    Step 6

    If acyclic, compute a topological order of the dependency graph nodes.

  7. 7
    Step 7

    Traverse the topological order and execute semantic rules to compute each target attribute value.

  8. 8
    Step 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 EE
  • operators ++ and ∗* on integer literals and identifiers We’ll compute:
  • E.typeE.\text{type}: synthesized type of an expression
  • Env.lookupEnv.\text{lookup}: (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):

  1. E→intE \rightarrow \text{int}
    • E.type←intE.\text{type} \leftarrow \text{int}
  2. E→idE \rightarrow id
    • E.type←Env[id]E.\text{type} \leftarrow Env[id]
  3. E→E1+E2E \rightarrow E_1 + E_2
    • E.type←checkAdd(E1.type,E2.type)E.\text{type} \leftarrow \text{checkAdd}(E_1.\text{type}, E_2.\text{type})
  4. E→E1∗E2E \rightarrow E_1 * E_2
    • E.type←checkMul(E1.type,E2.type)E.\text{type} \leftarrow \text{checkMul}(E_1.\text{type}, E_2.\text{type})

Here, checkAdd\text{checkAdd} and checkMul\text{checkMul} return int\text{int} if both operands are int\text{int} (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:

1+2∗31 + 2 * 3

The parse tree is:

  • root: E=E1+E2E = E_1 + E_2
  • E1=1E_1 = 1
  • E2=E3∗E4E_2 = E_3 * E_4
  • E3=2E_3 = 2, E4=3E_4 = 3

Dependency graph construction

Attribute nodes (attribute occurrences):

  • T(1)\text{T}(1): (E1.type)(E_1.\text{type})
  • T(2)\text{T}(2): (E3.type)(E_3.\text{type})
  • T(3)\text{T}(3): (E4.type)(E_4.\text{type})
  • T(2∗3)\text{T}(2*3): (E2.type)(E_2.\text{type})
  • T(1+2∗3)\text{T}(1+2*3): (E.type)(E.\text{type})

Semantic-rule dependencies:

  • E1→1E_1 \rightarrow 1: T(1)\text{T}(1) assigned directly.
  • E3→2E_3 \rightarrow 2: T(2)\text{T}(2) assigned directly.
  • E4→3E_4 \rightarrow 3: T(3)\text{T}(3) assigned directly.
  • E2→E3∗E4E_2 \rightarrow E_3 * E_4:
    • T(2)→T(2∗3)\text{T}(2) \rightarrow \text{T}(2*3)
    • T(3)→T(2∗3)\text{T}(3) \rightarrow \text{T}(2*3)
  • E→E1+E2E \rightarrow E_1 + E_2:
    • T(1)→T(1+2∗3)\text{T}(1) \rightarrow \text{T}(1+2*3)
    • T(2∗3)→T(1+2∗3)\text{T}(2*3) \rightarrow \text{T}(1+2*3)

Evaluation order

A valid topological evaluation order is:

  1. compute T(1)\text{T}(1), T(2)\text{T}(2), T(3)\text{T}(3) (independent leaves)
  2. compute T(2∗3)\text{T}(2*3) from T(2)\text{T}(2) and T(3)\text{T}(3)
  3. compute T(1+2∗3)\text{T}(1+2*3) from T(1)\text{T}(1) and T(2∗3)\text{T}(2*3)

This mirrors the intuitive bottom-up flow for synthesized attributes. The dependency graph is what guarantees that any implementation won’t attempt to compute E2.typeE_2.\text{type} before its children’s types exist.

3

Footnotes

  1. Wikipedia — Attribute grammar (intro to inherited/synthesized attributes) - https://en.wikipedia.org/wiki/Attribute_grammar - Defines attribute grammars and inherited/synthesized attributes. ↩

  2. 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. ↩

  3. 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

1 / 5
Question · Term

What is an inherited attribute?

Click to reveal
Answer · Definition

An attribute whose value is provided to a node from its surrounding context (often parent), then used by computations inside the subtree.

Knowledge Check

Question 1 of 4
Q1Single choice

In an attribute grammar, a dependency graph edge a→ba \rightarrow b most directly means: