Syntax-Directed Definitions (SDDs): S-Attributed vs L-Attributed Attribute Grammars
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
,
an SDD provides semantic rules that define how attributes of and/or 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 ,
- 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 , an SDD might include semantic rules like:
- (synthesized)
- (inherited)
- (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 (in ) depends only on:
- attributes of the parent , and
- attributes of left siblings (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:
Assume each id has an intrinsic type known from the symbol table; we’ll represent it as id.type.
Let:
- be synthesized type of expression .
- be inherited “expected type” passed from parent context.
S-attributed SDD (semantic rules)
For production 1:
- (No inherited needed beyond what is already passed)
For production 2:
- (inherited from parent only)
- (synthesized from child)
For production 3:
- (inherited from parent only)
- (inherited from parent only)
- (synthesized)
Why it is S-attributed:
Each inherited attribute (theExpectedpassed to children) depends only on the parent’sExpected, not on sibling attributes.
Attribute evaluation explanation
Consider a parse tree node for E → E1 + E2.
- When the evaluator visits node
E, it already knowsE.Expected(inherited from above). - It can compute:
E1.Expected := E.ExpectedE2.Expected := E.Expected
- Then it computes synthesized types from children:
- first
E1.type(bottom-up) E2.type(bottom-up)- then
E.typeusingE1.typeandE2.type.
- first
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:
Or simpler expression-like with sequencing:
But to keep it close to standard SDD examples, we’ll use:
-
where
Bappears left ofC.
Attribute idea
We maintain an inherited attribute Env (environment) flowing to the right, updated based on left part B.
Let:
A.envbe inherited environment from parent.B.envinherited from parent.C.envinherited from a combination of parent and left sibling synthesized info.
Production rules showing L-attributed behavior
For :
Semantic rules:
- (inherited from parent)
whereB.defsis synthesized information computed fromB(definitions introduced by the left subtree).- (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 :
- Evaluate/visit node
Awith knownA.env. - Assign:
B.env := A.env
- Compute synthesized attributes for
B, especiallyB.defs. - Now assign:
C.env := extend(B.defs, A.env)
- Compute synthesized attributes for
C. - Finally compute synthesized attributes for
AfromB.defsandC.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
- 1Step 1
Mark which attributes flow upward (synthesized) and which flow from parent/other nodes (inherited) in each production.
- 2Step 2
For production , compute inherited attributes of each child using only attributes of (no sibling dependence).
- 3Step 3
After inherited values are available, evaluate synthesized attributes for .. and then compute ’s synthesized attributes.
- 4Step 4
For each child , evaluate inherited attributes using and synthesized attributes of left siblings . This enables computing first, then , etc.
- 5Step 5
If some inherited attribute of 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 annotationsFor each production, specify equations for inherited and synthesized attributes."
S-attributed check
2. Classify attributesVerify inherited attributes depend only on the parent (no sibling dependence)."
L-attributed check
3. Classify attributesVerify inherited attributes depend on the parent and left siblings only."
Compute attributes
4. Evaluate in an order consistent with dependenciesUse bottom-up for synthesized; use top-down or left-to-right for inherited as permitted."
SDD Mastery Flashcards
Knowledge Check
In an S-attributed attribute grammar, inherited attributes of a child may depend on which other attributes?