cdlab week 5
cdlab week 5
1.Aim: Write a program to generate LL(1) parsing table for expression grammar
Description: To generate the LL(1) parsing table, the program follows multiple
preprocessing steps to ensure the grammar is suitable for LL(1) parsing. It first eliminates
left recursion, which is necessary because LL(1) parsers cannot handle left-recursive rules.
Then, it applies left factoring to remove ambiguities caused by common prefixes in
production rules.
After transforming the grammar, the program computes FIRST and FOLLOW functions:
• FIRST function: The set of terminals that can appear at the beginning of any string
derived from a non-terminal.
• FOLLOW function The set of terminals that can appear immediately after a non-
terminal in some valid derivation.
Using these sets, the program constructs the LL(1) parsing table, mapping each (non-
terminal, terminal) pair to a production rule. This table is then printed in a structured
format, making it easier to visualize how an LL(1) parser would process an input string.
By using this parsing table, an LL(1) parser can efficiently analyze an expression by making
single-symbol lookahead decisions, ensuring a deterministic and efficient parsing process.
Algorithm:
Code:
from collections import defaultdict
def eliminate_left_recursion(grammar):
new_grammar = {}
for nt in grammar:
alpha_rules = []
beta_rules = []
for rule in grammar[nt]:
if rule[0] == nt:
alpha_rules.append(rule[1:])
else:
beta_rules.append(rule)
if alpha_rules:
new_nt = nt + "'"
new_grammar[nt] = [beta + [new_nt] for beta in beta_rules]
new_grammar[new_nt] = [alpha + [new_nt] for alpha in alpha_rules] + [['ε']]
else:
new_grammar[nt] = grammar[nt]
return new_grammar
def left_factoring(grammar):
new_grammar = {}
for nt, rules in grammar.items():
prefix_map = defaultdict(list)
for rule in rules:
prefix_map[rule[0]].append(rule)
def get_symbols(grammar):
terminals = set()
non_terminals = set(grammar.keys())
for rules in grammar.values():
for rule in rules:
for symbol in rule:
if not symbol.isupper() and symbol != 'ε':
terminals.add(symbol)
terminals.add('$') # End-of-input symbol
def first_of(symbol):
if symbol in terminals:
return {symbol}
if symbol == 'ε':
return {'ε'}
result = set()
for rule in grammar[symbol]:
for s in rule:
sub_first = first_of(s)
result.update(sub_first - {'ε'})
if 'ε' not in sub_first:
break
else:
result.add('ε')
return result
for nt in non_terminals:
first[nt] = first_of(nt)
return first
changed = True
while changed:
changed = False
for nt, rules in grammar.items():
for rule in rules:
trailer = follow[nt].copy()
for symbol in reversed(rule):
if symbol in non_terminals:
if trailer - follow[symbol]:
follow[symbol].update(trailer)
changed = True
if 'ε' in first_sets[symbol]:
trailer.update(first_sets[symbol] - {'ε'})
else:
trailer = first_sets[symbol]
else:
trailer = {symbol}
return follow
if 'ε' in first_of_rule:
for terminal in follow_sets[nt]:
table[nt][terminal] = rule
return table
# Example Grammar
grammar = {
'E': [['T', "E'"], ],
"E'": [['+', 'T', "E'"], ['ε']],
'T': [['F', "T'"], ],
"T'": [['*', 'F', "T'"], ['ε']],
'F': [['(', 'E', ')'], ['id']]
}
grammar = eliminate_left_recursion(grammar)
grammar = left_factoring(grammar)
terminals, non_terminals = get_symbols(grammar)
first_sets = compute_first_sets(grammar, terminals, non_terminals)
follow_sets = compute_follow_sets(grammar, terminals, non_terminals, first_sets)
parsing_table = construct_parsing_table(grammar, terminals, non_terminals, first_sets,
follow_sets)
Output1 :
Conclusion:
The program successfully constructs an LL(1) parsing table for the given expression grammar
by eliminating left recursion, performing left factoring, and computing FIRST and FOLLOW
sets. This table helps in predictive parsing, enabling top-down parsing without backtracking.
The approach ensures that the grammar is compatible with the LL(1) parsing technique,
making it deterministic and efficient.