0% found this document useful (0 votes)
7 views

CD Practical Soft Copy

The document contains multiple experiments related to programming concepts in C and C++. Experiment 1 focuses on designing a lexical analyzer to recognize identifiers, Experiment 2 checks if a given expression is a valid regular expression, Experiment 3 computes First and Follow sets for a given context-free grammar, and Experiment 4 implements a predictive parsing table for LL(1) grammar.

Uploaded by

jockyshortvlogs
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views

CD Practical Soft Copy

The document contains multiple experiments related to programming concepts in C and C++. Experiment 1 focuses on designing a lexical analyzer to recognize identifiers, Experiment 2 checks if a given expression is a valid regular expression, Experiment 3 computes First and Follow sets for a given context-free grammar, and Experiment 4 implements a predictive parsing table for LL(1) grammar.

Uploaded by

jockyshortvlogs
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 34

Experiment - 1

Q1.Write a program to Design Lexical Analyzer to recognize identifiers.


Code:-
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// Returns 'true' if the character is a DELIMITER.
bool isDelimiter(char ch)
{
if (ch == ' ' || ch == '+' || ch == '-' || ch == '*' ||
ch == '/' || ch == ',' || ch == ';' || ch == '>' ||
ch == '<' || ch == '=' || ch == '(' || ch == ')' ||
ch == '[' || ch == ']' || ch == '{' || ch == '}')
return (true);
return (false);
}
// Returns 'true' if the character is an OPERATOR.
bool isOperator(char ch)
{
if (ch == '+' || ch == '-' || ch == '*' ||
ch == '/' || ch == '>' || ch == '<' ||
ch == '=')
return (true);
return (false);
}
// Returns 'true' if the string is a VALID IDENTIFIER.
bool validIdentifier(char* str)
{if (str[0] == '0' || str[0] == '1' || str[0] == '2' ||
str[0] == '3' || str[0] == '4' || str[0] == '5' ||
str[0] == '6' || str[0] == '7' || str[0] == '8' ||
str[0] == '9' || isDelimiter(str[0]) == true)
return (false);
return (true);
}
// Returns 'true' if the string is a KEYWORD.
bool isKeyword(char* str)
{
if (!strcmp(str, "if") || !strcmp(str, "else") ||
!strcmp(str, "while") || !strcmp(str, "do") ||
!strcmp(str, "break") ||
!strcmp(str, "continue") || !strcmp(str, "int")
|| !strcmp(str, "double") || !strcmp(str, "float")
|| !strcmp(str, "return") || !strcmp(str, "char")
|| !strcmp(str, "case") || !strcmp(str, "char")
|| !strcmp(str, "sizeof") || !strcmp(str, "long")
|| !strcmp(str, "short") || !strcmp(str, "typedef")
|| !strcmp(str, "switch") || !strcmp(str, "unsigned")
|| !strcmp(str, "void") || !strcmp(str, "static")
|| !strcmp(str, "struct") || !strcmp(str, "goto"))
return (true);
return (false);
}
// Returns 'true' if the string is an INTEGER.
bool isInteger(char* str)
{
int i, len = strlen(str);
if (len == 0)
return (false);
for (i = 0; i < len; i++) {
if (str[i] != '0' && str[i] != '1' && str[i] != '2'
&& str[i] != '3' && str[i] != '4' && str[i] != '5'
&& str[i] != '6' && str[i] != '7' && str[i] != '8'
&& str[i] != '9' || (str[i] == '-' && i > 0))
return (false);
}
return (true);
}
// Returns 'true' if the string is a REAL NUMBER.
bool isRealNumber(char* str)
{
int i, len = strlen(str);
bool hasDecimal = false;
if (len == 0)
return (false);
for (i = 0; i < len; i++) {
if (str[i] != '0' && str[i] != '1' && str[i] != '2'
&& str[i] != '3' && str[i] != '4' && str[i] != '5'
&& str[i] != '6' && str[i] != '7' && str[i] != '8'
&& str[i] != '9' && str[i] != '.' ||
(str[i] == '-' && i > 0))
return (false);
if (str[i] == '.')
hasDecimal = true;
}
return (hasDecimal);
}
// Extracts the SUBSTRING.
char* subString(char* str, int left, int right)
{
int i;
char* subStr = (char*)malloc(
sizeof(char) * (right - left + 2));
for (i = left; i <= right; i++)
subStr[i - left] = str[i];
subStr[right - left + 1] = '\0';
return (subStr);
}
// Parsing the input STRING.
void parse(char* str)
{
int left = 0, right = 0;
int len = strlen(str);
while (right <= len && left <= right) {
if (isDelimiter(str[right]) == false)
right++;
if (isDelimiter(str[right]) == true && left == right) {
if (isOperator(str[right]) == true)
printf("'%c' IS AN OPERATOR\n", str[right]);
right++;
left = right;
} else if (isDelimiter(str[right]) == true && left != right
|| (right == len && left != right)) {
char* subStr = subString(str, left, right - 1);
if (isKeyword(subStr) == true)
printf("'%s' IS A KEYWORD\n", subStr);
else if (isInteger(subStr) == true)
printf("'%s' IS AN INTEGER\n", subStr);
else if (isRealNumber(subStr) == true)
printf("'%s' IS A REAL NUMBER\n", subStr);
else if (validIdentifier(subStr) == true
&& isDelimiter(str[right - 1]) == false)
printf("'%s' IS A VALID IDENTIFIER\n", subStr);
else if (validIdentifier(subStr) == false
&& isDelimiter(str[right - 1]) == false)
printf("'%s' IS NOT A VALID IDENTIFIER\n", subStr);
left = right;
}
}
return;
}
// DRIVER FUNCTION
int main()
{
// maximum length of string is 100 here
char str[100] = "int a = b + 1c; ";
parse(str); // calling the parse function
return (0);
}

Output:-
Experiment - 2
Q2.Write a program to give an Expression & check whether it is regular Expression or
not?
Code:-
#include <iostream>
#include <regex>

int main() {
std::string expression;

std::cout << "Enter an expression: ";


std::cin >> expression;

try {
// Attempt to compile the regular expression, but don't actually use it
std::regex re(expression);
std::cout << "The given expression is a valid regular expression." << std::endl;
} catch (const std::regex_error& e) {
if (e.code() == std::regex_constants::error_badrepeat) {
std::cout << "The given expression contains an invalid repetition (e.g., *, +, {n,m})." <<
std::endl;
} else {
std::cout << "The given expression is not a valid regular expression." << std::endl;
}
}

return 0;
}
Output:-
Experiment - 3

Q3.Write a program to compute First and Follow using given CFG.


Code:-
#include <iostream>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
// Functions to calculate Follow
void followfirst(char, int, int);
void follow(char c);
// Function to calculate First
void findfirst(char, int, int);
int count, n = 0;
// Stores the final result
// of the First Sets
char calc_first[10][100];
// Stores the final result
// of the Follow Sets
char calc_follow[10][100];
int m = 0;
// Stores the production rules
char production[10][10];
char f[10], first[10];
int k;
char ck;
int e;
int main(int argc, char** argv)
{
int jm = 0;
int km = 0;
int i, choice;
char c, ch;
count = 8;
// The Input grammar
strcpy(production[0], "X=TnS");
strcpy(production[1], "X=Rm");
strcpy(production[2], "T=q");
strcpy(production[3], "T=#");
strcpy(production[4], "S=p");
strcpy(production[5], "S=#");
strcpy(production[6], "R=om");
strcpy(production[7], "R=ST");
int kay;
char done[count];
int ptr = -1;
// Initializing the calc_first array
for (k = 0; k < count; k++) {
for (kay = 0; kay < 100; kay++) {
calc_first[k][kay] = '!';
}
}
int point1 = 0, point2, xxx;
for (k = 0; k < count; k++) {
c = production[k][0];
point2 = 0;
xxx = 0;
for (kay = 0; kay <= ptr; kay++)
if (c == done[kay])
xxx = 1;
if (xxx == 1)
continue;
// Function call
findfirst(c, 0, 0);
ptr += 1;
// Adding c to the calculated list
done[ptr] = c;
printf("\n First(%c) = { ", c);
calc_first[point1][point2++] = c;
// Printing the First Sets of the grammar
for (i = 0 + jm; i < n; i++) {
int lark = 0, chk = 0;
for (lark = 0; lark < point2; lark++) {
if (first[i] == calc_first[point1][lark]) {
chk = 1;
break;
}
}
if (chk == 0) {
printf("%c, ", first[i]);
calc_first[point1][point2++] = first[i];
}
}
printf("}\n");
jm = n;
point1++;
}
printf("\n");
printf("-----------------------------------------------"
"\n\n");
char donee[count];
ptr = -1;
// Initializing the calc_follow array
for (k = 0; k < count; k++) {
for (kay = 0; kay < 100; kay++) {
calc_follow[k][kay] = '!';
}
}
point1 = 0;
int land = 0;
for (e = 0; e < count; e++) {
ck = production[e][0];
point2 = 0;
xxx = 0;
// Checking if Follow of ck
// has already been calculated
for (kay = 0; kay <= ptr; kay++)
if (ck == donee[kay])
xxx = 1;
if (xxx == 1)
continue;
land += 1;
// Function call
follow(ck);
ptr += 1;
// Adding ck to the calculated list
donee[ptr] = ck;
printf(" Follow(%c) = { ", ck);
calc_follow[point1][point2++] = ck;
// Printing the Follow Sets of the grammar
for (i = 0 + km; i < m; i++) {
int lark = 0, chk = 0;
for (lark = 0; lark < point2; lark++) {
if (f[i] == calc_follow[point1][lark]) {
chk = 1;
break;
}
}
if (chk == 0) {
printf("%c, ", f[i]);
calc_follow[point1][point2++] = f[i];
}
}
printf(" }\n\n");
km = m;
point1++;
}
}
void follow(char c)
{
int i, j;
// Adding "$" to the follow
// set of the start symbol
if (production[0][0] == c) {
f[m++] = '$';
}
for (i = 0; i < 10; i++) {
for (j = 2; j < 10; j++) {
if (production[i][j] == c) {
if (production[i][j + 1] != '\0') {
// Calculate the first of the next
// Non-Terminal in the production
followfirst(production[i][j + 1], i,
(j + 2));
}
if (production[i][j + 1] == '\0'
&& c != production[i][0]) {
// Calculate the follow of the
// Non-Terminal in the L.H.S. of the
// production
follow(production[i][0]);
}
}
}
}
}
void findfirst(char c, int q1, int q2)
{
int j;
// The case where we
// encounter a Terminal
if (!(isupper(c))) {
first[n++] = c;
}
for (j = 0; j < count; j++) {
if (production[j][0] == c) {
if (production[j][2] == '#') {
if (production[q1][q2] == '\0')
first[n++] = '#';
else if (production[q1][q2] != '\0'
&& (q1 != 0 || q2 != 0)) {
// Recursion to calculate First of New
// Non-Terminal we encounter after
// epsilon
findfirst(production[q1][q2], q1,
(q2 + 1));
}
else
first[n++] = '#';
}
else if (!isupper(production[j][2])) {
first[n++] = production[j][2];
}
else {
// Recursion to calculate First of
// New Non-Terminal we encounter
// at the beginning
findfirst(production[j][2], j, 3);
}
}
}
}
void followfirst(char c, int c1, int c2)
{
int k;
// The case where we encounter
// a Terminal
if (!(isupper(c)))
f[m++] = c;
else {
int i = 0, j = 1;
for (i = 0; i < count; i++) {
if (calc_first[i][0] == c)
break;
}
// Including the First set of the
// Non-Terminal in the Follow of
// the original query
while (calc_first[i][j] != '!') {
if (calc_first[i][j] != '#') {
f[m++] = calc_first[i][j];
}
else {
if (production[c1][c2] == '\0') {
// Case where we reach the
// end of a production
follow(production[c1][0]);
}
else {
// Recursion to the next symbol
// in case we encounter a "#"
followfirst(production[c1][c2], c1,
c2 + 1);
}
}
j++;
}
}
}
Output:-
Experiment - 4

Q4.Write a program for implementation of Predictive Parsing Table for LL (1) grammar.
Code:-
#include <stdio.h>
#include <string.h>

char prol[7][10] = { "S", "A", "A", "B", "B", "C", "C" };


char pror[7][10] = { "A", "Bb", "Cd", "aB", "@", "Cc", "@" };
char prod[7][10] = { "S->A", "A->Bb", "A->Cd", "B->aB", "B->@", "C->Cc", "C->@" };
char first[7][10] = { "abcd", "ab", "cd", "a@", "@", "c@", "@" };
char follow[7][10] = { "S", "S", "$", "a$", "b$", "c$", "d$" };
char table[5][6][10];

int numr(char c) {
switch (c) {
case 'S': return 0;
case 'A': return 1;
case 'B': return 2;
case 'C': return 3;
case 'a': return 0;
case 'b': return 1;
case 'c': return 2;
}
return 4; // You might want to handle other cases more gracefully.
}

int main() {
int i, j, k;

for (i = 0; i < 5; i++)


for (j = 0; j < 6; j++)
strcpy(table[i][j], " ");

printf("The following is the predictive parsing table for the following grammar:\n");
for (i = 0; i < 7; i++)
printf("%s\n", prod[i]);

printf("\nPredictive parsing table is\n");


for (i = 0; i < 7; i++) {
k = strlen(first[i]);
for (j = 0; j < 10; j++) {
if (first[i][j] != '@')
strcpy(table[numr(prol[i][0]) + 1][numr(first[i][j]) + 1], prod[i]);
}
}
for (i = 0; i < 7; i++) {
if (strlen(pror[i]) == 1) {
if (pror[i][0] == '@')
k = strlen(follow[i]);
for (j = 0; j < k; j++) {
strcpy(table[numr(prol[i][0]) + 1][numr(follow[i][j]) + 1], prod[i]);
}
}
}
strcpy(table[0][0], " ");
strcpy(table[0][1], "a");
strcpy(table[0][2], "b");
strcpy(table[0][3], "c");
strcpy(table[0][4], "d");
strcpy(table[0][5], "S");
strcpy(table[1][0], "S");
strcpy(table[2][0], "A");
strcpy(table[3][0], "B");
strcpy(table[4][0], "C");

printf("\n--\n");

for (i = 0; i < 5; i++) {


for (j = 0; j < 6; j++) {
printf("%-10s", table[i][j]);
if (j == 5)
printf("\n");
}
}

return 0;
}
Output:-
Experiment - 5
Q5.Write a program to develop an operator precedence parser.
Code:-
#include <stdio.h>
#include <string.h>

int main() {
char stack[20], ip[20], opt[10][10], ter[10];
int i, j, k, n, top = 0, col, row;

for (i = 0; i < 10; i++) {


stack[i] = '\0';
ip[i] = '\0';
for (j = 0; j < 10; j++) {
opt[i][j] = '\0';
}
}

printf("Enter the number of terminals:\n");


scanf("%d", &n);

printf("Enter the terminals:\n");


scanf("%s", ter);

printf("Enter the table values:\n");

for (i = 0; i < n; i++) {


for (j = 0; j < n; j++) {
printf("Enter the value for %c %c:", ter[i], ter[j]);
scanf(" %c", &opt[i][j]);
}
}

printf("\n** OPERATOR PRECEDENCE TABLE **\n");

for (i = 0; i < n; i++) {


printf("\t%c", ter[i]);
}

printf("\n");
for (i = 0; i < n; i++) {
printf("%c", ter[i]);
for (j = 0; j < n; j++) {
printf("\t%c", opt[i][j]);
}
printf("\n");
}

stack[top] = '$';
printf("Enter the input string:");
scanf("%s", ip);
i = 0;
printf("\nSTACK\t\t\tINPUT STRING\t\t\tACTION\n");
printf("\n%s\t\t\t%s\t\t\t", stack, ip);
while (i <= strlen(ip)) {
for (k = 0; k < n; k++) {
if (stack[top] == ter[k])
col = k;
if (ip[i] == ter[k])
row = k;
}
if ((stack[top] == '$') && (ip[i] == '$')) {
printf("String is accepted\n");
break;
} else if ((opt[col][row] == '<') || (opt[col][row] == '=')) {
stack[++top] = opt[col][row];
stack[++top] = ip[i];
printf("Shift %c", ip[i]);
i++;
} else {
if (opt[col][row] == '>') {
while (stack[top] != '<') {
top--;
}
top--;
printf("Reduce");
} else {
printf("\nString is not accepted");
break;
}
}
printf("\n");
for (k = 0; k <= top; k++) {
printf("%c", stack[k]);
}
printf("\t\t\t");
for (k = i; k < strlen(ip); k++) {
printf("%c", ip[k]);
}
printf("\t\t\t");
}

return 0;
}
Output:-
PRACTICAL: 06
Write a program to design LALR Bottom up Parser.
LALR Parser is lookahead LR parser. It is the most powerful parser which can handle large
classes of grammar. The size of CLR parsing table is quite large as compared to other parsing
table. LALR reduces the size of this table.LALR works similar to CLR. The only difference is , it
combines the similar states of CLR parsing table into one single state.
The general syntax becomes [A->∝.B, a ]
where A->∝.B is production and a is a terminal or right end marker $
LR(1) items=LR(0) items + look ahead
CODE
class LRParser:
def __init__(self, grammar, actions, gotos):
self.grammar = grammar
self.actions = actions
self.gotos = gotos
self.stack = []

def parse(self, input_tokens):


input_tokens.append('$') # Add end-of-input marker
self.stack = [0] # Initial state

i=0
while True:
current_state = self.stack[-1]
current_token = input_tokens[i]

action = self.actions.get((current_state, current_token), None)

if action is None:
print("Error: Parsing failed.")
break

if action[0] == 'shift':
self.stack.append(current_token)

29
self.stack.append(action[1])
i += 1
elif action[0] == 'reduce':
production = self.grammar[action[1]]
for _ in range(2 * len(production[1])):
self.stack.pop()
non_terminal = production[0]
next_state = self.gotos.get((self.stack[-1], non_terminal), None)
if next_state is None:
print("Error: Parsing failed.")
break
self.stack.append(non_terminal)
self.stack.append(next_state)
elif action[0] == 'accept':
print("Parsing successful.")
break

def display_stack(self):
print("Stack:", self.stack)

# Example Usage
grammar = {
0: ('S', ['E']),
1: ('E', ['E', '+', 'T']),
2: ('E', ['T']),
3: ('T', ['T', '*', 'F']),
4: ('T', ['F']),
5: ('F', ['(', 'E', ')']),
6: ('F', ['id'])
}

actions = {
(0, 'id'): ('shift', 5),

30
(0, '('): ('shift', 4),
(1, '+'): ('shift', 6),
(1, '$'): ('reduce', 2),
(2, '+'): ('reduce', 2),
(2, '*'): ('shift', 7),
(2, ')'): ('reduce', 2),
(2, '$'): ('reduce', 2),
(3, '+'): ('reduce', 4),
(3, '*'): ('reduce', 4),
(3, ')'): ('reduce', 4),
(3, '$'): ('reduce', 4),
(4, 'id'): ('shift', 5),
(4, '('): ('shift', 4),
(5, '+'): ('shift', 6),
(5, '*'): ('shift', 7),
(5, ')'): ('shift', 8),
(6, 'id'): ('shift', 5),
(6, '('): ('shift', 4),
(7, 'id'): ('shift', 5),
(7, '('): ('shift', 4),
(8, '+'): ('reduce', 1),
(8, '*'): ('reduce', 1),
(8, ')'): ('reduce', 1),
(8, '$'): ('reduce', 1)
}

gotos = {
(0, 'E'): 1,
(0, 'T'): 2,
(0, 'F'): 3,
(4, 'E'): 9,
(4, 'T'): 2,
(4, 'F'): 3,
(5, 'T'): 10,

31
(5, 'F'): 3,
(6, 'T'): 11,
(6, 'F'): 3,
(7, 'F'): 12,
}

# Create an instance of LRParser


parser = LRParser(grammar, actions, gotos)

# Input string: "id + id * id"


input_tokens = ['id', '+', 'id', '*', 'id']

# Parse the input string


parser.parse(input_tokens)

# Display the final stack


parser.display_stack()
OUTPUT
Stack: [0, 'id', 5, '+', 6, 'id', 5, '*', 7, 'id', 5]

32
PRACTICAL : 07
Write a program for generating various intermediate code forms
i) Three address code
a. Triple b. Quadruple c. Indirect triple

Intermediate Code:
Intermediate code is an abstraction used in compilers to represent the program's semantics in a
simpler and more structured form than the source code. It serves as a bridge between the high-level
source code and the low-level machine code or assembly language. Various forms of intermediate
code exist, providing different levels of abstraction.

Three Address Code (TAC):


Three Address Code is a type of intermediate code representation where each instruction has at most
three operands. It simplifies the complex expressions present in the source code into a series of
simple operations.

Triple:
A basic form of three-address code representation containing three fields: operator, operand1, and
operand2.
Quadruple:
Similar to a triple but includes an additional field, often used for the result or destination operand.
Indirect Triple:
A variation of the triple where the operands may be references to memory locations.

CODE
class ThreeAddressCode:
def __init__(self):
self.instructions = []

def add_instruction(self, operator, operand1=None, operand2=None, result=None):


instruction = {'operator': operator, 'operand1': operand1, 'operand2': operand2, 'result':
result}
self.instructions.append(instruction)

33
def display_code(self):
for index, instruction in enumerate(self.instructions, start=1):
print(f"{index}. {instruction['operator']} {instruction['operand1']}
{instruction['operand2']} {instruction['result']}")

# Example Usage
tac = ThreeAddressCode()

# Generate Three Address Code


tac.add_instruction('=', '2', None, 'a')
tac.add_instruction('=', '4', None, 'b')
tac.add_instruction('+', 'a', 'b', 'temp')
tac.add_instruction('*', 'temp', '5', 'result')

# Display Three Address Code


print("Three Address Code:")
tac.display_code()
OUTPUT
Three Address Code:
1. = 2 None a
2. = 4 None b
3. + a b temp
4. * temp 5 result

34
PRACTICAL: 08
Write a program for generating various intermediate code forms
Polish notation: a. Infix to prefix b. Infix to postfix

Intermediate Code:
Intermediate code is an abstraction used in compilers to represent the program's semantics in a
simpler and more structured form than the source code. It serves as a bridge between the high-level
source code and the low-level machine code or assembly language. Various forms of intermediate
code exist, providing different levels of abstraction.

Three Address Code (TAC):


Three Address Code is a type of intermediate code representation where each instruction has at most
three operands. It simplifies the complex expressions present in the source code into a series of
simple operations.

Triple:
A basic form of three-address code representation containing three fields: operator, operand1, and
operand2.
Quadruple:
Similar to a triple but includes an additional field, often used for the result or destination operand.
Indirect Triple:
A variation of the triple where the operands may be references to memory locations.
CODE
class PolishNotationConverter:
def __init__(self):
self.operator_stack = []

def infix_to_prefix(self, infix_expression):


prefix_expression = []
for token in reversed(infix_expression):
if token.isalnum():
prefix_expression.append(token)
elif token == ')':
self.operator_stack.append(token)

35
elif token == '(':
while self.operator_stack and self.operator_stack[-1] != ')':
prefix_expression.append(self.operator_stack.pop())
self.operator_stack.pop() # Pop '(' from the stack
else:
while self.operator_stack and self.precedence(self.operator_stack[-1]) >=
self.precedence(token):
prefix_expression.append(self.operator_stack.pop())
self.operator_stack.append(token)

while self.operator_stack:
prefix_expression.append(self.operator_stack.pop())

return ''.join(reversed(prefix_expression))

def infix_to_postfix(self, infix_expression):


postfix_expression = []
for token in infix_expression:
if token.isalnum():
postfix_expression.append(token)
elif token == '(':
self.operator_stack.append(token)
elif token == ')':
while self.operator_stack and self.operator_stack[-1] != '(':
postfix_expression.append(self.operator_stack.pop())
self.operator_stack.pop() # Pop '(' from the stack
else:
while self.operator_stack and self.precedence(self.operator_stack[-1]) >=
self.precedence(token):
postfix_expression.append(self.operator_stack.pop())
self.operator_stack.append(token)

while self.operator_stack:
postfix_expression.append(self.operator_stack.pop())

36
return ''.join(postfix_expression)

def precedence(self, operator):


if operator == '+' or operator == '-':
return 1
elif operator == '*' or operator == '/':
return 2
return 0

# Example Usage
converter = PolishNotationConverter()

# Infix expression: (a + b) * (c - d)
infix_expr = ['(', 'a', '+', 'b', ')', '*', '(', 'c', '-', 'd', ')']

# Convert to Prefix notation


prefix_result = converter.infix_to_prefix(infix_expr)
print("Prefix Notation:", prefix_result)

# Convert to Postfix notation


postfix_result = converter.infix_to_postfix(infix_expr)
print("Postfix Notation:", postfix_result)
OUTPUT
Prefix Notation: *+ab-cd
Postfix Notation: ab+cd-*

37
PRACTICAL : 09

Write a program to perform heap storage allocation strategies such as –


a. Creation

b. Insertion
c. Deletion
d. Display
Heap Storage Allocation:
Heap storage refers to the dynamic memory space that a program can allocate during its
execution. Unlike the stack, which is used for local variables and function call management, the
heap is used for dynamic memory allocation. The heap is managed by the programmer, and
memory is allocated and deallocated explicitly.
Heap Data Structures:
One common use of the heap is for implementing priority queues, where elements with higher
priorities are served before elements with lower priorities. Binary heaps are often used for this
purpose. There are two types of binary heaps:
1. Min-Heap:
 In a min-heap, the value of each node is less than or equal to the values of its children.
The minimum value is at the root.
2. Max-Heap:
 In a max-heap, the value of each node is greater than or equal to the values of its
children. The maximum value is at the root.
Basic Operations on Heaps:
1. Insertion:
 To insert an element into the heap, the new element is placed at the next available
position (usually at the bottom of the heap). Then, the heap property is restored by
swapping the new element with its parent until the heap property is satisfied.
2. Deletion:
 To delete an element from the heap (typically the minimum or maximum element,
depending on the heap type), the element is removed from the root. The last element
in the heap is moved to the root, and the heap property is restored by swapping the
element with its smaller or larger child until the heap property is satisfied.
Heap Storage Allocation Strategies:

38
Heap storage allocation strategies involve managing the dynamic allocation and deallocation of
memory on the heap. Some strategies include:
1. First-Fit:
 Allocates the first available block of memory that is large enough to accommodate the
requested size.
2. Best-Fit:
 Allocates the smallest available block of memory that is large enough to
accommodate the requested size. It minimizes fragmentation.
3. Worst-Fit:
 Allocates the largest available block of memory. This strategy tends to create more
fragmentation.
Infix to Prefix and Postfix Conversion:
Infix notation is the standard mathematical notation where operators are written between their
operands. Prefix and postfix notations are alternative notations where the operators are placed
before or after their operands, respectively.
 Infix Expression: a + b
 Prefix Expression: + a b
 Postfix Expression: a b +
The conversion involves using stacks to manage operators and operands and rearranging the
expression accordingly.
CODE
class Heap:
def __init__(self):
self.heap_list = [0] # The first element is a dummy element (not used)
self.current_size = 0

def create_heap(self, values):


self.heap_list = [0] + values
self.current_size = len(values)
self.build_heap()

def build_heap(self):
i = self.current_size // 2
while i > 0:

39
self.heapify_down(i)
i -= 1

def insert(self, value):


self.heap_list.append(value)
self.current_size += 1
self.heapify_up(self.current_size)

def delete_min(self):
if self.current_size == 0:
return None

min_value = self.heap_list[1]
self.heap_list[1] = self.heap_list[self.current_size]
self.current_size -= 1
self.heap_list.pop()
self.heapify_down(1)

return min_value

def display(self):
print("Heap:", self.heap_list[1:])

def heapify_up(self, i):


while i // 2 > 0:
if self.heap_list[i] < self.heap_list[i // 2]:
self.heap_list[i], self.heap_list[i // 2] = self.heap_list[i // 2], self.heap_list[i]
i //= 2

def heapify_down(self, i):


while (i * 2) <= self.current_size:
mc = self.min_child(i)
if self.heap_list[i] > self.heap_list[mc]:
self.heap_list[i], self.heap_list[mc] = self.heap_list[mc], self.heap_list[i]

40
i = mc

def min_child(self, i):


left_child = i * 2
right_child = (i * 2) + 1

if right_child > self.current_size:


return left_child
else:
if self.heap_list[left_child] < self.heap_list[right_child]:
return left_child
else:
return right_child

# Example Usage
heap = Heap()

# Create a heap
values = [9, 6, 5, 2, 3]
heap.create_heap(values)

# Display the initial heap


heap.display()
heap.insert(1)
heap.display()
min_value = heap.delete_min()
print("Deleted Min Value:", min_value)
heap.display()

OUTPUT
Heap: [2, 3, 5, 6, 9]
Heap: [1, 3, 2, 6, 9, 5]
Deleted Min Value: 1
Heap: [2, 3, 5, 6, 9]

41
PRACTICAL: 10
Implement Code optimization technique for any given intermediate code form.

Code optimization is a crucial step in the compilation process to improve the efficiency of the
generated machine code. One common intermediate code form used for optimization is Three-
Address Code (TAC). In this example, we'll implement a simple constant folding optimization
technique for TAC.
Constant Folding:
Constant folding is an optimization technique that evaluates constant expressions at compile-time
rather than runtime. It replaces expressions involving only constants with their computed values.
Example of Constant Folding:
Original TAC:
1. t1 = 5 + 3
2. t2 = t1 * 2
3. result = t2 - 4

Optimized TAC (After Constant Folding):


1. t1 = 8
2. t2 = 16
3. result = 12
CODE
class TACOptimizer:
def __init__(self, tac):
self.tac = tac
self.optimized_tac = []

def optimize(self):
for instruction in self.tac:
if '=' in instruction:
parts = instruction.split(' ')
result_var = parts[0]
operator = parts[2]
operand1, operand2 = parts[1], parts[3]

42
# Check if both operands are constants
if operand1.isnumeric() and operand2.isnumeric():
# Perform constant folding
result_value = str(eval(operand1 + operator + operand2))
self.optimized_tac.append(f"{result_var} = {result_value}")
else:
self.optimized_tac.append(instruction)
else:
self.optimized_tac.append(instruction)

def display_optimized_tac(self):
print("Optimized TAC:")
for instruction in self.optimized_tac:
print(instruction)
tac_code = [
"t1 = 5 + 3",
"t2 = t1 * 2",
"result = t2 - 4"
]
optimizer = TACOptimizer(tac_code)
print("Original TAC:")
for instruction in tac_code:
print(instruction)
optimizer.optimize()
optimizer.display_optimized_tac()
OUTPUT
Original TAC:
t1 = 5 + 3
t2 = t1 * 2
result = t2 - 4
Optimized TAC:
t1 = 5 + 3
t2 = t1 * 2
result = t2 - 4

43

You might also like