6.2 S-Attributed vs L-Attributed SDDs & Evaluation Orders
π‘ 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:
- 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 (). No assembly worker ever needs instructions from the finished car above them, nor does any station inspect parts to their right.
- L-Attributed (Hierarchical Left-to-Right Briefing):
- The floor supervisor hands down a project spec sheet (inherited from parent ).
- The station installing the electrical wiring harness inspects the engine already installed by the station to its left ().
- Crucially, no station ever asks for a part from a station to its right (), 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.
πTable of Contents
- ποΈ Architectural Foundations: S-Attributed vs L-Attributed
- π Formal Mathematical Foundations: Definitions & Invariants
- ποΈ Dependency Graphs & Topological Evaluation Orders
- βοΈ Bottom-Up Evaluation of S-Attributed SDDs on an LR Parser
- π Real-World Production Context: How Industry Builds This
- π Numerical Applications & Worked Examples
- π― Exam & Interview Pitfall Check
ποΈ 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 Definition
- β’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.
L-Attributed Definition
- β’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.
π 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:
Any semantic rule defining attribute must have the form:
Where are synthesized attributes of the children, or lexical values of terminal symbols.
π² L-Attributed Definitionsβ
An SDD is L-Attributed if for every production , and each semantic rule computing an inherited attribute :
- depends only on:
- Inherited attributes of the parent non-terminal (i.e. ).
- Synthesized or inherited attributes of symbols strictly to the left of , namely .
- Inherited attributes of itself, in such a way that no cycle is formed.
- Any synthesized attribute of the head depends only on:
- Inherited attributes of .
- Any attributes of , provided there are no circular dependencies.
β οΈ Strict Non-L-Attributed Invariants: An SDD is immediately disqualified from being L-Attributed if:
- An inherited attribute of child depends on a synthesized attribute of its parent (which causes an immediate circular deadlock).
- An inherited attribute of child depends on any attribute of a right sibling where .
π Formal Set Containment Hierarchyβ
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:
SDD Evaluation Pipeline via Dependency Graph
From parse tree to attribute DAG to topological evaluation execution
Parse Tree Construction
Grammar produces parse tree nodes for every production instance.
Dependency Graph Formulation
Nodes = attribute instances. Directed edge (u, v) = u is used to compute v.
Topological Sort
Linearizes the DAG into an evaluation order u_1, u_2, ..., u_k.
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 such that for every directed edge , .
- If a directed cycle exists (), 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 :
-
The RHS symbols occupy the top positions of the stack:
val[top - r + 1]throughval[top]. -
The semantic action computes the synthesized attribute:
-
The parser pops states and values from the stack: .
-
The parser pushes the GOTO state and the newly synthesized value:
Value Stack Trace During S-Attributed LR Reduction
Reducing E -> E + T with semantic action val[ntop] = val[top-2] + val[top]
Identify Handle E + T
Execute Semantic Function
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 ():
Because is reduced immediately after 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 and non-terminals :
Determine analytically whether this SDD is:
- S-Attributed
- L-Attributed
- Neither S-Attributed nor L-Attributed
Step-by-Step Analytical Solution:
-
Classify Each Attribute:
- : Synthesized (computed for LHS from RHS ).
- : Synthesized (computed for LHS from terminal ).
- : Synthesized (computed for LHS from RHS ).
- : Inherited (computed for RHS child ).
- : Synthesized (computed for LHS from and ).
-
Is it S-Attributed?
- An SDD is S-Attributed if and only if all attributes are synthesized.
- Attribute is an inherited attribute.
- Therefore, the grammar is NOT S-Attributed.
-
Is it L-Attributed?
-
Inspect all semantic rules computing inherited attributes.
-
In production (3): , the semantic rule is:
-
Non-terminal is the parent (LHS) of the production.
-
For an SDD to be L-Attributed, an inherited attribute of a child can depend only on inherited attributes of the parent or on attributes of symbols to its left.
-
However, is a synthesized attribute of the parent!
-
Furthermore, itself depends on (), which in turn depends on ().
-
This creates an immediate directed cycle in the dependency graph:
-
-
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:
Classify this SDD and trace the attribute evaluation order for the statement float x, y;.
Step-by-Step Analytical Trace:
-
Classification:
- : Synthesized attribute of .
- : Inherited attribute of .
- In , rule : is the left sibling of . This satisfies the L-Attributed rule.
- In , rule : depends on the inherited attribute of its parent . This satisfies the L-Attributed rule.
- Contains inherited attributes NOT S-Attributed.
- All inherited attributes depend strictly on left siblings or parent inherited attributes Strictly L-Attributed.
-
Attribute Evaluation Sequence for
float x, y;:- Step 1: .
- Step 2: .
- Step 3: .
- Step 4: .
- Step 5: .
The information flow is: is synthesized bottom-up, passed horizontally to , and then pushed top-down through to annotate both identifiers in the symbol table.
π― Exam & Interview Pitfall Checkβ
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 () 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.
Trap 1: Assuming an SDD without cycles is always L-Attributed.
Answer: Classic trap. Consider with rule . The dependency graph is a DAG with zero cycles. However, (left sibling) depends on (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 (). Every S-Attributed SDD is automatically L-Attributed.