LL(1)分析法(二) ——预测分析表构建与语法分析实现(编译原理)

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集
    在这里插入图片描述

在这里插入图片描述

  • 25
    点赞
  • 141
    收藏
    觉得还不错? 一键收藏
  • 44
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 44
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值