Rajat Prasad CD File
Rajat Prasad CD File
CO-302
LAB FILE
Theory:
NFA (Nondeterministic Finite Automaton)
• An NFA allows multiple transitions for the same input symbol from a given state.
• No ε-transitions allowed.
Applications
• Lexical analysis (token recognition).
// 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> ¤tStates,
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);
while (!q.empty()) {
set<int> current = q.front(); q.pop();
int currID = stateID[current];
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:
• 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;
string inputStr;
cout << "Enter input string: ";
cin >> inputStr;
if (finalStates.count(currentState)) {
cout << "Accepted\n";
} else {
cout << "Rejected (Did not end in final state)\n";
}
return 0;
}
OUTPUT:
EXPERIMENT – 03
Theory: Tokens are the smallest units in a program that are meaningful to the compiler.
Common token types:
• Separators (e.g., ,, ;, (, ))
• 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;
// 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;
}
int main() {
string program;
tokenize(program);
return 0;
}
OUTPUT:
EXPERIMENT – 04
Theory:
Lexical Analyzer (Lexer):
First phase of a compiler that scans source code and breaks it into tokens (keywords,
identifiers, literals, etc.).
• 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 = {
'(', ')', '{', '}', '[', ']', ';', ',', ' '
};
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";
}
}
}
int main() {
string code;
cout << "Enter code line: ";
getline(cin, code);
return 0;
}
OUTPUT:
EXPERIMENT – 05
Theory:
Recursive Descent Parsing is a top-down parser built from a set of mutually recursive
procedures.
• Non-left-recursive
CODE:
#include <iostream>
#include <string>
using namespace std;
string input;
int pos = 0;
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;
return 0;
}
OUTPUT:
EXPERIMENT – 06
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;
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>
// 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);
}
}
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;
}
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.
CODE:
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <string>
using namespace std;
if (first[ch].find('e') == first[ch].end()) {
epsilonPossible = false;
break;
}
}
}
if (epsilonPossible)
first[symbol].insert('e');
}
}
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;
bool isTerminal(char c) {
return !isupper(c) && c != 'e';
}
if (isTerminal(ch)) {
first[symbol].insert(ch);
break;
}
computeFirst(ch);
for (char f : first[ch]) {
if (f != 'e') first[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();
// 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;
if (isTerminal(sym)) {
firstSet.insert(sym);
break;
}
if (first[sym].count('e') == 0)
break;
if (i == rhs.size() - 1)
hasEpsilon = true;
}
if (hasEpsilon) {
for (char f : follow[lhs])
parsingTable[lhs][f] = "e";
}
}
}
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;
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) {
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;
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;
while (!parseStack.empty()) {
char top = parseStack.top();
char currentInput = input[ip];
cout << "Stack top: " << top << ", Input: " << currentInput << "\n";
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";
}
}
return 0;
}
OUTPUT: