CD Lab
CD Lab
Shift-reduce parsing is based on a bottom-up parsing technique, where the parser starts with the
input tokens and aims to construct a parse tree by applying a set of grammar rules. The two main
actions involved in shift-reduce parsing are “shift” and “reduce.”
Shift: During the shift operation, the parser reads the next input token and pushes it onto a stack.
The parser maintains a buffer to hold the remaining input tokens. The shift operation moves the
parser’s current position to the right in the input stream.
Reduce: The reduce operation applies a grammar rule to the tokens on top of the stack. If the tokens
on the stack match the right-hand side of a grammar rule, they are replaced with the corresponding
non-terminal symbol from the left-hand side of that rule. The reduce operation moves the parser’s
current position to the left in the input stream.
Accept: If the stack contains the start symbol only and the input buffer is empty at the same time
then that action is called accept.
Error: A situation in which the parser cannot either shift or reduce the symbols, it cannot even
perform accept action then it is called an error action.
Shift-reduce parsing continues until it reaches the end of the input stream and constructs a valid
parse tree. It employs a parsing table, often generated using techniques like the LR(1) or LALR(1)
parsing algorithm, to determine the shift or reduce action to take at each step.
Example 1:
Code
#include <iostream>
#include <stack>
#include <vector>
{"S", "E"},
{"E", "E+T"},
{"E", "T"},
{"T", "T*F"},
{"T", "F"},
{"F", "(E)"},
{"F", "id"}
};
p.push(string(1, i[j++]));
string t, s;
t = p.top() + t;
p.pop();
if (t != r.second) {
p.push(r.first);
for (char c : t) {
p.push(string(1, c));
stack<string> p;
int j = 0;
if (j < i.size()) {
shift(p, i, j);
} else {
reduce(p);
int main() {
string i;
cin >> i;
if (srParser(i)) {
} else {
return 0;
Output:
Experiment -7. Write a program to find out the leading of the non‐terminals in a grammar.
Code
#include <iostream>
#include <unordered_set>
#include <vector>
#include <algorithm>
unordered_set<char> leading;
if (production.first[0] == nonTerminal) {
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"}
};
return 0;
#include <iostream>
#include <stack>
s.push(value);
cout << "Pushed " << value << " onto the stack." << endl;
if (!s.empty()) {
s.pop();
cout << "Popped " << topElement << " from the stack." << endl;
} else {
cout << "Stack is empty. Cannot perform pop operation." << endl;
void displayOperation(stack<int> s) {
while (!s.empty()) {
s.pop();
}
int main() {
stack<int> myStack;
// Push operation
pushOperation(myStack, 10);
pushOperation(myStack, 20);
pushOperation(myStack, 30);
displayOperation(myStack);
// Pop operation
popOperation(myStack);
popOperation(myStack);
popOperation(myStack);
pushOperation(myStack, 40);
pushOperation(myStack, 50);
displayOperation(myStack);
return 0;
}
Exp 5;-Write a program to perform Left Factoring on a Grammar.
theory ➖
Left Factoring in a Grammar:
1. Definition:
2. Common Prefixes:
non-terminal.
- For each set of productions sharing a common prefix, create a new non-
terminal.
c. Split Productions:
- Replace the original productions with the new non-terminal, creating two
sets of productions: one with the common prefix and one without.
Example:
Original Grammar:
A → αβ | αγ | δ
A → αA'
A' → β | γ | ε
Example:
Original Grammar:
E→T+E|T
T→F*T|F
F → ( E ) | id
E → T E'
E' → +E | ε
T → F T'
T' → *T | ε
F → ( E ) | id
CODE
#include <iostream>
#include <vector>
#include <string>
struct ProductionRule {
string nonTerminal;
vector<string> productions;
};
size_t j = 0;
if (!prefix.empty()) {
grammar.push_back({newNonTerminal, {rule.productions[0].substr(prefix.size())}});
int main() {
vector<ProductionRule> grammar = {
};
printGrammar(grammar);
leftFactor(grammar);
printGrammar(grammar);
return 0;
}
EXP 4:- Write a program to remove left Recursion from a Grammar.
theory :-
1. Definition:
terminal A can directly derive a string beginning with itself. The presence of left
2. Types of Recursion:
Left Recursion: A → Aα
Right Recursion: A → αA
4. Left Factoring:
Before eliminating left recursion, it's common to perform left factoring. This
(pronounced A prime).
c. Split Productions:
- A → βA'
Example:
Original Grammar:
A → Aα | β
After Transformation:
A → βA'
A' → αA' | ε
Example:
Original Grammar:
E→E+T|T
T→T*F|F
F → ( E ) | id
E → TE'
E' → +TE' | ε
T → FT'
T' → *FT' | ε
F → (E) | id
CODE
#include <iostream>
#include <vector>
#include <string>
struct ProductionRule {
string nonTerminal;
vector<string> productions;
};
vector<string> newProductions;
if (production[0] == grammar[j].nonTerminal[0]) {
newProductions.push_back(alpha + production.substr(1));
} else {
newProductions.push_back(production);
grammar[i].productions = newProductions;
vector<string> newNonTerminalProductions;
}
grammar.push_back({grammar[i].nonTerminal + "'", newNonTerminalProductions});
vector<string> newProductions;
vector<string> remainingProductions;
} else {
newProductions.push_back("epsilon");
grammar[i].productions = newProductions;
int main() {
vector<ProductionRule> grammar = {
{"E", {"E+T", "T"}},
};
printGrammar(grammar);
removeLeftRecursion(grammar);
printGrammar(grammar);
return 0;
}
Experiment 2: Write a program to check whether a string belong to grammar
Theory:
Grammar Overview:
● Production Rules: The grammar specifies a set of
production rules that define how valid strings are
formed.
● Terminals and Non-terminals: Terminals are the actual
symbols in the language (characters or tokens), while
non-terminals represent groups of symbols.
Generate a String:
o Continue the process until you generate a string of
terminals.
Example:
Consider a simple grammar:
Initial Symbol: S
Production Rules:
S → aS
S → Sb
S → ab
Now, let's check if the string "aab" belongs to this grammar:
Start:
Start with the initial symbol S.
Generate String:
Continue applying rules: aaS → aab
String Rejection:
If, during the process, you reach a point where there's no rule to apply or the
generated string doesn't match the input string, then the input string does not
belong to the grammar.
Conclusion:
Checking whether a string belongs to a grammar involves systematically
applying production rules to generate a string and comparing it with the input
string. If they match, the string belongs to the grammar; otherwise, it doesn't.
This process is fundamental to the theory of formal languages and automata.
Code:
#include <iostream>
#include <cctype>
bool isDigit(char c) {
return std::isdigit(c);
}
bool isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/';
}
if (isExpression(input)) {
std::cout << "The string belongs to the grammar.\n";
} else {
std::cout << "The string does not belong to the grammar.\n";
}
return 0;
}
Experiment 3: write a program to check whether a string include keyword or not
Theory:
Tokenization:
Tokenize the input string into individual units, such as words or symbols. This
can be done using lexical analysis techniques.
Case Sensitivity:
Decide whether the keyword matching process is case-sensitive or
case-insensitive. Some programming languages distinguish between uppercase
and lowercase letters in keywords.
Example:
Consider a simple scenario in a programming language with keywords like "if,"
"else," and "while." Given the input string "if (x > 0) {", you would:
Define Keywords:
Keywords: "if," "else," "while."
Tokenization:
Tokenize the input string: ["if", "(", "x", ">", "0", ")", "{"]
String Rejection:
If, during the process, none of the tokens match the defined keywords, the
string does not include any keywords. Alternatively, you might track the
positions of the identified keywords within the string.
Conclusion:
Checking whether a string includes a keyword involves defining a set of
keywords, tokenizing the string, and comparing the tokens with the keywords.
This process is fundamental to lexical analysis, which is a crucial step in parsing
and interpreting programming languages.
Code:
#include <iostream>
#include <algorithm>
int main() {
std::string input, keyword;
if (input.find(keyword) != std::string::npos) {
std::cout << "String includes the keyword.\n";
} else {
std::cout << "String does not include the keyword.\n";
}
return 0;
}