Skip to content
Published on

[Compiler] 08. Syntax-Directed Translation - SDD and SDT

Authors

Syntax-Directed Translation - SDD and SDT

Syntax-Directed Translation is a technique that attaches attributes and semantic rules to each production rule of the grammar, performing translation simultaneously with parsing.


1. Syntax-Directed Definition (SDD) Overview

A Syntax-Directed Definition (SDD) adds attributes and semantic rules to a grammar.

Types of Attributes

  1. Synthesized Attribute: Computed bottom-up from child node attribute values.
  2. Inherited Attribute: Computed top-down from parent or sibling node attribute values.

Synthesized Attribute Example: Arithmetic Expression Evaluation

Production              Semantic Rule
------------------------------------------------------
L -> E '\n'           L.val = E.val
E -> E1 + T           E.val = E1.val + T.val
E -> T                E.val = T.val
T -> T1 * F           T.val = T1.val * F.val
T -> F                T.val = F.val
F -> ( E )            F.val = E.val
F -> digit            F.val = digit.lexval

Let us examine the annotated parse tree for input 3 * 5 + 4:

              L (val = 19)
              |
           E (val = 19)
          / | \
    E(15)   +   T(4)
    |           |
  T(15)       F(4)
  / | \        |
T(3) * F(5)  digit(4)
|       |
F(3)  digit(5)
|
digit(3)

Inherited Attribute Example: Type Declarations

Production              Semantic Rule
------------------------------------------------------
D -> T L              L.inh = T.type
T -> int              T.type = integer
T -> float            T.type = float
L -> L1 , id          L1.inh = L.inh
                      addtype(id.entry, L.inh)
L -> id               addtype(id.entry, L.inh)

Let us examine the evaluation for input float id1, id2, id3:

       D
      / \
     T    L (inh = float)
     |   / | \
   float L  , id3  -> addtype(id3, float)
        / | \
       L  , id2    -> addtype(id2, float)
       |
      id1          -> addtype(id1, float)

The L.inh attribute propagates from parent to child, setting the float type for all identifiers.


2. Dependency Graph

A dependency graph is constructed to determine the computation order among attributes.

Constructing a Dependency Graph

  • Create a node for each attribute instance.
  • If attribute a is needed to compute attribute b, add an edge from a to b.
Example: E -> E1 + T  where  E.val = E1.val + T.val

  E1.val ----+
              +--> E.val
  T.val  ----+

Evaluation Order

If the dependency graph has no cycles, attributes can be evaluated through topological sort.

Topological sort result (possible evaluation order):
  digit(3).lexval -> F.val -> T.val ->
  digit(5).lexval -> F.val -> T1.val ->
  digit(4).lexval -> F.val -> T2.val ->
  E1.val -> E.val -> L.val

If there are cycles, attributes cannot be evaluated, which indicates a design error in the SDD.


3. S-Attributed and L-Attributed Definitions

S-Attributed Definition

An SDD that uses only synthesized attributes.

Characteristics:
- Uses only synthesized attributes
- Can be evaluated with post-order traversal of the parse tree
- Naturally combines with Bottom-Up parsing (LR parser)

S-attributed definitions are the simplest and easiest to implement. Semantic rules are executed during the reduce action of an LR parser.

L-Attributed Definition

An SDD where left-to-right order is guaranteed in attribute computation.

Condition: For A -> X1 X2 ... Xn,
  Inherited attributes of Xi can only depend on:
  1. Inherited attributes of A
  2. Attributes of X1, X2, ..., Xi-1 (symbols to the left of Xi)
  3. Attributes of Xi itself (as long as there are no cycles)

The containment relationship of L-attributed definitions is:

S-attributed definitions < L-attributed definitions

All S-attributed definitions are L-attributed definitions.
L-attributed definitions naturally combine with Top-Down parsing.

4. Syntax-Directed Translation Scheme (SDT)

A Syntax-Directed Translation Scheme (SDT) inserts semantic actions within the body of production rules.

SDT Notation

Semantic actions are enclosed in curly braces and placed within the production rule body.

// Converting S-attributed SDD to SDT (actions at end of rule)
E -> E1 + T  { E.val = E1.val + T.val }
E -> T       { E.val = T.val }

// Converting L-attributed SDD to SDT (actions in the middle)
D -> T { L.inh = T.type } L
T -> int  { T.type = integer }
T -> float { T.type = float }

5. Implementing SDT in Bottom-Up Parsers

How to implement S-attributed definitions in an LR parser.

Storing Attribute Values in the Parsing Stack

Parsing stack: stores attribute values along with states

+------+------+------+------+------+------+
| s0   | s1   | s2   | s3   | s4   | s5   |
| -    | E    | +    | T    | *    | F    |
| -    | 15   | -    | 4    | -    | 2    |
+------+------+------+------+------+------+
         val          val          val

Executing Semantic Actions on Reduction

Rule: T -> T1 * F  { T.val = T1.val * F.val }

Stack before reduction:  ... | T(3) | * | F(5)
Stack after reduction:   ... | T(15)
  -> Computation: 3 * 5 = 15

Implementation in Yacc

expr : expr '+' term   { $$ = $1 + $3; }
     | term            { $$ = $1; }
     ;

$$ is the attribute of the left-side nonterminal, and $1, $2, $3 are the attributes of right-side symbols.


6. Implementing SDT in Top-Down Parsers

How to implement L-attributed definitions in a recursive descent parser.

Inherited Attributes as Parameters, Synthesized Attributes as Return Values

// D -> T L
// T.type is a synthesized attribute, L.inh is an inherited attribute
void D() {
    String type = T();    // Receive T's synthesized attribute as return
    L(type);              // Pass L's inherited attribute as parameter
}

String T() {
    if (lookahead == INT) {
        match(INT);
        return "integer";
    } else if (lookahead == FLOAT) {
        match(FLOAT);
        return "float";
    }
    throw new SyntaxError("Expected type");
}

void L(String inheritedType) {
    if (lookahead == ID) {
        addType(currentToken, inheritedType);
        match(ID);
        if (lookahead == COMMA) {
            match(COMMA);
            L(inheritedType);  // Pass inherited attribute
        }
    }
}

7. SDT Application Examples

7.1 Infix to Postfix Conversion

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

Tracing the actions for input 9 - 5 + 2:

E -> T R
  T: print(9)     -> output: 9
  R -> - T R
    T: print(5)   -> output: 5
    print('-')     -> output: -
    R -> + T R
      T: print(2) -> output: 2
      print('+')   -> output: +
      R -> epsilon

Final output: 9 5 - 2 +

7.2 Array Type Construction

// Grammar: handling array type declarations like int [2][3]
B -> int                 { B.type = integer }
   | float               { B.type = float }

C -> [ num ] C1          { C.type = array(num.val, C1.type) }
   | epsilon             { C.type = B.type }

T -> B C                 { T.type = C.type }

Evaluating int [2][3]:

B.type = integer
C -> [2] C1
  C1 -> [3] C2
    C2 -> epsilon
    C2.type = integer        (inherited from B.type)
  C1.type = array(3, integer)
C.type = array(2, array(3, integer))
T.type = array(2, array(3, integer))

8. Marker Nonterminals and Action Transformation

To handle intermediate actions in Bottom-Up parsers, actions must be moved to the end of production rules. Marker nonterminals are used for this purpose.

Marker Nonterminal Transformation

// Original SDT
B -> a { action1 } b { action2 }

// Transformation using marker nonterminal
B -> a M b { action2 }
M -> epsilon { action1 }

Marker nonterminal M reduces to the empty string while executing action1.

Example: Marker Usage in Type Declarations

// Original SDT (L-attributed)
D -> T { L.inh = T.type } L

// Marker nonterminal transformation
D -> T M L
M -> epsilon  { /* copy T.type from stack to use as L.inh */ }

9. Applications of Attribute Grammars

Syntax-directed translation is utilized in various phases of a compiler.

Application Areas

Phase                    Use of Attributes
------------------------------------------------------
Lexical Analysis        Token attribute values (integer, string)
Syntax Analysis         Parse tree / AST construction
Type Checking           Synthesis and verification of type attributes
Intermediate Code Gen.  Synthesis of three-address code
Code Optimization       Data flow attributes
Code Generation         Register allocation, instruction selection

Type Checking SDD Example

E -> E1 + E2   { E.type = if E1.type == integer and E2.type == integer
                          then integer
                          else if E1.type == float or E2.type == float
                          then float
                          else type_error }

E -> num       { E.type = integer }
E -> num.num   { E.type = float }
E -> id        { E.type = lookup(id.entry) }

10. Implementation Strategy Summary

Which SDD Type to Choose?

+------------------+-------------------+--------------------+
| Characteristic   | S-Attributed Def. | L-Attributed Def.  |
+------------------+-------------------+--------------------+
| Attribute Types  | Synthesized only  | Synthesized + Inh. |
| Evaluation Dir.  | Bottom -> Up      | Left -> Right      |
| Parser Type      | Bottom-Up (LR)   | Top-Down (LL)      |
| Complexity       | Simple            | Moderate           |
| Expressiveness   | Limited           | Rich               |
+------------------+-------------------+--------------------+

Approach in Real Compilers

Most modern compilers construct an AST during the parsing phase and then compute attributes through multiple traversals of the AST. This approach is free from SDD constraints while enabling systematic implementation.

Phase 1: Parsing -> AST construction (using synthesized attributes)
Phase 2: AST traversal -> Symbol table construction (simulating inherited attributes)
Phase 3: AST traversal -> Type checking
Phase 4: AST traversal -> Intermediate code generation

Summary

ConceptKey Content
Synthesized AttributePropagated child to parent, computed bottom-up
Inherited AttributePropagated parent/sibling to child, computed top-down
Dependency GraphDirected graph determining attribute computation order
S-Attributed Def.Uses only synthesized attributes, combines with LR
L-Attributed Def.Guarantees left-to-right evaluation, combines with LL
SDTInserts semantic actions in production rule body
Marker NonterminalTechnique to move intermediate actions to rule end

Syntax-directed translation is the core technique for assigning meaning to parsing results. In the next article, we will cover how to generate intermediate code using this technique.