Skip to main content

6.1 Syntax-Directed Definitions (SDD) & Syntax-Directed Translation (SDT) Schemes

πŸ“šModule 01Syntax-Directed Definitions & Translation Schemes⏱️5 min read
🎯High-Yield For:Semester Exams β€’ GATE CSE β€’ Technical Interviews

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

🍳 The Everyday Analogy: The Declarative Blueprint vs The Construction Trigger​

A parser alone only checks grammar. It verifies that salary = hours * 50; is a syntactically valid sentence, but it has no understanding of what salary is, whether hours is an integer or string, or what machine instructions to generate.

Semantic Analysis attaches concrete meaning to syntax trees using two complementary formalisms:

  1. Syntax-Directed Definition (SDD) β€” The Declarative Blueprint:
    • Associates mathematical quantities (Attributes) with grammar symbols.
    • For every production rule, it specifies mathematical equations (Semantic Rules) defining how attributes relate to each other.
    • It specifies what values to compute without specifying when or in what order to evaluate them.
  2. Syntax-Directed Translation Scheme (SDT) β€” The Imperative Trigger:
    • Embeds explicit program code fragments (Semantic Actions inside { ... }) directly into the production bodies.
    • It specifies exact execution timing: "When the parser reaches this position during tree traversal or stack reduction, immediately execute this block of code!"

πŸ’» Bridging to Computer Science: Synthesized and Inherited Attribute Propagation​

In compiler syntax-directed translation:

  • Attributes decorate the parse tree nodes.
  • Synthesized Attributes flow strictly upward (from children to parent). They represent bottom-up computations, such as evaluating the numeric value of an expression.
  • Inherited Attributes flow downward or laterally (from parent or left siblings to a node). They represent contextual information, such as passing a declared variable type down to a list of identifier names.

πŸ—οΈ Architectural Foundations: The SDD & SDT Pipeline​

Architecture Flow

The Syntax-Directed Translation Lifecycle

From declarative grammar equations to deterministic attribute evaluation over parse trees

πŸ“œGrammar

Context-Free Syntax

Underlying CFG specifying valid syntactic sentences.

β†’
Decorated with
🏷️SDD

Semantic Rules

Declarative attribute definitions associated with productions.

β†’
Instantiates
πŸ•ΈοΈGraph

Dependency Graph

Directed edges capture data dependencies between node attributes.

β†’
Topological Sort
⚑Execution

Annotated Tree

Evaluation order executes semantic actions and populates values.

βš–οΈ SDD vs SDT: Declarative vs Imperative Formalisms​

SDD vs SDT: Declarative Specification vs Imperative Action

Contrasting pure attribute equations against parser-synchronized code execution

Declarative

Syntax-Directed Definition (SDD)

πŸ“œ
Dominant Architecture / DomainTheoretical Formalism, Specification
  • β€’High-level declarative specification; zero order-of-execution constraints.
  • β€’Uses pure mathematical functions without arbitrary side effects.
  • β€’Evaluated over an Annotated Parse Tree using topological sorting.
  • β€’Ideal for formal reasoning and automated compiler generation.
"Specifies what the relationships are; the compiler figures out the evaluation order."
Imperative

Syntax-Directed Translation (SDT)

⚑
Dominant Architecture / DomainYacc, Bison, Production Compilers
  • β€’Imperative execution scheme; embeds action code inside { }.
  • β€’Order of execution is explicitly determined by parser tree traversal.
  • β€’Can perform arbitrary side effects: emit assembly, modify symbol table.
  • β€’Directly implemented during Shift/Reduce bottom-up reductions.
"Specifies exact code to execute at exact moments during parsing."

πŸ“ Formal Mathematical Foundations: Attributes & Rules​

🏷️ What is an Attribute?​

An Attribute is any quantity associated with a grammar symbol XX. Examples include:

  • val: The numeric value of an expression.
  • type: The data type of a variable (int, float, pointer).
  • addr: The memory location or temporary register holding a value.
  • name: The string lexeme of an identifier.

🌳 Synthesized vs Inherited Attributes​

Attributes are categorized into two fundamental classes based on how their values flow through the parse tree:

1. Synthesized Attributes (Bottom-Up Flow ⬆️)​

An attribute aa of non-terminal AA is Synthesized if its value at parse tree node NN is computed solely from:

  • Attribute values of the children of NN.
  • Other attributes of NN itself.

For production Aβ†’X1X2…XkA \to X_1 X_2 \dots X_k:

A.a=f(X1.b,X2.c,…,Xk.d)A.a = f(X_1.b, X_2.c, \dots, X_k.d)

The Terminal Invariant: Terminals can ONLY possess synthesized attributes! Their values are computed by the lexical analyzer (e.g. NUM.lexval = 42) and supplied directly to the parse tree. Terminals can NEVER have inherited attributes.

2. Inherited Attributes (Top-Down & Sibling Flow β¬‡οΈβž‘οΈ)​

An attribute bb of non-terminal BB is Inherited if its value at parse tree node NN is computed from:

  • Attribute values of the parent of NN.
  • Attribute values of the siblings of NN.
  • Other attributes of NN itself.

For production Aβ†’X1X2…B…XkA \to X_1 X_2 \dots B \dots X_k:

B.b=f(A.a,X1.c,X2.d)B.b = f(A.a, X_1.c, X_2.d)

Inherited attributes pass contextual information downward, such as:

  • Passing declared variable types from a declaration header down into a list of identifier names.
  • Passing base register addresses down into array index offsets.

Attribute Data Flow Topology

Comparing the directional propagation models of Synthesized vs Inherited attributes

Top Tier

Synthesized Attributes (Bottom-Up: ⬆️ Children β†’ Parent)

🌳

A.a = f(X1, X2, ... Xk). Children calculate sub-results and pass them upward to the parent node.

Terminals ONLY possess synthesized attributesComputed naturally during Post-Order reductionsCanonical: Expression values (E.val)
↓Natural match for Shift-Reduce bottom-up parsing
Bottom Tier

Inherited Attributes (Top-Down / Lateral: ⬇️ Parent / ➑️ Siblings β†’ Child)

🌿

B.b = f(Parent.a, LeftSiblings.c). Contextual information passed down from enclosing scope or left siblings.

Terminals CANNOT have inherited attributesComputed during Pre-Order traversalsCanonical: Type distribution (T.type β†’ L.in)

🌲 The Annotated Parse Tree (Decorated Parse Tree)​

An Annotated Parse Tree (or Decorated Parse Tree) is a standard parse tree where each node displays the concrete values of its associated attributes.

Canonical Example: The Desk Calculator SDD​

Consider an SDD that computes the numeric values of arithmetic expressions:

ProductionSemantic RuleAttribute Nature
L→E nL \to E \ \text{n}L.val=E.valL.val = E.valSynthesized
E→E1+TE \to E_1 + TE.val=E1.val+T.valE.val = E_1.val + T.valSynthesized
E→TE \to TE.val=T.valE.val = T.valSynthesized
T→T1×FT \to T_1 \times FT.val=T1.val×F.valT.val = T_1.val \times F.valSynthesized
T→FT \to FT.val=F.valT.val = F.valSynthesized
F→(E)F \to ( E )F.val=E.valF.val = E.valSynthesized
F→digitF \to \text{digit}F.val=digit.lexvalF.val = \text{digit}.lexvalSynthesized

(Subscripts E1E_1 and T1T_1 are used solely to distinguish production occurrences).


βš™οΈ Syntax-Directed Translation Schemes (SDT)​

An SDT embeds semantic action blocks {… }\{ \dots \} within production right-hand sides:

E -> E '+' T    { printf("+"); }
| T
T -> T '*' F { printf("*"); }
| F
F -> ID { printf("%s", $1); }

Infix to Postfix Translation via SDT​

When parsing input a + b * c with the SDT above:

  1. F -> ID matches a β€…β€ŠβŸΉβ€…β€Š\implies prints a.
  2. F -> ID matches b β€…β€ŠβŸΉβ€…β€Š\implies prints b.
  3. F -> ID matches c β€…β€ŠβŸΉβ€…β€Š\implies prints c.
  4. T -> T * F reduces β€…β€ŠβŸΉβ€…β€Š\implies prints *.
  5. E -> E + T reduces β€…β€ŠβŸΉβ€…β€Š\implies prints +.

Output Stream: a b c * + (Standard Reverse Polish / Postfix Notation!).


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

In industrial bottom-up compiler generators (such as Yacc, GNU Bison, and Tree-sitter):

Industrial bottom-up parsers do not construct full parse trees in RAM to evaluate synthesized attributes!

  • Alongside the state stack, the engine maintains a parallel Value Stack (yyvsp).
  • In a semantic action:
    • $$ represents the synthesized attribute of the left-hand side non-terminal (AA).
    • $1 represents the attribute of the first symbol on the RHS (X1X_1).
    • $2 represents the attribute of the second symbol on the RHS (X2X_2), and so on.
  • During reduction by Aβ†’X1X2X3A \to X_1 X_2 X_3:
    $$ = $1 + $3;  /* Pops 3 values from value stack, computes sum, pushes result */
  • This enables O(1)O(1) attribute evaluation on-the-fly during parsing with zero tree allocation!

In production parsers, semantic actions can emit intermediate code, insert records into the global symbol table, or print debug AST dumps directly during the reduction step.


πŸ“ Numerical Applications & Worked Examples​

Example 1: Annotated Parse Tree Evaluation​

Evaluate the annotated parse tree for the input expression:

w=3Γ—5+4Β nw = 3 \times 5 + 4 \ \text{n}

Using the Desk Calculator SDD. Determine the computed val attribute at every node.

Step-by-Step Analytical Trace:

  1. Leaf Lexical Values:

    • digit1(3)\text{digit}_1 (3): lexval=3lexval = 3
    • digit2(5)\text{digit}_2 (5): lexval=5lexval = 5
    • digit3(4)\text{digit}_3 (4): lexval=4lexval = 4
  2. Evaluate Subtree 3Γ—53 \times 5:

    • F1β†’digit1β€…β€ŠβŸΉβ€…β€ŠF1.val=3F_1 \to \text{digit}_1 \implies F_1.val = 3.
    • T1β†’F1β€…β€ŠβŸΉβ€…β€ŠT1.val=3T_1 \to F_1 \implies T_1.val = 3.
    • F2β†’digit2β€…β€ŠβŸΉβ€…β€ŠF2.val=5F_2 \to \text{digit}_2 \implies F_2.val = 5.
    • Tβ†’T1Γ—F2β€…β€ŠβŸΉβ€…β€ŠT.val=T1.valΓ—F2.val=3Γ—5=15T \to T_1 \times F_2 \implies T.val = T_1.val \times F_2.val = 3 \times 5 = 15.
    • E1β†’Tβ€…β€ŠβŸΉβ€…β€ŠE1.val=15E_1 \to T \implies E_1.val = 15.
  3. Evaluate Subtree +Β 4+ \ 4:

    • F3β†’digit3β€…β€ŠβŸΉβ€…β€ŠF3.val=4F_3 \to \text{digit}_3 \implies F_3.val = 4.
    • T2β†’F3β€…β€ŠβŸΉβ€…β€ŠT2.val=4T_2 \to F_3 \implies T_2.val = 4.
  4. Evaluate Root Expression EE:

    • Eβ†’E1+T2β€…β€ŠβŸΉβ€…β€ŠE.val=E1.val+T2.val=15+4=19E \to E_1 + T_2 \implies E.val = E_1.val + T_2.val = 15 + 4 = 19.
  5. Top-Level Goal:

    • Lβ†’EΒ nβ€…β€ŠβŸΉβ€…β€ŠL.val=E.val=19L \to E \ \text{n} \implies L.val = E.val = 19.

Final Result: L.val=19L.val = 19. The semantic evaluation respects operator precedence automatically through tree depth!


Example 2: Type Distribution via Inherited Attributes​

Consider the variable declaration grammar:

Dβ†’TΒ LTβ†’int∣floatLβ†’L1,Β id∣id\begin{aligned} D &\to T \ L \\ T &\to \text{int} \mid \text{float} \\ L &\to L_1, \ \text{id} \mid \text{id} \end{aligned}

Design an SDD using an inherited attribute in_type to propagate the type from TT down to all identifiers in list LL, and insert each identifier with its type into the symbol table.

Step-by-Step Semantic Rules:

ProductionSemantic RuleAttribute Classification
D→T LD \to T \ LL.in_type=T.typeL.in\_type = T.typeL.in_typeL.in\_type is Inherited (from sibling TT)
T→intT \to \text{int}T.type=integerT.type = \text{integer}T.typeT.type is Synthesized
T→floatT \to \text{float}T.type=realT.type = \text{real}T.typeT.type is Synthesized
L→L1, idL \to L_1, \ \text{id}L1.in_type=L.in_typeL_1.in\_type = L.in\_type
addType(id.entry,L.in_type)\text{addType}(\text{id}.entry, L.in\_type)
L1.in_typeL_1.in\_type is Inherited (from parent LL)
L→idL \to \text{id}addType(id.entry,L.in_type)\text{addType}(\text{id}.entry, L.in\_type)Semantic Action

For input int x, y, z;:

  • T.type=integerT.type = \text{integer} is synthesized at TT.
  • L.in_typeL.in\_type inherits integer\text{integer} from its left sibling TT.
  • As LL expands downward, L1.in_typeL_1.in\_type inherits integer\text{integer} from parent LL.
  • Each identifier x, y, z receives type integer\text{integer} and records it in the symbol table!

🎯 Exam & Interview Pitfall Check​

Core Conceptual Questions

Question 1: "Can terminal symbols ever possess inherited attributes in an SDD?"
Key Focus Points:

  • Strict Rule: Never. Terminal symbols can ONLY possess synthesized attributes.
  • Reason: Terminal symbols are generated and recognized by the lexical analyzer (scanner). Their attributes (such as lexval or lexeme) are supplied by the lexer before parsing begins and flow upward into the parse tree.

Question 2: "What is the primary difference between an SDD and an SDT?"
Key Focus Points:

  • SDD (Syntax-Directed Definition): High-level declarative specification. Uses mathematical equations without evaluation order constraints or arbitrary side effects.
  • SDT (Syntax-Directed Translation Scheme): Imperative implementation scheme. Embeds program fragments { actions } directly inside production right-hand sides with explicit execution timing.
Common Interview Traps: Architectural Trade-Offs

Trap 1: Assuming circular dependencies can be evaluated by top-down parsers.
Answer: Impossible. If an SDD has a directed cycle in its dependency graph (e.g. A.s=B.i+1A.s = B.i + 1 and B.i=A.s+2B.i = A.s + 2), no topological sort exists. The definition is ill-formed, and no parser can evaluate it.

Trap 2: Believing inherited attributes can only flow from parent to child.
Answer: Incomplete. An inherited attribute at node NN can receive values from its parent AND from its sibling nodes (specifically its left siblings in L-attributed SDDs).


πŸ’¬

Discussion & Doubts