本文介绍消除文法左递归的算法,并输出新文法产生式。
消除左递归在语法分析阶段是比较重要的一个过程,尤其在自顶向下的分析过程中,编译器会尝试各个推导式,如果存在左递归,那么推导过程将会用永无止境。
比较显式的左递归我们称之为直接左递归,比如说
其中表示非空的表达式,
表示不以非终结符A开头的表达式。
那么我们知道对于非终结符A来说,最终只能以到
来结束,否则依然会含有非终结符A,因此我们可以将文法直接改为
然而并非所有的左递归都这么显而易见,还是会存在很多间接左递归
那么我们就需要一个通用的算法来消除所有的直接左递归和间接左递归。
⫸ Algorithm
算法也不难理解,大致流程如下:
-
将所有非终结符先进行整理编号,即所有非终结符转换为
-
对每一个
,我们将其所有满足
的产生式
替换为
,其中
-
从
开始至
,依次执行步骤2,每完成一个
的替换就消除当前该
的所有直接左递归
该算法的伪代码如下图所示:
我们可以注意到,每一个完成替换后,那么
所有产生式右边的第一个符号要么是终结符,要么是编号
的非终结符
,在消除此时产生式中的直接左递归后,就只剩下终结符和编号
非终结符
打头的右部了。这样一来,最终的文法一方面不可能有直接左递归,另一方面不可能有间接左递归(因为只能小编号的非终结符推出大编号的非终结符)。
C ☺ D E
from copy import deepcopy
n = eval(input('请输入文法产生式的个数:'))
print('请输入文法产生式:')
gen = dict()
left = dict()
num = 0
for i in range(n):
g = input().replace(' ', '')
assert g[1:3] == '->'
gen.setdefault(g[0], [[], []])
left[g[0]] = num
num += 1
start = 3
for i in range(3, len(g)):
if g[i] == '|' or i == len(g) - 1:
i += (1 if i == len(g) - 1 else 0)
assert start < i
if g[0].isupper():
gen[g[0]][0].append(g[start:i])
elif g[0].islower():
gen[g[0]][1].append(g[start:i])
start = i + 1
# 将没有出现在产生式左部的非终结符当作终结符
for key in gen:
nonterminal = deepcopy(gen[key][0])
for item in nonterminal:
if item[0] not in left.keys():
gen[key][0].remove(item)
gen[key][1].append(item)
assert gen[key][1] != []
# 替换Ai->Ajβ (j<i)
more_gen = dict()
for key in gen:
rmv = []
for item in gen[key][0]:
if left[item[0]] < left[key]:
rmv.append(item)
for k in gen[item[0]][1]:
gen[key][1].append(k + item[1:])
for k in gen[item[0]][0]:
gen[key][0].append(k + item[1:])
for r in rmv:
gen[key][0].remove(r)
# 消除直接左递归
nonterminal = deepcopy(gen[key][0])
flag = True
terminal = deepcopy(gen[key][1])
for item in nonterminal:
if item[0] == key:
sym = '[' + key + '\']'
more_gen.setdefault(sym, []).extend([item[1:], (item[1:]) + sym])
if flag:
for k in terminal:
gen[key][1].append(k + sym)
gen[key][0].remove(item)
flag = False
for key in gen:
gen[key][0].extend(gen[key][1])
gen[key] = gen[key][0]
gen.update(more_gen)
print("\nAfter Remove Left Recursion:")
for key in gen:
print(key + ' -> ', end='')
print(*gen[key], sep=' | ')
➷ Tips
-
读入输入时先除去所有空格,并规定终结符和非终结符仅能为单个字母,小写字母代表终结符,大写字母代表非终结符
-
所有产生式用一个字典存储,每个产生式左边的非终结符作为键值,每个键值对应一个列表,列表里有两个子列表(见3)
-
对每一个产生式的右部进行分类,非终结符打头的一类,终结符打头的一类
-
这里将没有出现在产生式左部的非终结符当作终结符,从而无需对这些非终结符进行编号
⚠️ Python中可迭代对象的直接赋值是引用,复制赋值则需要
deepcopy()