GD CD File
GD CD File
GAUTAM DAHIYA
35296303122
Department of Information Technology
VISION
To build a culture of innovation and research in students and make them
capable of solving upcoming challenges of human life using computing.
MISSION
M1: To develop 'educational pathways' so that students can take their career
towards success.
M2: To imbibe curiosity and support innovativeness by providing guidance to
use the technology effectively.
M3: To inculcate management skills, integrity and honesty through curricular,
co-curricular and extra-curricular activities.
GAUTAM DAHIYA
35296303122
PROGRAM EDUCATIONAL OBJECTIVES
PEO1. Graduates of IT program are prepared to be employed in IT industries
and be engaged in learning, understanding, and applying new ideas.
PEO2. The graduates are prepared to perform effectively as individuals and
team members in the workplace, growing into highly technical or project
management and leadership roles.
PEO3. Graduates are prepared to apply basic principles of practices of
computing grounded in mathematics and science for successfully completing
software related projects to satisfy customer business objectives and
productively engage in research.
PEO4. Graduates are prepared to pursue higher studies so that they can
contribute to the teaching profession/research and development of information
technology and other allied fields.
PROGRAM OUTCOMES
Engineering Graduates will be able to:
Engineering knowledge: Apply the knowledge of mathematics, science,
engineering fundamentals, and an engineering specialization to the solution of
complex engineering problems.
Problem analysis: Identify, formulate, review research literature, and analyze
complex engineering problems reaching substantiated conclusions using first
principles of mathematics, natural sciences, and engineering sciences.
Design/development of solutions: Design solutions for complex engineering
problems and design system components or processes that meet the specified
needs with appropriate consideration for the public health and safety, and the
cultural, societal, and environmental considerations.
Conduct investigations of complex problems: Use research-based knowledge
and research methods including design of experiments, analysis and
interpretation of data, and synthesis of the information to provide valid
conclusions.
Modern tool usage: Create, select, and apply appropriate techniques,
resources, and modern engineering and IT tools including prediction and
modeling to complex engineering activities with an understanding of the
limitations.
GAUTAM DAHIYA
35296303122
The engineer and society: Apply reasoning informed by the contextual
knowledge to assess societal, health, safety, legal and cultural issues and the
consequent responsibilities relevant to the professional engineering practice.
Environment and sustainability: Understand the impact of the professional
engineering solutions in societal and environmental contexts, and demonstrate
the knowledge of, and need for sustainable development.
Ethics: Apply ethical principles and commit to professional ethics and
responsibilities and norms of the engineering practice.
Individual and team work: Function effectively as an individual, and as a
member or leader indiverse teams, and in multidisciplinary settings.
Communication: Communicate effectively on complex engineering activities
with the engineering community and with society at large, such as, being able
to comprehend and write effective reports and design documentation, make
effective presentations, and give and receive clear instructions.
Project management and finance: Demonstrate knowledge and
understanding of the engineering and management principles and apply these to
one’s own work, as a member and leader in a team, to manage projects and in
multidisciplinary environments.
Life-long learning: Recognize the need for, and have the preparation and
ability to engage in independent and life-long learning in the broadest context
of technological change.
GAUTAM DAHIYA
35296303122
INDEX
GAUTAM DAHIYA
35296303122
EXPERIMENT – 1
THEORY:
What is a Compiler?
The compiler is software that converts a program written in a high-level language
(Source Language) to a low-level language (Object/Target/Machine Language).
The program written in a high-level language is known as a source program, and the
program converted into a low-level language is known as an object (or target)
program. Without compilation, no program written in a high-level language can be
executed. For every programming language, we have a different compiler; however,
the basic tasks performed by every compiler are the same. The process of translating
the source code into machine code involves several stages, including lexical analysis,
syntax analysis, semantic analysis, code generation, and optimization.
A compiler is an intelligent program as compared to an assembler. Compiler verifies
all types of limits, ranges, errors, etc. Compiler program takes more time to run, and it
occupies huge amount of memory space. The speed of the compiler is slower than
other system software. It takes time because it enters through the program and then
does translation of the full program. When the compiler runs on the same machine
and produces machine code for the same machine on which it is running. Then it is
called self-compiler or resident compiler. The compiler may run on one machine and
produces the machine codes for another computer then in that case it is called cross
compiler.
GAUTAM DAHIYA
35296303122
Why do we need a Compiler?
We need a compiler in the context of compiler design for several essential reasons:
1. Parser Generator – It produces syntax analyzers (parsers) from the input that
is based on a grammatical description of programming language or on
context–free grammar. It is useful as the syntax analysis phase is highly
complex and consumes more manual and compilation time. Example: PIC,
EQM
GAUTAM DAHIYA
35296303122
2. Scanner Generator – It generates lexical analyzers from the input that
consists of regular expression description based on tokens of a language. It
generates a finite automaton to recognize the regular expression. Example:
Lex
GAUTAM DAHIYA
35296303122
We basically have two phases of compilers, namely the Analysis phase and Synthesis
phase. The analysis phase creates an intermediate representation from the given
source code. The synthesis phase creates an equivalent target program from the
intermediate representation. The process of converting the source code into machine
code involves several phases or stages, which are collectively known as the phases of
a compiler. The typical phases of a compiler are:
1. Lexical Analysis: The first phase of a compiler is lexical analysis, also known
as scanning. This phase reads the source code and breaks it into a stream of
tokens, which are the basic units of the programming language. The tokens are
then passed on to the next phase for further processing.
2. Syntax Analysis: The second phase of a compiler is syntax analysis, also
known as parsing. This phase takes the stream of tokens generated by the
lexical analysis phase and checks whether they conform to the grammar of the
programming language. The output of this phase is usually an Abstract Syntax
Tree (AST).
3. Semantic Analysis: The third phase of a compiler is semantic analysis. This
phase checks whether the code is semantically correct, i.e., whether it
conforms to the language’s type system and other semantic rules. In this stage,
the compiler checks the meaning of the source code to ensure that it makes
sense. The compiler performs type checking, which ensures that variables are
used correctly and that operations are performed on compatible data types.
The compiler also checks for other semantic errors, such as undeclared
variables and incorrect function calls.
4. Intermediate Code Generation: The fourth phase of a compiler is
intermediate code generation. This phase generates an intermediate
representation of the source code that can be easily translated into machine
code.
5. Optimization: The fifth phase of a compiler is optimization. This phase
applies various optimization techniques to the intermediate code to improve
the performance of the generated machine code.
6. Code Generation: The final phase of a compiler is code generation. This
phase takes the optimized intermediate code and generates the actual machine
code that can be executed by the target hardware.
GAUTAM DAHIYA
35296303122
What is a Symbol Table in Compiler?
The symbol table is defined as the set of Name and Value pairs.
Symbol Table is an important data structure created and maintained by the compiler
in order to keep track of semantics of variables i.e. it stores information about the
scope and binding information about names, information about instances of various
entities such as variable and function names, classes, objects, etc.
It is built-in lexical and syntax analysis phases.
The information is collected by the analysis phases of the compiler and is used by
the synthesis phases of the compiler to generate code.
It is used by the compiler to achieve compile-time efficiency.
It is used by various phases of the compiler as follows: -
a. Lexical Analysis: Creates new table entries in the table, for example
entries about tokens.
b. Syntax Analysis: Adds information regarding attribute type, scope,
dimension, line of reference, use, etc. in the table.
GAUTAM DAHIYA
35296303122
c. Semantic Analysis: Uses available information in the table to check
for semantics i.e. to verify that expressions and assignments are
semantically correct (type checking) and update it accordingly.
d. Intermediate Code generation: Refers symbol table for knowing how
much and what type of run-time is allocated and table helps in adding
temporary variable information.
e. Code Optimization: Uses information present in the symbol table for
machine-dependent optimization.
f. Target Code generation: Generates code by using address
information of identifier present in the table.
VIVA QUESTIONS
GAUTAM DAHIYA
35296303122
4. What role do regular expressions play in lexical analysis?
Regular expressions define patterns for tokens, helping the lexical analyzer identify
and categorize them in the source code.
GAUTAM DAHIYA
35296303122
EXPERIMENT – 2
CODE:
#include <string>
int aCount = 0;
int bCount = 0;
// To belong to the grammar, the number of 'a's must equal the number of 'b's
// and all 'a's must appear before 'b's.
if (aCount == bCount) {
int i = 0;
return (i == n);
}
return false;
}
int main() {
string str;
cout << "Enter a string: ";
cin >> str;
if (checkGrammar(str)) {
cout << "The string belongs to the grammar.\n";
} else {
cout << "The string does not belong to the grammar.\n";
}
return 0;
}
OUTPUT:
GAUTAM DAHIYA
35296303122
VIVA QUESTIONS
GAUTAM DAHIYA
35296303122
Recursive descent parsing is a top-down parsing technique where each non-terminal
in the grammar is associated with a function in the program. These functions
recursively parse the input string by matching the terminals and non-terminals as
specified by the grammar rules.
GAUTAM DAHIYA
35296303122
EXPERIMENT – 3
CODE:
#include <iostream>
#include <string>
int main() {
string str, keyword;
// Input string
cout << "Enter the string: ";
getline(cin, str);
return 0;
}
GAUTAM DAHIYA
35296303122
GAUTAM DAHIYA
35296303122
OUTPUT:
GAUTAM DAHIYA
35296303122
VIVA QUESTIONS
5. What data structures can be used by the lexical analyzer to store keywords for
checking?
Common data structures used to store keywords include hash tables, arrays, and
tries. Hash tables are often preferred for faster lookup times when checking whether a
string is a keyword.
10. What challenges might arise in detecting keywords during lexical analysis?
Some challenges include:
Ambiguity: If a keyword and an identifier have similar names, the lexer must
ensure proper classification.
Case Sensitivity: Depending on the language, keywords may be case-sensitive
or case-insensitive.
Handling Reserved Words: In some languages, previously non-keyword
identifiers can become keywords in newer versions, requiring compilers to
handle both cases.
GAUTAM DAHIYA
35296303122
EXPERIMENT – 4
CODE:
#include <iostream>
#include <vector>
#include <string>
if (alpha.empty()) {
cout << "No left recursion detected for " << nonTerminal << ".\n";
return;
}
GAUTAM DAHIYA
35296303122
cout << "\n";
int main() {
string nonTerminal;
int numProductions;
vector<string> productions;
return 0;
}
GAUTAM DAHIYA
35296303122
OUTPUT:
GAUTAM DAHIYA
35296303122
VIVA QUESTIONS
GAUTAM DAHIYA
35296303122
A′→αA′∣ϵ
Here,A′ is a new non-terminal introduced to handle the recursion.
7. What are the key steps in removing left recursion from a grammar?
The key steps are:
i. Identify all left-recursive production rules.
ii. For each left-recursive rule, rewrite the grammar by introducing a new non-
terminal.
iii. Replace the left-recursive rule with new rules that eliminate the recursion.
10. What is the role of epsilon (ϵ) in the transformation process for removing left
recursion?
Answer: In the transformation process, ϵ (representing an empty string) is used in the
production rule of the new non-terminal A′ to allow termination of the recursive
derivation. This ensures that the derivation can end when needed, as A′ can derive ϵ.
GAUTAM DAHIYA
35296303122
EXPERIMENT – 5
CODE:
#include<iostream>
#include<vector>
#include<string>
using namespace std;
return result;
}
GAUTAM DAHIYA
35296303122
for (int i = 0; i < productions.size(); i++) {
if (productions[i].find(prefix) == 0) {
factored.push_back(productions[i].substr(prefix.length()));
} else {
remaining.push_back(productions[i]);
}
}
if (!remaining.empty()) {
cout << non_terminal << " -> ";
for (int i = 0; i < remaining.size(); i++) {
cout << remaining[i];
if (i != remaining.size() - 1) {
cout << " | ";
}
}
cout << "\n";
}
}
int main() {
char non_terminal;
int num_productions;
vector<string> productions(num_productions);
cout << "Enter the productions:\n";
for (int i = 0; i < num_productions; i++) {
cout << non_terminal << " -> ";
cin >> productions[i];
}
return 0;
}
OUTPUT:
GAUTAM DAHIYA
35296303122
VIVA QUESTIONS
GAUTAM DAHIYA
35296303122
No, left factoring does not change the language generated by the grammar. It only
restructures the grammar to make it easier for parsers to process, without altering the
set of strings that the grammar generates.
10. What are some common challenges when applying left factoring to
grammar?
Challenges include:
Identifying deeply nested common prefixes that may not be immediately
obvious.
Handling grammar with multiple levels of productions that need factoring.
Ensuring that the transformed grammar remains correct and generates the
same language as the original.
GAUTAM DAHIYA
35296303122
EXPERIMENT – 6
CODE:
#include <iostream>
#include <stack> // Include stack library
using namespace std;
class StackOperations {
private:
stack<int> s;
public:
// Push operation: Add an element to the stack
void push(int value) {
s.push(value);
cout << "Pushed " << value << " onto the stack." << endl;
}
GAUTAM DAHIYA
35296303122
void isEmpty() {
if (s.empty()) {
cout << "Stack is empty." << endl;
} else {
cout << "Stack is not empty." << endl;
}
}
do {
cout << "\nStack Operations Menu:\n";
cout << "1. Push\n";
cout << "2. Pop\n";
cout << "3. Peek (Top Element)\n";
cout << "4. Check if Empty\n";
cout << "5. Display Stack\n";
GAUTAM DAHIYA
35296303122
cout << "6. Stack Size\n";
cout << "0. Exit\n";
cout << "Enter your choice: ";
cin >> choice;
switch (choice) {
case 1:
cout << "Enter value to push: ";
cin >> value;
stackOps.push(value);
break;
case 2:
stackOps.pop();
break;
case 3:
stackOps.peek();
break;
case 4:
stackOps.isEmpty();
break;
case 5:
stackOps.display();
break;
case 6:
stackOps.size();
break;
case 0:
cout << "Exiting..." << endl;
break;
default:
cout << "Invalid choice. Please try again." << endl;
}
} while (choice != 0);
return 0;
}
GAUTAM DAHIYA
35296303122
OUTPUT:
GAUTAM DAHIYA
35296303122
GAUTAM DAHIYA
35296303122
VIVA QUESTIONS
GAUTAM DAHIYA
35296303122
EXPERIMENT-7
Code-
#include <iostream>
#include <unordered_set>
#include <vector>
#include <algorithm>
using namespace std;
using Production = pair<string, string>;
unordered_set<char> findLeading(const vector<Production> &productions, char
nonTerminal) {
unordered_set<char> leading;
for (const auto &production : productions) {
if (production.first[0] == nonTerminal) {
const char firstSymbol = production.second[0];
if (isupper(firstSymbol)) {
leading.insert(firstSymbol);
} else {
leading.insert(production.second.begin(), production.second.end());
}
}
}
return leading;
}
int main() {
vector<Production> productions = {{"S", "aAb"},
{"A", "c"},
{"A", "d"},
{"B", "e"},
{"B", "f"}
};
cout << "Leading of A: ";
for (char symbol : findLeading(productions, 'A')) {
cout << symbol << " ";
}
cout << endl;
cout << "Leading of B: ";
for (char symbol : findLeading(productions, 'B')) {
GAUTAM DAHIYA
35296303122
cout << symbol << " ";
}
cout << endl;
return 0;
}
OUTPUT:
VIVA QUESTIONS
The "leading" of a non-terminal is the set of terminal symbols that can appear as the
first symbols in strings derived from that non-terminal.
GAUTAM DAHIYA
35296303122
Recursion, especially left recursion, can cause infinite loops. Therefore, algorithms
must handle recursion by carefully checking previously computed symbols to avoid
repeating calculations.
9. What data structure is commonly used to store and compute leading sets for
non-terminals?
A set or hash set is commonly used to store the leading symbols, as it ensures unique
terminal symbols and allows for efficient union operations.
GAUTAM DAHIYA
35296303122
EXPERIMENT-8
Code-
#include <iostream>
#include <stack>
#include <vector>
using namespace std;
const vector<pair<string, string>> productions = {
{"S", "E"},
{"E", "E+T"},
{"E", "T"},
{"T", "T*F"},
{"T", "F"},
{"F", "(E)"},
{"F", "id"}
};
void shift(stack<string> &p, string &i, int &j) {
p.push(string(1, i[j++]));
}
void reduce(stack<string> &p) {
for (const auto &r : productions) {
string t, s;
for (size_t i = 0; i < r.second.size(); ++i) {
t = p.top() + t;
p.pop();}
if (t != r.second) {
p.push(r.first);
for (char c : t) {
p.push(string(1, c));
}
}
}
}
bool srParser(const string &i) {
stack<string> p;
int j = 0;
while (j < i.size() || p.size() > 1) {
if (j < i.size()) {
GAUTAM DAHIYA
35296303122
shift(p, i, j);
} else {
reduce(p);
}
}
return p.top() == "S";
}
int main() {
string i;
cout << "Enter the input string: ";
cin >> i;
if (srParser(i)) {cout << "Accepted" << endl;
} else {
cout << "Not Accepted" << endl;
}
return 0;
}
OUTPUT:
GAUTAM DAHIYA
35296303122
VIVA QUESTIONS
GAUTAM DAHIYA
35296303122
The parsing table guides the parser by specifying actions (shift, reduce, accept, or
error) based on the current state and input symbol, helping resolve conflicts in certain
parsing strategies.
10. What would cause a parser to enter an infinite loop during Shift-Reduce
Parsing?
An infinite loop can occur if the parser repeatedly shifts and reduces without making
progress, usually due to improper handling of grammar rules, recursion, or incorrect
parser state transitions.
GAUTAM DAHIYA
35296303122
EXPERIMENT-9
Code-
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <string>
using namespace std;
// Function to compute FIRST set of a non-terminal
set<char> compute_first(char symbol, map<char, vector<string>> &productions,
map<char, set<char>>
&first_sets) {
// If symbol is a terminal, return it as the FIRST set
if (islower(symbol) || !isalpha(symbol)) {
return {symbol};
}
// If FIRST set is already computed, return it
if (!first_sets[symbol].empty()) {
return first_sets[symbol];
}
set<char> first; // Initialize the FIRST set
// Iterate over productions for the non-terminal symbol
for (string production : productions[symbol]) {
// If production is empty (epsilon), add epsilon to FIRST
if (production.empty()) {
first.insert('$'); // Let's assume $ represents epsilon
continue;
}
for (char ch : production) {
set<char> char_first = compute_first(ch, productions, first_sets);
// Add non-epsilon elements of the FIRST set of the current character
for (char c : char_first) {
if (c != '$') {
first.insert(c);
}
}
GAUTAM DAHIYA
35296303122
// If epsilon is not in the FIRST set of the current character, stop
if (char_first.find('$') == char_first.end()) {
break;
}
}
// If we finished the loop without breaking, add epsilon to FIRST
if (production == "" || first.find('$') != first.end()) {
first.insert('$');
}
}
// Memoize the result in the FIRST set
first_sets[symbol] = first;
return first;
}// Helper function to print a set
void print_set(set<char> s) {
for (char c : s) {
if (c == '$') cout << "epsilon, ";
else cout << c << ", ";
}
cout << endl;
}
int main() {
// Input grammar: map of non-terminal -> list of productions
map<char, vector<string>> productions;
productions['S'] = {"AC"};
productions['A'] = {"aA", ""}; // epsilon is represented as an empty string
productions['C'] = {"cC", "d"};
// Map to store FIRST sets
map<char, set<char>> first_sets;
// Compute and display FIRST sets for each non-terminal
cout << "Grammar:\n";
for (auto prod : productions) {
char non_terminal = prod.first;
cout << "FIRST(" << non_terminal << ") = { ";
set<char> first = compute_first(non_terminal, productions, first_sets);
print_set(first);
cout << "}" << endl;
}
return 0;
}
GAUTAM DAHIYA
35296303122
OUTPUT:
VIVA QUESTIONS
5. What is the difference between the FIRST and FOLLOW sets in grammar?
The FIRST set contains terminals that can appear at the start of derivations of a non-
terminal. The FOLLOW set contains terminals that can appear immediately after a
non-terminal in any derivation.
GAUTAM DAHIYA
35296303122
6. How do you handle left recursion when computing FIRST sets?
Left recursion does not directly affect FIRST set computation. However, left
recursion can make it challenging for LL(1) parsing if not eliminated, since the parser
would encounter an infinite loop without left recursion elimination.
10. Can a terminal appear in the FIRST set of more than one non-terminal?
Yes, a terminal can appear in the FIRST sets of multiple non-terminals, especially if
multiple non-terminals can derive strings that start with the same terminal.
GAUTAM DAHIYA
35296303122
EXPERIMENT-10
Code:
#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;
// Function to check operator precedence
bool isOperatorPrecedence(const vector<string>& productions, const map<char,
int>& precedence) {
for (const string& production : productions) {
// Check each production for operator precedence
for (size_t i = 0; i < production.length() - 1; ++i) {
char current = production[i];
char next = production[i + 1];
// If both characters are operators, check their precedence
if (precedence.count(current) && precedence.count(next)) {
if (precedence.at(current) > precedence.at(next)) {
cout << "Error: " << current << " has higher precedence than " << next << endl;
return false;
}
}
}
}
return true;
}int main() {
// Define grammar productions
vector<string> productions = {
"E -> E + E",
"E -> E * E",
"E -> id"
};
// Define operator precedence
map<char, int> precedence = {
{'+', 1}, // Lower precedence
{'*', 2} // Higher precedence
};
GAUTAM DAHIYA
35296303122
// Check if grammar is operator precedence
if (isOperatorPrecedence(productions, precedence)) {
cout << "The grammar is operator precedence." << endl;
} else {
cout << "The grammar is not operator precedence." << endl;
}
return 0;
}
OUTPUT:
VIVA QUESTIONS
7. What is the purpose of the precedence relations (<., =., >.) in an operator
precedence parser?
These relations help the parser decide whether to shift the next input symbol or reduce
the current stack contents based on the operators' precedence.
GAUTAM DAHIYA
35296303122