0% found this document useful (0 votes)
3 views59 pages

cd_pbera (1)

The document is a practical file for a Compiler Design course at Delhi Technological University, detailing various programming experiments related to finite automata and parsing techniques. It includes a structured index of programs, such as converting NFA to DFA, implementing lexical analyzers, and constructing parsing tables. Each experiment is accompanied by theoretical explanations, code implementations, and expected learning outcomes.

Uploaded by

zerotwo02x02x
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views59 pages

cd_pbera (1)

The document is a practical file for a Compiler Design course at Delhi Technological University, detailing various programming experiments related to finite automata and parsing techniques. It includes a structured index of programs, such as converting NFA to DFA, implementing lexical analyzers, and constructing parsing tables. Each experiment is accompanied by theoretical explanations, code implementations, and expected learning outcomes.

Uploaded by

zerotwo02x02x
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 59

DELHI TECHNOLOGICAL UNIVERSITY

(Formerly Delhi College of Engineering)


Shahbad Daulatpur,Bawana Road,Delhi-110042

DEPARTMENT OF COMPUTER SCIENCE AND


ENGINEERING

COMPILER DESIGN PRACTICAL FILE

Subjectcode:CO302

Submitted to:
Mr. Lavendra Gautam
Submitted by:
Pritam Bera (2K22/CO/347)
B. Tech CSE’ 2026
INDEX

S.no. Aim Date Sign

1 Program to convert NFA to DFA 17-01-25

2 Program to build a DFA to accept strings that start and 24-01-25


end with same character(given a string of characters a
& b)

3 Program to detect tokens in a Program 24-01-25


(Eg-Keywords,operators, identifiers etc)

4 Write a program to implement lexical analyser 31-01-25

5 Program To implement the recursive descent parser 31-01-25

6 Write program to left factor the given grammer 07-02-25

7 Program to eliminate left factoring to right recursive in 07-02-25


the given grammar
8 Program to find first and follow of the given grammar 14-02-25

9 Write a program to construct LL(1) parsing table 28-02-25

10 Write a program to impelement non recursive 28-03-25


predictive parsing
11 Write a program to implement an error handler 28-03-25

12 Write a program to implement one pass compiler 04-04-25


EXPERIMENT - 1

AIM:
Program to convert NFA to DFA

THEORY:
A Non-Deterministic Finite Automaton (NFA) is a mathematical model consisting of a set of states, a
set of input symbols, a transition function, an initial state, and a set of accepting states. Unlike a
Deterministic Finite Automaton (DFA), an NFA allows transitions to multiple states from a single
state on the same input symbol.

The conversion of an NFA to a DFA involves creating a DFA that simulates the behavior of the given
NFA. This process typically involves the following steps:

Epsilon Closure (ε-Closure): In an NFA, ε-transitions allow moving from one state to another without
consuming any input. The ε-closure of a state is the set of states reachable from that state using ε-
transitions. Computing the ε-closure for each state is essential for subsequent steps.

Subset Construction: The Subset Construction algorithm is used to construct a DFA from the given
NFA. In this process, each state of the DFA corresponds to a set of states from the NFA. The initial
state of the DFA is the ε-closure of the initial state of the NFA, and transitions between states are
determined based on the transitions of the NFA.

State Equivalence: During the subset construction process, it's crucial to identify equivalent states in
the DFA to minimize the number of states and optimize the DFA's size. States are considered
equivalent if they represent the same set of states from the NFA and lead to the same set of states on
the same input symbols.

DFA Minimization (Optional): Once the DFA is constructed, it can be further optimized by
minimizing the number of states while preserving its functionality. DFA minimization algorithms,
such as the Hopcroft's algorithm or the Moore's algorithm, can be applied to achieve this
optimization.

CODE:

// C++ Program to illustrate how to convert e-nfa to DFA


#include<bits/stdc++.h>
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <algorithm>

#define MAX_LEN 100

std::string NFA_FILE;
std::string buffer;
int zz = 0;

// Structure to store DFA states and their


// status ( i.e new entry or already present)
struct DFA {
std::string states;
int count;
};

int last_index = 0;
std::ifstreamfp;
int symbols;

/* reset the hash map*/


void reset(std::vector<int>&ar) {
std::fill(ar.begin(), ar.end(), 0);
}

// Check which States are present in the e-closure

/* map the states of NFA to a hash set*/


void check(std::vector<int>&ar, const std::string& S) {
for (char c : S) {
// Set hash map for the position
// of the states which is found
ar[c - 'A']++;
}
}

// To find new Closure States


void state(std::vector<int>&ar, std::string& S) {
S.clear();
for (int j = 0; j <ar.size(); j++) {
if (ar[j] != 0)
S += static_cast<char>(j + 'A');
}
}

// To pick the next closure from closure set


int closure(const std::vector<int>&ar) {
for (int i = 0; i<ar.size(); i++) {
if (ar[i] == 1)
return i;
}
return 100;
}
// Check new DFA states can be
// entered in DFA table or not
int indexing(const std::vector<DFA>&dfa) {
for (int i = 0; i<last_index; i++) {
if (dfa[i].count == 0)
return 1;
}
return -1;
}

/* To Display epsilon closure*/


void Display_closure(int states, std::vector<int>&closure_ar,
std::vector<std::string>&closure_table,
const std::vector<std::vector<std::string>>& NFA_TABLE,
std::vector<std::vector<std::string>>& DFA_TABLE) {
for (int i = 0; i< states; i++) {
reset(closure_ar);
closure_ar[i] = 2;

// to neglect blank entry


if (NFA_TABLE[i][symbols] != "-") {

// copy the NFA transition state to buffer


buffer = NFA_TABLE[i][symbols];
check(closure_ar, buffer);
int z = closure(closure_ar);

// till closure get completely saturated


while (z != 100) {
if (NFA_TABLE[z][symbols] != "-") {
buffer = NFA_TABLE[z][symbols];

// call the check function


check(closure_ar, buffer);
}
closure_ar[z]++;
z = closure(closure_ar);
}
}

// print the e closure for every states of NFA


std::cout<< "\n e-Closure (" <<static_cast<char>(i + 'A') << ") :\t";

buffer.clear();
state(closure_ar, buffer);
closure_table[i] = buffer;
std::cout<<closure_table[i] << "\n";
}
}

/* To check New States in DFA */


int new_states(std::vector<DFA>&dfa, const std::string& S) {

// To check the current state is already


// being used as a DFA state or not in
// DFA transition table
for (int i = 0; i<last_index; i++) {
if (dfa[i].states == S)
return 0;
}

// push the new


dfa[last_index++].states = S;

// set the count for new states entered


// to zero
dfa[last_index - 1].count = 0;
return 1;
}

// Transition function from NFA to DFA


// (generally union of closure operation )
void trans(const std::string& S, int M, const std::vector<std::string>&clsr_t, int st,
const std::vector<std::vector<std::string>>& NFT, std::string& TB) {
std::vector<int>arr(st, 0);
std::string temp, temp2;

// Transition function from NFA to DFA


for (char c : S) {
int j = c - 'A';
temp = NFT[j][M];

if (temp != "-") {
for (char d : temp) {
int k = d - 'A';
temp2 = clsr_t[k];
check(arr, temp2);
}
}
}

temp.clear();
state(arr, temp);
TB = temp.empty() ? "-" : temp;
}

/* Display DFA transition state table*/


void Display_DFA(const std::vector<DFA>&dfa_states,
const std::vector<std::vector<std::string>>& DFA_TABLE) {
std::cout<< "\n\n********************************************************\n\n";
std::cout<< "\t\t DFA TRANSITION STATE TABLE \t\t \n\n";
std::cout<< "\n STATES OF DFA :\t\t";

for (int i = 1; i<last_index; i++)


std::cout<<dfa_states[i].states << ", ";
std::cout<< "\n";
std::cout<< "\n GIVEN SYMBOLS FOR DFA: \t";

for (int i = 0; i< symbols; i++)


std::cout<<i<< ", ";
std::cout<< "\n\n";
std::cout<< "STATES\t";

for (int i = 0; i< symbols; i++)


std::cout<< "|" <<i<< "\t";
std::cout<< "\n";

// display the DFA transition state table


std::cout<< "--------+-----------------------\n";
for (int i = 0; i<zz; i++) {
std::cout<<dfa_states[i + 1].states << "\t";
for (int j = 0; j < symbols; j++) {
std::cout<< "|" << DFA_TABLE[i][j] << " \t";
}
std::cout<< "\n";
}
}

// Driver Code
int main() {
int states;
std::string T_buf;

// creating a vector of dfa structures


std::vector<DFA>dfa_states(MAX_LEN);
states = 6, symbols = 2;

std::cout<< "\n STATES OF NFA :\t\t";


for (int i = 0; i< states; i++)
std::cout<<static_cast<char>(i + 'A') << ", ";
std::cout<< "\n";
std::cout<< "\n GIVEN SYMBOLS FOR NFA: \t";

for (int i = 0; i< symbols; i++)


std::cout<<i<< ", ";
std::cout<< "eps";
std::cout<< "\n\n";

std::vector<std::vector<std::string>> NFA_TABLE(states, std::vector<std::string>(symbols + 1));

// Hard coded input for NFA table


std::vector<std::vector<std::string>> DFA_TABLE(MAX_LEN, std::vector<std::string>(symbols));
NFA_TABLE[0][0] = "FC";
NFA_TABLE[0][1] = "-";
NFA_TABLE[0][2] = "BF";
NFA_TABLE[1][0] = "-";
NFA_TABLE[1][1] = "C";
NFA_TABLE[1][2] = "-";
NFA_TABLE[2][0] = "-";
NFA_TABLE[2][1] = "-";
NFA_TABLE[2][2] = "D";
NFA_TABLE[3][0] = "E";
NFA_TABLE[3][1] = "A";
NFA_TABLE[3][2] = "-";
NFA_TABLE[4][0] = "A";
NFA_TABLE[4][1] = "-";
NFA_TABLE[4][2] = "BF";
NFA_TABLE[5][0] = "-";
NFA_TABLE[5][1] = "-";
NFA_TABLE[5][2] = "-";
std::cout<< "\n NFA STATE TRANSITION TABLE \n\n\n";
std::cout<< "STATES\t";

for (int i = 0; i< symbols; i++)


std::cout<< "|" <<i<< "\t";
std::cout<< "eps\n";

// Displaying the matrix of NFA transition table


std::cout<< "--------+------------------------------------\n";
for (int i = 0; i< states; i++) {
std::cout<<static_cast<char>(i + 'A') << "\t";

for (int j = 0; j <= symbols; j++) {


std::cout<< "|" << NFA_TABLE[i][j] << " \t";
}
std::cout<< "\n";
}
std::vector<int>closure_ar(states);
std::vector<std::string>closure_table(states);

Display_closure(states, closure_ar, closure_table, NFA_TABLE, DFA_TABLE);


dfa_states[last_index++].states = "-";

dfa_states[last_index - 1].count = 1;
buffer.clear();

buffer = closure_table[0];
dfa_states[last_index++].states = buffer;

int Sm = 1, ind = 1;
int start_index = 1;

// Filling up the DFA table with transition values


// Till new states can be entered in DFA table
while (ind != -1) {
dfa_states[start_index].count = 1;
Sm = 0;
for (int i = 0; i< symbols; i++) {

trans(buffer, i, closure_table, states, NFA_TABLE, T_buf);

// storing the new DFA state in buffer


DFA_TABLE[zz][i] = T_buf;

// parameter to control new states


Sm = Sm + new_states(dfa_states, T_buf);
}
ind = indexing(dfa_states);
if (ind != -1)
buffer = dfa_states[++start_index].states;
zz++;
}
// display the DFA TABLE
Display_DFA(dfa_states, DFA_TABLE);

return 0;
}

OUTPUT:
LEARNING OUTCOME:
1. Understand the differences between Non-Deterministic Finite Automata (NFA) and
Deterministic Finite Automata (DFA).
2. Implement the ε-closure algorithm to compute the ε-closure of states in an NFA.
3. Apply the Subset Construction algorithm to convert an NFA to an equivalent DFA.
4. Identify equivalent states in the constructed DFA and understand the importance of state
equivalence in DFA optimization.
5. Optionally, apply DFA minimization algorithms to optimize the size of the DFA obtained from
the conversion process.
6. Analyze and evaluate the efficiency and effectiveness of the conversion process in terms of
time complexity and space complexity.
EXPERIMENT - 2

AIM:
Program to build a DFA to accept strings that start and end with same character(given a string of
characters a & b)

THEORY:
A Deterministic Finite Automaton (DFA) is a mathematical model consisting of a finite set of states,
a finite set of input symbols (alphabet), a transition function, an initial state, and a set of accepting
states. DFAs are used to recognize or accept strings that belong to a particular language.

To construct a DFA that accepts strings starting and ending with the same character, we need to
define the following:

1. States: The DFA will have states representing different scenarios of the input string.
2. Alphabet: The input alphabet consists of the characters 'a' and 'b'.
3. Transition Function: Define the transitions between states based on the input characters.
4. Initial State: The starting state of the DFA.
5. Accepting States: States where the DFA accepts the input string.
The construction of the DFA involves the following steps:
1. Define States: Typically, the DFA will have states representing different scenarios of the input
string, such as "start with 'a'", "start with 'b'", "end with 'a'", "end with 'b'", and "other scenarios".
2. Transition Function: Determine the transitions between states based on the input characters 'a' and
'b'. For example, if the current state represents "start with 'a'", and the next input character is 'a',
transition to a state representing "start with 'a'". Similarly, define transitions for other scenarios.
3. Initial State: Define the initial state of the DFA, usually representing the beginning of the string.
4. Accepting States: Specify the accepting states of the DFA, representing strings that start and end
with the same character.

CODE:

// C++ Program for DFA that accepts string


// if it starts and ends with same character

#include <bits/stdc++.h>
using namespace std;

// States of DFA
void q1(string, int);
void q2(string, int);
void q3(string, int);
void q4(string, int);

// Function for the state Q1


void q1(string s, int i)
{

// Condition to check end of string


if (i == s.length()) {
cout<< "Yes \n";
return;
}

// State transitions
// 'a' takes to q1, and
// 'b' takes to q2
if (s[i] == 'a')
q1(s, i + 1);
else
q2(s, i + 1);
}

// Function for the state Q2


void q2(string s, int i)
{
// Condition to check end of string
if (i == s.length()) {
cout<< "No \n";
return;
}

// State transitions
// 'a' takes to q1, and
// 'b' takes to q2
if (s[i] == 'a')
q1(s, i + 1);
else
q2(s, i + 1);
}

// Function for the state Q3


void q3(string s, int i)
{
// Condition to check end of string
if (i == s.length()) {
cout<< "Yes \n";
return;
}

// State transitions
// 'a' takes to q4, and
// 'b' takes to q3
if (s[i] == 'a')
q4(s, i + 1);
else
q3(s, i + 1);
}

// Function for the state Q4


void q4(string s, int i)
{
// Condition to check end of string
if (i == s.length()) {
cout<< "No \n";
return;
}

// State transitions
// 'a' takes to q4, and
// 'b' takes to q3
if (s[i] == 'a')
q4(s, i + 1);
else
q3(s, i + 1);
}

// Function for the state Q0


void q0(string s, int i)
{

// Condition to check end of string


if (i == s.length()) {
cout<< "No \n";
return;
}

// State transitions
// 'a' takes to q1, and
// 'b' takes to q3
if (s[i] == 'a')
q1(s, i + 1);
else
q3(s, i + 1);
}

// Driver Code
int main()
{
string s = "abbaabb";

// Since q0 is the starting state


// Send the string to q0
q0(s, 0);
}

OUTPUT:

Created by – rahul(355)
LEARNING OUTCOME:
1. Understand the concept of Deterministic Finite Automata (DFA) and its components.
2. Design a DFA to recognize strings that start and end with the same character.
3. Implement the transition function for the DFA based on the input alphabet.
4. Define the initial state and accepting states of the DFA.
5. Analyze the behavior of the DFA and its ability to recognize valid strings from the given
language.
6. Evaluate the correctness and efficiency of the DFA construction process.
EXPERIMENT - 3

AIM:
Program to detect tokens in a Program
(Eg-Keywords,operators, identifiers etc)

THEORY:
Tokenization is the process of breaking a sequence of characters into smaller units called tokens. In
programming languages, tokens represent the basic building blocks of the language, such as keywords,
operators, identifiers, literals, and punctuation symbols.

CODE:
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// Returns 'true' if the character is a DELIMITER.


bool isDelimiter(char ch)
{
if (ch == ' ' || ch == '+' || ch == '-' || ch == '*' ||
ch == '/' || ch == ',' || ch == ';' || ch == '>' ||
ch == '<' || ch == '=' || ch == '(' || ch == ')' ||
ch == '[' || ch == ']' || ch == '{' || ch == '}')
return (true);
return (false);
}

// Returns 'true' if the character is an OPERATOR.


bool isOperator(char ch)
{
if (ch == '+' || ch == '-' || ch == '*' ||
ch == '/' || ch == '>' || ch == '<' ||
ch == '=')
return (true);
return (false);
}

// Returns 'true' if the string is a VALID IDENTIFIER.


bool validIdentifier(char* str)
{
if (str[0] == '0' || str[0] == '1' || str[0] == '2' ||
str[0] == '3' || str[0] == '4' || str[0] == '5' ||
str[0] == '6' || str[0] == '7' || str[0] == '8' ||
str[0] == '9' || isDelimiter(str[0]) == true)
return (false);
return (true);
}

// Returns 'true' if the string is a KEYWORD.


bool isKeyword(char* str)
{
if (!strcmp(str, "if") || !strcmp(str, "else") ||
!strcmp(str, "while") || !strcmp(str, "do") ||
!strcmp(str, "break") ||
!strcmp(str, "continue") || !strcmp(str, "int")
|| !strcmp(str, "double") || !strcmp(str, "float")
|| !strcmp(str, "return") || !strcmp(str, "char")
|| !strcmp(str, "case") || !strcmp(str, "char")
|| !strcmp(str, "sizeof") || !strcmp(str, "long")
|| !strcmp(str, "short") || !strcmp(str, "typedef")
|| !strcmp(str, "switch") || !strcmp(str, "unsigned")
|| !strcmp(str, "void") || !strcmp(str, "static")
|| !strcmp(str, "struct") || !strcmp(str, "goto"))
return (true);
return (false);
}

// Returns 'true' if the string is an INTEGER.


bool isInteger(char* str)
{
int i, len = strlen(str);

if (len == 0)
return (false);
for (i = 0; i<len; i++) {
if (str[i] != '0' && str[i] != '1' && str[i] != '2'
&& str[i] != '3' && str[i] != '4' && str[i] != '5'
&& str[i] != '6' && str[i] != '7' && str[i] != '8'
&& str[i] != '9' || (str[i] == '-' &&i> 0))
return (false);
}
return (true);
}

// Returns 'true' if the string is a REAL NUMBER.


bool isRealNumber(char* str)
{
int i, len = strlen(str);
bool hasDecimal = false;

if (len == 0)
return (false);
for (i = 0; i<len; i++) {
if (str[i] != '0' && str[i] != '1' && str[i] != '2'
&& str[i] != '3' && str[i] != '4' && str[i] != '5'
&& str[i] != '6' && str[i] != '7' && str[i] != '8'
&& str[i] != '9' && str[i] != '.' ||
(str[i] == '-' &&i> 0))
return (false);
if (str[i] == '.')
hasDecimal = true;
}
return (hasDecimal);
}

// Extracts the SUBSTRING.


char* subString(char* str, int left, int right)
{
int i;
char* subStr = (char*)malloc(
sizeof(char) * (right - left + 2));

for (i = left; i<= right; i++)


subStr[i - left] = str[i];
subStr[right - left + 1] = '\0';
return (subStr);
}

// Parsing the input STRING.


void parse(char* str)
{
int left = 0, right = 0;
int len = strlen(str);

while (right <= len&& left <= right) {


if (isDelimiter(str[right]) == false)
right++;

if (isDelimiter(str[right]) == true && left == right) {


if (isOperator(str[right]) == true)
printf("'%c' IS AN OPERATOR\n", str[right]);

right++;
left = right;
} else if (isDelimiter(str[right]) == true &&left != right
|| (right == len&&left != right)) {
char* subStr = subString(str, left, right - 1);

if (isKeyword(subStr) == true)
printf("'%s' IS A KEYWORD\n", subStr);

else if (isInteger(subStr) == true)


printf("'%s' IS AN INTEGER\n", subStr);

else if (isRealNumber(subStr) == true)


printf("'%s' IS A REAL NUMBER\n", subStr);

else if (validIdentifier(subStr) == true


&&isDelimiter(str[right - 1]) == false)
printf("'%s' IS A VALID IDENTIFIER\n", subStr);

else if (validIdentifier(subStr) == false


&&isDelimiter(str[right - 1]) == false)
printf("'%s' IS NOT A VALID IDENTIFIER\n", subStr);
left = right;
}
}
return;
}

// DRIVER FUNCTION
int main()
{
// maximum length of string is 100 here
char str[100] = "int a = b + 1c; ";

parse(str); // calling the parse function

return (0);
}

OUTPUT:

Created by – rahul(355)
LEARNING OUTCOME:
1. Understand the concept of tokenization and its importance in programming language processing.
2. Identify and define different types of tokens, including keywords, operators, identifiers, literals,
and punctuation symbols.
3. Implement algorithms to tokenize input source code by scanning characters and recognizing
token patterns.
4. Categorize tokens into appropriate types based on predefined rules and patterns.
5. Handle special cases and edge cases in token detection, such as handling escape characters in
string literals, nested expressions, etc.
EXPERIMENT - 4

AIM:
Write a program to implement lexical analyser

THEORY:
A lexical analyzer, also known as a lexer or scanner, is the first phase of a compiler that breaks the input
source code into a sequence of tokens. These tokens represent the basic building blocks of the programming
language and serve as input to the subsequent phases of the compiler.

The process of lexical analysis involves scanning the input source code character by character, recognizing and
categorizing sequences of characters into different token types based on predefined rules and patterns.

CODE:
// C++ program to illustrate the implementation of lexical
// analyser
#include<bits/stdc++.h>
#include <cctype>
#include <iostream>
#include <string>
#include <vector>

#define MAX_LENGTH 100

// this function check for a delimiter(it is a piece of data


// that seprated it from other) to peform some specif case
// on it
bool isDelimiter(char chr)
{
return (chr == ' ' || chr == '+' || chr == '-'
|| chr == '*' || chr == '/' || chr == ','
|| chr == ';' || chr == '%' || chr == '>'
|| chr == '<' || chr == '=' || chr == '('
|| chr == ')' || chr == '[' || chr == ']'
|| chr == '{' || chr == '}');
}

// this function check for a valid operator eg:- +,-* etc


bool isOperator(char chr)
{
return (chr == '+' || chr == '-' || chr == '*'
|| chr == '/' || chr == '>' || chr == '<'
|| chr == '=');
}

// this function check for an valid identifier


bool isValidIdentifier(const std::string& str)
{
return (!isdigit(str[0]) && !isDelimiter(str[0]));
}

// 32 Keywords are checked in this function and return the


// result accordingly
bool isKeyword(const std::string& str)
{
const std::vector<std::string> keywords
= { "auto", "break", "case", "char",
"const", "continue", "default", "do",
"double", "else", "enum", "extern",
"float", "for", "goto", "if",
"int", "long", "register", "return",
"short", "signed", "sizeof", "static",
"struct", "switch", "typedef", "union",
"unsigned", "void", "volatile", "while" };
return std::find(keywords.begin(), keywords.end(), str) != keywords.end();
}

// check for an integer value


bool isInteger(const std::string& str)
{
if (str.empty()) {
return false;
}
return std::all_of(str.begin(), str.end(), ::isdigit);
}

// trims a substring from a given string's start and end


// position
std::string getSubstring(const std::string& str, int start, int end)
{
return str.substr(start, end - start + 1);
}

// this function parse the input


void lexicalAnalyzer(const std::string& input)
{
int left = 0, right = 0;
int len = input.length();

while (right <= len&& left <= right) {


if (!isDelimiter(input[right]))
right++;

if (isDelimiter(input[right]) && left == right) {


if (isOperator(input[right]))
std::cout<< "Token: Operator, Value: " << input[right] << std::endl;

right++;
left = right;
}
else if (isDelimiter(input[right]) &&left != right
|| (right == len&&left != right)) {
std::string subStr
= getSubstring(input, left, right - 1);

if (isKeyword(subStr))
std::cout<< "Token: Keyword, Value: " <<subStr<< std::endl;

else if (isInteger(subStr))
std::cout<< "Token: Integer, Value: " <<subStr<< std::endl;

else if (isValidIdentifier(subStr)
&& !isDelimiter(input[right - 1]))
std::cout<< "Token: Identifier, Value: " <<subStr<< std::endl;

else if (!isValidIdentifier(subStr)
&& !isDelimiter(input[right - 1]))
std::cout<< "Token: Unidentified, Value: " <<subStr<< std::endl;
left = right;
}
}
}

// main function
int main()
{
// Input 01
std::string lex_input = "int a = b * d + c";
std::cout<< "For Expression \"" <<lex_input<< "\":" << std::endl;
lexicalAnalyzer(lex_input);
std::cout<< " \n";
return 0;
}

OUTPUT:
LEARNING OUTCOME:
1. Understand the role and importance of lexical analysis in the compilation process.
2. Define token types and their corresponding patterns using regular expressions or finite automata.
3. Implement a lexical analyzer capable of tokenizing input source code into keywords, identifiers,
literals, operators, and punctuation symbols.
4. Handle special cases and edge cases in token recognition, such as handling escape characters in
string literals, nested expressions, etc.
5. Test and debug the lexical analyzer to ensure accurate tokenization of input source code.
EXPERIMENT –5

AIM:
Write a program to implement the recursive descent parser.

THEORY:
Recursive descent parsing is a top-down parsing technique where a set of recursive procedures is
used to process the input. Each procedure typically corresponds to a non-terminal symbol in the
grammar. The parser begins with the start symbol and recursively applies the production rules to
match the input against the grammar.

ALGORITHM:
1. Define grammar rules for the language to be parsed.
2. Implement recursive procedures for each non-terminal symbol in the grammar.
3. Start parsing from the start symbol using the corresponding recursive procedure.
4. Apply the production rules recursively to match the input against the grammar.
5. Handle syntax errors appropriately.

CODE:
#include <stdio.h>
#include <string.h>

#define SUCCESS 1
#define FAILED 0

int E(), Edash(), T(), Tdash(), F();


const char *cursor;
char string[64];

int main() {
puts("Enter the string");
scanf("%s", string);
cursor = string;
puts("");
puts("Input Action");
puts("--------------------------------");

if (E() && *cursor == '\0') {


puts("--------------------------------");
puts("String is successfully parsed");
return 0;
}
else {
puts("--------------------------------");
puts("Error in parsing String");
return 1;
}
}

int E() {
printf("%-16s E -> T E'\n", cursor);
if (T()) {
if (Edash())return SUCCESS;
elsereturn FAILED;
}
elsereturn FAILED;
}

int Edash() {
if (*cursor == '+') {
printf("%-16s E' -> + T E'\n", cursor);
cursor++;
if (T()) {
if (Edash()) return SUCCESS;
elsereturn FAILED;
}
elsereturn FAILED;
}
else {
printf("%-16s E' -> $\n", cursor);
return SUCCESS;
}
}

int T() {
printf("%-16s T -> F T'\n", cursor);
if (F()) {
if (Tdash()) return SUCCESS;
elsereturn FAILED;
}
elsereturn FAILED;
}

int Tdash() {
if (*cursor == '*') {
printf("%-16s T' -> * F T'\n", cursor);
cursor++;
if (F()) {
if (Tdash())return SUCCESS;
elsereturn FAILED;
}
elsereturn FAILED;
}
else {
printf("%-16s T' -> $\n", cursor);
return SUCCESS;
}
}

int F() {
if (*cursor == '(') {
printf("%-16s F -> ( E )\n", cursor);
cursor++;
if (E()) {
if (*cursor == ')') {
cursor++;
return SUCCESS;
}
elsereturn FAILED;
}
elsereturn FAILED;
}
else if (*cursor == 'i') {
printf("%-16s F ->i\n", cursor);
cursor++;
return SUCCESS;
}
elsereturn FAILED;
}
OUTPUT:
LEARNING OUTCOME:
1. Understand the principles of recursive descent parsing.
2. Gain hands-on experience in implementing a recursive descent parser.
3. Learn how to define grammar rules and corresponding recursive procedures.
4. Develop skills in handling syntax errors during parsing.
EXPERIMENT - 6

AIM:
Program to eliminate left factoring in the given grammar

THEORY:
Left factoring is a process used to remove common prefixes from the productions of a grammar.
When a grammar contains productions with common prefixes, it can lead to parsing ambiguities and
increase the complexity of parsing algorithms. Left factoring simplifies the grammar by introducing
new non-terminals to eliminate these common prefixes.

The general steps for eliminating left factoring from a grammar are as follows:

Identify productions with common prefixes.


Introduce new non-terminals for the common prefixes.
Replace the original productions with the new non-terminals and updated productions.
Update references to the original non-terminals in other productions accordingly.
By eliminating left factoring, we ensure that the resulting grammar is suitable for unambiguous
parsing and can be efficiently processed by parsing algorithms.

CODE:
#include <iostream>
#include <vector>
#include <string>

using namespace std;

// Structure to represent a production rule


struct ProductionRule {
char nonTerminal; // Non-terminal symbol
vector<string>productions; // List of production rules
};

// Function to eliminate left factoring


void eliminateLeftFactoring(vector<ProductionRule>& grammar) {
vector<ProductionRule>newGrammar; // Updated grammar without left factoring

for (const ProductionRule&rule : grammar) {


vector<string>commonPrefixes;
vector<string>nonCommonSuffixes;

// Find common prefixes and non-common suffixes for the productions


for (const string&prod :rule.productions) {
bool foundCommon = false;
for (const string&prefix :commonPrefixes) {
if (prod.find(prefix) == 0) {
foundCommon = true;
break;
}
}
if (!foundCommon) {
for (const string&suffix :nonCommonSuffixes) {
if (prod.substr(prod.length() - suffix.length()) == suffix) {
foundCommon = true;
break;
}
}
}

if (foundCommon) {
commonPrefixes.push_back(prod.substr(0, 1)); // Take single character as prefix
} else {
nonCommonSuffixes.push_back(prod.substr(1)); // Exclude the common prefix as suffix
}
}

// If common prefixes found, perform left factoring


if (!commonPrefixes.empty()) {
ProductionRulenewRule;
newRule.nonTerminal = rule.nonTerminal;

// Add common prefix productions


newRule.productions.push_back(commonPrefixes[0]);
char newNonTerminal = 'A'; // Start with 'A' for new non-terminal symbols

// Add non-common suffix productions


for (size_ti = 0; i<nonCommonSuffixes.size(); ++i) {
if (i == 0) {
newRule.productions.push_back(nonCommonSuffixes[i]);
} else {
// Create new non-terminal symbol and add production rule
newRule.productions.push_back(newNonTerminal + nonCommonSuffixes[i]);
ProductionRulenewNonTerminalRule;
newNonTerminalRule.nonTerminal = newNonTerminal++;
newNonTerminalRule.productions.push_back(nonCommonSuffixes[i]);
newGrammar.push_back(newNonTerminalRule);
}
}

newGrammar.push_back(newRule);
} else {
// If no common prefixes found, keep the rule unchanged
newGrammar.push_back(rule);
}
}

// Print the updated grammar without left factoring


cout<< "Grammar without left factoring:" <<endl;
for (const ProductionRule&rule :newGrammar) {
cout<<rule.nonTerminal<< " -> ";
for (size_ti = 0; i<rule.productions.size(); ++i) {
cout<<rule.productions[i];
if (i<rule.productions.size() - 1) {
cout<< " | ";
}
}
cout<<endl;
}
}

int main() {
// Example grammar
vector<ProductionRule> grammar = {
{'S', {"abc", "abcd", "abef"}},
{'A', {"abc", "abcd", "abef"}}
};

// Eliminate left factoring


eliminateLeftFactoring(grammar);

return 0;
}

OUTPUT:

LEARNING OUTCOME:
1. Understand the concept of left factoring and its importance in grammar simplification.
2. Identify common prefixes in productions and recognize the need for left factoring.
3. Implement an algorithm to eliminate left factoring from a given grammar.
4. Modify productions and introduce new non-terminals to achieve left factoring.
5. Verify the correctness of the left factoring transformation and ensure that the resulting grammar
retains its original language.
EXPERIMENT - 7

AIM:
Write a program to convert left recursive to right recursive grammer

THEORY:
Left recursion in a grammar occurs when a non-terminal directly or indirectly produces itself as the
first symbol in a production. This can lead to parsing challenges, including infinite loops and
ambiguity. To mitigate these issues, left recursion must be eliminated. This process involves creating
new non-terminals and rewriting production rules to remove the left recursion. By replacing left-
recursive productions with non-left-recursive alternatives, the grammar becomes suitable for parsing
algorithms, ensuring reliable and efficient parsing processes without encountering infinite loops or
ambiguity.

ALGORITHM:
1. . Identify left-recursive productions in the grammar.
2. For each left-recursive non-terminal:
- Create a new non-terminal symbol.
- Rewrite the productions to eliminate left recursion.
3. Update the grammar with the new non-terminals and productions.
4. Repeat steps 2 and 3 until all left recursion is eliminated

CODE:
#include <iostream>
#include <string>
#include <vector>
#include <map>
using namespace std;

map<char, vector<string>>eliminateLeftRecursion(map<char, vector<string>>& grammar) {


map<char, vector<string>>newGrammar;
vector<char>nonTerminals;
for (auto const&entry : grammar) {
nonTerminals.push_back(entry.first);
}

for (char A :nonTerminals) {


vector<string> alpha;
vector<string> beta;

for (const string&production : grammar.at(A)) {


if (production[0] == A) {
alpha.push_back(production.substr(1));
} else {
beta.push_back(production);
}
}

if (!alpha.empty()) {
char new_A = A + '\'';
newGrammar[new_A] = alpha;
newGrammar[A] = (beta.empty()) ? vector<string>{"ε"} : beta;
for (string&prod :newGrammar[A]) {
prod += new_A;
}
} else {
newGrammar[A] = grammar.at(A);
}
}
return newGrammar;
}

int main() {
map<char, vector<string>> grammar = {
{'E', {"E+T", "T"}},
{'T', {"T*F", "F"}},
{'F', {"(E)", "id"}}
};

map<char, vector<string>>newGrammar = eliminateLeftRecursion(grammar);

cout<< "Original Grammar:" <<endl;


for (auto const&entry : grammar) {
cout<<entry.first<< " -> ";
for (const string&prod :entry.second) {
cout<< prod << " | ";
}
cout<<endl;
}

cout<< "\nGrammar after eliminating left recursion:" <<endl;


for (auto const&entry :newGrammar) {
cout<<entry.first<< " -> ";
for (const string&prod :entry.second) {
cout<< prod << " | ";
}
cout<<endl;
}

return 0;
}

OUTPUT :
Created by – rahul(355)

LEARNING OUTCOME:
1. Understand the concept of left recursion in grammars.
2. Learn the algorithm to eliminate left recursion.
3. Gain hands-on experience in implementing a program to eliminate left recursion in a
grammar.
4. Learn how to update grammars to remove left recursion while preserving their original
structure.
EXPERIMENT – 8

AIM:
Program to find first and follow of the given grammar.

THEORY:
FIRST Set:

The FIRST set of a symbol (either a terminal or a non-terminal) in a grammar is the set of terminals
that begin the strings derivable from that symbol. In other words, it represents all the possible
terminals that can appear as the first symbol of a string generated from the given symbol.

To compute the FIRST set:


1. If the symbol is a terminal, its FIRST set is just itself.
2. If the symbol is a non-terminal, iterate through all its production rules:
- If the production rule begins with a terminal, add that terminal to the FIRST set.
- If the production rule begins with a non-terminal:
- Add the terminals from its FIRST set.
- If the production rule can derive the empty string (epsilon), continue to the next symbol in the
production rule and add terminals from its FIRST set.

FOLLOW Set:

The FOLLOW set of a non-terminal symbol in a grammar is the set of terminals that can immediately
follow the occurrences of that non-terminal in any derivation of a string.

To compute the FOLLOW set:


1. Start by putting $ (representing the end of the input) into the FOLLOW set of the start symbol.
2. For each production rule, look at where the non-terminal appears:
- If it's at the end, add terminals from the FOLLOW set of the non-terminal on the left-hand side.
- If it's followed by another symbol, add terminals from the FIRST set of that symbol (excluding
epsilon).
- If epsilon is in the FIRST set of the following symbol, add terminals from the FOLLOW set of the
non-terminal on the left-hand side.

These sets are crucial in constructing predictive parsers, which are used to parse strings based on
context-free grammars. They help in determining the appropriate production rules to use during
parsing.

CODE:
// C program to calculate the First and
// Follow sets of a given grammar
#include <ctype.h>
#include <stdio.h>
#include <string.h>

// Functions to calculate Follow


void followfirst(char, int, int);
void follow(char c);

// Function to calculate First


void findfirst(char, int, int);

int count, n = 0;

// Stores the final result


// of the First Sets
char calc_first[10][100];

// Stores the final result


// of the Follow Sets
char calc_follow[10][100];
int m = 0;

// Stores the production rules


char production[10][10];
char f[10], first[10];
int k;
char ck;
int e;

int main(int argc, char** argv)


{
int jm = 0;
int km = 0;
int i, choice;
char c, ch;
count = 8;

// The Input grammar


strcpy(production[0], "X=TnS");
strcpy(production[1], "X=Rm");
strcpy(production[2], "T=q");
strcpy(production[3], "T=#");
strcpy(production[4], "S=p");
strcpy(production[5], "S=#");
strcpy(production[6], "R=om");
strcpy(production[7], "R=ST");

int kay;
char done[count];
int ptr = -1;

// Initializing the calc_first array


for (k = 0; k < count; k++) {
for (kay = 0; kay < 100; kay++) {
calc_first[k][kay] = '!';
}
}
int point1 = 0, point2, xxx;

for (k = 0; k < count; k++) {


c = production[k][0];
point2 = 0;
xxx = 0;

// Checking if First of c has


// already been calculated
for (kay = 0; kay <= ptr; kay++)
if (c == done[kay])
xxx = 1;

if (xxx == 1)
continue;

// Function call
findfirst(c, 0, 0);
ptr += 1;

// Adding c to the calculated list


done[ptr] = c;
printf("\n First(%c) = { ", c);
calc_first[point1][point2++] = c;

// Printing the First Sets of the grammar


for (i = 0 + jm; i< n; i++) {
int lark = 0, chk = 0;

for (lark = 0; lark < point2; lark++) {

if (first[i] == calc_first[point1][lark]) {
chk = 1;
break;
}
}
if (chk == 0) {
printf("%c, ", first[i]);
calc_first[point1][point2++] = first[i];
}
}
printf("}\n");
jm = n;
point1++;
}
printf("\n");
printf("-----------------------------------------------"
"\n\n");
char donee[count];
ptr = -1;

// Initializing the calc_follow array


for (k = 0; k < count; k++) {
for (kay = 0; kay < 100; kay++) {
calc_follow[k][kay] = '!';
}
}
point1 = 0;
int land = 0;
for (e = 0; e < count; e++) {
ck = production[e][0];
point2 = 0;
xxx = 0;

// Checking if Follow of ck
// has already been calculated
for (kay = 0; kay <= ptr; kay++)
if (ck == donee[kay])
xxx = 1;

if (xxx == 1)
continue;
land += 1;

// Function call
follow(ck);
ptr += 1;

// Adding ck to the calculated list


donee[ptr] = ck;
printf(" Follow(%c) = { ", ck);
calc_follow[point1][point2++] = ck;

// Printing the Follow Sets of the grammar


for (i = 0 + km; i< m; i++) {
int lark = 0, chk = 0;
for (lark = 0; lark < point2; lark++) {
if (f[i] == calc_follow[point1][lark]) {
chk = 1;
break;
}
}
if (chk == 0) {
printf("%c, ", f[i]);
calc_follow[point1][point2++] = f[i];
}
}
printf(" }\n\n");
km = m;
point1++;
}
}

void follow(char c)
{
int i, j;

// Adding "$" to the follow


// set of the start symbol
if (production[0][0] == c) {
f[m++] = '$';
}
for (i = 0; i< 10; i++) {
for (j = 2; j < 10; j++) {
if (production[i][j] == c) {
if (production[i][j + 1] != '\0') {
// Calculate the first of the next
// Non-Terminal in the production
followfirst(production[i][j + 1], i,
(j + 2));
}
if (production[i][j + 1] == '\0'
&&c != production[i][0]) {
// Calculate the follow of the
// Non-Terminal in the L.H.S. of the
// production
follow(production[i][0]);
}
}
}
}
}

void findfirst(char c, int q1, int q2)


{
int j;

// The case where we


// encounter a Terminal
if (!(isupper(c))) {
first[n++] = c;
}
for (j = 0; j < count; j++) {
if (production[j][0] == c) {
if (production[j][2] == '#') {
if (production[q1][q2] == '\0')
first[n++] = '#';
else if (production[q1][q2] != '\0'
&& (q1 != 0 || q2 != 0)) {
// Recursion to calculate First of New
// Non-Terminal we encounter after
// epsilon
findfirst(production[q1][q2], q1,
(q2 + 1));
}
else
first[n++] = '#';
}
else if (!isupper(production[j][2])) {
first[n++] = production[j][2];
}
else {
// Recursion to calculate First of
// New Non-Terminal we encounter
// at the beginning
findfirst(production[j][2], j, 3);
}
}
}
}

void followfirst(char c, int c1, int c2)


{
int k;

// The case where we encounter


// a Terminal
if (!(isupper(c)))
f[m++] = c;
else {
int i = 0, j = 1;
for (i = 0; i< count; i++) {
if (calc_first[i][0] == c)
break;
}

// Including the First set of the


// Non-Terminal in the Follow of
// the original query
while (calc_first[i][j] != '!') {
if (calc_first[i][j] != '#') {
f[m++] = calc_first[i][j];
}
else {
if (production[c1][c2] == '\0') {
// Case where we reach the
// end of a production
follow(production[c1][0]);
}
else {
// Recursion to the next symbol
// in case we encounter a "#"
followfirst(production[c1][c2], c1,
c2 + 1);
}
}
j++;
}
}
}
LEARNING OUTCOME:
1. Understand the concepts of First and Follow sets in the context of context-free grammars.
2. Implement algorithms to compute the First and Follow sets of non-terminals in a given grammar.
3. Handle special cases and edge cases in the computation of First and Follow sets, such as nullable
non-terminals, left recursion, and cycles in the grammar.
4. Apply the computed First and Follow sets to construct predictive parsing tables for LL(1)
grammars.
5. Analyze and interpret the results of the First and Follow set computation to understand the syntax
of the given grammar and identify potential parsing conflicts.
EXPERIMENT – 9

AIM:
Write a program to construct LL(1) parsing table

THEORY: A top-down parser builds the parse tree from the top down,starting with the start
non-terminal.This is a type of top down parser. Here the 1stLrepresents that thescanning of
the Input will be done from Left to Right manner and secondL shows that in this Parsing
technique we are going to use Left most Derivation Tree. And finally the1represents the
number of look ahead, means how many symbols are we going to see when you want to
make a decision.

To construct the Parsing table,we have two functions:


1:First():If there is a variable,and from that variable if we try to drive all the strings then the
beginning Terminal Symbol is called the first.
2:Follow():What is the Terminal Symbol which follow a variable in the process of derivation.
Now, after computing the First and Follow set for each non-Terminal symbolwe must
construct the Parsing table.In the table Rows will contain the non-Terminals and the column
will contain the Terminal Symbols.
All the Null Productions of the Grammars will go under the Follow elements and the
remaining productions will lie under the elements of First set.

ALGORITHM:

1. Input no. of terminals.


2. Storealltheterminalswithanindexinahashmap<char,int>.
3. Storealltheproductionsinahashmap<char,vector<string>>.
4. Defineaparsingtableas vector<vector<string>>.
5. Iteratethroughproductions’hashmap:
a. ComputeFIRSTofR.H.S.
a. EnterthisproductionintheblockcorrespondingtoLHSandeachterminalin
the FIRST obtained in step a.
a. IfFIRSTcontains null
. ComputeFOLLOWofLHS.
. Addtheproduction:LHS->nulltoeachentrycorrespondingtoLHS and
terminals in FOLLOW computed in step i.
2.Printtheparsing table.

CODE:

#include<iostream>
#include<bits/stdc++.h>us
ing namespace std;
stringfirst(map<char,vector<string>>m,charch){
if(ch=='^'||!(ch>='A'&&ch<='Z'))
return ch+"";
stringans="";
for(int
i=0;i<m[ch].size();i++
){ string s = m[ch][i];
bool checknext=true;
for(intj=0;j<s.length()&&checknext;j++){

checknext= false;
if(s[j]=='^'||!(s[j]>='A'&&s[j]<='Z')){
if(ans.find(s[j])==string::npos)
ans.push_back(s[j]);
}

else
{
stringtemp= first(m,s[j]);
for(int k=0;k<temp.length();k++){
if(temp[k]=='^')
checknext=true;
else if(ans.find(temp[k])==string::npos)
ans.push_back(temp[k]);
}
if(checknext&&j==s.length()-1)
ans.push_back('^');
}
}

returnans;
}

stringfirstofstring(map<char,vector<string>>m,strings){
stringans="";
boolchecknext=true;
for(intj=0;j<s.length()&&checknext;j++){

checknext= false;
if(s[j]=='^'||!(s[j]>='A'&&s[j]<='Z')){
if(ans.find(s[j])==string::npos)
ans.push_back(s[j]);
}

else
{
stringtemp= first(m,s[j]);
for(int k=0;k<temp.length();k++){
if(temp[k]=='^')
checknext=true;
else if(ans.find(temp[k])==string::npos)
ans.push_back(temp[k]);
}
if(checknext&&j==s.length()-1)
ans.push_back('^');
}
}

if(ans=="")
return"^";
return ans;
}
stringfollow(map<char,vector<string>>prod,charstart,charch){
string ans="";
if(start==ch)
ans.push_back('$');
for(map<char,vector<string>>::iteratori=prod.begin();i!=prod.end();i++){

for(intj=0;j<i->second.size();j++){

if(i->second[j].find(ch)==string::npos)
continue;
string temp="";
boolparent=false;
for(int a=0;a<i->second[j].length();a++){
parent=false;
if(i->second[j][a]==ch){

if(a==i->second[j].length()-
1){ parent=true;

}else{
//cout<<"substr
"<<i->second[j].substr(a,i->second[j].length()-a-1)<<endl;
temp =
firstofstring(prod,i->second[j].substr(a+1,i->second[j].length()-a-1));
//cout<<"temp"<<temp;
for(int k=0;k<temp.length();k++){
if(temp[k]=='^'){
parent=true;
continue;
}
if(ans.find(temp[k])==string::npos)
ans.push_back(temp[k]);
}
}
if(parent){
//totacklethecasewhenparentissameas'ch'if(ch==
i->first)
continue;

temp=follow(prod,start,i->first);
for(intk=0;k<temp.length();k++){
if(ans.find(temp[k])==string::npos)
ans.push_back(temp[k]);
}
}

returnans;
}

intmain(){
map<char,vector<string>>prod; int
n;
intt;
cout<<"Enternumberofterminals:"; cin>>t;
map<char,int>terminals;
for(int i=0;i<t;i++){
char ch;
cin>>ch
;

terminals[ch]=i;
}
terminals['$']=t;
cout<<"Enternumberofproductions:";
cin>>n;
cout<<"Enterproductions:"<<endl;
char start;
for(inti=0;i<n;i++){
char ch;
string s;
cin>>ch;
if(i==0)
start=ch;
cout<<"->";
cin>>s;
prod[ch].push_back(s);
}

cout<<"Givengrammaris:"<<endl<<endl;
for(map<char,vector<string>>::iteratori=prod.begin();i!=prod.end();i++){
cout<<i->first<<" ->";
for(int j=0;j<i-
>second.size();j++){
cout<<i->second[j];
if(j!=i->second.size()-
1) cout<<" | ";
}
cout<<endl;
}

vector< vector< string >> table(prod.size(), vector<string>(t+1, "") );


for(map<char,vector<string>>::iteratori=prod.begin();i!=prod.end();i++){
for(intj=0;j<i->second.size();j++){
strings=firstofstring(prod,i->second[j]); bool
hasnull=false;
for(int k=0;k<s.length();k++){
if(s[k]=='^'){
hasnull=true;
continue;
}
string gg="";
gg=gg+i-
>first; gg=
gg+"->";
gg=gg+i->second[j];
table[i->first-'A'][terminals[s[k]]]=gg;

if(hasnull){
stringtemp=follow(prod,start,i->first); for(int
k=0;k<temp.length();k++){
stringss="";
ss+=i->first;
ss+="->^";
table[i->first-'A'][terminals[temp[k]]]=ss;
}

cout<<endl<<endl;
cout<<"Nonterminals\\Terminals|";
vector<char> vv(t+1);
for(map<char,int>::iterator i= terminals.begin(); i!=terminals.end();i++)
vv[i->second]=i->first;

for(int i=0;i<vv.size();i++)
cout<<" "<<vv[i]<<" |";
cout<<endl;
intii=0;
for(map<char, vector<string>>::iterator i=prod.begin(); i!=prod.end(),ii<table.size();
i++,ii++){

cout<<" "<<i->first<<" |";


for(int j=0;j<table[ii].size();j++){
cout<<""<<table[ii][j]<<"|";
}

cout<<endl;
}

Created by – rahul(355)
EXPERIMENT-10
AIM: WAP to implement non-recursive predictive parser

THEORY: Non-Recursive Predictive Parsing uses a parsing table that shows which production rule
to select from several alternatives available for expanding a given non-terminal and the first terminal
symbol that should be produced by that non-terminal.

The parsing table consists of rows and columns where there are two for each non-terminal and a
column for each terminal symbol, including $, the end marker for the input string. Each entry M[A,
a] in a table is either a production rule or an error.

It uses a stack containing a sequence of grammar symbols with the $ symbol placed at the bottom,
indicating the bottom of the stack. Initially, the start symbol resides on top. The stack is used to keep
track of all the non-terminals for which no prediction has been made yet.

The parser also uses an input buffer and an output stream. The string to be parsed is stored in the
input buffer. The end of the buffer uses a $ symbol to indicate the end of the input string

CODE:

#include<bits/stdc++.h>us
ing namespace std;

map<char,map<char,int>>M={
{'E', {{'a', 0}, {'(', 0}}},
{'D', {{'+', 1}, {')', 2}, {'$', 2}}},
{'T', {{'a', 3}, {'(', 3}}},
{'S', {{'*', 4}, {'+', 5}, {')', 5}, {'$', 5}}},
{'F', {{'(', 6}, {'a', 7}}},
};

map<int,string>Grammar={
{0, "E=TD"},
{1, "D=+TD"},
{2, "D=$"},
{3, "T=FS"},
{4, "S=*FS"},
{5, "S=$"},
{6, "F=(E)"},
{7, "F=a"}
};

voidprintStack(vector<char>s)
{

cout<<endl;
for(inti=0;i<s.size();i++) cout
<< s[i] <<'';
}

voidParse(strings)
{

vector<char> stk;
stk.push_back('#');
stk.push_back('E');
charpeak=stk.back();
int i = 0;
printStack(stk);
while(stk.size() > 0)
{
if(stk.size()<=3)
cout<<"\t\t\tinputs:"<<s.substr(i); else
cout<<"\t\tinputs:"<<s.substr(i); if
(peak == s[i])
{
if(stk.size()<=3)
cout<<"\t\t\t\tMatchedID";
else
cout<<"\t\t\tMatchedID"; stk.pop_back();
i++;
}

else if (islower(peak))
{

cout<<"Errorinparsing,expected:"<<peak<<"got:"<<s[i]<<endl;
return;
}
elseif(M[peak].find(s[i])==M[peak].end())
{

cout<<"Errorinparsing,invalidexpression"<<M[peak][s[i]]<<endl; return;
}
else
{

charpp=peak;
stk.pop_back();
stringprod=Grammar[M[pp][s[i]]];
int z = prod.length() - 1;
if(stk.size()<=3)
cout<<"\t\t\tAction:"<<prod; else
cout <<"\t\tAction : "<< prod;
while(prod[z]!='='&&prod[z]!='$')
{
stk.push_back(prod[z]);
z--;
}

//cout<<endl;
}

peak=stk.back();
printStack(stk);
if (peak == '#'&& s[i] == '$')
{

cout<<"Parsedsuccessfully"<<endl;
return ;
}
}

intmain(){
cout<<"Entersomeexpression:"; string
expr;
cin >>expr;
if(expr.back()!='$')
expr += "$";
Parse(expr);}
EXPERIMENT-11

AIM:Write a program to implement error handler

THEORY: Error handling in parsing involves detecting and recovering from syntax errors
encountered during the parsing process. When a parser encounters an error, it tries to identify the
point of failure and take appropriate action to continue parsing or report the error.

Error handlers can employ strategies like panic mode recovery, where the parser skips input until it
finds a synchronization point (e.g., a specific terminal symbol), or error productions, where the
parser uses special rules to synchronize its state with the input.

Effective error handling enhances parser robustness and helps provide meaningful feedback to users
about syntax errors in their input.

CODE:
#include <vector>
#include <string>
#include <sstream>
#include<unordered_set>
#include <iostream>

namespace std {

classErrorHandler{
public:
void handleError(const string& message, int lineNumber) {

stringstream errorStream;
errorStream <<"Error on line "<< lineNumber <<": "<< message <<endl;

errors_.push_back(errorStream.str());
}

bool hasErrors() const {


return !errors_.empty();
}

vector<string>getErrors()const{
return errors_;
}

private:
vector<string>errors_;
};

unordered_set<string>declaredVariables;
}
int main() {
using namespace std;

ErrorHandlererrorHandler;

cout <<"Enter your code (lines terminated by ';'):"<<endl;

stringstreamcodeStream;
string line;
int lineNumber = 1;
while (getline(cin, line)) {

codeStream<<line<<'\n';
lineNumber++;
}

while (getline(codeStream, line)) {

if (line.find(';') == string::npos && !line.empty()) {


errorHandler.handleError("Missingsemicolon",lineNumber);
}

lineNumber++;
}

if(errorHandler.hasErrors()){
cout <<"Compilation errors:"<<endl;
for(constauto&error:errorHandler.getErrors()){ cout
<< error;
}
} else {
cout <<"Compilation successful!"<<endl;
}

return 0;
}
OUTPUT:
EXPERIMENT-12
AIM:Write a program to implement a one pass compiler.

THEORY: A one-pass compiler is a compiler that traverses the source code exactly once, converting
it into executable code without the need for multiple passes or intermediate representations.

It typically performs lexical analysis, syntax analysis, semantic analysis, code generation, and
optimization in a single pass. One-pass compilers are memory-efficient and suitable for small and
medium-sized programs, but may lack certain optimizations and advanced features compared to
multi-pass compilers.

They are commonly used in embedded systems, scripting languages, and environments where
simplicity and speed are prioritized over advanced optimizations.

CODE:

#include<iostream>
#include <string>
#include <unordered_map>
#include <sstream>
#include <vector>
usingnamespacestd;

//Enumfortokentypes
enum class TokenType
{
KEYWORD,
IDENTIFIER,
OPERATOR,
LITERAL,
DELIMITER,
COMMENT
};

//Tokenstructure
struct Token
{
TokenTypetype;
string value;
};

class OnePassCompiler
{
public:
// Constructor
OnePassCompiler(){}

// Function to compile source code


void compile(const string &sourceCode)
{
stringstreamss(sourceCode);
string line;

while (getline(ss, line))


{
processLine(line);
}
}

private:
// Process each line of source code
voidprocessLine(conststring&line)
{
stringstreamss(line);
string token;

while (ss >>token)


{
Tokent=getTokenType(token);
//Processtokenbasedonitstype
processToken(t);
}
}

// Get token type


TokengetTokenType(conststring&token)
{
// Implement logic to identify token type
//Forsimplicity,assumetokenisakeyword
return {TokenType::KEYWORD, token};
}

// Process token
voidprocessToken(constToken&token)
{
// Implement actions based on token type
//Forsimplicity,printtokenvalueandtype
cout<<"Token:"<<token.value<<"Type:"<<static_cast<int>(token.type)<< endl;
}
};

int main()
{
//Instantiatetheonepasscompiler
OnePassCompiler compiler;

// Example source code


string sourceCode = "int main() {\n\tint x = 10;\n\treturn x;\n}";
// Compile the source code
compiler.compile(sourceCode);

return 0;
}

OUTPUT:

You might also like