Skip to main content

6.2 S-Attributed vs L-Attributed SDDs & Evaluation Orders

πŸ“šModule 01S-Attributed vs L-Attributed SDDs & Evaluation Orders⏱️5 min read
🎯High-Yield For:Semester Exams β€’ GATE CSE β€’ Technical Interviews

πŸ’‘ Core Intuition: The 3-Second Mental Model​

🍳 The Everyday Analogy: The Unidirectional Assembly Line vs Hierarchical Briefing​

Imagine an automotive manufacturing plant where a car moves along an automated line:

  1. S-Attributed (Pure Bottom-Up Assembly):
    • Component workers at the lowest tier manufacture sub-assemblies (engine block, transmission gear, wheel hub).
    • Higher-tier assembly workers only combine finished components delivered from below.
    • Information flows strictly upward (↑\uparrow). No assembly worker ever needs instructions from the finished car above them, nor does any station inspect parts to their right.
  2. L-Attributed (Hierarchical Left-to-Right Briefing):
    • The floor supervisor hands down a project spec sheet (inherited from parent ↓\downarrow).
    • The station installing the electrical wiring harness inspects the engine already installed by the station to its left (←\leftarrow).
    • Crucially, no station ever asks for a part from a station to its right (β†’\to), because that station hasn't even begun work yet!
    • Information flows downward and strictly left-to-right, making it 100% compatible with single-pass top-down compilers.

πŸ’» Bridging to Computer Science: Single-Pass Left-to-Right Evaluation Invariants​

In semantic analysis, compilers classify attribute grammars by their evaluation tractability:

  • S-Attributed Definitions: Every attribute is synthesized. They are evaluated naturally during standard bottom-up LR shift-reduce reductions without constructing an explicit parse tree in memory.
  • L-Attributed Definitions: Allow both synthesized and inherited attributes, but strictly forbid backward dependencies. Inherited attributes of a child symbol may depend only on parent inherited attributes or symbols to its left. This ensures they can be evaluated on-the-fly during a single Depth-First Search (DFS) traversal.

πŸ—οΈ Architectural Foundations: S-Attributed vs L-Attributed​

S-Attributed vs L-Attributed SDD: Core Architectural Distinctions

The structural boundary between bottom-up synthesis and single-pass left-to-right evaluation

S-Attributed

S-Attributed Definition

⬆️
Dominant Architecture / DomainLR Parsers, Bottom-Up Reductions, Yacc/Bison
  • β€’Every attribute in the grammar is strictly Synthesized.
  • β€’Zero inherited attributes are permitted.
  • β€’Values flow strictly upward from children to parent.
  • β€’Naturally evaluated during bottom-up shift-reduce parsing on a value stack.
"Every S-Attributed SDD is a strict subset of L-Attributed SDDs."
L-Attributed

L-Attributed Definition

➑️
Dominant Architecture / DomainLL Parsers, Depth-First Traversal, Single-Pass Compilers
  • β€’Allows both Synthesized and Inherited attributes.
  • β€’Inherited attributes of child X_j can depend only on parent inherited attributes or left siblings X_1 ... X_{j-1}.
  • β€’Strictly prohibits dependencies on right siblings or child synthesized attributes of parent.
  • β€’Evaluated on-the-fly during a single Depth-First Search (DFS) traversal.
"L stands for Left-to-right: dependencies flow strictly downward and to the right."

πŸ“ Formal Mathematical Foundations: Definitions & Invariants​

πŸ›οΈ S-Attributed Definitions​

An SDD is S-Attributed if and only if every attribute of every non-terminal is synthesized.

For every production rule:

Aβ†’X1X2…XnA \to X_1 X_2 \dots X_n

Any semantic rule defining attribute A.sA.s must have the form:

A.s=f(X1.s1,X2.s2,…,Xn.sn)A.s = f(X_1.s_1, X_2.s_2, \dots, X_n.s_n)

Where Xi.siX_i.s_i are synthesized attributes of the children, or lexical values of terminal symbols.

🌲 L-Attributed Definitions​

An SDD is L-Attributed if for every production Aβ†’X1X2…XnA \to X_1 X_2 \dots X_n, and each semantic rule computing an inherited attribute Xj.iX_j.i:

  1. Xj.iX_j.i depends only on:
    • Inherited attributes of the parent non-terminal AA (i.e. A.iA.i).
    • Synthesized or inherited attributes of symbols strictly to the left of XjX_j, namely X1,X2,…,Xjβˆ’1X_1, X_2, \dots, X_{j-1}.
    • Inherited attributes of XjX_j itself, in such a way that no cycle is formed.
  2. Any synthesized attribute of the head A.sA.s depends only on:
    • Inherited attributes of AA.
    • Any attributes of X1,X2,…,XnX_1, X_2, \dots, X_n, provided there are no circular dependencies.

⚠️ Strict Non-L-Attributed Invariants: An SDD is immediately disqualified from being L-Attributed if:

  1. An inherited attribute of child XjX_j depends on a synthesized attribute of its parent A.sA.s (which causes an immediate circular deadlock).
  2. An inherited attribute of child XjX_j depends on any attribute of a right sibling XkX_k where k>jk > j.

πŸ“Š Formal Set Containment Hierarchy​

⚑The Formal SDD Expressive Containment Hierarchylaw
S-Attributed SDDs⊊L-Attributed SDDs⊊Non-Circular SDDs⊊All SDDs\text{S-Attributed SDDs} \subsetneq \text{L-Attributed SDDs} \subsetneq \text{Non-Circular SDDs} \subsetneq \text{All SDDs}

Every S-Attributed SDD is automatically L-Attributed because the constraint on inherited attributes is vacuously satisfied (there are zero inherited attributes).


πŸ—οΈ Dependency Graphs & Topological Evaluation Orders​

To determine whether an SDD can be evaluated deterministically, compilers construct a Dependency Graph:

Architecture Flow

SDD Evaluation Pipeline via Dependency Graph

From parse tree to attribute DAG to topological evaluation execution

🌳Stage 1

Parse Tree Construction

Grammar produces parse tree nodes for every production instance.

β†’
Attach Attributes
πŸ•ΈοΈStage 2

Dependency Graph Formulation

Nodes = attribute instances. Directed edge (u, v) = u is used to compute v.

β†’
Cycle Check
πŸ”’Stage 3

Topological Sort

Linearizes the DAG into an evaluation order u_1, u_2, ..., u_k.

β†’
Code Generation
⚑Stage 4

Attribute Evaluation

Execute semantic functions strictly in topological order.

πŸ”„ Circularity & Topological Ordering​

  • Circularity Theorem: A valid attribute evaluation order exists for a parse tree if and only if its dependency graph contains no directed cycles (it is a Directed Acyclic Graph / DAG).
  • A Topological Sort of a DAG produces a linear sequence u1,u2,…,umu_1, u_2, \dots, u_m such that for every directed edge (ui,uj)(u_i, u_j), i<ji < j.
  • If a directed cycle exists (X.aβ†’Y.bβ†’X.aX.a \to Y.b \to X.a), the SDD is circular, and no sequential compiler can evaluate the attributes.

βš™οΈ Bottom-Up Evaluation of S-Attributed SDDs on an LR Parser​

S-Attributed definitions are exceptionally efficient because they require zero explicit parse tree allocation. They evaluate on-the-fly during bottom-up shift-reduce reductions!

🧱 The Parallel Value Stack​

Alongside the state stack, the parser maintains a parallel Value Stack val[]:

State Stack:  [ s0 | s1 | s2 | s3 ]
Value Stack: [ v0 | v1 | v2 | v3 ]
Top Pointer: ^ top

When reducing by production rule Aβ†’X1X2…XrA \to X_1 X_2 \dots X_r:

  1. The RHS symbols occupy the top rr positions of the stack: val[top - r + 1] through val[top].

  2. The semantic action computes the synthesized attribute:

    A.s=f(val[topβˆ’r+1],…,val[top])A.s = f(\text{val}[\text{top} - r + 1], \dots, \text{val}[\text{top}])
  3. The parser pops rr states and values from the stack: top=topβˆ’r\text{top} = \text{top} - r.

  4. The parser pushes the GOTO state and the newly synthesized value:

    top=top+1;val[top]=A.s;\text{top} = \text{top} + 1; \quad \text{val}[\text{top}] = A.s;

Value Stack Trace During S-Attributed LR Reduction

Reducing E -> E + T with semantic action val[ntop] = val[top-2] + val[top]

Parser Controller
Grammar Stack
Value Stack
Semantic Engine
1
Parser Controller→Grammar Stack

Identify Handle E + T

2
Parser Controller→Semantic Engine

Execute Semantic Function

3
Grammar Stack→Value Stack

Pop RHS & Push LHS


🏭 Real-World Production Context: How Industry Builds This​

In industrial compiler engines (such as GNU Bison, Yacc, and LLVM Frontends):

Yacc and Bison natively support only S-Attributed SDDs with actions at the very end of productions (A -> X Y { action }). When an engineer writes an embedded action in Bison:

stmt: IF expr { emit_branch_zero(); } THEN stmt ;

Bison translates this into an L-Attributed equivalent by introducing an empty Marker Non-Terminal (MM):

M→ϡ{emit_branch_zero();}stmt→IF expr M THEN stmt\begin{aligned} M &\to \epsilon \quad \{ \text{emit\_branch\_zero}(); \} \\ stmt &\to \text{IF} \ expr \ M \ \text{THEN} \ stmt \end{aligned}

Because M→ϡM \to \epsilon is reduced immediately after exprexpr is parsed, its action fires at the exact desired moment!

In hand-written recursive-descent parsers (e.g. Clang, Rustc), L-Attributed inherited attributes are passed as function arguments, while synthesized attributes are returned as function return values:

fn parse_expr(&mut self, inherited_type: Type) -> SynthesizedASTNode {
// inherited_type passed downward
let child = self.parse_term(inherited_type);
// returns synthesized AST node upward
child
}

πŸ“ Numerical Applications & Worked Examples​

Example 1: Rigorous SDD Classification​

Consider the following grammar and semantic rules over terminals {id,num}\{\text{id}, \text{num}\} and non-terminals {S,A,B,C}\{S, A, B, C\}:

ProductionSemanticΒ Rules1)β€…β€ŠSβ†’Aβ€…β€ŠBS.val=A.val+B.val2)β€…β€ŠAβ†’idA.val=id.lexval3)β€…β€ŠBβ†’Aβ€…β€ŠCB.val=A.valΓ—C.val,C.in=B.val4)β€…β€ŠCβ†’numC.val=C.in+num.lexval\begin{array}{lll} \text{Production} & \text{Semantic Rules} \\ \hline 1)\; S \to A \; B & S.val = A.val + B.val \\ 2)\; A \to \text{id} & A.val = \text{id}.lexval \\ 3)\; B \to A \; C & B.val = A.val \times C.val, \quad C.in = B.val \\ 4)\; C \to \text{num} & C.val = C.in + \text{num}.lexval \\ \end{array}

Determine analytically whether this SDD is:

  1. S-Attributed
  2. L-Attributed
  3. Neither S-Attributed nor L-Attributed

Step-by-Step Analytical Solution:

  1. Classify Each Attribute:

    • S.valS.val: Synthesized (computed for LHS SS from RHS A,BA, B).
    • A.valA.val: Synthesized (computed for LHS AA from terminal id\text{id}).
    • B.valB.val: Synthesized (computed for LHS BB from RHS A,CA, C).
    • C.inC.in: Inherited (computed for RHS child CC).
    • C.valC.val: Synthesized (computed for LHS CC from C.inC.in and num\text{num}).
  2. Is it S-Attributed?

    • An SDD is S-Attributed if and only if all attributes are synthesized.
    • Attribute C.inC.in is an inherited attribute.
    • Therefore, the grammar is NOT S-Attributed.
  3. Is it L-Attributed?

    • Inspect all semantic rules computing inherited attributes.

    • In production (3): Bβ†’AΒ CB \to A \ C, the semantic rule is:

      C.in=B.valC.in = B.val
    • Non-terminal BB is the parent (LHS) of the production.

    • For an SDD to be L-Attributed, an inherited attribute of a child XjX_j can depend only on inherited attributes of the parent or on attributes of symbols to its left.

    • However, B.valB.val is a synthesized attribute of the parent!

    • Furthermore, B.valB.val itself depends on C.valC.val (B.val=A.valΓ—C.valB.val = A.val \times C.val), which in turn depends on C.inC.in (C.val=C.in+…C.val = C.in + \dots).

    • This creates an immediate directed cycle in the dependency graph:

      B.val⟢C.in⟢C.val⟢B.valB.val \longrightarrow C.in \longrightarrow C.val \longrightarrow B.val
  4. Verdict: The SDD is Neither S-Attributed nor L-Attributed (it is circular).


Example 2: Type Declaration SDD & Dependency Tracing​

Given the standard C-style variable declaration grammar:

ProductionSemanticΒ RulesDβ†’Tβ€…β€ŠLL.in=T.typeTβ†’intT.type=integerTβ†’floatT.type=realLβ†’L1,β€…β€ŠidL1.in=L.in,addType(id.entry,L.in)Lβ†’idaddType(id.entry,L.in)\begin{array}{ll} \text{Production} & \text{Semantic Rules} \\ \hline D \to T \; L & L.in = T.type \\ T \to \textbf{int} & T.type = \text{integer} \\ T \to \textbf{float} & T.type = \text{real} \\ L \to L_1, \; \textbf{id} & L_1.in = L.in, \quad \text{addType}(\textbf{id}.entry, L.in) \\ L \to \textbf{id} & \text{addType}(\textbf{id}.entry, L.in) \\ \end{array}

Classify this SDD and trace the attribute evaluation order for the statement float x, y;.

Step-by-Step Analytical Trace:

  1. Classification:

    • T.typeT.type: Synthesized attribute of TT.
    • L.inL.in: Inherited attribute of LL.
    • In Dβ†’TΒ LD \to T \ L, rule L.in=T.typeL.in = T.type: TT is the left sibling of LL. This satisfies the L-Attributed rule.
    • In Lβ†’L1,idL \to L_1, \textbf{id}, rule L1.in=L.inL_1.in = L.in: L1L_1 depends on the inherited attribute of its parent LL. This satisfies the L-Attributed rule.
    • Contains inherited attributes β€…β€ŠβŸΉβ€…β€Š\implies NOT S-Attributed.
    • All inherited attributes depend strictly on left siblings or parent inherited attributes β€…β€ŠβŸΉβ€…β€Š\implies Strictly L-Attributed.
  2. Attribute Evaluation Sequence for float x, y;:

    • Step 1: Tβ†’floatβ€…β€ŠβŸΉβ€…β€ŠT.type=realT \to \textbf{float} \implies T.type = \text{real}.
    • Step 2: Dβ†’TΒ Lβ€…β€ŠβŸΉβ€…β€ŠL.in=T.type=realD \to T \ L \implies L.in = T.type = \text{real}.
    • Step 3: Lβ†’L1,id2β€…β€ŠβŸΉβ€…β€ŠL1.in=L.in=realL \to L_1, \textbf{id}_2 \implies L_1.in = L.in = \text{real}.
    • Step 4: L1β†’id1β€…β€ŠβŸΉβ€…β€ŠaddType(entry(x),real)L_1 \to \textbf{id}_1 \implies \text{addType}(\text{entry}(x), \text{real}).
    • Step 5: addType(entry(y),L.in=real)\text{addType}(\text{entry}(y), L.in = \text{real}).

The information flow is: T.typeT.type is synthesized bottom-up, passed horizontally to LL, and then pushed top-down through LL to annotate both identifiers in the symbol table.


🎯 Exam & Interview Pitfall Check​

Core Conceptual Questions

Question 1: "Can an L-Attributed SDD be evaluated during bottom-up parsing without modifications?"
Key Focus Points:

  • General Case: Not directly, because inherited attributes require top-down information before the handle is completed.
  • With Marker Non-Terminals: Any L-Attributed SDD with semantic actions embedded inside rules can be transformed into an S-Attributed equivalent on an LR parser by inserting empty marker non-terminals (Mβ†’Ο΅M \to \epsilon) to execute actions at intermediate positions.

Question 2: "Why does an inherited attribute depending on a parent's synthesized attribute guarantee a circular deadlock?"
Key Focus Points:

  • A parent node's synthesized attribute can only be computed after all of its children have finished evaluating.
  • If a child requires that synthesized value to begin its own computation, a cyclic dependency is created: the parent waits for the child, while the child waits for the parent.
Common Interview Traps: Architectural Trade-Offs

Trap 1: Assuming an SDD without cycles is always L-Attributed.
Answer: Classic trap. Consider A→B CA \to B \ C with rule B.in=C.valB.in = C.val. The dependency graph is a DAG with zero cycles. However, BB (left sibling) depends on CC (right sibling). This requires a right-to-left or multi-pass traversal, violating the single left-to-right pass definition of L-attributed grammars.

Trap 2: Believing S-Attributed and L-Attributed are disjoint classes.
Answer: False. S-Attributed is a strict subset of L-Attributed (S-AttributedβŠ‚L-Attributed\text{S-Attributed} \subset \text{L-Attributed}). Every S-Attributed SDD is automatically L-Attributed.


πŸ’¬

Discussion & Doubts