Syntax-Directed Definitions (SDDs): S-Attributed vs L-Attributed Attribute Grammars

Syntax-Directed Definitions (SDDs): S-Attributed vs L-Attributed Attribute Grammars

Verified Sources
Nov 4, 2026

In compiler construction, a syntax-directed definition (often abbreviated SDD or called an attribute grammar ) specifies semantics by attaching attribute computation rules to a context-free grammar.

For each production of the form
A→X1X2…XnA \rightarrow X_1 X_2 \dots X_n,
an SDD provides semantic rules that define how attributes of AA and/or XiX_i are computed from other attributes. Attribute dependencies determine when each attribute can be evaluated, which leads to restricted classes like S-attributed and L-attributed.

Visual dependency intuition (attribute flow)

Syntax-Directed Definitions (SDDs) & Attribute Grammars — S-Attributed vs L-Attributed

1) Define Syntax-Directed Definitions (SDDs)

Core idea

An SDD augments a context-free grammar with rules of the form:

  • For each production A→X1X2…XnA \rightarrow X_1 X_2 \dots X_n,
  • you attach semantic equations that compute attributes.

An attribute grammar typically distinguishes:

  • synthesized attributes: flow upward (bottom-up).
  • inherited attributes: flow downward/sideways (top-down or left-to-right).

Canonical production form and semantic equations

For example, for A→X1X2A \rightarrow X_1X_2, an SDD might include semantic rules like:

  • A.val:=f(X1.val,X2.val)A.\text{val} := f(X_1.\text{val}, X_2.\text{val}) (synthesized)
  • X1.type:=A.typeX_1.\text{type} := A.\text{type} (inherited)
  • X2.type:=X1.typeX_2.\text{type} := X_1.\text{type} (inherited from left sibling)

Why restrictions matter

Not every attribute grammar can be evaluated with a simple single traversal. Restrictions like S-attributed and L-attributed ensure there exists an evaluation order that avoids circular dependencies and supports linear-time parsing/translation strategies.

Key terms (quick map)

  • attribute dependency
  • evaluation order
  • context-free grammar

2) S-Attributed vs L-Attributed: Differences

Both classes restrict which inherited attributes may depend on other attributes. The difference is how much sibling information is allowed.

S-attributed (stronger restriction)

Definition (informal but standard):
An attribute grammar is S-attributed if every inherited attribute of a child depends only on:

  • attributes of the parent (and possibly synthesized attributes of nodes above it),
  • not on attributes of siblings.

This means inherited information comes only from “above” (parent), not from “sideways” (other children).

L-attributed (weaker restriction → left-to-right evaluation)

An attribute grammar is L-attributed if every inherited attribute of a symbol XiX_i (in A→X1…XnA \rightarrow X_1 \dots X_n) depends only on:

  • attributes of the parent AA, and
  • attributes of left siblings X1,…,Xi−1X_1, \dots, X_{i-1} (specifically their synthesized attributes, since those can be computed left-to-right).

This enables an evaluation strategy consistent with left-to-right parsing (common with many practical parser/translator designs).

3) Example of an S-attributed definition (attribute evaluation)

We’ll use an SDD that computes expression type checking in a simple setting, using inherited attributes only from the parent.

Grammar

Let nonterminal E represent expressions. We use a context parameter Expected inherited from the parent to force checking a target type:

Productions:

  1. E→idE \rightarrow \text{id}
  2. E→(E)E \rightarrow (E)
  3. E→E  +  EE \rightarrow E \;+\; E

Assume each id has an intrinsic type known from the symbol table; we’ll represent it as id.type.
Let:

  • E.typeE.\text{type} be synthesized type of expression EE.
  • E.ExpectedE.\text{Expected} be inherited “expected type” passed from parent context.

S-attributed SDD (semantic rules)

For production 1: E→idE \rightarrow \text{id}

  • E.type:=id.typeE.\text{type} := \text{id.type}
  • (No inherited needed beyond what is already passed)

For production 2: E→(E1)E \rightarrow (E_1)

  • E1.Expected:=E.ExpectedE_1.\text{Expected} := E.\text{Expected} (inherited from parent only)
  • E.type:=E1.typeE.\text{type} := E_1.\text{type} (synthesized from child)

For production 3: E→E1+E2E \rightarrow E_1 + E_2

  • E1.Expected:=E.ExpectedE_1.\text{Expected} := E.\text{Expected} (inherited from parent only)
  • E2.Expected:=E.ExpectedE_2.\text{Expected} := E.\text{Expected} (inherited from parent only)
  • E.type:=numericComposedType(E1.type,E2.type)E.\text{type} := \text{numericComposedType}(E_1.\text{type}, E_2.\text{type}) (synthesized)

Why it is S-attributed:
Each inherited attribute (the Expected passed to children) depends only on the parent’s Expected, not on sibling attributes.

Attribute evaluation explanation

Consider a parse tree node for E → E1 + E2.

  1. When the evaluator visits node E, it already knows E.Expected (inherited from above).
  2. It can compute:
    • E1.Expected := E.Expected
    • E2.Expected := E.Expected
  3. Then it computes synthesized types from children:
    • first E1.type (bottom-up)
    • E2.type (bottom-up)
    • then E.type using E1.type and E2.type.

This schedule is consistent with bottom-up computation of synthesized attributes and top-down propagation of inherited attributes from the parent only—matching the S-attributed restriction.

Simple dependency diagram

4) Example of an L-attributed definition (left-to-right evaluation)

We’ll use a classic example: compute the type of an identifier and support a left-to-right symbol table context (or equivalently, a “prefix context” inherited to the right operand).

Grammar

Let Stmt be:

  • S→declare id  SS \rightarrow \text{declare id} \; S
  • S→S1  S2S \rightarrow S_1 \; S_2

Or simpler expression-like with sequencing:

  • P→P1  ;  P2P \rightarrow P_1 \; ; \; P_2

But to keep it close to standard SDD examples, we’ll use:

  • A→B  CA \rightarrow B \; C where B appears left of C.

Attribute idea

We maintain an inherited attribute Env (environment) flowing to the right, updated based on left part B.

Let:

  • A.env be inherited environment from parent.
  • B.env inherited from parent.
  • C.env inherited from a combination of parent and left sibling synthesized info.

Production rules showing L-attributed behavior

For A→B  CA \rightarrow B\;C:

Semantic rules:

  • B.env:=A.envB.\text{env} := A.\text{env} (inherited from parent)
  • C.env:=extend(B.defs,A.env)C.\text{env} := \text{extend}(B.\text{defs}, A.\text{env})
    where B.defs is synthesized information computed from B (definitions introduced by the left subtree).
  • A.defs:=merge(B.defs,C.defs)A.\text{defs} := \text{merge}(B.\text{defs}, C.\text{defs}) (synthesized)

Why it is L-attributed

For the inherited attribute of the second child C.env:

  • it depends on the parent A.env, and
  • it depends on the left sibling B.defs (synthesized).
  • it does not depend on the right sibling (none exists yet) and does not depend on siblings to the right.

Thus it satisfies the L-attributed restriction.

Attribute evaluation (left-to-right)

Given the parse of A→B  CA \rightarrow B\;C:

  1. Evaluate/visit node A with known A.env.
  2. Assign:
    • B.env := A.env
  3. Compute synthesized attributes for B, especially B.defs.
  4. Now assign:
    • C.env := extend(B.defs, A.env)
  5. Compute synthesized attributes for C.
  6. Finally compute synthesized attributes for A from B.defs and C.defs.

This works with a deterministic left-to-right traversal because C only needs synthesized results from B, the left sibling, before C’s inherited attributes can be evaluated.

Dependency diagram

Evaluating attributes in S-attributed vs L-attributed grammars

  1. 1
    Step 1

    Mark which attributes flow upward (synthesized) and which flow from parent/other nodes (inherited) in each production.

  2. 2
    Step 2

    For production A→X1…XnA \rightarrow X_1\dots X_n, compute inherited attributes of each child using only attributes of AA (no sibling dependence).

  3. 3
    Step 3

    After inherited values are available, evaluate synthesized attributes for X1X_1..XnX_n and then compute AA’s synthesized attributes.

  4. 4
    Step 4

    For each child XiX_i, evaluate inherited attributes using AA and synthesized attributes of left siblings X1..Xi−1X_1..X_{i-1}. This enables computing X1X_1 first, then X2X_2, etc.

  5. 5
    Step 5

    If some inherited attribute of XiX_i depends on a right sibling’s attributes, the grammar is not L-attributed; if it depends on siblings at all, not S-attributed.

Pro Tip

When designing an SDD, start by listing each inherited attribute’s dependencies. If any dependency is on a sibling, you likely need an L-attributed (not S-attributed) design—or refactor the semantics.

Common pitfall

Don’t assume “bottom-up parsing” automatically works for any attribute grammar. Attribute evaluation order must satisfy dependency constraints, or you’ll create cycles/undefined reads.

FAQ and Edge Cases

Dependency Allowances (S-attributed vs L-attributed)

Higher bar = more dependency sources allowed for inherited attributes of a child.

From SDD rules to executable attribute evaluation

Attach semantic rules

1. Write SDD annotations

For each production, specify equations for inherited and synthesized attributes."

S-attributed check

2. Classify attributes

Verify inherited attributes depend only on the parent (no sibling dependence)."

L-attributed check

3. Classify attributes

Verify inherited attributes depend on the parent and left siblings only."

Compute attributes

4. Evaluate in an order consistent with dependencies

Use bottom-up for synthesized; use top-down or left-to-right for inherited as permitted."

SDD Mastery Flashcards

1 / 5
Question · Term

What is an SDD?

Click to reveal
Answer · Definition

A syntax-directed definition is a grammar augmented with semantic rules that compute attributes for grammar symbols.

Knowledge Check

Question 1 of 4
Q1Single choice

In an S-attributed attribute grammar, inherited attributes of a child may depend on which other attributes?