LL(1)分析法
博主所有编译原理博客描述项目代码均上传至百度网盘可直接下载 链接:https://pan.baidu.com/s/1QUo_kdW1q_bpR7fSoZGq2g?pwd=snpy
提取码:snpy
前期回顾与任务规划
下面对已完成的任务和未完成的任务进行梳理
已完成的任务
- 消除左递归
- 提取公共左因子
- 求解FIRST集
- 求解FOLLOW集
待完成的任务
- 判断文法是否为LL(1)文法
- 构建LL(1)预测分析表
- 对输入表达式进行LL(1)分析
- 异常处理机制
对于已完成的任务有不清楚的请参考第一篇文章
LL(1)分析法(一) ——文法预处理以及FIRST集FOLLOW集求解(编译原理)
下面废话少说让我们继续完成接下来的任务吧!!!加油!!
LL(1)文法判断
LL(1)文法的三个条件
对于这三个条件其实也不难理解,因为是从左往右分析,肯定不能含有左递归。第二个和第三个条件是为了确保当面临一个输入符号时,都只有准确的唯一指派,这样就不会产生回溯。
对应程序设计思路
- 对于条件一由于第一步便是消除左递归因此,经处理后的文法一定是无左递归的。
- 对于条件二,还记得我们之前对FIRST集求解后的改进吗,我们不仅仅求解了FIRST集,还记录了FIRST集中的每个元素都分别是由哪几个产生式产生的。因此这里就会很容易。如果FIRST集中的一个元素对应多个产生式对应列表长度,那么就说明,在面对同一个输入符的时候会有多个可能指派因此不是正规文法。
- 对于条件三,也不难只要前两个条件满足,这个只需查找FIRST集中含有空串的判断其与FOLLOW集是否交集为空即可。
代码实现
def LL_judge(infinite):
name_list = infinite.keys()
flag = True
for name in name_list: # 验证第二个条件
for first in infinite[name].FIRST.keys():
if len(infinite[name].FIRST[first]) > 1: # 对应多个产生式因此不是
flag = False
break
if flag: # 第二个条件满足判断第三个条件
for name in name_list:
first_set = set(infinite[name].FIRST.keys())
follow_set = infinite[name].FOLLOW
if 'ε' in first_set:
if len(first_set.intersection(follow_set)) > 0:
flag = False
break
return flag
LL(1)预测分析表生成
预测分析表是一个M[A,a]形式的矩阵,其中A为非终结符,a是终结符,M[A,a]存放的是一条A的产生式,指出当面对输入字符a时所应采用的候选式,也有可能存放出错标志
LL(1)构建算法描述
这个是书上的描述其实并不是很好理解,但还记得我们之前改进的FIRST集求法吗?在此处会排上大用。因为在求解FIRST集时我们已经对FIRST集的每个元素建立了映射关系,因此此处只需将映射关系稍加处理即可。
即:
对于步骤三更简单了因为用到FOLLOW集产生式对应为空串因此根本不需要考虑其他
实现代码
def LL_create_table(infinite):
"""
建立LL(1)预测分析表
:param infinite: 经处理后的文法信息
:return: 建立的LL(1)预测分析表
"""
inputSet = set()
name_list = infinite.keys()
table = {}
for name in name_list:
for s in infinite[name].FIRST.keys():
inputSet.add(s)
for s in infinite[name].FOLLOW:
inputSet.add(s)
inputSet.discard('ε')
for s in inputSet:
for name in name_list:
table.setdefault(name,dict())
table[name].setdefault(s,'')
if s in infinite[name].FIRST.keys(): # 如果在内则直接将对应值放入表
table[name][s] = infinite[name].FIRST[s]
elif 'ε' in infinite[name].FIRST.keys() and s in infinite[name].FOLLOW:
table[name][s] = 'ε'
else:
table[name][s] = 'ERROR'
for s in inputSet:
print('{:<15}'.format(s), end="|")
print()
for name in table.keys():
for s in inputSet:
print('{:<15}'.format(str(table[name][s])),end="|")
print()
测试结果:
这部分的实现其实有了前面部分的铺垫并不是很困难。如果有不懂的地方查看我上一篇文章在求解时的改进。
LL(1)语法分析实现
大致处理流程
递归程序描述
其实这个递归程序已经把下面编码的设计思路说的很清楚了。首先把‘#’和开始符推入栈,然后再对输入符号进行分析,查找LL(1)预测分析表执行,根据表中的产生式进行入栈出栈操作,并进行错误处理。
具体代码实现
def LL_analyse(table, express, start=0):
"""
实现对输入表达式进行分析
:param table: 之前建立的预测分析表
:param express: 要分析的表达式
:param start: 用户指定的开始符号
:return:分析过程的各个状态
"""
stack = [] # 建栈
name_list = list(table.keys())
actions = [] # 记录分析过程中的动作
temp_stack = [] # 记录每一个分析步骤栈的状态
temp_express = [] # 记录每一个分析步骤输入串的状态
create_uses = [] # 记录每一个步骤使用的产生式
stack.append('#') # ‘#’号压入栈
stack.append(name_list[start])
actions.append('初始化')
temp_stack.append(list(stack)) # 注意python
temp_express.append(express)
create_uses.append(' ')
i = 0
while True:
if i >= len(express):
break
else:
a = express[i] # 读取输入字符存入a
if len(stack) >0:
X = stack.pop() # 弹出栈顶元素
else:
actions.append("异常表达式,分析结束")
temp_express.append(express[i:])
temp_stack.append(list(stack))
create_uses.append("ERROR 异常")
break
if X.islower() or X in op or X == '#': # 处理栈顶为终结符的情况
if X == '#':
actions.append("LL(1)分析结束")
temp_express.append(express[i:])
temp_stack.append(list(stack))
create_uses.append("#")
break
else: # 两个终结符匹配
actions.append('GET NEXT')
temp_stack.append(list(stack))
create_uses.append(' ')
temp_express.append(express[i:])
i += 1
elif X.isupper(): # 处理栈顶为非终结符的情况
if table[X][a] == 'ERROR': # 此时为ERROR则需要进行错误处理
actions.append("ERROR" + "跳过" + "{}".format(a)) # 进入下一个
stack.append(X) # 将X中的元素重新放回
temp_express.append(express[i:])
create_uses.append(" ")
i += 1 # 跳过当前元素
elif table[X][a] == 'synch':
actions.append("synch" + "弹出" + "{}".format(X))
temp_stack.append(list(stack))
create_uses.append(" ")
temp_express.append(express[i:])
else:
create = ''.join(table[X][a])
if table[X][a] != 'ε':
actions.append("POP,PUSH({})".format(create))
create_uses.append(X + "->" + str(table[X][a]))
for s in reversed(create):
stack.append(s)
temp_stack.append(list(stack))
temp_express.append(express[i:])
else:
actions.append("POP")
create_uses.append(X + "->" + str(table[X][a]))
temp_stack.append(list(stack))
temp_express.append(express[i:])
return temp_stack,temp_express,create_uses,actions
写起来还是蛮有难度的,现在附一下简单的输出结果,经与老师给出的答案比对,分析是正确的。
测试图片
现在我们的整体分析流程已经实现,并且经过测试后也已经能实现对输入表达式的文法分析,下面就是正式设计用户界面啦!
用户界面设计
几点设计说明
- 将FIRST集,FOLLOW集求解的结果保存到文本文件中,供用户查看
- 将LL(1)预测分析表保存到文本文件中,此处我是为了图省事,其实可以保存到xslx等中的,但都大同小异,我就没打算搞。
- 对用户输入进行判断,若进行补充
- 提供用户更改文法内容的接口
- 提供用户设置文法开始符号的接口
- 对用户设置的文法判断其是否为LL(1)文法并提供建议
运行截图:
预测分析表
FIRST集
FOLLOW集