0% found this document useful (0 votes)
10 views39 pages

Rajat Prasad CD File

This document is a lab file for Compiler Design (CO-302) at Delhi Technological University, detailing various experiments related to compiler construction. It includes programs for converting NFA to DFA, checking string acceptance by DFA, token identification, implementing a lexical analyzer, recursive descent parsing, left factoring of grammars, and more. Each experiment outlines its aim, theory, and provides corresponding code implementations.

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)
10 views39 pages

Rajat Prasad CD File

This document is a lab file for Compiler Design (CO-302) at Delhi Technological University, detailing various experiments related to compiler construction. It includes programs for converting NFA to DFA, checking string acceptance by DFA, token identification, implementing a lexical analyzer, recursive descent parsing, left factoring of grammars, and more. Each experiment outlines its aim, theory, and provides corresponding code implementations.

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/ 39

COMPILER DESIGN

CO-302
LAB FILE

DEPARTMENT OF COMPUTER SCIENCE AND


ENGINEERING DELHI TECHNOLOGICAL UNIVERSITY
(Formerly Delhi College of Engineering) Bawana Road,
Delhi- 110042)

SUBMITTED BY: SUBMITTED TO:

RAJAT PRASAD (2K22/CO/359) Mrs. ARUNA BHATT


INDEX

S.No. Experiment Date


1. Write a program to covert NFA to DFA

2. Write a program for acceptance of string by DFA

3. Write a program to find different tokens in a


program.

4. Write a program to implement Lexical Analyser.

5. Write a program to implement Recursive


Descent Parser.

6. Write a program to left factor the given grammar.

7. Write a program to convert left recursive


grammar to right recursive grammar.

8. Write a program to compute FIRST and FOLLOW.

9. Write a program to construct LL (1) parsing


table.

10. Write a program to implement non-recursive


predictive parsing.

11. Write a program to implement an error handler


Experiment – 1

AIM: Write a program to covert NFA to DFA

Theory:
NFA (Nondeterministic Finite Automaton)
• An NFA allows multiple transitions for the same input symbol from a given state.

• It can also have ε (epsilon) transitions (transitions without consuming input).

DFA (Deterministic Finite Automaton)


• A DFA has only one transition for each input symbol from a state.

• No ε-transitions allowed.

• Simpler to implement for lexical analysis in compilers.

Conversion: Subset Construction Algorithm


This method constructs a DFA from an NFA by treating each DFA state as a set of NFA
states:
1. Start with the epsilon closure of the NFA's start state.
2. For each DFA state and input symbol:

• Move to the set of states in NFA.

• Take epsilon closure of those states.

• Add as new DFA state if not seen before.


3. Repeat until no new states are added.

Applications
• Lexical analysis (token recognition).

• Used internally in regex engines.

• Base for deterministic pattern matching.


Code:
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
using namespace std;

// Function to get the next states for a given input from a set of NFA states
set<int> getNextStates(const vector<map<char, set<int>>> &nfa, const set<int> &currentStates,
char input) {
set<int> result;
for (int state : currentStates) {
if (nfa[state].count(input)) {
result.insert(nfa[state].at(input).begin(), nfa[state].at(input).end());
}
}
return result;
}

int main() {
int n, m;
cout << "Enter number of NFA states: ";
cin >> n;
vector<map<char, set<int>>> nfa(n);

cout << "Enter number of transitions: ";


cin >> m;
cout << "Enter transitions in format: from_state input_char to_state\n";
for (int i = 0; i < m; ++i) {
int from, to;
char input;
cin >> from >> input >> to;
nfa[from][input].insert(to);
}

char startInput = 'a'; // assuming inputs are from 'a' to 'z'


char endInput = 'z';
set<set<int>> dfaStates;
map<set<int>, int> stateID;
vector<map<char, int>> dfa;
queue<set<int>> q;

set<int> start = {0}; // NFA start state is 0


q.push(start);
dfaStates.insert(start);
stateID[start] = 0;
dfa.emplace_back();

while (!q.empty()) {
set<int> current = q.front(); q.pop();
int currID = stateID[current];

for (char c = startInput; c <= endInput; ++c) {


set<int> next = getNextStates(nfa, current, c);
if (next.empty()) continue;
if (dfaStates.find(next) == dfaStates.end()) {
int newID = dfa.size();
dfaStates.insert(next);
stateID[next] = newID;
dfa.emplace_back();
q.push(next);
}
dfa[currID][c] = stateID[next];
}
}

cout << "\nDFA States and Transitions:\n";


for (int i = 0; i < dfa.size(); ++i) {
for (auto &[ch, to] : dfa[i]) {
cout << "From state " << i << " --" << ch << "--> " << to << '\n';
}
}

return 0;
}

OUTPUT:
EXPERIMENT – 02

AIM: To write a program that checks whether a given string is accepted by a DFA.

Theory :
DFA (Deterministic Finite Automaton):
A DFA has a unique transition for each symbol from every state.

• Acceptance:
A string is accepted by a DFA if after processing all input characters starting
from the initial state, it ends in a final/accepting state.

• How it works:

• Start from the initial state.

• For each character, move to the next state using the transition table.

• If the final state reached is among accepting states, accept the string; else,
reject.

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

int main() {
int n, m, finalCount, startState;
cout << "Enter number of states in DFA: ";
cin >> n;

cout << "Enter number of transitions: ";


cin >> m;

map<pair<int, char>, int> transition;


cout << "Enter transitions (from_state input_char to_state):\n";
for (int i = 0; i < m; ++i) {
int from, to;
char input;
cin >> from >> input >> to;
transition[{from, input}] = to;
}

cout << "Enter start state: ";


cin >> startState;
cout << "Enter number of final (accepting) states: ";
cin >> finalCount;
set<int> finalStates;
cout << "Enter final states: ";
for (int i = 0; i < finalCount; ++i) {
int state;
cin >> state;
finalStates.insert(state);
}

string inputStr;
cout << "Enter input string: ";
cin >> inputStr;

int currentState = startState;


for (char ch : inputStr) {
if (transition.find({currentState, ch}) == transition.end()) {
cout << "Rejected (Invalid transition)\n";
return 0;
}
currentState = transition[{currentState, ch}];
}

if (finalStates.count(currentState)) {
cout << "Accepted\n";
} else {
cout << "Rejected (Did not end in final state)\n";
}

return 0;
}

OUTPUT:
EXPERIMENT – 03

AIM: To write a program to find different tokens in a given program.

Theory: Tokens are the smallest units in a program that are meaningful to the compiler.
Common token types:

• Keywords (e.g., int, if, while)

• Identifiers (e.g., variable names)

• Operators (e.g., +, -, *, ==)

• Separators (e.g., ,, ;, (, ))

• Constants (e.g., numbers, strings)

• Lexical analysis is the phase in a compiler where the input program is split into
tokens.

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

// Function to check if a string is a keyword


bool isKeyword(string str) {
string keywords[] = {"int", "float", "double", "char", "void", "if", "else",
"while", "for", "return", "break", "continue"};
for (const string& keyword : keywords) {
if (str == keyword) return true;
}
return false;
}

// Function to check if a string is an operator


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

// Function to tokenize the input program


void tokenize(string program) {
vector<string> tokens;
string current = "";

for (size_t i = 0; i < program.length(); i++) {


char ch = program[i];

// Skip whitespace
if (isspace(ch)) {
if (!current.empty()) {
tokens.push_back(current);
current = "";
}
continue;
}

// Handle operators
if (isOperator(ch)) {
if (!current.empty()) {
tokens.push_back(current);
current = "";
}
tokens.push_back(string(1, ch));
continue;
}

// Handle punctuation
if (ch == ';' || ch == ',' || ch == '(' || ch == ')' || ch == '{' || ch == '}') {
if (!current.empty()) {
tokens.push_back(current);
current = "";
}
tokens.push_back(string(1, ch));
continue;
}

// Build token
current += ch;
}

// Add last token if exists


if (!current.empty()) {
tokens.push_back(current);
}
// Print categorized tokens
cout << "\nToken Analysis:\n";
cout << "---------------\n";

for (const string& token : tokens) {


if (isKeyword(token)) {
cout << token << "\t-> Keyword\n";
}
else if (isOperator(token[0]) && token.length() == 1) {
cout << token << "\t-> Operator\n";
}
else if (token == ";" || token == "," || token == "(" || token == ")" ||
token == "{" || token == "}") {
cout << token << "\t-> Punctuation\n";
}
else if (isdigit(token[0])) {
cout << token << "\t-> Literal\n";
}
else {
cout << token << "\t-> Identifier\n";
}
}
}

int main() {
string program;

cout << "Enter a program (type 'END' on a new line to finish):\n";

// Read multi-line input


string line;
while (getline(cin, line) && line != "END") {
program += line + " ";
}

cout << "\nInput Program:\n" << program << "\n";

tokenize(program);

return 0;
}
OUTPUT:
EXPERIMENT – 04

AIM : To write a program to implement a Lexical Analyser.

Theory:
Lexical Analyzer (Lexer):
First phase of a compiler that scans source code and breaks it into tokens (keywords,
identifiers, literals, etc.).

• It removes whitespace and comments, and passes a stream of tokens to the


parser.

• Similar to Experiment 3, but more systematic and can be extended for real
compilers.

CODE:

#include <iostream>
#include <sstream>
#include <vector>
#include <unordered_set>
#include <cctype>
using namespace std;

unordered_set<string> keywords = {
"int", "float", "if", "else", "while", "return", "for", "char", "double", "bool"
};

unordered_set<char> operators = {
'+', '-', '*', '/', '=', '>', '<', '!'
};

unordered_set<char> separators = {
'(', ')', '{', '}', '[', ']', ';', ',', ' '
};

bool isKeyword(const string &s) {


return keywords.count(s);
}

bool isNumber(const string &s) {


for (char c : s) if (!isdigit(c)) return false;
return !s.empty();
}

bool isIdentifier(const string &s) {


if (!isalpha(s[0]) && s[0] != '_') return false;
for (char c : s) {
if (!isalnum(c) && c != '_') return false;
}
return true;
}

void lexicalAnalyze(const string &code) {


string token;
for (size_t i = 0; i < code.length(); ++i) {
char ch = code[i];

if (isalnum(ch) || ch == '_') {
token += ch;
}
else {
if (!token.empty()) {
if (isKeyword(token)) cout << token << " => Keyword\n";
else if (isNumber(token)) cout << token << " => Constant\n";
else if (isIdentifier(token)) cout << token << " => Identifier\n";
else cout << token << " => Unknown\n";
token.clear();
}

if (operators.count(ch)) {
if (i + 1 < code.length() && code[i + 1] == '=') {
cout << ch << "=" << " => Operator\n";
++i;
} else {
cout << ch << " => Operator\n";
}
}
else if (separators.count(ch) && ch != ' ') {
cout << ch << " => Separator\n";
}
}
}

// check last token


if (!token.empty()) {
if (isKeyword(token)) cout << token << " => Keyword\n";
else if (isNumber(token)) cout << token << " => Constant\n";
else if (isIdentifier(token)) cout << token << " => Identifier\n";
else cout << token << " => Unknown\n";
}
}

int main() {
string code;
cout << "Enter code line: ";
getline(cin, code);

cout << "\nLexical Analysis:\n";


lexicalAnalyze(code);

return 0;
}

OUTPUT:
EXPERIMENT – 05

AIM : To write a program to implement a Recursive Descent Parser.

Theory:
Recursive Descent Parsing is a top-down parser built from a set of mutually recursive
procedures.

• Each non-terminal in the grammar is implemented as a function.

• Works for grammars that are:

• Non-left-recursive

• LL(1) (can decide next production based on 1 lookahead symbol)

CODE:

#include <iostream>
#include <string>
using namespace std;

string input;
int pos = 0;

bool E(); // forward declarations


bool E_();
bool T();
bool T_();
bool F();

bool match(char expected) {


if (input[pos] == expected) {
pos++;
return true;
}
return false;
}

bool F() {
if (match('(')) {
if (E() && match(')')) return true;
return false;
} else if (input[pos] == 'i') { // i for 'id'
pos++;
return true;
}
return false;
}

bool T_() {
if (match('*')) {
if (F() && T_()) return true;
return false;
}
return true; // ε-production
}

bool T() {
if (F() && T_()) return true;
return false;
}

bool E_() {
if (match('+')) {
if (T() && E_()) return true;
return false;
}
return true; // ε-production
}

bool E() {
if (T() && E_()) return true;
return false;
}

int main() {
cout << "Enter expression using 'i' for id (e.g. i+i*i): ";
cin >> input;

if (E() && pos == input.length()) {


cout << "Accepted by Recursive Descent Parser\n";
} else {
cout << "Rejected\n";
}

return 0;
}
OUTPUT:
EXPERIMENT – 06

AIM: To write a program to left factor the given grammar.

Theory: Left Factoring is a grammar transformation used to remove common prefixes


from productions.
• Helps in building LL(1) parsers by avoiding ambiguity in selecting productions.

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

int main() {
string nonTerminal, prod;
vector<string> productions;
cout << "Enter Non-Terminal: ";
cin >> nonTerminal;

int n;
cout << "Enter number of productions: ";
cin >> n;

cout << "Enter productions (without space, e.g. abcd):\n";


for (int i = 0; i < n; i++) {
cin >> prod;
productions.push_back(prod);
}

// Find longest common prefix


string prefix = productions[0];
for (int i = 1; i < n; i++) {
string temp = "";
for (int j = 0; j < min(prefix.length(), productions[i].length()); j++) {
if (prefix[j] == productions[i][j])
temp += prefix[j];
else
break;
}
prefix = temp;
}
if (prefix == "") {
cout << "No left factoring needed.\n";
} else {
cout << "Left Factored Grammar:\n";
cout << nonTerminal << " → " << prefix << nonTerminal << "'\n";
cout << nonTerminal << "' → ";
for (int i = 0; i < n; i++) {
cout << productions[i].substr(prefix.length());
if (i != n - 1) cout << " | ";
}
cout << endl;
}

return 0;
}

OUTPUT:
EXPERIMENT-07

AIM: To write a program to convert left recursive grammar to right recursive grammar.

Theory: Left recursion occurs when a non-terminal symbol on the left side of a
production rule refers to itself as the first symbol on the right-hand side (e.g., A → Aα |
β). A grammar is said to be in right recursion when the recursive call happens on the
rightmost part of the production (e.g., A → βA' and A' → αA' | ε).

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

using namespace std;

// Function to split a string by a delimiter


vector<string> split(const string &s, char delimiter) {
vector<string> tokens;
string token;
istringstream tokenStream(s);
while (getline(tokenStream, token, delimiter)) {
tokens.push_back(token);
}
return tokens;
}

// Function to trim whitespace from both ends of a string


string trim(const string &str) {
size_t start = str.find_first_not_of(" \t\n\r");
if (start == string::npos) return "";

size_t end = str.find_last_not_of(" \t\n\r");


return str.substr(start, end - start + 1);
}

// Function to convert left recursive production to right recursive


vector<string> convertLeftToRight(const string &nonTerminal, const vector<string>
&productions) {
vector<string> newProductions;
vector<string> alphaProductions;
vector<string> betaProductions;

// Separate productions into those that are left recursive (alpha) and others (beta)
for (const string &prod : productions) {
string trimmedProd = trim(prod);
if (!trimmedProd.empty() && trimmedProd[0] == nonTerminal[0]) {
alphaProductions.push_back(trimmedProd.substr(1));
} else if (!trimmedProd.empty()) {
betaProductions.push_back(trimmedProd);
}
}

// If there are no left recursive productions, return original


if (alphaProductions.empty()) {
return productions;
}

// Create new productions for the original non-terminal


for (const string &beta : betaProductions) {
string newProd = beta + " " + nonTerminal + "'";
newProductions.push_back(newProd);
}

// Create productions for the new non-terminal


vector<string> newNonTerminalProductions;
for (const string &alpha : alphaProductions) {
string newProd = alpha + " " + nonTerminal + "'";
newNonTerminalProductions.push_back(newProd);
}
newNonTerminalProductions.push_back("ε"); // Epsilon production

// Output the new productions for the new non-terminal


cout << "\nNew production for " << nonTerminal << "':\n";
cout << nonTerminal << "' -> ";
for (size_t i = 0; i < newNonTerminalProductions.size(); ++i) {
if (i != 0) cout << " | ";
cout << newNonTerminalProductions[i];
}
cout << endl;
return newProductions;
}

int main() {
cout << "Left Recursive Grammar to Right Recursive Grammar Converter\n";
cout << "Enter grammar productions in the format: A -> Aα | β\n";
cout << "Enter 'exit' to finish input\n\n";

while (true) {
string input;
cout << "Enter production: ";
getline(cin, input);

if (input == "exit") {
break;
}

// Parse the input


size_t arrowPos = input.find("->");
if (arrowPos == string::npos) {
cout << "Invalid format. Use 'A -> Aα | β' format.\n";
continue;
}

string nonTerminal = trim(input.substr(0, arrowPos));


string productionsStr = trim(input.substr(arrowPos + 2));

vector<string> productions = split(productionsStr, '|');

// Convert left recursive to right recursive


vector<string> newProductions = convertLeftToRight(nonTerminal, productions);

// Output the converted production


cout << "\nConverted production for " << nonTerminal << ":\n";
cout << nonTerminal << " -> ";
for (size_t i = 0; i < newProductions.size(); ++i) {
if (i != 0) cout << " | ";
cout << newProductions[i];
}
cout << "\n\n";
}

return 0;
}

OUTPUT:
EXPERIMENT-08

Aim:
To compute FIRST and FOLLOW sets for a given grammar, which are essential for
building LL(1) parsers.

Theory:
• FIRST of a non-terminal is the set of terminals that can appear at the beginning of
any string derived from that non-terminal.

• FOLLOW of a non-terminal is the set of terminals that can appear immediately to


the right of that non-terminal in any sentential form.

CODE:

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

map<char, vector<string>> productions;


map<char, set<char>> first, follow;
set<char> nonTerminals, terminals;

bool isTerminal(char ch) {


return !isupper(ch) && ch != 'e';
}

void computeFirst(char symbol) {


if (!first[symbol].empty()) return;

for (string prod : productions[symbol]) {


bool epsilonPossible = true;
for (char ch : prod) {
if (ch == 'e') {
first[symbol].insert('e');
break;
} else if (isTerminal(ch)) {
first[symbol].insert(ch);
break;
} else {
computeFirst(ch);
for (char f : first[ch]) {
if (f != 'e') first[symbol].insert(f);
}

if (first[ch].find('e') == first[ch].end()) {
epsilonPossible = false;
break;
}
}
}
if (epsilonPossible)
first[symbol].insert('e');
}
}

void computeFollow(char symbol) {


for (auto &[lhs, rhsList] : productions) {
for (string rhs : rhsList) {
for (size_t i = 0; i < rhs.size(); ++i) {
if (rhs[i] == symbol) {
size_t j = i + 1;
bool addFollowOfLHS = true;
while (j < rhs.size()) {
char next = rhs[j];
if (isTerminal(next)) {
follow[symbol].insert(next);
addFollowOfLHS = false;
break;
}
for (char f : first[next]) {
if (f != 'e') follow[symbol].insert(f);
}
if (first[next].count('e')) {
j++;
} else {
addFollowOfLHS = false;
break;
}
}
if (addFollowOfLHS && lhs != symbol) {
computeFollow(lhs);
follow[symbol].insert(follow[lhs].begin(), follow[lhs].end());
}
}
}
}
}
}
int main() {
int n;
cout << "Enter number of productions: ";
cin >> n;
cin.ignore();

cout << "Enter productions (e.g. E->TX or X->e):\n";


char startSymbol = 0;
for (int i = 0; i < n; ++i) {
string line;
getline(cin, line);
char lhs = line[0];
if (startSymbol == 0) startSymbol = lhs;

string rhs = line.substr(3);


productions[lhs].push_back(rhs);
nonTerminals.insert(lhs);

for (char ch : rhs)


if (isTerminal(ch) && ch != 'e')
terminals.insert(ch);
}

for (char nt : nonTerminals)


computeFirst(nt);

follow[startSymbol].insert('$'); // End of input marker

for (char nt : nonTerminals)


computeFollow(nt);

cout << "\nFIRST sets:\n";


for (auto &[nt, s] : first) {
cout << "FIRST(" << nt << ") = { ";
for (char c : s) cout << c << ' ';
cout << "}\n";
}

cout << "\nFOLLOW sets:\n";


for (auto &[nt, s] : follow) {
cout << "FOLLOW(" << nt << ") = { ";
for (char c : s) cout << c << ' ';
cout << "}\n";
}

return 0;
}

OUTPUT:
EXPERIMENT-09

Aim:
To construct an LL(1) parsing table from a given grammar.

Theory:
An LL(1) parser uses a table of production rules for predictive parsing. It is based on
the FIRST and FOLLOW sets. The table rows correspond to non-terminal symbols, and
columns correspond to terminal symbols. The entry in the table for a non-terminal and
terminal pair contains the production rule to apply.

CODE:
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <sstream>
#include <algorithm>
using namespace std;

map<char, set<char>> first, follow;


map<char, vector<string>> productions;
set<char> terminals, nonTerminals;

bool isTerminal(char c) {
return !isupper(c) && c != 'e';
}

void computeFirst(char symbol) {


if (!first[symbol].empty()) return;

for (string prod : productions[symbol]) {


for (int i = 0; i < prod.size(); ++i) {
char ch = prod[i];
if (ch == 'e') {
first[symbol].insert('e');
break;
}

if (isTerminal(ch)) {
first[symbol].insert(ch);
break;
}

computeFirst(ch);
for (char f : first[ch]) {
if (f != 'e') first[symbol].insert(f);
}

if (first[ch].find('e') == first[ch].end()) break;


if (i == prod.size() - 1) first[symbol].insert('e');
}
}
}

void computeFollow(char symbol) {


if (!follow[symbol].empty()) return;

for (auto &[lhs, rhsList] : productions) {


for (string rhs : rhsList) {
for (size_t i = 0; i < rhs.size(); ++i) {
if (rhs[i] == symbol) {
size_t j = i + 1;
while (j < rhs.size()) {
char next = rhs[j];
if (isTerminal(next)) {
follow[symbol].insert(next);
break;
}

for (char f : first[next]) {


if (f != 'e') follow[symbol].insert(f);
}

if (first[next].count('e')) {
++j;
} else {
break;
}
}
if (j == rhs.size() && lhs != symbol) {
computeFollow(lhs);
follow[symbol].insert(follow[lhs].begin(), follow[lhs].end());
}
}
}
}
}
}

int main() {
int prodCount;
cout << "Enter number of productions: ";
cin >> prodCount;
cin.ignore();

cout << "Enter productions (e.g. E->TX):\n";


char startSymbol = '\0';
for (int i = 0; i < prodCount; ++i) {
string line;
getline(cin, line);
char lhs = line[0];
if (startSymbol == '\0') startSymbol = lhs;
nonTerminals.insert(lhs);

string rhs = line.substr(3);


productions[lhs].push_back(rhs);

for (char ch : rhs)


if (isTerminal(ch) && ch != 'e') terminals.insert(ch);
}

terminals.insert('$'); // end of input

// FIRST sets
for (char nt : nonTerminals)
computeFirst(nt);

// FOLLOW sets
follow[startSymbol].insert('$');
for (char nt : nonTerminals)
computeFollow(nt);

// Display FIRST
cout << "\nFIRST sets:\n";
for (auto &[nt, s] : first) {
cout << "FIRST(" << nt << ") = { ";
for (char c : s) cout << c << ' ';
cout << "}\n";
}

// Display FOLLOW
cout << "\nFOLLOW sets:\n";
for (auto &[nt, s] : follow) {
cout << "FOLLOW(" << nt << ") = { ";
for (char c : s) cout << c << ' ';
cout << "}\n";
}
// Construct Parsing Table
map<char, map<char, string>> parsingTable;

for (auto &[lhs, rhsList] : productions) {


for (string rhs : rhsList) {
set<char> firstSet;
bool hasEpsilon = false;

for (int i = 0; i < rhs.size(); ++i) {


char sym = rhs[i];
if (sym == 'e') {
hasEpsilon = true;
break;
}

if (isTerminal(sym)) {
firstSet.insert(sym);
break;
}

for (char f : first[sym])


if (f != 'e') firstSet.insert(f);

if (first[sym].count('e') == 0)
break;

if (i == rhs.size() - 1)
hasEpsilon = true;
}

for (char t : firstSet)


parsingTable[lhs][t] = rhs;

if (hasEpsilon) {
for (char f : follow[lhs])
parsingTable[lhs][f] = "e";
}
}
}

// Display Parsing Table


cout << "\nLL(1) Parsing Table:\n";
cout << " ";
for (char t : terminals)
cout << t << " ";
cout << "\n";
for (char nt : nonTerminals) {
cout << nt << " | ";
for (char t : terminals) {
if (parsingTable[nt].count(t))
cout << nt << "->" << parsingTable[nt][t] << " | ";
else
cout << " | ";
}
cout << "\n";
}

return 0;
}

OUTPUT:
EXPERIMENT-10

Aim:
To implement non-recursive predictive parsing for LL(1) parsers.

Theory:
Non-recursive predictive parsing uses an explicit stack to keep track of parsing steps.
The stack simulates recursive calls without actual recursion, providing an iterative
parsing process.

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

map<pair<char, char>, string> parsingTable;


vector<char> terminals, nonTerminals;

bool isTerminal(char ch) {


return !isupper(ch) && ch != 'e';
}

int main() {
int n;
cout << "Enter number of entries in parsing table: ";
cin >> n;

cout << "Enter entries in format: NonTerminal Terminal Production (e.g., E a TX):\n";
for (int i = 0; i < n; ++i) {
char nt, t;
string prod;
cin >> nt >> t >> prod;
parsingTable[{nt, t}] = prod;
if (find(nonTerminals.begin(), nonTerminals.end(), nt) == nonTerminals.end())
nonTerminals.push_back(nt);
if (isTerminal(t) && find(terminals.begin(), terminals.end(), t) == terminals.end())
terminals.push_back(t);
}

char startSymbol;
cout << "Enter start symbol: ";
cin >> startSymbol;

string input;
cout << "Enter input string followed by $: ";
cin >> input;

stack<char> parseStack;
parseStack.push('$');
parseStack.push(startSymbol);

int ip = 0;
cout << "\nParsing steps:\n";

while (!parseStack.empty()) {
char top = parseStack.top();
char currentInput = input[ip];

cout << "Stack top: " << top << ", Current input: " << currentInput << "\n";

if (top == currentInput && top == '$') {


cout << "Input accepted!\n";
break;
}

if (top == currentInput) {
parseStack.pop();
ip++;
} else if (isTerminal(top)) {
cout << "Error: Terminal mismatch\n";
break;
} else {
parseStack.pop();
string production = parsingTable[{top, currentInput}];

if (production.empty()) {
cout << "Error: No rule for [" << top << ", " << currentInput << "]\n";
break;
}

cout << top << " -> " << production << "\n";

if (production != "e") {
for (int i = production.size() - 1; i >= 0; --i) {
parseStack.push(production[i]);
}
}
}
}

if (!parseStack.empty()) {
cout << "Input rejected.\n";
}

return 0;
}

OUTPUT:
EXPERIMENT – 11

Aim:
To implement an error handler that can detect and report errors in a parser.

Theory:
An error handler reports syntax errors during parsing by detecting mismatched
symbols or situations where no applicable production rule exists for a given non-
terminal and terminal pair.

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

map<pair<char, char>, string> parsingTable;

bool isTerminal(char ch) {


return !isupper(ch) && ch != 'e';
}

int main() {
int n;
cout << "Enter number of entries in parsing table: ";
cin >> n;

cout << "Enter entries in format: NonTerminal Terminal Production (e.g., E a TX):\n";
for (int i = 0; i < n; ++i) {
char nt, t;
string prod;
cin >> nt >> t >> prod;
parsingTable[{nt, t}] = prod;
}

char startSymbol;
cout << "Enter start symbol: ";
cin >> startSymbol;
string input;
cout << "Enter input string followed by $: ";
cin >> input;

stack<char> parseStack;
parseStack.push('$');
parseStack.push(startSymbol);

int ip = 0;
bool errorOccurred = false;

cout << "\nParsing steps:\n";

while (!parseStack.empty()) {
char top = parseStack.top();
char currentInput = input[ip];

cout << "Stack top: " << top << ", Input: " << currentInput << "\n";

if (top == currentInput && top == '$') {


cout << " Input accepted!\n";
break;
}

if (top == currentInput) {
parseStack.pop();
ip++;
} else if (isTerminal(top)) {
cout << " Error: Terminal mismatch. Expected '" << top << "' but got '" <<
currentInput << "'.\n";
errorOccurred = true;
break;
} else {
string production = parsingTable[{top, currentInput}];

if (production.empty()) {
cout << " Error: No rule for [" << top << ", " << currentInput << "]. Skipping
symbol.\n";
parseStack.pop(); // Panic-mode recovery
errorOccurred = true;
continue;
}

parseStack.pop();

if (production != "e") {
for (int i = production.size() - 1; i >= 0; --i) {
parseStack.push(production[i]);
}
}

cout << top << " -> " << production << "\n";
}
}

if (!errorOccurred && parseStack.empty()) {


cout << " Successfully parsed the string.\n";
} else if (errorOccurred) {
cout << " Parsing completed with errors.\n";
} else {
cout << " Parsing failed.\n";
}

return 0;
}

OUTPUT:

You might also like