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;
}
}
Similar Reads
Predictive Parser in Compiler Design In this, we will cover the overview of Predictive Parser and mainly focus on the role of Predictive Parser. And will also cover the algorithm for the implementation of the Predictive parser algorithm and finally will discuss an example by implementing the algorithm for precedence parsing. Letâs disc
2 min read
Shift Reduce Parser in Compiler Shift-reduce parsing is a popular bottom-up technique used in syntax analysis, where the goal is to create a parse tree for a given input based on grammar rules. The process works by reading a stream of tokens (the input), and then working backwards through the grammar rules to discover how the inpu
11 min read
Parse Tree in Compiler Design In compiler design, the Parse Tree depicts the syntactic structure of a string in accordance with a given grammar. It was created during the parsing phase of compilation, wherein syntax of the input source code is analyzed. A parse tree is a useful way of showing how a string or program would be der
4 min read
Role of Operator Precedence Parser In this, we will cover the overview of Operator Precedence Parser and mainly focus on the role of Operator Precedence Parser. And will also cover the algorithm for the construction of the Precedence function and finally will discuss error recovery in operator precedence parsing. Let's discuss it one
4 min read
Parsing - Introduction to Parsers Parsing, also known as syntactic analysis, is the process of analyzing a sequence of tokens to determine the grammatical structure of a program. It takes the stream of tokens, which are generated by a lexical analyzer or tokenizer, and organizes them into a parse tree or syntax tree.The parse tree v
6 min read