S-attributed, L-attributed and evaluation orders
How an SDD is evaluated: topological orders of the dependency graph, S-attributed definitions evaluated bottom-up during LR parsing, L-attributed definitions evaluated in one depth-first left-to-right pass (recursive descent), the exact condition for L-attributed rules, and a definition that is well defined but not L-attributed.
Why it exists
An SDD says what each attribute equals but not in which order to compute them, nor when during compilation. The most useful answer is: while parsing, in a single pass, with no explicit tree and no dependency graph. That works only if the rules are written so that every value is available when it is needed. The two classes of definitions that allow this, S-attributed and L-attributed, are what GATE asks about.
Prerequisites
You need attributes, annotated parse trees and the dependency graph (syntax-directed definitions).
Precise definitions
Evaluation order in general. For a given parse tree, any topological order of the dependency graph is a valid order; if the graph has a cycle there is none. The compiler could build the tree and the graph and sort them, but that is slow and memory-hungry, so practical compilers restrict the SDD.
S-attributed definition. An SDD in which all attributes are synthesized. Every rule defines an attribute of the left-hand symbol from attributes of the right-hand symbols. Evaluation: any bottom-up order works, for example the postorder of the tree. Consequently an S-attributed SDD can be evaluated by an LR parser: keep the attribute values on the parse stack next to the symbols (a value stack), and when the parser reduces by it computes the attributes of from the values of that lie on top of the stack and replaces them by the value of . The desk calculator is the standard example.
L-attributed definition ("left to right"). An SDD in which, in every production , each inherited attribute of depends only on
- the inherited attributes of the left-hand side , and
- any attributes (inherited or synthesized) of the symbols to the left of in the production, and
- the attributes of itself, provided they do not form a cycle.
Synthesized attributes are unrestricted (they may depend on anything in the production). In words: information may flow up, and down or to the right, but never from the right to the left and never from the left-hand side's synthesized attributes down to a child.
Containment. Every S-attributed SDD is L-attributed (it has no inherited attributes, so condition 1 to 3 hold vacuously). Not every L-attributed SDD is S-attributed. So S-attributed L-attributed (acyclic SDDs). There are definitions that are acyclic and well defined but not L-attributed (the example below).
Evaluating an L-attributed SDD in one pass. Visit the tree depth first, children left to right: before visiting a child compute its inherited attributes (they depend only on things already known); after all children have been visited compute the synthesized attributes of the node. This is exactly what a recursive-descent parser does naturally:
- a procedure for nonterminal takes the inherited attributes of as parameters;
- it returns the synthesized attributes of as its result (or through output parameters);
- before calling the procedure of it computes the arguments (the inherited attributes of ).
The same holds for LL(1) predictive parsing. L-attributed SDDs can also be evaluated during bottom-up parsing by first converting them to translation schemes and introducing marker nonterminals for the inherited attributes (translation schemes), but the details are beyond what is usually asked.
Removing left recursion changes the attributes. The expression grammar is S-attributed with E.val = E1.val + T.val. After left recursion is removed (, ) the value is accumulated with an inherited attribute R.i: R.i = T.val in ; R1.i = R.i + T.val in ; R.s = R.i in ; R.s = R1.s in ; E.val = R.s. The result is L-attributed, so it can be evaluated by the LL(1) parser.
How it works
The animation evaluates exactly these rules on the tree of 3 + 4 + 5. The sum so far travels down as the inherited attribute R.i: 3, then 7, then 12; when the input is exhausted () it is turned into the synthesized R.s, which is copied up to E.val. Note the shape: the tree leans to the right, yet the grouping is still , because the inherited attribute adds from the left.
Left recursion removed: the inherited attribute carries the running sum of 3 + 4 + 5: start
Parser run by a script, not typedAn L-attributed definition for the grammar without left recursion.
- 1E → T R { R.i = T.val; E.val = R.s }
- 2R → + T R1 { R1.i = R.i + T.val; R.s = R1.s }
- 3R → ε { R.s = R.i }
- 4T → num { T.val = num.lexval }
Step 0
After left recursion has been removed the tree for 3 + 4 + 5 leans to the right: E has the children T and R, and each R has + T R. The sum so far is passed down as the inherited attribute R.i (the first T gives R.i, every + adds the next T), and at the end R.s = R.i starts the way back up, copying the result to E.val. Information flows down and across (inherited) and then up (synthesized).
Now a definition that is not L-attributed. In the rule A.i = B.s makes the inherited attribute of depend on the synthesized attribute of its right sibling. A left-to-right pass reaches before is known and fails. The dependency graph is acyclic though, so a topological order exists (and the animation follows it): B.s, then A.i, then A.s, then S.val.
A definition that is not L-attributed (A.i depends on B.s): start
Parser run by a script, not typedA rule that uses an attribute of a right sibling.
- 1S → A B { A.i = B.s; S.val = A.s }
- 2A → a { A.s = A.i + 1 }
- 3B → b { B.s = 5 }
Step 0
In S → A B the rule A.i = B.s gives A an inherited attribute that depends on the synthesized attribute of its right sibling B. A single left-to-right pass could not evaluate it (B has not been visited when A is), but the dependency graph has no cycle, so a topological order exists: B.s first, then A.i, then A.s and S.val.
The recursive-descent program below implements the L-attributed definition of the sum. R has the inherited attribute R.i as its parameter and returns R.s; E computes the argument of R from T. Note how every rule of the SDD corresponds to a line of C.
#include <ctype.h>
#include <stdio.h>
static const char *p; /* the input */
static int T(void) { /* T -> num { T.val = num.lexval } */
int v = 0;
while (isdigit((unsigned char)*p)) {
v = v * 10 + (*p - '0');
p++;
}
return v;
}
static int R(int i) { /* i is the inherited attribute R.i; the result is R.s */
if (*p == '+') { /* R -> + T R1 */
p++;
int t = T();
return R(i + t); /* R1.i = R.i + T.val; R.s = R1.s */
}
return i; /* R -> epsilon: R.s = R.i */
}
static int E(void) { /* E -> T R */
int t = T();
return R(t); /* R.i = T.val; E.val = R.s */
}
static int eval(const char *s) {
p = s;
return E();
}
int main(void) {
printf("%d\n", eval("3+4+5"));
printf("%d\n", eval("10+20"));
printf("%d\n", eval("7"));
return 0;
}12
30
7Worked examples
EX 1Classify the definitions
Easy(a) The desk calculator (E.val = E1.val + T.val and so on): only synthesized attributes: S-attributed (hence also L-attributed). (b) Declarations with L.in = T.type, with L1.in = L.in and id.type = L.in: L.in is inherited from the sibling on the left, L1.in from the parent's inherited attribute: L-attributed, not S-attributed. (c) with A.i = B.s: depends on a right sibling: not L-attributed (but acyclic). (d) The sum with R.i: L-attributed. The static test was checked against an actual one-pass evaluator on all four.
EX 2Apply the L-attributed test rule by rule
EasyFor : C.i = B.s is fine (the left sibling ). D.i = A.i + C.s is fine (inherited of the parent and a left sibling). B.i = C.s is not allowed (right sibling). C.i = A.s is not allowed: A.s is a synthesized attribute of the parent, which is computed only after all children. A.s = D.s + B.s (synthesized) is allowed whatever it uses. A single violation makes the whole SDD non-L-attributed.
EX 3Evaluate during an LR parse
MediumParse 3 * 5 + 4 with the desk calculator on an LR parser with a value stack. After shifting 3 and reducing the stack top holds with value 3; reduce : value 3; shift *, shift 5, reduce to (5); reduce : pop the three entries with values 3, (no value), 5 and push with value ; reduce (15); shift +, 4, reduce to (4); reduce : . No tree is built: the values live on the parse stack, and each reduction computes exactly one new value.
EX 4Order of evaluation by dependency
MediumFor the declaration int a, b the valid orders must start with T.type, then L.in at the top, and only then the nodes below L.in. Exchanging L1.in and id_b.type is fine, since neither depends on the other, so there is more than one valid order. For a purely synthesized definition on a tree of nodes any order that evaluates every child before its parent is valid (a postorder, or a level-by-level order from the bottom): in general many. Questions give an SDD and several candidate orders: check each dependency edge.
EX 5True or false
GATE level(a) "Every S-attributed definition is L-attributed": true. (b) "Every L-attributed definition is S-attributed": false (the declaration definition). (c) "An S-attributed definition can be evaluated during bottom-up parsing": true, with the value stack. (d) "An L-attributed definition can be evaluated during LL(1) (top-down) parsing": true, inherited attributes as parameters. (e) "An L-attributed SDD is evaluated by a single depth-first left-to-right traversal of the parse tree": true. (f) "An SDD that is not L-attributed cannot be evaluated at all": false; it may be acyclic, then the graph order works, but not in one left-to-right pass. (g) "Inherited attributes can depend on attributes of a sibling to the right in an L-attributed SDD": false.
Cheat box
Cheat box
- S-attributed: only synthesized attributes. Bottom-up (postorder) evaluation; fits LR parsing with a value stack.
- L-attributed: an inherited attribute of uses only inherited attributes of the parent and attributes of (never right siblings, never the parent's synthesized attributes). One depth-first left-to-right pass.
- S-attributed L-attributed. L-attributed definitions never cause circularity.
- Recursive descent: inherited attributes become parameters, synthesized attributes become return values.
- Removing left recursion turns an S-attributed definition into an L-attributed one (inherited accumulator).
- Acyclic but not L-attributed is possible (a right sibling is used).
Traps and GATE patterns
Connections
Across subjects
No cross-subject links yet.
The definitions evaluated here are those of syntax-directed definitions. Executing the semantic rules as actions during parsing is translation schemes. The parsers involved are LL(1) and LR; the grammar transformation that creates the inherited attribute is in left recursion removal. Code generation uses these ideas in intermediate code generation.
PYQs
4 previous year questions from your book. I re-solved each one myself (1 by running or simulating, 3 by reasoning only); the book's key is shown next to my answer. All PYQs
Given the following syntax-directed translation rules:
Rule 1:
R → AB { B.i = R.i - 1; A.i = B.i; R.i = A.i + 1; }
Rule 2:
P → CD { P.i = C.i + D.i; D.i = C.i + 2; }
Rule 3:
Q → EF { Q.i = E.i + F.i; }
Which ONE is the CORRECT option among the
following?- (a)Rule 1 : is S-attributed and L-attributed; Rule 2 is S-attributed and not L-attributed; Rule 3 is neither S-attributed nor L-attributed
- (b)Rule 1 : is neither S-attributed nor L-attributed; Rule 2 is S-attributed and L-attributed; Rule 3 is S-attributed and L-attributed.
- (c)Rule 1 : is neither S-attributed nor L-attributed; Rule 2 is not S-attributed and is L-attributed; Rule 3 is S-attributed and L-attributed.
- (d)Rule 1 : is S-attributed and not L-attributed; Rule 2 is not S-attributed and is L-attributed; Rule 3 is S-attributed and L-attributed COMPILER DESIGN
Which of the following statements is/are FALSE?
- (a)An attribute grammar is a syntax-directed definition (SDD) in which the functions in the semantic rules have no side effects
- (b)The attributes in a L-attributed definition cannot always be evaluated in a depth- first order
- (c)Synthesized attributes can be evaluated by a bottom-up parser as the input is parsed
- (d)All L-attributed definitions based on LR(1) grammar can be evaluated using a bottom-up parsing strategy
Consider the production A ⟶ PQ and A ⟶ XY. Each of the five non-terminals A, P, Q, X and Y has two attributes:s is a synthesized attribute, and i is an inherited attribute. Consider the following rules:- Rule 1 : P.i = A.i + 2, Q.i = P.i + A.i and A.s = P.s + Q.s Rule 2 : X.i = A.i + Y.s and Y.i = X.s + A.i Which one of the following is TRUE?
- (a)Only Rule 2 is L-attributed
- (b)Neither Rule 1 nor Rule 2 is L-attributed.
- (c)Both Rule 1 and Rule 2 are L-attributed.
- (d)Only Rule 1 is L-attributed. COMPILER DESIGN
Consider the following grammar and the semantic
actions to support the inherited type declaration
attributes. Let X1, X2, X3, X4, X5 and X6 be the
placeholders for the non-terminals D, T, L or L1 in
the following table :
Production Rule
Semantic Action
D → TL
X1.type = X2.type
T → int
T.type = int
T →float
T.type = float
L → L1, id
X3.type = X4.type
addType(id.entry,
X5.type)
L → id
addType(id.entry,
X6.type)
Which one of the following are the appropriate choices
for X1, X2, X3 and X4?- (a)X1 = L, X2 = T, X3 = L1, X4 = L
- (b)X1 = T, X2 = L, X3 = L1, X4 = T
- (c)X1 = L, X2 = L, X3 = L1, X4 = T
- (d)X1 = T, X2 = L, X3 = T , X4 = L1
Self-check
Question 1
For the rule B.i = C.s is given. Is the SDD L-attributed?
Show answerHide
No: B.i is inherited and depends on an attribute of the sibling to its right.
Question 2
Which kind of parser can evaluate an S-attributed SDD as it parses, and what extra structure is needed?
Show answerHide
A bottom-up (LR) parser, with a value stack parallel to the symbol stack: at each reduction the new value is computed from the values of the right side.
Question 3
In a recursive-descent implementation of an L-attributed SDD, what do inherited and synthesized attributes become?
Show answerHide
Inherited attributes become parameters of the procedure; synthesized attributes become its return values.
Question 4
Is the SDD with the rules A.i = B.s, S.val = A.s for circular?
Show answerHide
No, it is acyclic (B.s, A.i, A.s, S.val), but it is not L-attributed.
Revision card
One-screen revision
- S-attributed: synthesized only, bottom-up, LR with value stack. L-attributed: one left-to-right depth-first pass, recursive descent (parameters and returns).
- L-attributed condition: inherited of may use inherited of parent and attributes of left siblings only.
- S-attributed L-attributed. Acyclic but not L-attributed exists.
- Removing left recursion gives inherited accumulators.
References
- ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 5 Syntax-Directed Translation (5.2 evaluation orders for SDDs: dependency graphs, S-attributed and L-attributed definitions; 5.5 implementing L-attributed SDDs)
Verification
scripts/enum-checks/cd_sdt.py checks the central claim on 4000 random attribute definitions: a one-pass depth-first left-to-right evaluator succeeds exactly when the static L-attributed condition holds, and then agrees with the dependency-graph evaluation (664 L-attributed, 2144 acyclic but not L-attributed, 1192 circular definitions among the random ones). The sum by an inherited attribute (3 + 4 + 5) and the swapped-operator grammar are checked against Python on 1000 random inputs; the recursive-descent C program on this page is compiled and run by npm run verify:c. The textbook condition for L-attributed definitions is quoted unchanged. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand or by a small script (token counts, FIRST and FOLLOW sets, LL(1) tables and moves, LR item sets, state counts and parses, operator-precedence values, attribute evaluation and printing schemes, activation-record counts and access links, parameter-passing results, three-address code, jumping and backpatched code, leaders and blocks, value numbering and DAGs, gen/kill and data flow sets, Sethi-Ullman labels and live ranges); long C programs were not re-read line by line (they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose); tables and animations are machine output and were read, not re-run; No other defect found. Status raised to self-checked; verified needs a textbook or human check.
last verified: never
My notes (saved in this browser)