原理
-
本次实验是设计一个LL(1)分析器,给定任意文法,自动消除一切左递归(前提是文法中不含回路,且产生式右部不含有空字),自动回溯(提取最左公因子),自动生成FIRST集,自动生成FOLLOW集,自动生成LL(1)分析表,给定一个输入串,输出该输入串的分析过程。
-
本次实验需要的原理知识在此不过多阐述,可以参考《编译原理教程》第四版(胡元义主编)
-
需要注意的是,书本上自动消除一切左递归的算法中,我认为会出现第一个产生式的直接左递归没有消除(不知道对不对,看两层循环中中第一个产生式没有消除直接左递归),所以消除完所有产生式的左递归后,在进行处理,判断是否含有直接左递归,有则消除;其他算法都是根据书上的原理进行实现的。
-
设计一个类LL1,初始化传入参数:终结符(Tset),非终结符(NTset),文法开始符(S)(需要注意的是,本次实验必须要求文法开始符是字符S), 产生式(Production), FIRST集(firstset),FOLLOW集(followset), LL(1)分析表(Table)
class LL1: def __init__(self, Tset, NTset, S, Production, firstset, followset, Table): self.Tset = Tset self.NTset = NTset self.S = S self.Production = Production self.firstset = firstset self.followset = followset self.Table = Table
各个参数的数据存放形式: Tset:[]列表 NTset:[]列表 S: 'S' 字符串 Production:{},例如:A->aA|b, 就是表示为{'A': ["aA","b"]} firstset: {}, 例如:fisrt(A):(a,b),就是表示为{‘A’:["a","b"]} followset:{},同firstset一样 Table:{}嵌套字典,例如:M[A,a] = (A->a),那么就是表示为{'A':{'a':['A->a']}}
-
LL1类的各个方法:
- removeLeftRecursion(self):消除一切左递归
- removeLeftCommonFactor(self):消除最左公因子
- createFirst(self),createFirstSet(self): 生成FIRST集
- createFollow(self),createFollowSet(self):生成FOLLOW集
- createTable(self):生成LL1分析表
-
其他函数是跟界面各个按钮绑定的函数,跟实验主旨没啥大的关系,就不阐述了
-
本次实验GUI是用Python tkinter
实验效果
LL(1)分析器界面:
![图片.png](https://i.loli.net/2020/06/05/jb7fIozdSsGYmMF.png)
点击打开文件,选择文法的txt文件,就是把文件的内容读到文本框中
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200605095310512.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjEwMDQ1Ng==,size_16,color_FFFFFF,t_70)
点击消除左递归,消除左因子,生成first集,生成follow集按钮,就是生成对应的结果。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200605095628605.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjEwMDQ1Ng==,size_16,color_FFFFFF,t_70)
点击生成Table表,就会生成一个新窗口,显示结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200605095740309.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjEwMDQ1Ng==,size_16,color_FFFFFF,t_70)
然后在输入框输入判定的字符串
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200605095845884.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjEwMDQ1Ng==,size_16,color_FFFFFF,t_70)
点击判断栈情况,输入该字符串的分析过程
源代码(main.py)
import tkinter
import tkinter.filedialog #对话框
import tkinter.ttk # 表格
"""
Production产生式存放:
A->a | b
{A:['a','b']}
firstset集存在,follow集同理
first(A) = (a,b),则{'A':[a,b]}
table表(M表)例如:M[A,(] = "A->(abc"
{'A':{'(':["A->(abc"]}}
"""
class LL1:
def __init__(self, Tset, NTset, S, Production, firstset, followset, Table):
self.Tset = Tset
self.NTset = NTset
self.S = S
self.Production = Production
self.firstset = firstset
self.followset = followset
self.Table = Table
# 消除一切左递归
def removeLeftRecursion(self):
i = 1
while i <= len(self.NTset):
j = 1
while j <= i-1:
# set转换为list,才能进行下标取值操作
Ai = self.NTset[i-1] # Ai非终结符
Aj = self.NTset[j-1] # Aj非终结符
rmList = [x for x in self.Production[Ai] if x.startswith(Aj)]
if rmList:
addList = [y+x.replace(Aj, "", 1) if y != 'ε' else x.replace(Aj, "", 1)
for x in rmList for y in self.Production[Aj]]
# 本来不需要替换的,保留
self.Production[Ai] = list(
filter((lambda x: x not in rmList), self.Production[Ai]))
# 追加新替换的式子进来
# extend,append函数没有返回值的,所以不能list().extend(),否则返回None
self.Production[Ai].extend(addList)
# print("去除间接左递归", self.Production)
# 消除直接左递归
LeftRecursionProduction = [
x for x in self.Production[Ai] if x.startswith(Ai)] # 左递归的产生式
notLeftRecursionProduction = [
x for x in self.Production[Ai] if not x.startswith(Ai)] # 不是左递归的产生式
if LeftRecursionProduction: # 含有左递归的产生式
newNT = Ai+"'" # 新终结符,如 A,A'
if newNT not in self.NTset:
self.NTset.append(newNT)
if notLeftRecursionProduction:
self.Production[Ai] = [
x+newNT for x in notLeftRecursionProduction]
else:
self.Production[Ai] = [newNT]
newList = [
x.replace(x[0], '', 1)+newNT for x in LeftRecursionProduction]
newList.append('ε') # 追加空字,哑f西no
self.Production[newNT] = newList # 加进产生式的字典中
j = j+1
i = i+1
"""
特殊判断,第一个非终结符对应的产生式本身是直接左递归,
注意是第一个非终结符,因为上面的两层循环判断了除第一个非终结符的情况了
"""
LeftRecursionProduction = [
x for x in self.Production[self.NTset[0]] if x.startswith(self.NTset[0])]
notLeftRecursionProduction = [
x for x in self.Production[self.NTset[0]] if not x.startswith(self.NTset[0])]
if LeftRecursionProduction:
newNT = 'S'+"'"
if newNT not in self.NTset: # 新的终结符判断是否存在,不存在插入
self.NTset.append(newNT)
if notLeftRecursionProduction:
self.Production['S'] = [
x+newNT for x in notLeftRecursionProduction]
else:
self.Production['S'] = [newNT]
newList = [x.replace(x[0], '', 1) +
newNT for x in LeftRecursionProduction]
newList.append('ε') # 追加空字,哑f西no
self.Production[newNT] = newList # 加进产生式的字典中
# 去除多余的生成式,dfs
"""
fromkeys方法注意:
如果每个key的value都为同一个对象,
则操作该key的value时,所有的key的value都会改变
"""
judge = dict.fromkeys(self.NTset, 0) # 字典,非终结符做key,0做valu
stack = []
stack.append(self.S)
judge[self.S] = 1 # 标志为已访问
while(len(stack) > 0 and (0 in judge.values())):
top = stack.pop()
rightPro = self.Production[top] # 产生式右部
for item in rightPro:
i = 0
while i < len(item):
ch = ""
if item[i] in self.NTset:
ch += item[i]
if i != len(item)-1:
if item[i+1] == "'":
ch += "'"
if judge[ch] == 0:
stack.append(ch)
judge[ch] = 1 # 标志为访问过
i = i+1
for key, values in judge.items():
if values == 0:
self.Production.pop(key) # 去除产生式
self.NTset.remove(key) # 去除非终结符
# 消除最左公因子
"""
算法思想:将每个非终结符的产生式右部,也就是多个字符串,按照首个字符分类,
然后进行转换,新转换生成的新非终结符的产生式更新到self实例中,进行转换。
"""