CS 3723
 Programming Languages 
   Semantic Actions  


0. Translation Using Semantic Actions: The idea is to attach semantic code (often called semantic actions) to each grammar rule. During the course of a shift-reduce parse, each time a reduction is carried out, the corresponding semantic action code is executed. One wants to arrange things so that this carries out a translation.

Start with the following simple grammar:

Grammar:
Arithmetic Expressions
P  −−>  E
E  −−>  E + T  |  T
T  −−>  T * F  |  F
F  −−>  ( E )  |  id

Use the following shift-reduce table for this grammar:

Parser: Shift-Reduce Table
     |  id |  *  |  +  |  (  |  )  |  $  |
−−−−−+−−−−−+−−−−−+−−−−−+−−−−−+−−−−−+−−−−−+
  P  |     |     |     |     |     | acc |  (s = "shift")
  E  |     |     |  s  |     |  s  |  r  |
  T  |     |  s  |  r  |     |  r  |  r  |  (r = "reduce")
  F  |     |  r  |  r  |     |  r  |  r  |
  id |     |  r  |  r  |     |  r  |  r  |  (acc = "accept")
  *  |  s  |     |     |  s  |     |     |
  +  |  s  |     |     |  s  |     |     |    
  (  |  s  |     |     |  s  |     |     |
  )  |     |  r  |  r  |     |  r  |  r  |
  $  |  s  |     |     |  s  |     |     |
−−−−−+−−−−−+−−−−−+−−−−−+−−−−−+−−−−−+−−−−−+

The actual input (with identifiers) is: $ ( a + b ) * c $. To the parser, the input sentence will have pointers to the actual identifiers. We show these as id tags in red:

Input Sentence
$ ( id + id ) * id $
     |    |      |
     a    b      c


1. Translate to RPN: See Reverse Polish Notation to understand the target language. Here is the grammar rewritten, with semantic actions in red:

Grammar: Arithmetic Expressions
Semantic Actions in red
P  −−>  E

E  −−>  E + T   output("+");

E  −−>  T

T  −−>  T * F   output("*");

T  −−>  F

F  −−>  ( E )

F  −−>  id       output(id.tag);

Shift-Reduce Actions Semantic Actions in red
Stack               Curr    Rest of Input       Action
(top at right)      Sym
---------------------------------------------------------------------------------
$                   (       id + id ) * id $    shift
$  (                id      + id ) * id $       shift
$  ( id             +       id ) * id $         reduce: F −−> id     output("a");
$  ( F              +       id ) * id $         reduce: T −−> F
$  ( T              +       id ) * id $         reduce: E −−> T
$  ( E              +       id ) * id $         shift
$  ( E  +           id      ) * id $            shift
$  ( E  +  id       )       * id $              reduce: F −−> id     output("b");
$  ( E  +  F        )       * id $              reduce: T −−> F
$  ( E  +  T        )       * id $              reduce: E −−> E + T  output("+");
$  ( E              )       * id $              shift
$  ( E )            *       id $                reduce: F −−> ( E )
$  F                *       id $                reduce: T −−> F
$  T                *       id $                shift
$  T  *             id      $                   shift
$  T  *  id         $                           reduce: F −−> id     output("c");
$  T  *  F          $                           reduce: T −−> T * F  output("*");
$  T                $                           reduce: E −−> T
$  E                $                           reduce: P −−> E
$  P                $                           accept

Arithmetic expression: $ ( a + b ) * c $
Translation to RPN:     a b + c *

Notice that the result of parsing, and carrying out the associated semantic actions, is to accept the input and to translate it to RPN, which is shown afterward in red above. This method will translate an arbitrarily complicated arithmetic expression (one recognized by this grammar) into RPN.


2. Translate to Intermediate Code: Be careful below to note that the lowercase t has nothing to do with the uppercase T. Also, when there are two Es or two Ts in the same grammar rule, the tags on them on the stack will be different, so we need subscripts to keep track of which tag is being created or used.

Grammar: Arithmetic Expressions Semantic Actions in red
P  −−>  E        P.tag = E.tag; output("print(" + P.tag + ")");

E1 −−>  E2 + T   E1.tag = "t" + nextint();
                  output(E1.tag + "=" E2.tag + "+" T.tag);

E  −−>  T        E.tag = T.tag;

T1 −−>  T2 * F   T1.tag = "t" + nextint();
                  output(T1.tag + "=" T2.tag + "*" F.tag);

T  −−>  F        T.tag = F.tag;

F  −−>  ( E )    F.tag = E.tag;

F  −−>  id       F.tag = id.tag;

The table below shows the shift-reduce parse of the same sentence, showing the stack, current symbol, remaining symbols, and next action to take at each stage. The semantic tags are shown in red below the id items and the stack items.

Shift-Reduce Actions Tag field below stack in red
Stack               Curr    Rest of Input       Action
(top at right)      Sym
-------------------------------------------------------------------
$                   (       id + id ) * id $    shift
$  (                id      + id ) * id $       shift
$  ( id             +       id ) * id $         reduce: F −−> id
     a
$  ( F              +       id ) * id $         reduce: T −−> F
     a
$  ( T              +       id ) * id $         reduce: E −−> T
     a
$  ( E              +       id ) * id $         shift
     a
$  ( E  +           id      ) * id $            shift
     a
$  ( E  +  id       )       * id $              reduce: F −−> id
     a     b
$  ( E  +  F        )       * id $              reduce: T −−> F
     a     b
$  ( E  +  T        )       * id $              reduce: E −−> E + T
     a     b
[output("t1 = a + b;");]
$  ( E              )       * id $              shift
     t1
$  ( E )            *       id $                reduce: F −−> ( E )
     t1
$  F                *       id $                reduce: T −−> F
   t1
$  T                *       id $                shift
   t1
$  T  *             id      $                   shift
   t1
$  T  *  id         $                           reduce: F −−> id
   t1    c
$  T  *  F          $                           reduce: T −−> T * F
   t1    c
[output("t2 = t1 * c;");]
$  T                $                           reduce: E −−> T
   t2
$  E                $                           reduce: P −−> E
   t2
$  P                $                           accept
   t2
[output("print(t2);");]

Arithmetic expression: $ ( a + b ) * c $
Translation to Intermediate code:
t1 = a + b;
t2 = t1 * c;
print(t2);

Notice again that the result of parsing, and carrying out the associated semantic actions, is to accept the input and to translate it to a sequence of intermediate code statements, which would be much closer to a simple machine language. As before, the input arithmetic expression can be arbitrarily complicated. This intermediate code is shown afterward in red above.

Parse Tree with Tags (in red)
                          E-\
                          |  t2
                          T-\
                          |  t2=t1*c
           +--------------+----+
           |              |    |
           T-\            |    |
           |  t1          |    |
           F-\            |    |
           |  t1          |    |
 +---------+----------+   |    |
 |         |          |   |    |
 |         E-\        |   |    |
 |         |  t1=a*b  |   |    |
 |   +-----+----+     |   |    |
 |   |     |    |     |   |    |
 |   E-\   |    |     |   |    |
 |   |  a  |    |     |   |    |
 |   T-\   |    T-\   |   |    |
 |   |  a  |    |  b  |   |    |
 |   F-\   |    F-\   |   |    F-\
 |   |  a  |    |  b  |   |    |  c
 (  id-\   +   id-\   )   *   id-\
        a          b              c

(Revision date: 2014-09-30. Please use ISO 8601, the International Standard.)