Syntax-directed definitions: synthesized and inherited attributes
Syntax-directed definitions: attributes attached to grammar symbols, semantic rules attached to productions, synthesized and inherited attributes, annotated parse trees, the dependency graph and circular definitions, with the desk calculator, binary numbers and declaration types as examples.
Why it exists
A parser only says whether a sentence fits the grammar. A compiler needs more: the value of an expression, the type of a name, the code for a statement. Syntax-directed translation attaches this computation to the grammar: every grammar symbol gets attributes (a value, a type, a code string, a location) and every production gets rules that say how to compute the attributes from each other. The computation is then driven by the structure of the parse tree. A definition that only describes what to compute, without saying in which order or with which parser, is a syntax-directed definition (SDD).
Prerequisites
You need parse trees and a parsing method (LR parsing); the examples use the layered expression grammar.
Precise definitions
Attribute. A named value attached to a grammar symbol, for example E.val, T.type, id.name. Every node of a parse tree labelled by that symbol has its own copy (an attribute instance).
Semantic rule. A rule attached to a production that defines an attribute of one of the symbols of that production in terms of attributes of the symbols of the same production: E.val = E1.val + T.val. When a symbol occurs twice in a production the occurrences are distinguished by subscripts (E → E1 + T), the one on the left of the arrow keeping the plain name.
Syntax-directed definition (SDD). A context-free grammar together with attributes and semantic rules. In the form used for GATE the rules have no side effects (they are functions); an SDD with side effects, such as printing, is better called a translation scheme (next pages).
Synthesized attribute. An attribute of a nonterminal at a node that is defined by a rule of the production at , in terms of attributes of 's children (and possibly of itself). Information flows up the tree. Attributes of tokens (num.lexval, id.name) are synthesized: they are supplied by the lexical analyser.
Inherited attribute. An attribute of a nonterminal at a node that is defined by a rule of the production at the parent of , in terms of attributes of the parent, of itself and of 's siblings. Information flows down and sideways. Inherited attributes are convenient when the structure of the parse tree does not match the structure of the program: for example the type in a declaration int a, b, c is in the tree on the left of the list, while the names that need it are in the tree below the list.
Recognising the kind. Look at the rule: if the attribute defined is of the symbol on the left of the production, it is synthesized; if it is of a symbol on the right, it is inherited. (A symbol can have both kinds.) The start symbol has no inherited attributes, unless given initial values.
Annotated parse tree. A parse tree with the values of all attributes written at its nodes. Evaluating an SDD for a sentence means building its parse tree and then filling in the attributes.
Dependency graph. Draw a node for every attribute instance of the parse tree and, for every rule, an edge from each attribute it uses to the attribute it defines. An order in which the attributes can be evaluated is a topological order of this graph; it exists exactly if the graph has no cycle. A definition that can produce a cyclic dependency graph for some sentence is circular and is not well defined: for with the rules B.i = A.s and A.s = B.i each attribute waits for the other.
Side-effect-free, S-attributed and L-attributed. The two important restricted forms, S-attributed (only synthesized attributes) and L-attributed (inherited attributes limited to depend on the left), are the subject of evaluation orders.
How it works
First a definition with synthesized attributes only, the desk calculator. Each production computes val from the children:
| Production | Rule |
|---|---|
E.val = E1.val + T.val | |
E.val = T.val | |
T.val = T1.val * F.val | |
T.val = F.val | |
F.val = E.val | |
F.val = num.lexval |
The animation builds the parse tree of 3 * 5 + 4 with a real parser, then evaluates one attribute per step in a topological order. Nodes whose attributes are still unknown are dashed; the one just evaluated is shaded. Every value is computed from values that already exist, from the leaves up, and the root gets 19.
S-attributed definition: the value of 3 * 5 + 4: start
Parser run by a script, not typedSynthesized attributes, evaluated bottom-up on the parse tree.
- 1E → E1 + T { E.val = E1.val + T.val }
- 2E → T { E.val = T.val }
- 3T → T1 * F { T.val = T1.val * F.val }
- 4T → F { T.val = F.val }
- 5F → ( E ) { F.val = E.val }
- 6F → num { F.val = num.lexval }
Step 0
The parse tree of 3 * 5 + 4 is built first. Every node of the nonterminals E, T and F has one attribute, val, to be filled in. The leaves num carry the value of the lexeme from the scanner (lexval). Each semantic rule defines the val of a node from the val of its children, so a node can be evaluated only after its children: the evaluation goes from the leaves up.
Now an inherited attribute. In the declaration int a, b, c the grammar is , , , with T.type synthesized, L.in inherited (: L.in = T.type; : L1.in = L.in and id.type = L.in; : id.type = L.in). The type is created at the top and must travel down to the identifiers, so the evaluation order is top-down here.
Inherited attribute: the type of a, b, c in int a, b, c: start
Parser run by a script, not typedL.in is inherited: it flows down and sideways.
- 1D → T L { L.in = T.type }
- 2T → int { T.type = int }
- 3T → float { T.type = float }
- 4L → L1 , id { L1.in = L.in; id.type = L.in }
- 5L → id { id.type = L.in }
Step 0
The declaration int a, b, c has a tree in which the list L is left recursive. The type is known at T, but the identifiers are deep in the tree under L. The attribute L.in is inherited: it carries the type down from the parent. Each id gets its type from the L above it. The arrows of dependence now go downwards, so the tree is evaluated from the top.
Worked examples
EX 1Evaluate the annotated tree
EasyUsing the desk calculator, evaluate 2 * (3 + 4). The parse tree has at the top with and with . Values: F.val = 2 and T1.val = 2; inside the parentheses 3 + 4 = 7, so E.val = 7 and F.val = 7; then T.val = 2 * 7 = 14, and E.val = 14 at the root. A GATE question gives the SDD and asks for the value at the root for a given string; the method is the same, bottom-up.
EX 2Synthesized or inherited?
EasyFor each rule decide: (a) E.val = E1.val + T.val in : defines the left-hand symbol: synthesized. (b) L.in = T.type in : defines , a symbol on the right: inherited. (c) id.type = L.in in : defines the symbol id on the right: inherited. (d) A.s = A.i + 1 in : defines the left symbol, even though it uses A.i: synthesized. The attribute A.i is inherited from the parent; one symbol can have both kinds of attributes.
EX 3Binary number to value
Medium with N.val = 2 * N1.val for the first, N.val = 2 * N1.val + 1 for the second and N.val = 1 for the last. Input 1101: the innermost derived from the first 1 has value 1; then 1 followed by 1 gives ; followed by 0 gives ; followed by 1 gives . The attribute is synthesized (S-attributed), so the evaluation is bottom-up and can be done while an LR parser reduces. (The values for 300 random binary strings agree with int(s, 2) in the checks.)
EX 4Dependency graph and order
MediumFor the declaration int a, b (tree: , , ): the attribute instances are T.type, L.in (top), L1.in, id_a.type, id_b.type. Edges: T.type → L.in; L.in → L1.in; L.in → id_b.type; L1.in → id_a.type. A topological order: T.type, L.in, L1.in, id_b.type, id_a.type (or with the last two swapped). In every valid order T.type comes first and L.in before everything below it. The number of valid orders is the number of linear extensions of the graph; GATE questions usually ask which orders are valid.
EX 5Is the definition circular?
GATE levelProduction with rules B.i = A.s and A.s = B.i: the graph has the edges A.s → B.i and B.i → A.s, a cycle: the attributes cannot be evaluated; the definition is circular. By contrast with A.i = B.s, S.val = A.s, with A.s = A.i + 1, with B.s = 5 has no cycle: B.s first, then A.i, then A.s, then S.val = 6, although the order is not left to right (see the third animation on the next page). Testing circularity of an arbitrary SDD is possible but may need time exponential in the size of the grammar; for S-attributed and L-attributed definitions circularity cannot occur.
Cheat box
Cheat box
- SDD = grammar + attributes + semantic rules (no side effects). Evaluate on the parse tree.
- Synthesized: defined at the left-hand symbol from the children (flows up). Tokens' lexical attributes are synthesized.
- Inherited: defined for a right-side symbol from the parent and siblings (flows down and sideways).
- The kind is decided by which side of the arrow the defined attribute is on.
- Dependency graph: edge from used attribute to defined attribute; an evaluation order is a topological order; a cycle makes the definition circular.
- Repeated symbols get subscripts: .
Traps and GATE patterns
Connections
Builds on
Across subjects
No cross-subject links yet.
SDDs are evaluated on the trees built by the parsers of LL(1) parsing and LR parsing. The restricted classes and the evaluation order are on S-attributed and L-attributed definitions; executing actions during parsing is translation schemes. The main application is the generation of intermediate code (code and temporaries are attributes). Types attached to names are kept in the symbol table described in phases of a compiler.
PYQs
4 previous year questions from your book. I re-solved each one myself (3 by running or simulating, 1 by reasoning only); the book's key is shown next to my answer. All PYQs
Consider the following two syntax-directed definitions SDD1 and SDD2 for type declarations. SDD1 Grammar (G1) Semantic Rules D→TV D.type = T.type V.type = T.type T→ int T.type = int T→ float T.type = float V→V1 id V1.type = V.type put(id.entry,V.type) V→ id put(id.entry,V.type) SDD2 Grammar (G2) Semantic Rules D→D1 id D.type = D1.type put(id.entry, D1.type ) D→T id D.type = T.type put(id.entry, T.type ) T→ int T.type = int T→ float T.type = float D is the start symbol, and int, float and id are the three terminals. The non-terminal V1 is the same as V and the non-terminal D1 is the same as D. Here, the subscript is used to differentiate the grammar symbols on the two sides of a production. The function put updates the symbol table with the type information for an identifier. Let P and Q be the languages specified by grammars G1 and G2, respectively. Which of the following statements is/are true?
- (a)The languages P and Q are the same
- (b)SDD2 is S-attributed and contains only synthesized attributes
- (c)SDD1 is L-attributed and contains only inherited attributes
- (d)The specifications of SDD1 and SDD2 are such that the same entries get added to the symbol table
Consider the following syntax-directed definition
(SDD)
S → D H T U { S.val = D.val + H.val + T.val + U.val; }
D → "M" D1 { D.val = 5 + D1.val; }
D → ε { D.val = -5; }
H → "L" H1 { H.val = 5 * 10 + H1.val; }
H → ε { H.val = -10; }
T → "C" T1 { T.val = 5 * 100 + T1.val; }
T → ε { T.val = -5; }
U → "K" { U.val = 5; }
Given "MMLK" as the input, which one of the
following options is the CORRECT value
computed by the SDD (in the attribute S.val)?- (a)45
- (b)50
- (c)55
- (d)60
Consider the syntax directed translation given by the following grammar and semantic rules. Here N,I,F and B are non-terminals. N is the starting non- terminal, and #, 0, and 1 are lexical tokens corresponding to input letters "#", "0", and "1", respectively. X.val denotes the synthesized attribute (a numeric value) associated with a non-terminal X.I1 and F1 denote occurrences of I and F on the right- hand side of a production, respectively. For the tokens 0 and 1, 0.val=0 and 1.val= 1 N → I # F N.val = I.val + F.val I → I1 B I.val = (2 I1.val) + B.val I → B I.val = B.val F → B F1 F.val = 1⁄2 (B.val + F1.val) F → B F.val = 1⁄2 B.val B → 0 B.val = 0.val B → 1 B.val = 1.val The value computed by the translation scheme for the input string: 10#011 is ____________. (Rounded off to three decimal places)
Consider the following grammar along with
translation rules.
S → S1 # T { S.val = S1.val * T.val }
S → T { S.val = T.val }
T → T1 % R { T.val = T1.val + R.val }
T → R { T.val = R.val }
R → id { R.val = id.val }
Here # and % are operators and id is a token that
represents an integer and id.val represents the
corresponding integer value. The set of non-terminals
is {S, T, R, P} and a subscripted non-terminal
indicates an instance of the non-terminal.
Using this translation scheme, the computed value of
S.val for root of the parse tree for the expression
20#10%5#8%2%2 is ______
COMPILER DESIGNSelf-check
Question 1
In the rule A.i = B.s is given. Is A.i synthesized or inherited?
Show answerHide
Inherited: it is an attribute of a symbol on the right side of the production (and it depends on a sibling).
Question 2
With the binary number grammar, what is N.val for 1011?
Show answerHide
11: 1, then 2 + 0 = 2, then 4 + 1 = 5, then 10 + 1 = 11.
Question 3
What is the dependency graph of a circular definition characterised by?
Show answerHide
It contains a cycle for some parse tree, so no topological order of the attribute evaluations exists.
Question 4
Which attributes are supplied by the lexical analyser?
Show answerHide
Attributes of the terminals, such as num.lexval and id.name; they are treated as synthesized and need no semantic rule.
Revision card
One-screen revision
- SDD: grammar + attributes + rules; evaluate on the parse tree.
- Synthesized = defined at the left symbol (up); inherited = defined for a right symbol (down and sideways).
- Dependency graph needs to be acyclic; evaluation order = topological order.
- Subscripts distinguish repeated symbols ().
- Typical uses: expression values, declaration types, code.
References
- ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 5 Syntax-Directed Translation (5.1 syntax-directed definitions, inherited and synthesized attributes, evaluating an SDD at the nodes of a parse tree)
- ·Cooper and Torczon, Engineering a Compiler, Chapter 4 Context-Sensitive Analysis (attribute grammars)
Verification
Every attribute value in the animations is computed by evaluating the semantic rules on a parse tree built by a real SLR(1) parser, in a topological order of the dependency graph (scripts/cd_sdt.py). scripts/enum-checks/cd_sdt.py checks the results against Python's own evaluation of 1500 random arithmetic expressions and 300 binary numbers, the declaration types of 300 random declarations, and the circularity detection; tests/cd-traces.test.ts rechecks the final labels. The subscript convention (E1 for the right-side occurrence of E) follows the Dragon book. 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)