G27
Self-checkedTier 1 12 min readcd.sdt-translation-schemes

Translation schemes and semantic actions

Syntax-directed translation schemes: semantic actions embedded in productions, postfix schemes run at reductions, rules for placing actions so that attribute values are available, running a scheme with an LR parser and with an LL(1) parser, and the standard infix-to-postfix translation.

Why it exists

An SDD (syntax-directed definitions) lists the rules but leaves the schedule open. Compilers usually want the work done during parsing, in the order the parser meets the input: print an instruction as soon as it is known, add a name to the symbol table when it is declared. A syntax-directed translation scheme (SDT) writes this schedule down: semantic actions (program fragments in braces) are placed inside the right sides of the productions, and an action runs when the parser reaches its position.

Prerequisites

You need S-attributed and L-attributed definitions and the way they are evaluated (evaluation orders).

Precise definitions

Syntax-directed translation scheme. A context-free grammar with semantic actions embedded in the right sides of productions, for example E→E1+T {print +}E \to E_1 + T\ \{\text{print } +\}. An action may be anywhere: at the start, in the middle, or at the end. Think of the action as an extra node of the parse tree (a leaf with no children); the actions are executed in the order they are met in a depth-first, left-to-right traversal of that tree, which is the order in which a parser can execute them.

Postfix translation scheme. An SDT in which every action is at the right end of its production. Postfix schemes work with bottom-up parsing: the action of a production is executed when the parser reduces by it (the action is run right after the symbols of the right side have been recognised). Every S-attributed SDD can be turned into a postfix scheme by writing each rule as an action at the end of its production: E → E1 + T { E.val = E1.val + T.val }. In an LR parser the attribute values are kept on the value stack beside the symbols.

Actions inside productions. A bottom-up parser cannot run an action in the middle of a production at the moment it passes it, because it does not yet know which production it is in. Two cures: (1) use a top-down parser, which knows the production when it expands the symbol; (2) for an LR parser, replace the action by a marker nonterminal M→ε {action}M \to \varepsilon\ \{\text{action}\} placed where the action was, so that the action becomes a postfix action of the production M→εM \to \varepsilon, executed by a reduction of the empty string. (Markers may introduce parsing conflicts.)

Turning an L-attributed SDD into an SDT (the rules of placement):

  1. An action that computes an inherited attribute of a symbol XX is placed immediately before XX in the right side.
  2. An action that computes a synthesized attribute of the left-hand symbol is placed at the end of the production.
  3. An action must never refer to an attribute of a symbol that lies to its right (it has not been parsed yet), and when it refers to an inherited attribute of the left-hand symbol that must already have been computed (by an action before this production was entered).

For the declaration example:

D -> T { L.in = T.type } L
T -> int { T.type = int }
T -> float { T.type = float }
L -> { L1.in = L.in } L1 , id { addtype(id.entry, L.in) }
L -> id { addtype(id.entry, L.in) }

(Here addtype is a side effect: it records the type of the identifier in the symbol table, which is exactly why one writes a scheme and not a definition.)

Left recursion in schemes. When left recursion is removed from the grammar of an SDT that prints or computes, the actions have to be moved with the symbols. The infix-to-postfix scheme on the left recursive grammar,

E -> E1 + T { print('+') }
E -> T
T -> T1 * F { print('*') }
T -> F
F -> ( E )
F -> num { print(num.val) }

becomes, after left recursion removal, a scheme in which the action follows the operand it belongs to:

E -> T R
R -> + T { print('+') } R | epsilon
T -> F S
S -> * F { print('*') } S | epsilon
F -> ( E ) | num { print(num.val) }

Both print the postfix form of the expression: operands in input order, each operator after its operands.

Where actions print. For S→a S {print}∣…S \to a\ S\ \{\text{print}\} \mid \dots an action at the end runs after the nested SS finished, so with recursion the outputs appear in the reverse of the order of the input symbols (the innermost first); an action at the start runs before the nested SS is entered, so the output follows the input order. This is the key to the common GATE question "what does this SDT print?"

How it works

First the postfix scheme on the LR parser: print + after E + T, print * after T * F, print the value after num. The animation parses 3 + 4 * 5. Nothing is printed on a shift. At each reduction the action of that production runs: the 3 is printed when F → num is reduced, the * when T → T * F is reduced (after both operands have been printed), the + last. The output grows as 3, 3 4, 3 4 5, 3 4 5 *, 3 4 5 * +.

Postfix translation scheme run by an LR parser: 3 + 4 * 5: start

Parser run by a script, not typed

LR parse of E → E + T | T, T → T * F | F, F → ( E ) | num with the actions {print +} after E + T, {print *} after T * F and {print num.val} after num: postfix translation.

  1. 1E → E + T { print + }
  2. 2E → T
  3. 3T → T * F { print * }
  4. 4T → F
  5. 5F → ( E )
  6. 6F → num { print num.val }
stack (top at the right) (top at the right)
(empty)
input
num+num*num$
action start

Step 0

A postfix translation scheme puts every action at the very end of its production. An LR parser can then run each action exactly when it reduces by that production, so the output appears in the order of the reductions. Here the actions print the postfix form of the expression.

0 / 14
computed by script: the semantic rules are evaluated on a real parse tree, one attribute per step. python3 scripts/gen-cd-sdt-traces.py

The same translation with a top-down parser and the scheme with actions inside the productions. The actions are pushed on the stack with the grammar symbols; when an action reaches the top of the stack it is executed. The tree shows the action nodes as leaves (dashed until executed).

The same translation by an LL(1) parser with embedded actions: 3 + 4 * 5: start

Parser run by a script, not typed

LL(1) parse with actions embedded in the productions (translation scheme): the stack holds symbols and actions, top at the left.

  1. 1E → T R
  2. 2R → + T {print +} R
  3. 3R → ε
  4. 4T → F S
  5. 5S → * F {print *} S
  6. 6S → ε
  7. 7F → ( E )
  8. 8F → num {print num.val}
stack (top at the left) (top at the left)
E$
input
3+4*5$
action start
parse tree so far
E
parsing table M[A, a]
+*()num$
EE→T RE→T R
RR→+ T RR→εR→ε
TT→F ST→F S
SS→εS→* F SS→εS→ε
FF→( E )F→num

Step 0

A translation scheme for a top-down parser: the actions sit inside the productions, between the symbols, and are pushed on the stack with them. When an action reaches the top of the stack it is executed. The left recursion of the expression grammar has been removed, with the actions moved along.

0 / 22
computed by script: the semantic rules are evaluated on a real parse tree, one attribute per step. python3 scripts/gen-cd-sdt-traces.py

Worked examples

EX 1Infix to postfix

Easy

Translate 3 + 4 * 5 with the scheme above: the output is 3 4 5 * +. To check, evaluate it as a postfix expression with a stack: push 3, 4, 5; * pops 5 and 4 and pushes 20; + pops 20 and 3 and pushes 23, which equals 3+4⋅53 + 4 \cdot 5. (This equality is also checked for 1500 random expressions.) The multiplication comes out before the addition because T→T∗FT \to T * F is reduced before E→E+TE \to E + T.

EX 2Which order do the prints come out in?

Easy

S→a S {print 1}∣b S {print 2}∣c {print 3}S \to a\ S\ \{\text{print } 1\} \mid b\ S\ \{\text{print } 2\} \mid c\ \{\text{print } 3\} and the input aabc. The reductions happen in the order S→cS \to c, S→bSS \to b S, S→aSS \to a S, S→aSS \to a S, so the output is 3 2 1 1: the reverse of the input order. If the same actions were placed at the start of each right side (S→{print 1} a SS \to \{\text{print } 1\}\ a\ S and so on) and run by a top-down parser, the output would follow the input: 1 1 2 3. Both claims were checked on 400 random strings.

EX 3Operators with swapped meanings

Medium

A favourite GATE trick: E→E1∗T {E.val=E1.val+T.val}∣T {E.val=T.val}E \to E_1 * T\ \{E.val = E_1.val + T.val\} \mid T\ \{E.val = T.val\}, T→T1+F {T.val=T1.val∗F.val}∣F {T.val=F.val}T \to T_1 + F\ \{T.val = T_1.val * F.val\} \mid F\ \{T.val = F.val\}, F→num {F.val=num.lexval}F \to \text{num}\ \{F.val = \text{num.lexval}\}. Compute the value for 2 * 3 + 4 * 5. The grammar gives + the higher precedence (it is generated at the TT level) and both are left associative, so the tree groups as (2∗(3+4))∗5(2 * (3 + 4)) * 5 in the grammar's symbols: TT for 3 + 4 has value 3⋅4=123 \cdot 4 = 12 (the action of + multiplies), EE for 2 * (…) is 2+12=142 + 12 = 14 (the action of * adds), and the last * 5 gives 14+5=1914 + 5 = 19. The value is 19, which equals Python's value of 2 + 3 * 4 + 5 (the symbols' meanings swapped). Do not use the usual meaning of the symbols: follow the actions and the grammar's own precedence.

EX 4Where must the action go?

Medium

For A→B CA \to B\ C with the rule C.i = B.s (L-attributed), the scheme is A→B {C.i=B.s} CA \to B\ \{C.i = B.s\}\ C: the action sits right before CC and after BB, so B.s is known. For B.i = A.i the action goes before BB: A→{B.i=A.i} B CA \to \{B.i = A.i\}\ B\ C. The rule A.s = C.s for the left side goes at the end: A→B C {A.s=C.s}A \to B\ C\ \{A.s = C.s\}. An action { B.i = C.s } before BB is not allowed: it uses an attribute of a symbol to its right. In a question that asks "which of these schemes is a valid translation of the SDD" check these three placement rules.

EX 5Actions in the middle and the parser

GATE level

S→a {print 1} S b {print 2}∣εS \to a\ \{\text{print } 1\}\ S\ b\ \{\text{print } 2\} \mid \varepsilon (balanced strings). A top-down parser executes the first action as soon as it has matched a and decided on this production, so for aabb it prints 1 1 2 2. A bottom-up parser, run on the same scheme without markers, cannot execute the first action at that moment; by introducing the marker M→ε {print 1}M \to \varepsilon\ \{\text{print } 1\} and the production S→a M S b {print 2}S \to a\ M\ S\ b\ \{\text{print } 2\} the action is executed at the reduction of MM (immediately after the a has been shifted) and the output is again 1 1 2 2. An answer to such a question depends on the parser type stated; read it carefully.

Cheat box

Cheat box

  • SDT = grammar with actions in braces inside the right sides; actions execute in depth-first left-to-right order of the tree (actions as leaves).
  • Postfix SDT: all actions at the end; executed at reductions of an LR parser (value stack). Every S-attributed SDD gives one.
  • Placement: inherited attribute just before its symbol; synthesized attribute at the end; never use a symbol to the right.
  • Actions in the middle: top-down parser executes them as met; bottom-up needs marker nonterminals M→εM \to \varepsilon.
  • End-of-production print with recursion gives reverse order; start-of-production print gives input order.
  • Infix to postfix: print operands, and each operator after its second operand.

Traps and GATE patterns

Connections

A scheme is the executable form of an SDD; S-attributed and L-attributed definitions give the two standard shapes (evaluation orders). The parsers are LR and LL(1); the left-recursion transformation is in grammar transformations. The main use is the generation of intermediate code while parsing.

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

GATE 2024 · MSQQ4 · page 273 of the book
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
GATE 2021 · MCQ · 1 markQ7 · page 274 of the book
Consider the following grammar (that admits a
series of declarations, followed by expressions) and
the associated syntax directed translation (SDT)
actions, given as pseudo-code:

P → D* E*
 D → int ID {record that ID.lexeme is of type int}
 D → bool ID {record that ID. lexeme is of type bool}
 E → E1 + E2 {check that E1.type = E2.type = int;
set E.type := int}
E → !E1 {check that E1.type = bool; set E.type =
bool}

E → ID {set E.Type := int}

With respect to the above grammar, which one of
the following choices is correct?
  • (a)The actions can be used to type-check syntactically correct integer variable declarations and integer expressions.
  • (b)The actions can be used to correctly type- check any syntactically correct program.
  • (c)The actions can be used to type-check syntactically correct Boolean variable declarations and Boolean expressions
  • (d)The actions will lead to an infinite loop.
GATE 2019 · MCQ · 2 marksQ11 · page 275 of the book
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
GATE 2016 · MCQQ12 · page 275 of the book
Consider
the
following
Syntax
Directed
Translation Scheme (SDTS), with non-terminals
{S, A} and terminals {a,b}.
                       S → aA   { print 1 }
                       S → a    { print 2 }
                       A → Sb   { print 3 }

Using the above SDTS, the output printed by a
bottom-up parser, for the input aab is:
  • (a)1 3 2
  • (b)2 2 3
  • (c)2 3 1
  • (d)syntax error

Self-check

Question 1

What does the scheme E→E1+T {print +}∣TE \to E_1 + T\ \{\text{print } +\} \mid T, T→num {print num.val}T \to \text{num}\ \{\text{print num.val}\} print for 1 + 2 + 3?

Show answer

1 2 + 3 +: numbers when they are reduced, each + right after its right operand (left-associative grouping).

Question 2

Where do you place the action for an inherited attribute of XX in A→YXZA \to Y X Z?

Show answer

Immediately before XX (after YY), so that it may use the attributes of YY and of the parent but nothing of ZZ.

Question 3

Why can an LR parser not run an action in the middle of a production directly?

Show answer

It decides which production applies only when it reduces; in the middle of the right side it does not yet know it is in that production. A marker nonterminal turns the action into the end action of an empty production.

Question 4

For S→a S {print x}∣cS \to a\ S\ \{\text{print } x\} \mid c with a bottom-up parser and the input aac, how often does the action run, and which reduction runs it first?

Show answer

Twice. The innermost S→aSS \to a S (the one that contains the second a) is reduced first, then the outer one. For prints in the actions this means the output is produced from the inside outwards.

Revision card

One-screen revision

  • SDT: actions in braces inside productions; run in depth-first left-to-right order of the tree.
  • Postfix SDT (actions at the end): run at LR reductions. Middle actions: top-down, or markers for LR.
  • Placement: inherited before the symbol, synthesized at the end, nothing from the right.
  • End actions with recursion print in reverse input order; start actions in input order.
  • Infix to postfix: 3 + 4 * 5 gives 3 4 5 * +.

References

  • ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (2nd edition), Chapter 5 Syntax-Directed Translation (5.3 applications, 5.4 syntax-directed translation schemes: postfix translation schemes, parser-stack implementation, SDTs with actions inside productions, eliminating left recursion from SDTs, SDTs for L-attributed definitions)

Verification

The two translation animations run real parsers (an SLR(1) LR parser and an LL(1) table-driven parser, scripts/gen-cd-sdt-traces.py); the printed postfix string is evaluated as a postfix expression and compared with Python's value of the infix expression, and the postorder of the parse tree is compared with the shunting-yard algorithm on 1500 random expressions (scripts/enum-checks/cd_sdt.py). The orders in which actions at the start or at the end of productions print (input order versus reverse input order) were checked on 400 random strings. The expression with swapped operator meanings (value 19) equals Python's value of the reinterpreted expression. How a real parser generator implements marker nonterminals is described in outline only. 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