求文法符号的FIRST和FOLLOW集合
- 自上而下分析:求FIRST、FOLLOW集
预备介绍
在自上而下分析的过程中我们需要对指定文法 进行“消除二义性”、“消除左递归”、“消除回溯”等操作,而许多方面也涉及到文法符号的FIRST集和FOLLOW集的运用。
具体分析
FIRST集合和FOLLOW集合的求法在网上有很多,我也把我看到过的比较好的一种讲述方法呈现给大家参考,并且本文附带我自己编写的求两个集合的代码也供大家参考。
内容一:FIRST集合
首先我们了解一下FIRST集合的求法,对比了许多网上的方法,基本大同小异,这里贴一个给大家参考:链接
大致过程如下:
扫描所有的产生式,对于每一个X,连续使用以下规则,直至每个集合的FIRST集合不再增大为止,每一遍扫描所有产生式如果有FIRST集合变化则重新扫描:
1.如果X是终结符,则FIRST(X) = {X}.
2.如果X是非终结符 ,且有产生式 X → a… 则把a加入到FIRST集合中.
若 X → ε也是其中一条产生式,则把ε也加入到FIRST集合中.
3.如果X是非终结符,且 X → Y… 是一条产生式,其中Y是非终结符,那么则把FIRST(Y) \ {ε}(即FIRST集合去除ε)加入到FIRST(X)中.
更复杂些的,对于产生式 X → Y1 Y2…Yi-1 Yi…Yk ,其中Y1,…,Yi-1都是非终结符:
① 对于任意的j,满足 1 <= j <= i-1 ,且FIRST(Yj)都含有ε,则把
FIRST(Yi) \ {ε} 加入到FIRST(X)中.
② 对于任意的p,满足 1 <= p <= k ,且FIRST(Yp)都含有ε,则把
ε加入到FIRST(X)中.
注意: 每一遍对所有产生式的扫描若有一点点改动就要再全部扫描一遍。
接下来是具体代码分析,本文将拆分开来分析,完整源码见 FIRST集合
定义这个函数,确定形参和返回值:
其中形参lst为嵌套列表,第一层为所有产生式,第二层为每一条产生式的具体元素。
# 形参lst为嵌套列表,内容为每一条产生式.返回值为一个字典,记录所有FIRST集合的数据
def get_first(lst):
初始化一些要用到的变量: 其中rst为返回的字典,flag用来判断本次扫描是否有集合的变化。同时引入标点符号库,判断一些特殊符号的终结符
rst = {
}
flag = 1
punc = string.punctuation # 引入标点符号库
存储每个非终结符:
for i in lst:
rst["".join(i[:i.index('→')])] = [] # 将每一个非终结符存入字典
循环扫描所有产生式:
对于每一条产生式,我的做法是,首先获取这一条产生式,然后找到这条产生式中→和 | 的索引。
# 获取这一条产生式的所有'|'的索引值
temp_or = []
temp_push = []
for k in range(len(j)):
if j[k] == '|':
temp_or.append(k)
# 获取这一条产生式'→'的索引值
temp_get = j.index('→')
# 转换成列表方便组合
temp_push.append(temp_get)
# 合并查找索引列表
temp_search = temp_push + temp_or
如此,我们把这几个分隔位置存在一个列表里,就可以根据这里的列表,来轻松地获取我们需要的产生式的右部。当然也可以直接在处理的时候查找→和 | 的位置。
接下来就是按照上面条件开始判断,循环体的代码如下:
while flag == 1:
flag = 0
for j in lst:
# 获取这一条产生式的所有'|'的索引值
temp_or = []
temp_push = []
for k in range(len(j)):
if j[k] == '|':
temp_or.append(k)
# 获取这一条产生式'→'的索引值
temp_get = j.index('→')
# 转换成列表方便组合
temp_push.append(temp_get)
# 合并查找索引列表
temp_search = temp_push + temp_or
# 按条件判断
if "".join(j[:temp_get]).islower() or ("".join(j[:temp_get]) in punc):
if "".join(j[:temp_get]) not in rst["".join(j[:temp_get])]:
rst["".join(j[:temp_get])].append("".join(j[:temp_get]))
flag = 1
for m in temp_search:
if "".join(j[m + 1:m + 2]).islower() or ("".join(j[m + 1:m + 2]) in punc)\
or "".join(j[m + 1:m + 2]) == 'ε':
if "".join(j[m + 1:m + 2]) not in rst["".join(j[:temp_get])]:
rst["".join(j[:temp_get])].append("".join(j[m + 1:m + 2]))
flag = 1
if "".join(j[m + 1:m + 2]).isupper() :
if "".join(j[m + 2:m + 3]) == "'":
if 'ε' in rst["".join(j[m + 1:m + 3])]: # temp_delete用于储存FIRST(...) \ {ε}的数据
temp_delete = rst["".join(j[m + 1:m + 3])].remove('ε')
# 将两个列表转换成结合进行交集运算若交集为空集则说明之前没有进行此操作可以添加
if not set(temp_delete) <= set(rst["".join(j[:temp_get])]):
rst["".join(j[:temp_get])].extend(temp_delete)
flag =1
else:
if not set(rst["".join(j[m + 1:m + 3])]) <= set(rst["".join(j[:temp_get])]):
rst["".join(j[:temp_get])].extend(rst["".join(j[m + 1:m + 3])])
flag = 1
else:
if 'ε' in rst["".join(j[m + 1:m + 2])]:
temp_delete = rst["".join(j[m + 1:m + 2])].remove('ε')
if not set(temp_delete) <= set(rst["".join(j[:temp_get])]):
rst["".join(j[:temp_get])].extend(temp_delete)
flag = 1
else:
if not set(rst["".join(j[m + 1:m + 2])]) <= set(rst["".join(j[:temp_get])]):
rst["".join(j[:temp_get])].extend(rst["".join(j[m + 1:m + 2])])
flag = 1
return rst
内容二:构造任何符号串的FIRST集合
我们能求任何一个文法符号的FIRST集合后,有时候会碰到要求一个符号串的FIRST集合的情况,比如:A→Abc 求FIRST(bc)
介绍一下求符号串FIRST集合的方法.
对于文法G的任何符号串α = X1 X2 … Xn 构造集合FIRST(α):
1.置FIRST(α) = FIRST(X1) \ {ε} .即把X1的FIRST集合元素去掉ε后给α的FIRST集合来作为其第一批元素.
2.若对于任何 1 <= j <= i-1 ,ε∈ FIRST(Xj),则把FIRST(Xi) \ {ε}加入到FIRST(α)中.
特别的:
若所有的FIRST(Xj)均含有ε,其中1 <= j <= n,那么把ε也加入到FIRST(α)中.
显然:若α = ε,则FIRST(α) = {ε}.
接下来是具体代码分析,下载源码见 FIRST_BUNCH
## 形参lst为符号串,形式为列表,内容为每一个符号.返回值为一个列表,记录所求FIRST集合
## 形参lst_origin为嵌套列表,可以通过这个调用get_first获取所有非终结符的FIRST集合
def get_first_bunch(lst,lst_origin):
punc = string.punctuation # 引入标点符号库
temp_rst = get_first(lst_origin)
if (lst[0] in punc) or lst[0] == 'ε' or lst[0].islower():
rst = [lst[0]]
else:
rst = temp_rst[lst[0]][:]
if 'ε' in rst:
rst.remove('ε')
i = 0
for i in range(len(lst)):
if 'ε' not in temp_rst[lst[i]]:
break
rst.extend(temp_rst[lst[i]])
if 'ε' in rst:
rst.remove('ε')