Practical 1
Aim: Right linear grammar to left linear grammar
import sys
class Grammar:
def main(self):
temp = [''] * 20
left = [''] * 20
tempn = [''] * 20
right = ''
count = 0
print("Enter The Right linear grammar (separated by '-'), e.g., S-aB")
print("Enter 'q' to Quit")
str_list = []
while True:
line = input()
if line == 'q':
break
str_list.append(line)
print("The Right linear grammar is:")
for rule in str_list:
print(rule)
print("Terminals are:")
try:
for rule in str_list:
for j in range(2, len(rule)):
if 'a' <= rule[j] <= 'z':
if not temp[0]:
temp[count] = rule[j]
else:
if rule[j] not in temp[:count+1]:
count += 1
temp[count] = rule[j]
except IndexError:
pass
print(" ".join([c for c in temp if c]))
print("\nNon-terminals are:")
count = 0
try:
for rule in str_list:
for j in range(2, len(rule)):
if 'A' <= rule[j] <= 'Z':
if not tempn[0]:
tempn[count] = rule[j]
else:
if rule[j] not in tempn[:count+1]:
count += 1
tempn[count] = rule[j]
except IndexError:
pass
print(" ".join([c for c in tempn if c]))
print("\nThe Left linear Grammar is:")
for rule in str_list:
cr = 0
if 'A' <= rule[0] <= 'Z' and 'A' <= rule[-1] <= 'Z':
if (len(rule) - 2) // 2 >= 1:
for j in range(2, len(rule)):
left[cr] = rule[j]
cr += 1
for k in range((len(rule) - 2) // 2):
right = left[k]
left[k] = left[(len(rule) - 2) - 1 - k]
left[(len(rule) - 2) - 1 - k] = right
print(f"{left[0]}'-{rule[0]}'", end='')
for k in range((len(rule) - 2) - 1, 0, -1):
print(left[k], end='')
print()
else:
print(f"{rule[-1]}'-{rule[0]}'")
else:
print(f"Z'-{rule[0]}'", end='')
for j in range(2, len(rule)):
print(rule[j], end='')
print()
if __name__ == "__main__":
grammar = Grammar()
grammar.main()
Practical 2
Aim: Conversion of NDFA to DFA
import pandas as pd
# Input NFA
nfa = {input(f"State {i + 1}: "): {input(f"Symbol {j + 1}: "): input(f"End state(s) for state {i + 1} through symbol {j + 1}:
").split()
for j in range(int(input("No of Transitions: ")))} for i in range(int(input("No of States: ")))}
print("\nNFA:", nfa, "\nNFA Table:")
print(pd.DataFrame(nfa).transpose())
# Input Final States of NFA
nfa_final = input("\nEnter Final State(s) of NFA: ").split()
# Convert NFA to DFA
dfa, new_states, paths = {}, [], list(nfa[next(iter(nfa))].keys())
initial = "".join(nfa[next(iter(nfa))][paths[0]])
dfa[initial] = {}; new_states.append(initial)
while new_states:
curr = new_states.pop(0)
dfa[curr] = {}
for path in paths:
state = "".join(sorted(set(sum((nfa[s][path] for s in curr if path in nfa[s]), []))))
if state:
dfa[curr][path] = state
if state not in dfa: new_states.append(state)
print("\nDFA:", dfa, "\nDFA Table:")
print(pd.DataFrame(dfa).transpose().fillna("-"))
# Determine DFA Final States
dfa_final = [s for s in dfa if any(f in s for f in nfa_final)]
print("\nFinal States of the DFA:", dfa_final)
Practical 3
Aim: Implementation of Warshall Algorithm and Kleen Closure
a)Warshall Algorithm
nv, INF = 4, 999
def floyd_warshall(G):
for k in range (nv):
for i in range (nv):
for j in range (nv):
G[i][j] = min(G[i][j], G[i][k] + G[k][j])
for row in G:
print( " " .join("INF" if x == INF else str(x) for x in row ))
G=[
[0,3,INF,5],
[2,0,INF,4],
[INF,1,0,4],
[INF,INF,2,0]
]
floyd_warshall(G)
b) Transitive closure
def transitive_closure(graph):
V = len(graph)
closure=[[0]* V for _ in range(V)]
for i in range(V):
for j in range(V):
closure[i][j] = graph[i][j]
for k in range(V):
for i in range(V):
for j in range(V):
closure[i][j] = closure[i][j] or (closure[i][k] and closure[k][j])
return closure
graph= [
[1, 1, 0, 1],
[0, 1, 1, 0],
[0, 0, 1, 1],
[0, 0, 0, 1]
]
result = transitive_closure(graph)
print("transitive closure:")
for row in result:
print(row)
Practical 4
Aim: Generating Simple Precedence Matrix
import numpy as np
from collections import deque
def topological_sort(precedence_matrix):
# Number of vertices
n = len(precedence_matrix)
# Compute in-degrees of each node
in_degree = [sum(col) for col in zip(*precedence_matrix)]
# Build adjacency list representation of the graph
graph = {i: [j for j, val in enumerate(precedence_matrix[i]) if val] for i in range(n)}
# Initialize a queue with all nodes having zero in-degree
queue = deque(i for i in range(n) if in_degree[i] == 0)
linear_order = []
# Process nodes in the queue
while queue:
node = queue.popleft()
linear_order.append(node)
# Decrement in-degree of neighboring nodes
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
# Check if all nodes were processed
if len(linear_order) == n:
return linear_order
# If not, there is a cycle in the graph
raise ValueError("Graph has a cycle or is not a DAG.")
# Example usage
precedence_matrix = np.array([
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
[0, 0, 0, 0]
])
print("Topological order:", topological_sort(precedence_matrix))
Practical 5
Aim: Parsing using Simple Precedence Matrix
# Simple Precedence Parser in Python
# Define the grammar terminals and non-terminals
terminals = ['+','*','i','(',')','$']
non_terminals = ['E','T','F']
# Simple Precedence matrix
#+*i()$
matrix = [
['>','<','<','<','>','>'], # +
['>','>','<','<','>','>'], # *
['>','>','>','','','>'], # i
['<','<','<','<','=',''], # (
['>','>','','','>','>'], # )
['<','<','<','<','',''], # $
]
# Function to get the precedence relation from the matrix
def get_precedence(a,b):
if a in terminals and b in terminals:
i = terminals.index(a)
j = terminals.index(b)
return matrix[i][j]
return None
# Function to parse the input string
def parse(input_string):
stack = ['$']
i=0
input_string += '$'
print(f"Stack\tInput\tAction")
print(f"{''.join(stack)}\t{input_string[i:]}\t")
while i < len(input_string):
top = stack[-1]
current_input = input_string[i]
precedence = get_precedence(top, current_input)
if precedence == '<' or precedence == '=':
stack.append(current_input)
i += 1
action = "Shift"
elif precedence == '>':
while get_precedence(stack[-2], stack[-1]) != '<':
stack.pop()
stack.pop()
action = "Reduce"
elif precedence == '':
print(f"Error: No precedence relation between {top} and {current_input}.")
return False
else:
print(f"Error: unrecognized action.")
return False
print(f"{''.join(stack)}\t{input_string[i:]}\t{action}")
if stack == ['$'] and input_string[i] == '$':
print("Parsing Successful!")
return True
print("Parsing Failed!")
return False
# Example Usage
input_string = "i+i*i"
parse(input_string)
Practical 6
Aim: Linearizing Simple Precedence Matrix to Precedence Functions
import numpy as np
# Sample terminal symbols
symbols = ['a', 'b', 'c']
# Input precedence matrix
# '<': -1, '=': 0, '>': 1
precedence_matrix = np.array([
[0, -1, 1],
[1, 0, -1],
[-1, 1, 0]
])
def compute_precedence_function(matrix, symbols):
n = len(symbols)
# Initialize Precedence functions f and g
f = [0] * n
g = [0] * n
# Assign precedence functions based on the matrix
for i in range(n):
for j in range(n):
if matrix[i][j] == 1:
f[i] = max(f[i], g[j] + 1)
elif matrix[i][j] == -1:
g[i] = max(g[i], f[j] + 1)
return f, g
# Compute precedence functions
f, g = compute_precedence_function(precedence_matrix, symbols)
# Output the precedence functions for each symbol
print("Precedence Function f:", {symbols[i]: f[i] for i in range(len(symbols))})
print("Precedence Function g:", {symbols[i]: g[i] for i in range(len(symbols))})
Practical 7
Aim: Generating Operator Precedence Matrix
def create_precedence_matrix(operators):
precedence_rules = {
('+','+'): '.>', ('+','*'): '<.', ('+','('): '<.', ('+',')'): '.>',
('*','+'): '.>', ('*','*'): '.>', ('*','('): '<.', ('*',')'): '.<',
('(','+'): '<.', ('(','*'): '<.', ('(','('): '<.', (')','+'): '>.',
(')','*'): '>.', (')',')'): '.>'
}
return [[precedence_rules.get((op1, op2), '.') for op2 in operators] for op1 in operators]
def print_precedence_matrix(operators, matrix):
print("Precedence Matrix\n " + " ".join(operators))
for op, row in zip(operators, matrix): print(op, " ".join(row))
if __name__ == "__main__":
operators = ['+', '*', '(', ')']
print_precedence_matrix(operators, create_precedence_matrix(operators))
Practical 8
Aim: Program for computation of FIRST AND FOLLOW of non-terminals.
from collections import defaultdict
# Input grammar as a dictionary
grammar = {
'S': ['AB', 'BC'],
'A': ['a', 'ε'],
'B': ['b'],
}
# FIRST and FOLLOW sets stored as Dictionaries
first = defaultdict(set)
follow = defaultdict(set)
# Function to compute the FIRST set for a given symbol
def compute_first(symbol):
# If already computed, return the FIRST set
if symbol in first and first[symbol]:
return first[symbol]
# If the symbol is terminal (lowercase), add it to its FIRST set
if symbol.islower() or symbol == 'ε':
first[symbol].add(symbol)
return first[symbol]
# If the symbol is non-terminal, iterate over its productions
for prod in grammar.get(symbol, []):
if prod == 'ε':
first[symbol].add('ε')
else:
for char in prod:
first_set = compute_first(char)
first[symbol].update(first_set - {'ε'}) # Add FIRST without ε
if 'ε' not in first_set:
break # Stop if no ε, as we found a terminal
else:
# If we reached the end and encountered ε, add ε to FIRST set
first[symbol].add('ε')
return first[symbol]
# Function to compute the FOLLOW set for a given symbol
def compute_follow(symbol):
if follow[symbol]:
return follow[symbol]
if symbol == 'S':
follow[symbol].add('$') # Assuming $ is the end of input marker
for lhs, productions in grammar.items():
for production in productions:
if symbol in production:
for i in range(len(production)):
if production[i] == symbol:
if i + 1 < len(production):
next_symbol = production[i + 1]
first_next = compute_first(next_symbol)
follow[symbol].update(first_next - {'ε'})
if 'ε' in first_next:
follow[symbol].update(compute_follow(lhs))
else:
if lhs != symbol:
follow[symbol].update(compute_follow(lhs))
return follow[symbol]
# Compute FIRST sets for all non-terminals
for non_terminal in grammar.keys():
compute_first(non_terminal)
# Compute FOLLOW sets for all non-terminals
for non_terminal in grammar.keys():
compute_follow(non_terminal)
# Display the FIRST and FOLLOW sets
print("FIRST sets:")
for key, value in first.items():
print(f"{key}: {value}")
print("\nFOLLOW sets:")
for key, value in follow.items():
print(f"{key}: {value}")
Practical 9
Aim: Conversion of Infix to Postfix notation, Postfix to Infix notations
a) Infix to Postfix notation
def precedence(op): return {'+': 1, '-': 1, '*': 2, '/': 2, '^': 3}.get(op, 0)
def is_operand(char): return char.isalnum()
def infix_to_postfix(expression):
stack, output = [], []
for char in expression:
if is_operand(char): output.append(char)
elif char == '(': stack.append(char)
elif char == ')':
while stack[-1] != '(': output.append(stack.pop())
stack.pop()
else:
while stack and precedence(char) <= precedence(stack[-1]): output.append(stack.pop())
stack.append(char)
return ''.join(output + stack[::-1])
expression = "A+B*(C-D)"
print("Infix Expression:", expression)
print("Postfix Expression:", infix_to_postfix(expression))
b) Postfix to Infix notations
def precedence(op): return {'+': 1, '-': 1, '*': 2, '/': 2, '^': 3}.get(op, 0)
def is_operand(char): return char.isalnum()
def infix_to_postfix(expression):
stack, output = [], []
for char in expression:
if is_operand(char): output.append(char)
elif char == '(': stack.append(char)
elif char == ')':
while stack[-1] != '(': output.append(stack.pop())
stack.pop()
else:
while stack and precedence(char) <= precedence(stack[-1]): output.append(stack.pop())
stack.append(char)
return ''.join(output + stack[::-1])
expression = "A+B*(C-D)"
print("Infix Expression:", expression)
print("Postfix Expression:", infix_to_postfix(expression))
Practical 10
Aim: Generation of three address code and DAG for the given arithmetic expression
class ThreeAddressCode:
def __init__(self):
self.temp_count = 0
self.code = []
def new_temp(self):
self.temp_count += 1
return f't{self.temp_count}'
def generate_tac(self, expression):
postfix = self.infix_to_postfix(expression)
return self.postfix_to_tac(postfix)
def infix_to_postfix(self, expression):
precedence = {'+': 1, '-': 1, '*': 2, '/': 2}
output = []
stack = []
tokens = list(expression.replace(" ", ""))
for token in tokens:
if token.isalnum(): # Operand
output.append(token)
elif token == '(': # Left parenthesis
stack.append(token)
elif token == ')': # Right parenthesis
while stack and stack[-1] != '(':
output.append(stack.pop())
stack.pop() # Remove the '('
else: # Operator
while (stack and stack[-1] in precedence and
precedence[stack[-1]] >= precedence[token]):
output.append(stack.pop())
stack.append(token)
while stack: # Pop all the operators in the stack
output.append(stack.pop())
return output
def postfix_to_tac(self, postfix):
stack = []
for token in postfix:
if token.isalnum():
stack.append(token)
else:
arg2 = stack.pop()
arg1 = stack.pop()
temp = self.new_temp()
self.code.append(f'{temp} = {arg1} {token} {arg2}')
stack.append(temp)
return self.code
class DAGNode:
def __init__(self, value):
self.value = value
self.children = []
self.label = None
def __repr__(self):
return f"{self.value} (label: {self.label})"
class DAG:
def __init__(self):
self.nodes = []
def find_or_create_node(self, value):
for node in self.nodes:
if node.value == value:
return node
# Create a new node if not found
new_node = DAGNode(value)
self.nodes.append(new_node)
return new_node
def create_dag(self, expression):
postfix = self.infix_to_postfix(expression)
return self.build_dag_from_postfix(postfix)
def infix_to_postfix(self, expression):
precedence = {'+': 1, '-': 1, '*': 2, '/': 2, '(': 0, ')': 0}
output = []
stack = []
tokens = list(expression.replace(" ", ""))
for token in tokens:
if token.isalnum():
output.append(token)
elif token == '(':
stack.append(token)
elif token == ')':
while stack and stack[-1] != '(':
output.append(stack.pop())
stack.pop()
else:
while (stack and stack[-1] in precedence and
precedence[stack[-1]] >= precedence[token]):
output.append(stack.pop())
stack.append(token)
while stack:
output.append(stack.pop())
return output
def build_dag_from_postfix(self, postfix):
stack = []
for token in postfix:
if token.isalnum():
node = self.find_or_create_node(token)
stack.append(node)
else:
arg2 = stack.pop()
arg1 = stack.pop()
node = self.find_or_create_node(f"{arg1.value} {token} {arg2.value}")
node.children.extend([arg1, arg2])
stack.append(node)
return stack[0]
def display(self, node, level=0):
if node:
print(' ' * (level * 4) + str(node))
for child in node.children:
self.display(child, level + 1)
# Example
expression = "(a + b) * (c + d)"
# Generate TAC
tac_generator = ThreeAddressCode()
tac = tac_generator.generate_tac(expression)
print("Three Address Code:")
for line in tac:
print(line)
# Generate DAG
dag = DAG()
root = dag.create_dag(expression)
print("\nDirected Acyclic Graph:")
dag.display(root)