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
SLR Parser (with Examples)
LR parsers is an efficient bottom-up syntax analysis technique that can be used to parse large classes of context-free grammar is called LR(k) parsing. L stands for left-to-right scanningR stands for rightmost derivation in reversek is several input symbols. when k is omitted k is assumed to be 1.Ad
4 min read
Error Recovery in LR Parsing
LR parser is a bottom-up parser. It is always used to deal with context-free Grammar (CFGs). It is generally used by computer programming language compilers as well as other associated tools also. LR parser produces leftmost derivation while using the input taking from left to right. By building up
3 min read
Algorithm for non recursive Predictive Parsing
Prerequisite - Classification of Top Down Parsers Predictive parsing is a special form of recursive descent parsing, where no backtracking is required, so this can predict which products to use to replace the input string. Non-recursive predictive parsing or table-driven is also known as LL(1) parse
4 min read
Compiler Design SLR(1) Parser Using Python
Prerequisite: LR Parser, SLR Parser SLR (1) grammar SLR stands for Simple LR grammar. It is an example of a bottom-up parser. The "L" in SLR represents the scanning that advances from left to right and the "R" stands for constructions of derivation in reverse order, and the â(1)â represents the numb
15+ min read
Bottom-up Parsers
Bottom-up parsing is a type of syntax analysis method where the parser starts from the input symbols (tokens) and attempts to reduce them to the start symbol of the grammar (usually denoted as S). The process involves applying production rules in reverse, starting from the leaves of the parse tree a
13 min read