0% found this document useful (0 votes)
6 views26 pages

CD File

Uploaded by

ducatmaterials
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)
6 views26 pages

CD File

Uploaded by

ducatmaterials
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/ 26

Guru Tegh Bahadur Institute of Technology, Delhi

(Affiliated to Guru Gobind Singh Indraprastha University, Dwarka, New Delhi)

Department of Computer Science & Engineering

COMPILER DESIGN

CIC-351

Submitted to: Submitted by:


MS. ESHA SAXENA Name: PAVLEEN KAUR
Class: CSE-2
Enroll. No.: 10713202722

Index

S.No Name of Experiment Date T.Sign


.
1 Practice of LEX/YACC of Compiler
design.
2 Write a program to check whether a
string include keyword or not.
3 Write a program to check whether a
string contains an alphabet or not.
4 Write a program to show all the
operations of a stack.
5 Write a program to remove left recursion
from a grammar.
6 Write a program to perform left
factoring on a grammar.
7 Write a program to find out the FIRST
of the Non-terminals in a grammar.
8 Implementing Programs using Flex
Lexical analyzer tool).
9 Elaborate DAG Representation with
examples.
10

11

12

13
EXPERIMENT-01
Aim:
Practice of LEX/YACC of Compiler design.

Theory:
1) LEX (Lexical Analyzer Generator)
Purpose:
LEX is a lexical analyzer generator that takes a specification of tokens (patterns of
characters, like keywords, operators, identifiers, etc.) and generates a lexical
analyzer (or scanner). The lexical analyzer reads the input, identifies tokens, and
passes them to the parser (generated by YACC).

How LEX Works:


LEX works by specifying patterns using regular expressions. Each regular
expression describes a pattern of characters that make up a token. LEX translates
these patterns into C code for scanning text.
1. Input: A specification file containing regular expressions and actions to
perform when these patterns are matched.
2. Output: C code that implements a lexical analyzer.
3. Execution: The generated lexical analyzer reads the input stream, identifies
tokens by matching them against the patterns, and returns the tokens to the
parser.

Structure of a LEX Program:


A typical LEX program is divided into three sections:
1. Definition Section: Used for defining variables, constants, or libraries.
2. Rules Section: Contains regular expressions and associated actions.
3. User Code Section: Contains user-defined helper functions, typically in C.

Advantages of LEX:
• Automated Lexical Analysis: It simplifies the process of tokenizing input by
using regular expressions to automatically generate C code for token
recognition.
• Efficiency: LEX-generated scanners are highly efficient and fast.
2) YACC (Yet Another Compiler-Compiler)
Purpose:
YACC is a parser generator that reads a formal grammar specification and
generates C code for a parser. The parser processes tokens (provided by the lexical
analyzer) and constructs the syntactic structure of the input (typically in the form of
a parse tree or abstract syntax tree).

How YACC Works:


YACC takes a context-free grammar as input and generates a bottom-up parser
(typically a LALR(1) parser). It processes tokens provided by the lexical analyzer
(LEX) and applies grammar rules to determine if the input sequence of tokens is
syntactically valid.
1. Input: A specification file that defines the grammar rules.
2. Output: C code that implements the syntax analyzer (parser).
3. Execution: The parser reads tokens, applies grammar rules, and reports
syntax errors or builds an abstract syntax tree (AST).

Structure of a YACC Program:


A typical YACC program is divided into three sections:
• Definition Section: Declarations of tokens and types.
• Rules Section: Grammar rules, each with an associated action.
• User Code Section: Auxiliary C functions and code.

Advantages of YACC:
• Automated Parser Generation: YACC automates the process of writing
parsers, making it easier to design complex grammars.
• Error Detection: YACC can handle syntax errors efficiently by reporting
errors when tokens do not match grammar rules.
EXPERIMENT-02
Aim:
Write a program to check whether a string include keyword or not.

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

int main() {
std::string str, keyword;
std::cout << "Enter a string: ";
std::getline(std::cin, str);
std::cout << "Enter a keyword: ";
std::getline(std::cin, keyword);

if (str.find(keyword) != std::string::npos) {
std::cout << "The keyword is present in the string." << std::endl;
} else {
std::cout << "The keyword is not present in the string." << std::endl;
}

return 0;
}

Output:

EXPERIMENT-03
Aim:
Write a program to check whether a string contains an alphabet or not.

Code:
#include <iostream>
#include <string>
#include <cctype> // For isalpha function

int main() {
std::string str;
bool hasAlphabet = false;

std::cout << "Enter a string: ";


std::getline(std::cin, str);

for (char c : str) {


if (isalpha(c)) {
hasAlphabet = true;
break;
}
}

if (hasAlphabet) {
std::cout << "The string contains at least one alphabet character." << std::endl;
} else {
std::cout << "The string does not contain any alphabet characters." <<
std::endl;
}

return 0;
}

Output:

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

Code:
#include <iostream>
#include <stack>

int main() {
std::stack<int> stack;
int choice, value;

do {
std::cout << "\nStack Operations Menu:";
std::cout << "\n1. Push";
std::cout << "\n2. Pop";
std::cout << "\n3. Top";
std::cout << "\n4. Is Empty";
std::cout << "\n5. Size";
std::cout << "\n6. Exit";
std::cout << "\nEnter your choice: ";
std::cin >> choice;

switch (choice) {
case 1:
std::cout << "Enter value to push: ";
std::cin >> value;
stack.push(value);
std::cout << value << " pushed into the stack." << std::endl;
break;

case 2:
if (!stack.empty()) {
std::cout << "Popped value: " << stack.top() << std::endl;
stack.pop();
} else {
std::cout << "Stack is empty." << std::endl;
}
break;
case 3:
if (!stack.empty()) {
std::cout << "Top value: " << stack.top() << std::endl;
} else {
std::cout << "Stack is empty." << std::endl;
}
break;

case 4:
if (stack.empty()) {
std::cout << "Stack is empty." << std::endl;
} else {
std::cout << "Stack is not empty." << std::endl;
}
break;

case 5:
std::cout << "Stack size: " << stack.size() << std::endl;
break;

case 6:
std::cout << "Exiting..." << std::endl;
break;

default:
std::cout << "Invalid choice. Please try again." << std::endl;
break;
}
} while (choice != 6);

return 0;
}
Output:
EXPERIMENT-05
Aim:
Write a program to remove left recursion from a grammar.

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

// A structure to represent a production rule


struct Production {
char nonTerminal;
vector<string> symbols;
};

// Function to remove left recursion


vector<Production> removeLeftRecursion(const vector<Production> &
productions) {
vector<Production> newProductions;

for (const Production& prod : productions) {


vector<string> newSymbols;
vector<string> newSymbolsRec;

// Group symbols into recursive and non-recursive


for (const string&symbol: prod.symbols){
if (symbol[0]==prod.nonTerminal){
newSymbolsRec.push_back(symbol.substr(1));
} else{
newSymbols.push_back(symbol);
}
}

// If there's left recursion, modify the production


if(!newSymbolsRec.empty()){
char newNonTerminal = prod.nonTerminal+1;
newProductions.push_back({prod.nonTerminal, newSymbols});
vector<string> newRecSymbols;
for(const string& recSymbol:newSymbolsRec){
newRecSymbols.push_back(recSymbol+newNonTerminal);
}
newRecSymbols.push_back(""); //Add an epsilon production
newProductions.push_back({newNonTerminal, newRecSymbols});
}else{
newProductions.push_back(prod);
}
}
return newProductions;
}

int main(){
vector<Production> productions={
{'E',{"E+T","T"}},
{'T',{"T*F","F"}},
{'F',{"(E)","id"}}
};
vector<Production> newProductions=removeLeftRecursion(productions);

cout<<"Original Productions:"<<endl;
for(const Production& prod:productions){
cout<<prod.nonTerminal<<".>";
for(const string& symbol:prod.symbols){
cout<<symbol<<"|";
}
cout<<endl;
}

cout<<"\nProductions after removing left recursion:"<<endl;


for(const Production& prod:newProductions){
cout<<prod.nonTerminal<<".>";
for(const string&symbol:prod.symbols){
cout<<symbol<<"|";
}
cout<<endl;
}
return 0;
}
Output:

EXPERIMENT-06
Aim:
Write a program to perform left factoring on a grammar.

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

// Function to find the longest common prefix of two strings


string longestCommonPrefix(string s1, string s2) {
int len = min(s1.length(), s2.length());
string result = "";
for (int i = 0; i < len; i++) {
if (s1[i] == s2[i]) {
result += s1[i];
} else {
break;
}
}
return result;
}

// Function to perform left factoring on a grammar


void leftFactorGrammar(map<string, vector<string>> &grammar) {
map<string, vector<string>> newGrammar;

for (auto &rule : grammar) {


string nonTerminal = rule.first;
vector<string> productions = rule.second;

if (productions.size() > 1) {
string commonPrefix = productions[0];
for (int i = 1; i < productions.size(); i++) {
commonPrefix = longestCommonPrefix(commonPrefix, productions[i]);
if (commonPrefix.empty()) break;
}

// If there's a common prefix, perform left factoring


if (!commonPrefix.empty()) {
cout << nonTerminal << " -> " << commonPrefix << nonTerminal <<
"'\n";

vector<string> newProductions;
for (string &prod : productions) {
string suffix = prod.substr(commonPrefix.length());
if (suffix.empty()) {
suffix = "ε"; // ε represents an empty production
}
newProductions.push_back(suffix);
}

cout << nonTerminal << "' -> ";


for (int i = 0; i < newProductions.size(); i++) {
if (i > 0) cout << " | ";
cout << newProductions[i];
}
cout << "\n";
} else {
// If no common prefix, just print the original rule
cout << nonTerminal << " -> ";
for (int i = 0; i < productions.size(); i++) {
if (i > 0) cout << " | ";
cout << productions[i];
}
cout << "\n";
}
} else {
// Only one production, print it as is
cout << nonTerminal << " -> " << productions[0] << "\n";
}
}
}

int main() {
int n;
cout << "Enter the number of grammar rules: ";
cin >> n;

map<string, vector<string>> grammar;

// Input grammar
for (int i = 0; i < n; i++) {
string nonTerminal, arrow, production;
cout << "Enter non-terminal: ";
cin >> nonTerminal >> arrow; // Arrow input: '->'

vector<string> productions;
cout << "Enter productions (separated by space, end with newline): ";
while (cin >> production) {
productions.push_back(production);
if (cin.peek() == '\n') break; // End input when newline is encountered
}
grammar[nonTerminal] = productions;
}

cout << "\nLeft Factored Grammar:\n";


leftFactorGrammar(grammar);

return 0;
}

Output:
EXPERIMENT-07
Aim:
Write a program to find out the FIRST of the Non-terminals in a grammar.

Code:
#include <iostream>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <string>
using namespace std;
// Production rules of the grammar
unordered_map<string, vector<string>> productions;
// FIRST sets of non-terminals
unordered_map<string, unordered_set<char>> firstSets;
// Calculate the FIRST set for a non-terminal symbol
void calculateFirstSet(const string &nonTerminal)
{
if (firstSets.find(nonTerminal) != firstSets.end())
{
return; // Already calculated
}
unordered_set<char> firstSet;
for (const string &production : productions[nonTerminal])
{
char symbol = production[0];

if (isupper(symbol))
{
// Non-terminal symbol
calculateFirstSet(string(1, symbol)); // Recurse
const unordered_set<char> &subFirstSet = firstSets[string(1, symbol)];
firstSet.insert(subFirstSet.begin(), subFirstSet.end());

if (subFirstSet.find('e') != subFirstSet.end())
{
// If epsilon is in the FIRST set of the non-terminal, consider next symbol
for (size_t i = 1; i < production.size(); ++i)
{
char nextSymbol = production[i];
if (isupper(nextSymbol))
{
calculateFirstSet(string(1, nextSymbol)); // Recurse
const unordered_set<char> &nextFirstSet = firstSets[string(1,
nextSymbol)];
firstSet.insert(nextFirstSet.begin(), nextFirstSet.end());
if (nextFirstSet.find('e') == nextFirstSet.end())
{
break;
}
}
else
{
firstSet.insert(nextSymbol);
break;
}
}
}
}
else
{
// Terminal symbol
firstSet.insert(symbol);
}
}
firstSets[nonTerminal] = firstSet;
}
int main()
{
// Example grammar productions
productions["S"] = {"aBC", "b"};
productions["B"] = {"b", "C"};
productions["C"] = {"c", "e"};
// Calculate FIRST sets for each non-terminal
for (const auto &production : productions)
{
calculateFirstSet(production.first);
}
// Print the FIRST sets
for (const auto &nonTerminal : firstSets)
{
cout << "FIRST(" << nonTerminal.first << "): {";
for (char symbol : nonTerminal.second)
{
cout << symbol << " ";
}
cout << "}" << endl;
}
return 0;
}

Output:
EXPERIMENT-08

Aim:
Implementing Programs using Flex (Lexical analyzer tool).

Introduction of Flex:
Flex is a lexical analyzer generator, which is a tool for programming that
recognizes lexical patterns in the input with the help of flex specifications. Scroll
below to see the list of flex programs.

Structure of a Flex program


Definition Section
%%
Rules Section
%%
User Code
Program (a): Print ‘Hello World’
%{

#undef yywrap

#define yywrap() 1

%}

%%

[\n] { printf("Hello World\n");

%%

main()

{ yylex(); //calling the rules section

}
Program (b): Program to check if the given letter is a vowel or not.
%{

/*To find whether given letter is a vowel or not*/

#undef yywrap

#define yywrap() 1

void display(int);

%}

%%

[a|e|i|o|u|] {

int flag=1;

display(flag);

return;

.+ {

int flag=0;

display(flag);

return;

%%

void display(int flag)

{ if(flag==1)

printf("The given letter [%s] is a vowel",yytext);

else

printf("The given letter [%s] is NOT a vowel",yytext);

main()

{ printf("Enter a letter to check if it is a vowel or not");

yylex();

}

EXPERIMENT-09

Aim:
Elaborate DAG Representation with examples.

Directed Acyclic Graph :


The Directed Acyclic Graph (DAG) is used to represent the structure of basic
blocks, to visualize the flow of values between basic blocks, and to provide
optimization techniques in the basic block. To apply an optimization technique to a
basic block, a DAG is a three-address code that is generated as the result of an
intermediate code generation.
• Directed acyclic graphs are a type of data structure and they are used to
apply transformations to basic blocks.
• The Directed Acyclic Graph (DAG) facilitates the transformation of basic
blocks.
• DAG is an efficient method for identifying common sub-expressions.
• It demonstrates how the statement’s computed value is used in subsequent
statements.

Examples of directed acyclic graph:

Directed Acyclic Graph Characteristics :


A Directed Acyclic Graph for Basic Block is a directed acyclic graph with the
following labels on nodes.
• The graph’s leaves each have a unique identifier, which can be variable
names or constants.

• The interior nodes of the graph are labelled with an operator symbol.
• In addition, nodes are given a string of identifiers to use as labels for storing
the computed value.
• Directed Acyclic Graphs have their own definitions for transitive closure and
transitive reduction.
• Directed Acyclic Graphs have topological orderings defined.

Algorithm for construction of Directed Acyclic Graph :


There are three possible scenarios for building a DAG on three address codes:
Case 1 – x = y op z
Case 2 – x = op y
Case 3 – x = y

Directed Acyclic Graph for the above cases can be built as follows :

Step 1 –
• If the y operand is not defined, then create a node (y).
• If the z operand is not defined, create a node for case(1) as node(z).
Step 2 –
• Create node(OP) for case(1), with node(z) as its right child and node(OP) as
its left child (y).
• For the case (2), see if there is a node operator (OP) with one child node (y).
• Node n will be node(y) in case (3).
Step 3 –
Remove x from the list of node identifiers. Step 2: Add x to the list of attached
identifiers for node n.

Example :
T0 = a + b —Expression 1
T1 = T0 + c —-Expression 2
d = T0 + T1 —–Expression 3

Expression 1 : T0 = a + b

Expression 2: T1 = T0 + c

Expression 3 : d = T0 + T1


Example :
T1 = a + b
T2 = T1 + c
T3 = T1 x T2

Example :
T1 = a + b
T2 = a – b
T3 = T1 * T2
T4 = T1 – T3
T5 = T4 + T3

You might also like