A recursive descent parser is a top-down parser that processes input based on a set of recursive functions, where each function corresponds to a grammar rule. It parses the input from left to right, constructing a parse tree by matching the grammar's production rules. This parser is simple to implement and is suitable for LL(1) grammars, where decisions can be made based on a single lookahead token. While straightforward, recursive descent parsers struggle with left-recursive grammars and may require grammar transformations to handle such cases effectively.
A Predictive Parser is a special case of Recursive Descent Parser, where no Back Tracking is required.
By carefully writing a grammar, means eliminating left recursion and left factoring from it, the resulting grammar will be a grammar that can be parsed by a recursive descent parser.
By carefully writing a grammar means eliminating left recursion and left factoring from it, the resulting grammar will be a grammar that can be parsed by a recursive descent parser.
Example:
| Before removing left recursion | After removing left recursion |
|---|
E –> E + T | T T –> T * F | F F –> ( E ) | id | E –> T E’ E’ –> + T E’ | e T –> F T’ T’ –> * F T’ | e F –> ( E ) | id |
Algorithm for Recursive Descent Parser
S()
{ Choose any S production, S ->X1X2…..Xk;
for (i = 1 to k)
{
If ( Xi is a non-terminal)
Call procedure Xi();
else if ( Xi equals the current input, increment input)
Else /* error has occurred, backtrack and try another possibility */
}
}
Let's understand it better with an example:
The given grammar is:
E → i E'
E' → + i E' | ε
Function E()
E()
{
if (input == 'i') { // If the input is 'i' (identifier)
input++; // Consume 'i'
}
E'(); // Call E' to check for further expressions
}
- It checks for
i (identifier). - If found, it moves the input pointer ahead.
- Calls
E'() to check if a + operation exists.
Function E'()
void E`() {
if (input == '+') {
input++; // Consume the '+'
if (input == 'i') {
input++; // Consume the 'i'
}
E`(); // Recursively process more additions
} else {
return; // If no '+', return (ε production)
}
}
- It checks for
+ i. - If found, it consumes them and calls
E'() recursively. - If no
+, it returns (ε production).
Main Function
Main()
{
E(); // Start parsing from E
if (input == '$') // If we reach end of input
Parsing Successful;
}
- Calls
E() to start parsing. - Checks if the input ends with
$, which indicates a successful parse.
Example Input Parsing
Let’s consider the example input:
i + i $
Processing step by step:
E() starts → input == i, so consume i- Call
E'() → input == +, so consume + input == i, so consume i- Call
E'() again → no +, so return. - Back to
Main(), input == $ → Parsing Successful
Important points about recursive descent parsers
- Top-Down Parsing: It starts from the start symbol and recursively applies grammar rules to break down the input.
- One Function per Non-Terminal: Each grammar rule has a corresponding function in the parser, making the implementation straightforward.
- Uses Recursion: The parser calls functions within themselves to process different parts of the input, matching the recursive nature of grammar rules.
- Works Best with LL(1) Grammars: It’s most effective for grammars that can be parsed with a single token lookahead, typically simple, non-left-recursive grammars.
- Easy to Implement: The structure is easy to follow and implement, making it a good choice for small compilers or interpreters.
- Error Handling: It can detect syntax errors and report them, making it useful for debugging input strings.
Code Implementation of a Recursive Descent Parser
C
#include <stdio.h>
#include <string.h>
#define SUCCESS 1
#define FAILED 0
// Function prototypes
int E(), Edash(), T(), Tdash(), F();
const char *cursor;
char string[64];
int main()
{
puts("Enter the string");
scanf("%s", string); // Read input from the user
cursor = string;
puts("");
puts("Input Action");
puts("--------------------------------");
// Call the starting non-terminal E
if (E() && *cursor == '\0')
{ // If parsing is successful and the cursor has reached the end
puts("--------------------------------");
puts("String is successfully parsed");
return 0;
}
else
{
puts("--------------------------------");
puts("Error in parsing String");
return 1;
}
}
// Grammar rule: E -> T E'
int E()
{
printf("%-16s E -> T E'\n", cursor);
if (T())
{ // Call non-terminal T
if (Edash())
{ // Call non-terminal E'
return SUCCESS;
}
else
{
return FAILED;
}
}
else
{
return FAILED;
}
}
// Grammar rule: E' -> + T E' | $
int Edash()
{
if (*cursor == '+')
{
printf("%-16s E' -> + T E'\n", cursor);
cursor++;
if (T())
{ // Call non-terminal T
if (Edash())
{ // Call non-terminal E'
return SUCCESS;
}
else
{
return FAILED;
}
}
else
{
return FAILED;
}
}
else
{
printf("%-16s E' -> $\n", cursor);
return SUCCESS;
}
}
// Grammar rule: T -> F T'
int T()
{
printf("%-16s T -> F T'\n", cursor);
if (F())
{ // Call non-terminal F
if (Tdash())
{ // Call non-terminal T'
return SUCCESS;
}
else
{
return FAILED;
}
}
else
{
return FAILED;
}
}
// Grammar rule: T' -> * F T' | $
int Tdash()
{
if (*cursor == '*')
{
printf("%-16s T' -> * F T'\n", cursor);
cursor++;
if (F())
{ // Call non-terminal F
if (Tdash())
{ // Call non-terminal T'
return SUCCESS;
}
else
{
return FAILED;
}
}
else
{
return FAILED;
}
}
else
{
printf("%-16s T' -> $\n", cursor);
return SUCCESS;
}
}
// Grammar rule: F -> ( E ) | i
int F()
{
if (*cursor == '(')
{
printf("%-16s F -> ( E )\n", cursor);
cursor++;
if (E())
{ // Call non-terminal E
if (*cursor == ')')
{
cursor++;
return SUCCESS;
}
else
{
return FAILED;
}
}
else
{
return FAILED;
}
}
else if (*cursor == 'i')
{
printf("%-16s F -> i\n", cursor);
cursor++;
return SUCCESS;
}
else
{
return FAILED;
}
}
Recursive Descent Parser with Example in Compiler Design
Explore
Compiler Design Basics
Lexical Analysis
Syntax Analysis & Parsers
Syntax Directed Translation & Intermediate Code Generation
Code Optimization & Runtime Environments
Practice Questions