0% found this document useful (0 votes)
16 views33 pages

CD FILE (1) - Merged

The document provides information about compiler design including: - An example LEX program that prints "Hello, World!" or "Goodbye!" depending on the input string. - An example YACC program that can print strings and assign values to identifiers based on the input. - The output of running the YACC program on a sample input prints the strings and shows the assignment.
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)
16 views33 pages

CD FILE (1) - Merged

The document provides information about compiler design including: - An example LEX program that prints "Hello, World!" or "Goodbye!" depending on the input string. - An example YACC program that can print strings and assign values to identifiers based on the input. - The output of running the YACC program on a sample input prints the strings and shows the assignment.
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/ 33

COMPILER DESIGN

Faculty name: Dr. DEEPAK GUPTA

Student name: DHRUV AGGARWAL


Roll No.: 11114802721
Semester: 5th
Group: 5C6

Maharaja Agrasen Institute of Technology, PSP Area,


Sector – 22, Rohini, New Delhi – 110085
MAHARAJA AGRASEN INSTITUTE OF TECHNOLOGY
VISION
To nurture young minds in a learning environment of high academic value and
imbibe spiritual and ethical values with technological and management
competence.

MISSION
The Institute shall endeavor to incorporate the following basic missions in the
teaching methodology:

 Engineering Hardware – Software Symbiosis: Practical exercises in all Engineering


and Management disciplines shall be carried out by Hardware equipment as well as
the related software enabling deeper understanding of basic concepts and
encouraging inquisitive nature.
 Life – Long Learning: The Institute strives to match technological advancements and
encourage students to keep updating their knowledge for enhancing their skills and
inculcating their habit of continuous learning.
 Liberalization and Globalization: The Institute endeavors to enhance technical and
management skills of students so that they are intellectually capable and competent
professionals with Industrial Aptitude to face the challenges of globalization.
 Diversification: The Engineering, Technology and Management disciplines have
diverse fields of studies with different attributes. The aim is to create a synergy of the
above attributes by encouraging analytical thinking.
 Digitization of Learning Processes: The Institute provides seamless opportunities
for innovative learning in all Engineering and Management disciplines through
digitization of learning processes using analysis, synthesis, simulation, graphics,
tutorials and related tools to create a platform for multi-disciplinary approach.
 Entrepreneurship: The Institute strives to develop potential Engineers and Managers
by enhancing their skills and research capabilities so that they emerge as successful
entrepreneurs and responsible citizens.
SOURCE CODE:

1. LEX Program:

%{
#include <stdio.h>
%}

%%
"hello" { printf("Hello, World!\n"); }
"bye" { printf("Goodbye!\n"); }
. { /* Ignore all other characters */ }
%%

int main() {
yylex();
return 0;
}
2. YACC Program:

%{
#include <stdio.h>
%}

%%
statement: "print" STRING { printf("Printing: %s\n", $2); }
| "assign" ID "=" INT { printf("%s = %d\n", $2, $4); }
;
%%

3. Sample Input:

print "Hello, Lex and Yacc!"


assign x = 42
print "Value of x is", x

OUTPUT:
Experiment No – 2

Aim: Write a program to check whether a string belong to the grammar or not.
a) S -> aS
S -> Sb
S -> ab
String of the form: aab
b) S -> aSa
S -> bSb
S -> a
S -> b
The Grammar generated is: All Odd Length Palindromes
c) S -> aSbb
S -> abb
The Grammar generated is: anb2n, where n>1
d). S -> aSb
S -> ab
The Grammar generated is: anbn, where n>0

Source Code:
#include <iostream>
#include <string>

using namespace std;

bool Grammar_1(string str) {


int countA = 0;
int countB = 0;
bool foundB = false;

for (char c : str) {


if (c == 'a') {
if (foundB) {
// 'a' should not appear after 'b'
return false;
}
countA++;
} else if (c == 'b') {
foundB = true;
countB++;
} else {
// If any character other than 'a' or 'b' is encountered, it's not in the grammar
return false;
}
}

// Check if the counts of 'a's and 'b's follow the grammar rules
if (countA >= 2 * countB) {
return true;
}

return false;
}

bool Grammar_2(const string& input) {


int aCount = 0;
int bCount = 0;
int len = input.length();

// Check if the string is not empty and the first and last characters are the same
if (len == 0 || input[0] != input[len - 1]) {
return false;
}

// Count 'a's and 'b's in the string


for (int i = 0; i < len; i++) {
if (input[i] == 'a') {
aCount++;
} else if (input[i] == 'b') {
bCount++;
} else {
// Invalid character encountered
return false;
}
}

// Check if 'a's and 'b's match the grammar rules


if (aCount % 2 == 0 && bCount % 2 == 0) {
// Even number of 'a's and 'b's, and they can be paired according to the grammar rules
return false;
} else {
// Either an odd number of 'a's or an odd number of 'b's, not following the grammar
return true;
}
}

bool isGrammarA(string s) {
int countA = 0;
int countB = 0;

for (char c : s) {
if (c == 'a') {
countA++;
} else if (c == 'b') {
countB++;
} else {
return false; // Invalid string
}
}

// String has the form anbn


return countA >= 1 && countB == countA;
}

bool isGrammarB(string s) {
int countA = 0;
int countB = 0;

for (char c : s) {
if (c == 'a') {
countA++;
} else if (c == 'b') {
countB++;
} else {
return false; // Invalid string
}
}

// String has the form a^nb^2n


return countA >= 1 && countB == 2 * countA;
}

int main() {
int choice;
cout << "Choose the Grammar to check:\n";
cout << "Grammar 1 (Form of 'aab') \n";
cout << "Grammar 2 (Odd Length Palindromes)\n";
cout << "Grammar 3 (anbn)\n";
cout << "Grammar 4 (a^nb^2n)\n";
cout << "Enter your choice (1/2/3/4): ";
cin >> choice;
string input;
cout << "Enter a string: ";
cin >> input;

bool result = false;

switch (choice) {
case 1:
result = Grammar_1(input);
break;
case 2:
result = Grammar_2(input);
break;
case 3:
result = isGrammarA(input);
break;
case 4:
result = isGrammarB(input);
break;
default:
cout << "Invalid choice! Please select 1, 2, 3, or 4." << endl;
return 1;
}

if (result) {
cout << "Accepted! The string belongs to the selected Grammar." << endl;
} else {
cout << "Rejected! The string does not belong to the selected Grammar." << endl;
}

cout << "\nMudit Gupta";

return 0;
}

Output:
Experiment No – 3
Aim: Write a program to check whether a string include keyword or not.

Source Code:
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>

using namespace std;

int countMatchingWords(const string& input, const string& wordsToMatch) {


istringstream iss(wordsToMatch);
vector<string> targetWords;
string word;

// Tokenize the words to match


while (iss >> word) {
targetWords.push_back(word);
}

int count = 0; // Initialize the count of matching words

// Tokenize the input string and count matching words


istringstream inputIss(input);
while (inputIss >> word) {
if (find(targetWords.begin(), targetWords.end(), word) != targetWords.end()) {
count++; // Increment the count for a matching word
}
}

return count;
}

int main() {
string inputString;
string wordsToMatch = "alignas alignof and and_eq asm auto bitand bitor bool break case
catch char class compl const constexpr const_cast continue decltype default"; // Words to match
in the paragraph

cout << "Enter a string or a sentence: ";


getline(cin, inputString);

int matchingWordCount = countMatchingWords(inputString, wordsToMatch);


if (matchingWordCount > 0) {
cout << "It contains " << matchingWordCount << " of the specified words." << endl;
} else {
cout << "It does not contain any of the specified words." << endl;
}

cout << "\nMade by Mudit Gupta";

return 0;
}

Output:
Experiment No – 4

Aim:
Write a program to remove left recursion from a Grammar.

Source Code:
#include <iostream>
#include <vector>
#include <sstream>

using namespace std;

// Function to check if a symbol is non-terminal


bool isNonTerminal(char symbol) {
return symbol >= 'A' && symbol <= 'Z';
}

// Function to eliminate left recursion


void eliminateLeftRecursion(char nonTerminal, vector<string>& productions) {
vector<string> newProductions;

// Separate productions with and without left recursion


for (const string& production : productions) {
if (production[0] == nonTerminal) {
// A -> Aα becomes A -> β1A' | β2A' | ... | βkA'
string beta = production.substr(1);
newProductions.push_back(beta + nonTerminal + "'");
} else {
// A -> α | β | ... | δ remains the same
newProductions.push_back(production);
}
}

// Add ε production for the new non-terminal A'


newProductions.push_back("");

// Add productions for A'


for (const string& production : productions) {
if (production[0] == nonTerminal) {
// A' -> α1A' | α2A' | ... | αmA' | ε
string alpha = production.substr(1);
newProductions.push_back(alpha + nonTerminal + "'");
}
}

// Print the new productions


cout << nonTerminal << " -> ";
for (size_t i = 0; i < newProductions.size(); ++i) {
cout << newProductions[i];
if (i < newProductions.size() - 1) {
cout << " | ";
}
}
cout << endl;
}

int main() {
char nonTerminal;
vector<string> productions;

// Input the non-terminal and productions


cout << "Enter non-terminal: ";
cin >> nonTerminal;

cin.ignore(); // Consume the newline character

cout << "Enter productions separated by '|': ";


string productionsInput;
getline(cin, productionsInput);

stringstream ss(productionsInput);
string production;
while (getline(ss, production, '|')) {
productions.push_back(production);
}

// Check if left recursion exists


if (productions[0][0] == nonTerminal) {
cout << "Left recursion detected. Eliminating left recursion..." << endl;
eliminateLeftRecursion(nonTerminal, productions);
} else {
cout << "No left recursion detected." << endl;
}

return 0;
}

Output:
Experiment No – 5

Aim: Write a program to remove left factoring from a Grammar.

Source Code:
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

// Function to perform left factoring on a given set of production rules


void leftFactor(vector<string>& productionRules) {
// Iterate through each production rule
for (size_t i = 0; i < productionRules.size(); ++i) {
// Find common prefixes with subsequent rules
for (size_t j = i + 1; j < productionRules.size(); ++j) {
size_t k = 0;
// Find the length of the common prefix
while (k < productionRules[i].size() && k < productionRules[j].size() &&
productionRules[i][k] == productionRules[j][k]) {
k++;
}

// If a common prefix is found, refactor the rules


if (k > 0) {
string commonPrefix = productionRules[i].substr(0, k);
string remaining1 = productionRules[i].substr(k);
string remaining2 = productionRules[j].substr(k);

// Generate a new non-terminal symbol


string newNonTerminal = "X" + to_string(i);

// Refactor the rules


productionRules[i] = commonPrefix + newNonTerminal;
productionRules[j] = commonPrefix + newNonTerminal + remaining2;

// Add a new production rule for the common prefix


productionRules.push_back(newNonTerminal + remaining1);
}
}
}
}

int main() {
vector<string> productionRules;

// Input production rules until an empty line is entered


cout << "Enter production rules (empty line to stop):\n";
while (true) {
string rule;
getline(cin, rule);

if (rule.empty()) {
break;
}

productionRules.push_back(rule);
}

// Perform left factoring


leftFactor(productionRules);

// Display the refactored production rules


cout << "\nLeft Factored Grammar:\n";
for (const string& rule : productionRules) {
cout << rule << '\n';
}

return 0;
}

Output:
Experiment No – 6

Aim: Write a program to show all the operations of a stack.

Source Code:
#include <iostream>
#include <vector>
using namespace std;

class Stack
{
private:
vector<int> stack; // Using a vector to implement the stack
public:
void push(int element)
{
stack.push_back(element);
cout << "Pushed: " << element << endl;
}

void pop()
{
if (!stack.empty())
{
int poppedElement = stack.back();
stack.pop_back();
cout << "Popped: " << poppedElement << endl;
}
else
{
cout << "Stack underflow: Cannot pop from an empty stack." << endl;
}
}

void peek()
{
if (!stack.empty())
{
cout << "Top element: " << stack.back() << endl;
}
else
{
cout << "Stack is empty. Cannot peek." << endl;
}
}

bool isEmpty()
{
return stack.empty();
}
};

int main()
{
Stack myStack;

myStack.push(10);
myStack.push(20);
myStack.push(30);

myStack.peek();

myStack.pop();
myStack.pop();
myStack.pop();

myStack.peek(); // Try peeking when the stack is empty

return 0;
}

Experiment No – 7

Aim: Write a program to find out the leading of the non‐terminals in a grammar.
Source Code:
#include <iostream>
#include <unordered_map>
#include <set>
#include <sstream>
using namespace std;

// Function to add elements of one set to another


void addSet(set<char> &destination, const set<char> &source)
{
destination.insert(source.begin(), source.end());
}

// Function to find leading sets for non-terminals


unordered_map<char, set<char>> findLeadingSets(const string &grammar)
{
unordered_map<char, set<char>> leadingSets;

istringstream grammarStream(grammar);
string production;

// Parse each production rule


while (getline(grammarStream, production))
{
istringstream ruleStream(production);

char nonTerminal;
ruleStream >> nonTerminal;

// Ignore the arrow (->) part


ruleStream.ignore(2);

string body;
ruleStream >> body;

// Determine the first set of the body


set<char> firstSet;
for (char symbol : body)
{
if (isupper(symbol))
{
// If the symbol is a non-terminal, add its leading set to the first set
addSet(firstSet, leadingSets[symbol]);
// If the non-terminal can derive the empty string, continue to the next symbol
if (leadingSets[symbol].count('$') == 0)
{
break;
}
}
else
{
// If the symbol is a terminal, add it to the first set
firstSet.insert(symbol);
break;
}
}

// Add the first set to the leading set of the non-terminal


leadingSets[nonTerminal].insert(firstSet.begin(), firstSet.end());
}

return leadingSets;
}

// Function to display leading sets


void displayLeadingSets(const unordered_map<char, set<char>> &leadingSets)
{
cout << "Leading sets:\n";
for (const auto &entry : leadingSets)
{
cout << entry.first << ": { ";
for (char symbol : entry.second)
{
cout << symbol << ' ';
}
cout << "}\n";
}
}

int main()
{
// Example grammar
string grammar =
"S -> AbCd\n"
"A -> a | $\n"
"B -> b | $\n"
"C -> c | $\n"
"D -> d | $\n";

// Find leading sets


unordered_map<char, set<char>> leadingSets = findLeadingSets(grammar);

// Display leading sets


displayLeadingSets(leadingSets);
return 0;
}

Output:
Experiment No – 8

Aim: Write a program to Implement Shift Reduce parsing for a String.


Source Code:
#include <iostream>
#include <stack>
#include <vector>
#include <unordered_map>
using namespace std;

// Define production rules for a simple grammar


vector<string> productions = {"E->E+T", "E->T", "T->T*F", "T->F", "F->(E)", "F->id"};

// Define a parsing table (for simplicity, hardcoded in this example)


unordered_map<string, unordered_map<string, string>> parsingTable = {
{"E", {{"(", "shift T"}, {"id", "shift T"}}},
{"T", {{"(", "shift F"}, {"id", "shift F"}}},
{"F", {{"(", "shift (E)"}, {"id", "shift id"}}}};

// Perform shift operation


void shift(stack<string> &parseStack, vector<string> &input)
{
parseStack.push(input[0]);
input.erase(input.begin());
}

// Perform reduce operation


void reduce(stack<string> &parseStack, string production)
{
// Get the right-hand side of the production
size_t arrowPos = production.find("->");
string rhs = production.substr(arrowPos + 2);

// Pop symbols from the stack based on the length of the rhs
for (size_t i = 0; i < rhs.size(); ++i)
{
parseStack.pop();
}

// Push the non-terminal on the left-hand side of the production


parseStack.push(production.substr(0, arrowPos));
}
// Perform parsing
void parse(stack<string> &parseStack, vector<string> &input)
{
while (!input.empty())
{
string stackTop = parseStack.top();
string nextInput = input[0];

// Check if the top of the stack and the next input form a valid pair in the parsing table
if (parsingTable[stackTop].find(nextInput) != parsingTable[stackTop].end())
{
// Valid pair, perform the specified action
string action = parsingTable[stackTop][nextInput];
if (action.substr(0, 5) == "shift")
{
shift(parseStack, input);
}
else if (action.substr(0, 5) == "reduce")
{
reduce(parseStack, action.substr(6));
}
}
else
{
cout << "Error: Invalid pair (" << stackTop << ", " << nextInput << ")" << endl;
break;
}

// Display the current state of the stack and input


cout << "Stack: ";
stack<string> tempStack = parseStack;
while (!tempStack.empty())
{
cout << tempStack.top() << " ";
tempStack.pop();
}
cout << "\tInput: ";
for (const auto &symbol : input)
{
cout << symbol << " ";
}
cout << endl;
}
}

int main()
{
// Input string for parsing
vector<string> input = {"id", "+", "id", "*", "id", "$"};

// Stack for the parse


stack<string> parseStack;

// Push the start symbol onto the stack


parseStack.push("E");

// Perform parsing
parse(parseStack, input);

return 0;
}

Output:
Experiment No – 9

Aim:
Write a program to find out the FIRST of the Non‐terminals in a grammar.

Theory:
The concept of "First" in the context of formal language theory and context-free grammars
refers to the set of terminal symbols that can begin a string derived from a given symbol
(terminal or non-terminal). The First set is a crucial component in various parsing algorithms
and is used for constructing predictive parsing tables. Here's the theory on the "First" of non-
terminals in a grammar:

1. Definition of First:
o The First(X) set for a symbol X is defined as the set of terminal symbols that
can appear as the first symbol in some string derivable from X in a single
derivation step.
2. Calculation of First Sets:
o The First sets are calculated based on the production rules of a grammar. The
process involves considering all possible derivations from a non-terminal and
identifying the terminal symbols that can appear as the first symbol.
3. Rules for Calculating First Sets:
o For terminals, First(T) is {T}.
o For non-terminals, First(N) includes the First sets of the alternatives in its
production rules. If an alternative can produce an empty string, the First set of
the next symbol in the production is also included.
4. Application in LL Parsing:
o First sets are fundamental in LL parsing algorithms, particularly in the
construction of parsing tables. In LL parsers, the First set is used to predict
which production to apply based on the next input symbol.
5. Handling ε-Productions:
o If a non-terminal can derive an empty string (ε), the First set of that non-
terminal includes ε as well. This is because ε can be the first symbol in the
derived string.
Source Code:
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <vector>

using namespace std;

// Data structure to represent a production rule


struct Production
{
string nonTerminal;
string expression;
};

// Function to calculate the FIRST set for a non-terminal


unordered_set<char> calculateFirst(const string &nonTerminal, const
vector<Production> &productions, unordered_map<string, unordered_set<char>>
&firstCache)
{
// If FIRST set for the non-terminal is already calculated, return it
if (firstCache.find(nonTerminal) != firstCache.end())
{
return firstCache[nonTerminal];
}

unordered_set<char> firstSet;

for (const Production &production : productions)


{
if (production.nonTerminal == nonTerminal)
{
char firstSymbol = production.expression[0];

if (isalpha(firstSymbol) && islower(firstSymbol))


{
firstSet.insert(firstSymbol);
}
else if (isalpha(firstSymbol) && isupper(firstSymbol))
{
unordered_set<char> firstSetOfSymbol =
calculateFirst(string(1, firstSymbol), productions, firstCache);
firstSet.insert(firstSetOfSymbol.begin(),
firstSetOfSymbol.end());
}
else if (firstSymbol == '\0')
{
firstSet.insert('\0');
}
}
}

// Cache the calculated FIRST set for future use


firstCache[nonTerminal] = firstSet;

return firstSet;
}

int main()
{
// Define the grammar with production rules
vector<Production> productions = {
{"S", "aAB"},
{"A", "b"},
{"A", ""},
{"B", "cC"},
{"C", "d"},
{"C", ""}};

// Cache for storing calculated FIRST sets


unordered_map<string, unordered_set<char>> firstCache;

// Calculate FIRST sets for each non-terminal


cout << "FIRST sets:" << endl;
for (const Production &production : productions)
{
string nonTerminal = production.nonTerminal;
unordered_set<char> firstSet = calculateFirst(nonTerminal,
productions, firstCache);

cout << nonTerminal << ": { ";


for (char symbol : firstSet)
{
cout << symbol << ' ';
}
cout << '}' << endl;
}

return 0;
}
Output:
Experiment No – 10

Aim:
Write a program to check whether a grammar is operator precedent.

Theory:
Operator precedence is a concept in formal language theory and compiler design that defines
the priority of different operators in expressions. It determines the order in which operators
are evaluated when they appear together in an expression. This concept is crucial for parsing
expressions and generating the correct execution order in programming languages. Here is
the theory on operator precedence:

1. Definition of Operator Precedence:


o Operator precedence is a set of rules that dictate the order in which operators
of different types are evaluated within an expression. It avoids ambiguity in
parsing and ensures a consistent interpretation of expressions.
2. Purpose of Operator Precedence:
o The main purpose of operator precedence is to establish a hierarchy among
operators, indicating which operations should be performed first. For example,
multiplication is typically given higher precedence than addition, so
multiplication is performed before addition in an expression like a * b + c.
3. Associativity:
o In addition to precedence, associativity defines the order of evaluation for
operators of the same precedence level. Operators can be left-associative
(evaluated left-to-right) or right-associative (evaluated right-to-left).
4. Operator Precedence in Programming Languages:
o Different programming languages may have different rules for operator
precedence. For example, in C-based languages, multiplication has higher
precedence than addition, whereas in some other languages, the rules might be
different.
5. Common Precedence Levels:
o Common precedence levels include arithmetic operators (e.g., *, /, %), relational
operators (e.g., <, >, ==), logical operators (e.g., &&, ||), and assignment
operators. Parentheses can be used to override default precedence and force a
specific order of evaluation.
Source Code:
#include <iostream>
#include <vector>
#include <unordered_set>
#include <stack>

using namespace std;

// Data structure to represent a production rule


struct Production
{
string left;
string right;
};

// Function to check if the grammar is operator precedence


bool isOperatorPrecedence(const vector<Production> &productions)
{
unordered_set<string> terminals;
unordered_set<string> nonTerminals;
unordered_set<string> operators;

// Collect terminals, non-terminals, and operators


for (const Production &production : productions)
{
nonTerminals.insert(production.left);
for (char symbol : production.right)
{
string symbolStr(1, symbol);
if (isalpha(symbol) && islower(symbol))
{
terminals.insert(symbolStr);
}
else
{
operators.insert(symbolStr);
}
}
}

// Check for overlapping symbols between terminals and operators


for (const string &terminal : terminals)
{
if (operators.find(terminal) != operators.end())
{
return false;
}
}
// Check if the grammar is operator precedence
for (const Production &production : productions)
{
string right = production.right;
if (right.length() >= 2 && isalpha(right[0]) && isalpha(right[1]) &&
isupper(right[1]))
{
return false; // Invalid adjacent non-terminals
}
}

return true;
}

int main()
{
// Define the grammar with production rules
vector<Production> productions = {
{"E", "E+T"},
{"E", "T"},
{"T", "T*F"},
{"T", "F"},
{"F", "(E)"},
{"F", "id"}};

// Check if the grammar is operator precedence


if (isOperatorPrecedence(productions))
{
cout << "The grammar is operator precedence." << endl;
}
else
{
cout << "The grammar is not operator precedence." << endl;
}

return 0;
}

Output:

You might also like