语法分析:自上而下分析(递归下降分析法+预测分析法)

语法分析:自上而下分析


知识背景

百度百科 “语法分析是编译过程的一个逻辑阶段。语法分析的任务是在词法分析的基础上将单词序列组合成各类语法短语,如“程序”,“语句”,“表达式”等等.语法分析程序判断源程序在结构上是否正确.源程序的结构由上下文无关文法描述.语法分析程序可以用YACC等工具自动生成。”

语法分析在编译中也是一个比较很重要的环节,通常情况下语法分析可以分为自上而下分析和自下而上分析。
本文主要介绍自上而下分析(从文法的开始符号出发)的大致框架和细节,并且附上我写的一些代码供大家讨论。首先我看了挺多的关于自上而下的分析,感觉这里一个老师的解释比较清晰,供大家参考 链接。接下来的内容,根据这里老师讲的一个大概框架,写一些我自己的理解,然后分析一下我的代码。

要想进行自上而下分析,我们可以用LL(1)分析法来分析,而这个分析法主要有两种具体实现:递归分析法预测分析法,有可能其他地方不是这么叫的,但是分析的方法大同小异。
在讨论两种方法前,首先要讨论一下一些预备知识:

Q1:什么是自上而下分析法?自上而下分析的前提是什么?

百度百科 “自上而下分析法是从文法开始符号开始,不断进行推导,直到推导所得的符号串与输入串相同为止。”

简单来解释这句话:

我们有一个既定的文法,和一个需要分析的符号串。接下来我们从文法的开始符号出发,反复地使用文法规定的一些产生式匹配符号串的每一个符号,直到所得的符号串和待分析的符号串相同,则为分析成功,反之匹配不成功。举个例子说明:
G(E):
E→aF
F→b|c

待分析的输入串:ab
1
从文法开始符号E出发,E→aF,a匹配成功后,指针指向F,找非终结符F的产生式合适的候选式 b匹配,于是匹配成功。

想要对一个文法进行自上而下的分析,要消除文法的二义性,消除左递归,提取左公共因子,计算FIRST集合和FOLLOW集合,判断文法是否为LL(1)型文法,一个文法经过这些步骤,并且是LL(1)文法,则可以用LL(1)分析法的两个具体实现去分析。

Q2:什么是分析过程中的回溯问题?
从上面的例子我们可以看出,在我们碰到非终结符的时候要把非终结符用它对应的产生式的右部来代替,但是一个非终结符往往不止一个候选式(比如上面那个例子的F,F→b|c就有b和c两个选项),这个时候就会出现一个问题,如果我们选择候选式来替代非终结符的时候不能准确判断,这一次的替代是否能够正确推导出匹配的结果,一旦选择的候选式不能推导成功,就要返回上一步,换一个候选式进行推导,这就是“回溯”。例如上面那个例子,我们在拓展F的时候选择了c,会发现匹配失败,然后再返回上一步,选择另一个候选式b,才匹配成功。

Q3:如何解决回溯的问题?
实际上,含有回溯的分析过程并非不可取,只是会浪费很多的资源和时间,因此我们要想办法消除回溯,也就是争取让每一次选择候选式都选择正确的那一个。这就涉及到了下文要讲的FIRST、FOLLOW集合了。

Q4:什么是文法中含有左递归?
左递归分为直接左递归和间接左递归

  • 直接左递归
    举一个小例子G(E):E→Ea
    在这个文法中虽然只有一个产生式,但是对这个产生式进行构造语法树的时候会发现:
    2
    这个树会无限向下扩展,无法匹配结束。
  • 间接左递归
    举一个小例子G(E):
    3
    这个文法推导三次后会发现又回到了文法的开始符号又一次出现了,因此又进入循环,无法结束匹配,这就是间接左递归。

Q5:如何解决文法含有左递归的问题?
对于直接左递归,我们通常把它转换成右递归很好理解,举个小例子:
G(E):E→Ea |b
对于这个含有直接左递归的文法,将它转换成右递归的具体做法就是引入一个新的非终结符,通常我们用当前非终结符加 ’ 来表示,将文法改为
G(E):
E→bE’
E’ →aE’|ε
可以很容易地证明这两个文法是等价的。

对于间接左递归,通常把非终结符带入来产生直接左递归,例如:
G(E):
4
把S右部中的Q用Q的产生式代替,再把Q中的R用R的产生式代替,删除无关产生式后,就能得到一个含有直接左递归的文法:
G(E): S→Sabc|abc|bc|c
然后再将这个产生式按照直接左递归的处理方法进行消除左递归处理。

更一般地,想要让程序自动地消除左递归,具体的做法如下:
1.把文法的所有非终结符进行排序S = [‘A’,‘B’,…]
2.做一个嵌套循环:
其中S[k]为排在S[j]之后的非终结符,bunch_i为非终结符和终结符组成的串

for j in range(len(s)):
        for k in range(j):
        	原产生式中:S[j]→S[k]bunch_1的S[k]用其对应的产生式代替
        	S[k]→bunch_2|bunch_3|...
        	推出:S[j]→bunch_2 bunch_1|bunch_3 bunch_1|...
        	如此,做完循环后该文法若有间接左递归,就将其转换成直接左递归了
        	消除直接左递归,具体做法见上文
循环结束后删除无用产生式

下面截取一些我写的消除左递归的代码进行讨论,完整源码下载请点击 下载

获取每一条产生式的所有候选式:

            temp_split = [] # 将每一个产生式分割以便求的长度来进行分类讨论
            # 获取这一条产生式的所有'|'的索引值
            temp_or = []
            temp_push = []
            for q in range(len(lst[j])):
                if lst[j][q] == '|':
                    temp_or.append(q)
            # 获取这一条产生式'→'的索引值
            temp_get = lst[j].index('→')
            # 转换成列表方便组合
            temp_push.append(temp_get)
            # 合并查找索引列表
            temp_search =  temp_push + temp_or
            # 把一个产生式以'→'和'|'为分隔符分割,将分割后的数据存储在嵌套列表temp_split里
            for m in range(len(temp_search)):
                if len(temp_search) == 1:
                    temp_split_1 = [lst[j][temp_search[m] + 1:][:]]
                    temp_split = temp_split_1[:]
                else:
                    if m == (len(temp_search) - 1):
                        temp_split.append(lst[j][temp_search[m] + 1:])
                    else:
                        temp_split.append(lst[j][temp_search[m] + 1:temp_search[m + 1]])

间接左递归转换成直接左递归:

            change = [s[j],"→"]
            # change = []
            print("ts:",temp_split)
            for n in temp_split:
                if "'" in n:
                    n[n.index("'") - 1] += "'"
                    n.remove("'")
                try:
                    if n[0] == s[k]:
                        k_push = lst[k][:]
                        temp_z = []
                        for z in range(len(k_push)):
                            if k_push[z] 
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

x1Nge.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值