Introduction

Parsing tells the compiler how a program is structured; it does not compute anything about the program. Syntax-directed translation attaches information to grammar symbols, called attributes, and attaches rules to productions that compute those attributes. The value of an expression, the type of a declared name, the code for a statement and the syntax tree of an expression are all attributes.

A syntax-directed definition (SDD) is a context-free grammar together with attributes and semantic rules. It says what must be computed but not when; the order of evaluation follows from the dependencies between attributes. Two restricted classes, S-attributed and L-attributed definitions, can always be evaluated in a fixed order during parsing, which is why examination questions concentrate on recognising them.

Prerequisites

  • Top-Down Parsing and LL(1): parse trees, the left-recursion-free expression grammar, and the order in which a top-down parser visits nodes.
  • Bottom-up parsing order (see the note Bottom-Up Parsing and Shift-Reduce): children are reduced before their parent.

Core concepts

TermDefinition
AttributeA value attached to a grammar symbol, written X.aX.a (a number, a type, a string of code, a pointer)
Semantic ruleAn equation attached to a production that defines one attribute in terms of others in the same production
Synthesized attributeAttribute of the head AA of production A→X1…XnA \to X_1 \ldots X_n, defined from attributes of the children XiX_i and already available inherited attributes of the head (or a leaf constant), with an acyclic local schedule. An S-attributed definition has only synthesized attributes and uses the child-only restriction
Inherited attributeAttribute of a body symbol XjX_j, defined from attributes of the head AA, of XjX_j itself, and of its siblings XiX_i
Annotated parse treeA parse tree with the value of every attribute shown at its node
Dependency graphOne node per attribute instance in the tree; an edge b→cb \to c when the rule for cc uses bb
S-attributed SDDEvery attribute is synthesized
L-attributed SDDEvery inherited attribute of XjX_j in A→X1…XnA \to X_1 \ldots X_n depends only on inherited attributes of AA, on attributes (of either kind) of X1,…,Xj−1X_1, \ldots, X_{j-1}, and on attributes of XjX_j itself without creating a cycle

Synthesized versus inherited: where the rule lives. A synthesized attribute is computed in the production where its symbol is the head, so its information flows up the tree. An inherited attribute is computed in the production where its symbol is in the body, so its information flows down or sideways. Terminals have synthesized attributes only, supplied by the lexical analyser (for example digit.lexval); the start symbol normally has no inherited attributes.

Side effects. A rule such as addType(id.entry, L.inh) or print(E.val) does not define an attribute; it is treated as defining a dummy attribute of the head so that it gets a place in the dependency graph.

How it works

Evaluation order

Any order consistent with the dependency graph, that is any topological sort, evaluates every attribute after the attributes it uses. If the graph has a cycle, no order exists and the SDD is circular for that tree. Testing whether an arbitrary SDD can be circular for some parse tree is decidable but takes exponential time, which is the practical reason for restricting attention to S- and L-attributed definitions.

ClassOrder that always worksFits naturally with
S-attributedAny bottom-up order, for example a postorder traversalLR parsing: compute the head's attributes at each reduction
L-attributedDepth-first, left to right: inherited attributes of a node before visiting it, synthesized ones after its childrenLL parsing and recursive descent

Every S-attributed SDD is L-attributed (there are no inherited attributes to restrict).

Example 1: an S-attributed desk calculator

ProductionSemantic rule
L→E  nL \to E \; \textbf{n}L.val=E.valL.val = E.val
E→E1+TE \to E_1 + TE.val=E1.val+T.valE.val = E_1.val + T.val
E→TE \to TE.val=T.valE.val = T.val
T→T1∗FT \to T_1 * FT.val=T1.val×F.valT.val = T_1.val \times F.val
T→FT \to FT.val=F.valT.val = F.val
F→(E)F \to ( E )F.val=E.valF.val = E.val
F→digitF \to \textbf{digit}F.val=digit.lexvalF.val = \textbf{digit}.lexval

(n\textbf{n} is an end-of-line token; subscripts distinguish two occurrences of the same nonterminal.) For the input 3 * 4 + 5 n the annotated parse tree has, bottom-up: F.val=3F.val = 3, T.val=3T.val = 3, F.val=4F.val = 4, T.val=12T.val = 12, E.val=12E.val = 12, then F.val=5F.val = 5, T.val=5T.val = 5, E.val=17E.val = 17, and L.val=17L.val = 17 at the root. Every value is computed from the children, so a postorder walk, or an LR parser at each reduction, evaluates the whole tree.

Example 2: an inherited attribute for declarations

ProductionSemantic rule
D→T  LD \to T \; LL.inh=T.typeL.inh = T.type
T→intT \to \textbf{int}T.type=integerT.type = \text{integer}
T→floatT \to \textbf{float}T.type=floatT.type = \text{float}
L→L1  ,  idL \to L_1 \; , \; \textbf{id}L1.inh=L.inhL_1.inh = L.inh; addType(id.entry,L.inh)\text{addType}(\textbf{id}.entry, L.inh)
L→idL \to \textbf{id}addType(id.entry,L.inh)\text{addType}(\textbf{id}.entry, L.inh)

The type is known at TT, on the left, but must reach every identifier in LL, on the right and further down: an inherited attribute carries it. For float x, y, z the parse tree has three LL nodes. The dependency graph has 10 nodes: T.typeT.type, three L.inhL.inh, three id.entry\textbf{id}.entry, and three dummy nodes for the addType calls. It has 9 edges: T.type→L.inhT.type \to L.inh at the top, two edges passing L.inhL.inh down to L1.inhL_1.inh, and two edges into each addType node (from that LL's inhinh and from its id.entry\textbf{id}.entry). The SDD is L-attributed: L.inhL.inh depends on its left sibling TT, and L1.inhL_1.inh on the head's inherited attribute.

Annotated parse tree of 3 \* 4 + 5 n and dependency graph of float x, y, z
Figure 1. Top: the annotated parse tree of 3 * 4 + 5 n with every val attribute, ending in L.val = 17. Bottom: the dependency graph of float x, y, z, with 10 attribute nodes and 9 edges carrying the type from T down to each addType call.

Example 3: inherited attributes after removing left recursion

Left-recursion removal moves the left operand of an operator into a different subtree. An inherited attribute carries it across:

ProductionSemantic rules
T→F  T′T \to F \; T'T′.inh=F.valT'.inh = F.val; T.val=T′.synT.val = T'.syn
T′→∗  F  T1′T' \to * \; F \; T'_1T1′.inh=T′.inh×F.valT'_1.inh = T'.inh \times F.val; T′.syn=T1′.synT'.syn = T'_1.syn
T′→εT' \to \varepsilonT′.syn=T′.inhT'.syn = T'.inh
F→digitF \to \textbf{digit}F.val=digit.lexvalF.val = \textbf{digit}.lexval

For 3 * 5 * 2: the first T′T' inherits 3; the next inherits 3×5=153 \times 5 = 15; the last inherits 15×2=3015 \times 2 = 30 and, being ε\varepsilon, copies it to synsyn; the 30 is passed back up to T.val=30T.val = 30. The running product is the left-associative value ((3×5)×2)((3 \times 5) \times 2), even though the tree leans to the right. The SDD is L-attributed: each T1′.inhT'_1.inh uses the head's inherited attribute and the left sibling FF.

Example 4: an S-attributed definition with positional weights

For binary numerals, N→LN \to L, L→L1B∣BL \to L_1 B \mid B, B→0∣1B \to 0 \mid 1, the rules L.val=2×L1.val+B.valL.val = 2 \times L_1.val + B.val, L.val=B.valL.val = B.val, B.val=0B.val = 0 or 11, and N.val=L.valN.val = L.val are all synthesized. The input 1101 gives N.val=13N.val = 13: the partial values are 1, 3, 6, 13.

Example 5: unfamiliar operators, familiar method

ProductionSemantic rule
E→E1  #  TE \to E_1 \;\#\; TE.val=E1.val×T.valE.val = E_1.val \times T.val
E→TE \to TE.val=T.valE.val = T.val
T→T1  &  FT \to T_1 \;\&\; FT.val=T1.val+F.valT.val = T_1.val + F.val
T→FT \to FT.val=F.valT.val = F.val
F→numF \to \textbf{num}F.val=num.lexvalF.val = \textbf{num}.lexval

The symbol # means multiplication and & means addition, and & is on the lower level TT, so it binds tighter. For 2 # 3 & 5 # 6 & 4 the tree groups the & pieces first: TT-values 22, 3+5=83 + 5 = 8 and 6+4=106 + 4 = 10, and then E.val=(2×8)×10=160E.val = (2 \times 8) \times 10 = 160. Reading the string with the usual "multiplication first" habit gives 2×3+5×6+4=402 \times 3 + 5 \times 6 + 4 = 40, the trap answer.

Example 6: a synthesized attribute that is not a value

ProductionSemantic rule
S→(S1)S2S \to ( S_1 ) S_2S.d=max⁡(S1.d+1,  S2.d)S.d = \max(S_1.d + 1,\; S_2.d)
S→εS \to \varepsilonS.d=0S.d = 0

S.dS.d is the nesting depth of a balanced string of parentheses. For (()())(), the outer pair encloses ()(), whose depth is 1, so it contributes 1+1=21 + 1 = 2; the trailing () contributes 1. Hence S.d=max⁡(2,1)=2S.d = \max(2, 1) = 2 at the root. The definition is S-attributed.

Example 7: a circular definition

ProductionSemantic rules
A→BA \to BA.s=B.iA.s = B.i; B.i=A.s+1B.i = A.s + 1
B→bB \to \textbf{b}none

For the only tree, AA over BB over b, the dependency graph has 2 nodes, A.sA.s and B.iB.i, and 2 edges, B.i→A.sB.i \to A.s and A.s→B.iA.s \to B.i: a cycle, so no evaluation order exists. The definition is not L-attributed either, because B.iB.i reads the head's synthesized attribute. L-attributed definitions can never be circular: their inherited attributes use only the head's inherited attributes, attributes from the left, or acyclic same-symbol dependencies. Respecting that local dependency order during a left-to-right depth-first visit makes each required source available.

Recognising the class

For each rule, ask: which symbol's attribute does it define, and from which positions does it read?

Production and ruleKind of the defined attributeS-attributed?L-attributed?
A→BCA \to B C: A.s=B.s+C.sA.s = B.s + C.ssynthesizedyesyes
A→BCA \to B C: C.i=A.i+B.sC.i = A.i + B.sinheritednoyes (head's inherited, left sibling)
A→BCA \to B C: B.i=C.sB.i = C.sinheritednono (depends on a right sibling)
A→BCA \to B C: C.i=A.sC.i = A.sinheritednono (depends on the head's synthesized attribute)
A→BCA \to B C: A.s=B.iA.s = B.isynthesizedno (B.iB.i is inherited)yes, if B.iB.i itself obeys the rule

A whole SDD is S-attributed only if every rule is of the first kind, and L-attributed only if every inherited attribute satisfies the left-to-right restriction.

Important properties and observations

  • S implies L. Every S-attributed SDD is L-attributed; the converse is false (Example 2).
  • Postorder suffices for S-attributed SDDs; L-attributed ones need one left-to-right depth-first pass with actions on the way down (inherited) and on the way up (synthesized).
  • Inherited attributes in LR parsing. Bottom-up parsers compute synthesized attributes at reductions easily. Inherited attributes need marker nonterminals or a separate pass (see the note Syntax-Directed Translation Schemes).
  • The grammar decides the shape. The same computation can be S-attributed on a left-recursive grammar (Example 1) and need inherited attributes on its left-recursion-free version (Example 3).
  • Syntax trees are attributes too. Rules like E.node=new Node(′+′,E1.node,T.node)E.node = \text{new Node}('+', E_1.node, T.node) build abstract syntax trees, the starting point of intermediate code generation.

Common misconceptions.

  • "An attribute of a body symbol is inherited." Only if it is defined in this production. B.sB.s used in A.s=B.s+C.sA.s = B.s + C.s is synthesized: it was defined in a production where BB is the head.
  • "L-attributed means only left recursion." It is about the direction of attribute flow (from the left), not about the grammar.
  • "Terminals can have inherited attributes." In the standard definition they have only synthesized ones from the lexer.
  • "Circular means the grammar is recursive." Circularity is about attribute dependencies in a parse tree, not about recursive productions; Example 6 is recursive and not circular, Example 7 is not recursive and circular.

GATE examination perspective

  • Classify an SDD. For each inherited attribute, check that it reads only the head's inherited attributes, attributes of symbols to its left, or attributes of the same symbol without creating a dependency cycle. A rule reading a right sibling or the head's synthesized attribute breaks L-attribution.
  • Evaluate an annotated parse tree. Build the tree for the given string and grammar, then apply the rules bottom-up (synthesized) or top-down and left to right (inherited). Watch for unusual rules (for example E.val=E1.val×T.valE.val = E_1.val \times T.val on a + production): apply what is written.
  • Dependency graphs. Count nodes (attribute instances, including dummy nodes for side effects if the question says so) and edges; find a topological order or detect a cycle.
  • Which parser suits which SDD. S-attributed with LR; L-attributed with LL or recursive descent.
  • Type propagation in declarations. This is the standard inherited-attribute example.
  • Operators with invented symbols. Map each symbol to its rule, find its level in the grammar, and group by levels; Example 5 gives 160, not 40.
  • Circularity. A dependency cycle in some parse tree makes the SDD circular. S-attributed and L-attributed definitions are never circular.

Summary

Sources

  1. A. V. Aho, M. S. Lam, R. Sethi, J. D. Ullman, Compilers, Principles, Techniques, and Tools, 2nd ed., Pearson Addison-Wesley, 2007: Section 5.1 (syntax-directed definitions; inherited and synthesized attributes; evaluating an SDD at the nodes of a parse tree), Section 5.2 (evaluation orders: dependency graphs, ordering the evaluation, S-attributed and L-attributed definitions, semantic rules with controlled side effects), Section 5.3.1 (construction of syntax trees).

Next steps