Implementing Translation Grammars in Compiler Design



Parsing is a key topic in compiler design. There are different types of parsers, including top-down and bottom-up parsers. In top-down parsing, syntax-directed translation plays an important role. Unlike regular grammars, translation grammars not only focus on syntax but also include action symbols that perform tasks like output generation or function calls. These grammars help the compiler convert code into an intermediate representation, which is essential for optimization and execution.

In this chapter, we will explore translation grammars, understand their structure, and look at examples for better understanding.

What is a Translation Grammar?

Translation grammar is a special type of grammar that includes action symbols. This grammar is written in {curly braces}, to produce output or call functions while parsing an input string.

To implement translation grammars, we generally use pushdown translators or recursive descent translators. These techniques are used to convert infix expressions to postfix notation. They generate machine instructions, and simplify expression evaluation.

Key Characteristics of Translation Grammars

Let us see some of the key characteristics of translation grammars −

  • Action Symbols − These are special symbols {A}, and this is used to generate output or perform operations.
  • Underlying Grammar − The same grammar without the action symbols is called the underlying grammar.
  • Epsilon Rules − Rules like A → {action} are treated as epsilon (ε) rules in the underlying grammar.

Translation Grammar for Infix to Postfix Conversion

Consider the grammar G1, which translates infix expressions to postfix notation −

  • Expr → Term Elist
  • Elist → + Term {+} Elist
  • Elist → ε
  • Term → Factor Tlist
  • Tlist → * Factor {*} Tlist
  • Tlist → ε
  • Factor → ( Expr )
  • Factor → var {var}

Here we can see how this grammar processes an expression like var + var * var

var {var} + var {var} * var {var} {*} {+}

The output string is −

{var} {var} {var} {*} {+}

Here, we get the postfix (Reverse Polish) notation for the given infix expression.

Translation Grammar for Infix

Implementing Translation Grammars Using Pushdown Translators

A pushdown translator is like push-down automata or we can say this is an extended pushdown machine that treats action symbols just like terminals and non-terminals.

Steps to Implement a Pushdown Translator −

  • Push action symbols onto the stack as if they were regular grammar symbols.
  • When an action symbol is encountered, output the corresponding action.
  • Pop the stack when rules are applied just like in normal parsing.

Pushdown Translator Table for Grammar G1

From this table, each action symbol generates output at the right time. This is preserving postfix order.

Input Stack Action Output
var {var} Retain {var}
+ Pop +
* Pop *

Implementing Translation Grammars Using Recursive Descent Translators

Another method could be a recursive descent translator processes to translate grammars using function calls. When an action symbol is encountered, it is either printed or triggers a function call.

Example: Recursive Descent Parser for Grammar G1

Here is how a recursive descent function would process expressions −

void Expr() {  
    if (inp == '(' || inp == var) {  
        Term();  
        Elist();  
    } else Reject();  
}  

void Elist() {  
    if (inp == '+') {  
        getInp();  // Move to next token  
        Term();  
        System.out.println('+');  
        Elist();  
    } else if (inp == Endmarker || inp == ')');  // Rule 3 (epsilon rule)  
    else Reject();  
}  

void Term() {  
    if (inp == '(' || inp == var) {  
        Factor();  
        Tlist();  
    } else Reject();  
}  

void Tlist() {  
    if (inp == '*') {  
        getInp();  
        Factor();  
        System.out.println('*');  
        Tlist();  
    } else if (inp == '+' || inp == ')' || inp == Endmarker);  // Rule 6 (epsilon rule)  
    else Reject();  
}  

void Factor() {  
    if (inp == '(') {  
        getInp();  
        Expr();  
        if (inp == ')') getInp();  
        else Reject();  
    } else if (inp == var) {  
        getInp();  
        System.out.println("var");  
    } else Reject();  
}

Let's now understand how it works

  • When a + is encountered, it prints + to move it to postfix.
  • When a * is encountered, it prints * at the correct moment.
  • Variables (var) are printed as they appear.

For var + var * var, this parser correctly translates it to postfix −

var var var * + 

Pushdown Translators vs Recursive Descent Translators

The following table compares and contrasts and pros and cons of Pushdown Translators and Recursive Descent Translators −

Method Pros Cons
Pushdown Translator Works well with stack-based parsing Complex to implement manually
Recursive Descent Easier to implement and debug May require extra checks for actions

Pushdown translators are often used in automated compiler tools, but recursive descent is more common in hand-written parsers.

Applications of Translation Grammars

Translation grammars are useful and it has a key role in the following:

  • Code Generation − Converting high-level code into an intermediate representation (IR).
  • Syntax-Directed Translation − Transforming source code into optimized machine instructions.
  • Expression Evaluation − Converting infix to postfix or prefix notation.
  • Compiler Construction − Used in parsers and interpreters.

Example: Translating a Custom Expression

Consider the following expression −

(a + b) * c  

Using grammar G1, the translation follows the steps shown below. The Derivation Tree looks like this −

Translating a Custom Expression

The generated Postfix Expression is −

a b + c *  

It demonstrates how action symbols correctly reorder expressions using translation grammars.

From this, we can get a detailed concept and we can remember them through the following table −

Concept Explanation
Action Symbols { } symbols that trigger output or function calls.
Underlying Grammar The same grammar without action symbols.
Pushdown Translator Uses stack-based parsing to output translation symbols.
Recursive Descent Translator Uses function calls to generate translation.
Postfix Notation Ensures correct operator precedence.

Conclusion

In this chapter, we explored translation grammars and their role in compiler design. We covered pushdown translators and recursive descent translators, which help convert infix expressions to postfix notation.

Through examples, we saw how action symbols guide code translation, optimize parsing, and improve expression evaluation. Translation grammars are essential in syntax-directed translation, helping transform source code into an efficient intermediate representation for compilation.

Advertisements