Yasir Explains/Compiler Design/Syntax-Directed Translation/Semantic Rules
Syntax-Directed Translation

Semantic Rules

On this page

What is a Semantic Rule?The ExampleSolution — Evaluating the RulesConstraints, and SDD vs. SDTKey Points
Syntax-Directed Translation

Semantic Rules

What a semantic rule is and its two kinds (attribute equations vs. side-effect actions), taught through one running example: the desk-calculator grammar evaluating the synthesized attribute val over ( 3 * 5 + 2 ) * 2 to print 34.

What is a Semantic Rule?

Definition. A semantic rule is attached to one grammar production and says how to compute an attribute of a symbol in that production, or how to perform an action.

An attribute is a named value carried by a grammar symbol — written X.attr. For example E.val (the value of E) or digit.lexval (the digit supplied by the lexer).

Two kinds of rule

  • Attribute-equation rule — defines an attribute as a function of other attributes. It just states a value.
Example
E.val = E₁.val + T.val
  • Side-effect / action rule — does something observable (print, emit code, update the symbol table) instead of defining a value.
Example
print(E.val)

How a rule references symbols

A rule may only mention attributes of symbols in its own production — the head and the body symbols. When a symbol appears more than once, subscripts say which occurrence is meant: the head keeps its plain name and body occurrences get ₁, ₂, …

Example
E → E + T E.val = E₁.val + T.val
(E₁ = body E, T = body T, E = head)

Without the subscript you could not tell the head E from the body E. Terminal attributes like digit.lexval are not computed by a rule — the lexer supplies them.

Read an attribute-equation rule like a spreadsheet formula: it states what a value is in terms of other values. It does not say when to run — the evaluator works that out.

The Example

Our one running example for this whole topic is the desk-calculator grammar with a synthesized attribute val.

Example
S → E n
E → E + T
E → T
T → T * F
T → F
F → ( E )
F → digit

(n is the end-of-line marker.) The semantic rules attach a val to every non-terminal:

Example
Production Semantic Rule
───────────── ──────────────────────────
S → E n print(E.val) ← side-effect (action)
E → E₁ + T E.val = E₁.val + T.val ← attribute equation
E → T E.val = T.val ← attribute equation
T → T₁ * F T.val = T₁.val * F.val ← attribute equation
T → F T.val = F.val ← attribute equation
F → ( E ) F.val = E.val ← attribute equation
F → digit F.val = digit.lexval ← attribute equation

Six of the seven rules are attribute equations — each defines the val of its head from the vals of its children. The single side-effect rule is print(E.val) on S → E n: it defines no attribute, it just prints the answer.

The input we will translate is:

Example
( 3 * 5 + 2 ) * 2 n

Solution — Evaluating the Rules

Every val is synthesized (defined on the head, computed from the children), so we evaluate bottom-up: each rule fires once its children's values exist.

Example
F.val = 3 F → digit (digit 3)
T.val = 3 T → F
F.val = 5 F → digit (digit 5)
T.val = 3 * 5 = 15 T → T₁ * F
E.val = 15 E → T
F.val = 2 F → digit (digit 2)
T.val = 2 T → F
E.val = 15 + 2 = 17 E → E₁ + T
F.val = 17 F → ( E ) (parenthesised group)
T.val = 17 T → F
F.val = 2 F → digit (digit 2)
T.val = 17 * 2 = 34 T → T₁ * F
E.val = 34 E → T
print(34) S → E n ← prints the result

Reading top to bottom: the inner group ( 3 * 5 + 2 ) evaluates to 17 (15 from 3 * 5, then + 2), that group becomes an F worth 17, multiplying by 2 gives 34, and S → E n finally prints it.

Annotated parse tree

The same evaluation shown on the tree, with each node's val filled in:

Example
S
| print(E.val) = print(34)
E.val=34
|
T.val=34
/ | \
T.val=17 * F.val=2
| |
F.val=17 digit 2
/ | \
( E.val=17 )
|
┌───────┴───────┐
E.val=15 + T.val=2
| |
T.val=15 F.val=2
/ | \ |
T.val=3 * F.val=5 digit 2
| |
F.val=3 digit 5
|
digit 3

The root prints 34.

Constraints, and SDD vs. SDT

No circular dependencies. A rule b = f(c₁, …, cₖ) means b depends on the cᵢ, so b is computable only after them. If two attributes depended on each other in a loop, no order could compute either. In our example there is no cycle: every val needs only its children's vals, so a single bottom-up pass always works (this all-synthesized form is called S-attributed).

SDD rules are declarative; SDT actions are ordered. The table above is an SDD — each rule states what a val is, not when to compute it. The same translation can be written as an SDT (translation scheme) by placing ordered actions inside the body, e.g. E → E₁ + T { E.val = E₁.val + T.val }, which runs the moment the parser reaches that spot. Same result (prints 34), different placement — see the Translation Schemes topic.

Key Points

  • A semantic rule belongs to one production and either computes an attribute (b = f(c₁, …, cₖ)) or performs an action.
  • Two kinds: attribute equations (like E.val = E₁.val + T.val) and side-effect rules (like print(E.val) in S → E n).
  • Rules reference only symbols in their own production; subscripts (E₁, T₁) distinguish repeated symbols, and terminal attributes (digit.lexval) come from the lexer.
  • Our example's val is synthesized, so it evaluates bottom-up in one pass; the input ( 3 * 5 + 2 ) * 2 n prints 34.
  • Rules must have no circular dependency. SDD rules are declarative (positionless); SDT actions are ordered and placed in the body.