CD Practical Soft Copy
CD Practical Soft Copy
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;
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
Q4.Write a program for implementation of Predictive Parsing Table for LL (1) grammar.
Code:-
#include <stdio.h>
#include <string.h>
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;
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("\n--\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;
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 = []
i=0
while True:
current_state = self.stack[-1]
current_token = input_tokens[i]
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,
}
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.
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 = []
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()
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.
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 = []
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))
while self.operator_stack:
postfix_expression.append(self.operator_stack.pop())
36
return ''.join(postfix_expression)
# Example Usage
converter = PolishNotationConverter()
# Infix expression: (a + b) * (c - d)
infix_expr = ['(', 'a', '+', 'b', ')', '*', '(', 'c', '-', 'd', ')']
37
PRACTICAL : 09
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 build_heap(self):
i = self.current_size // 2
while i > 0:
39
self.heapify_down(i)
i -= 1
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:])
40
i = mc
# Example Usage
heap = Heap()
# Create a heap
values = [9, 6, 5, 2, 3]
heap.create_heap(values)
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
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