求文法符号的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('ε')
if i + 1 == len(lst) and ('ε' in temp_rst[lst[i]]):
rst.append('ε')
return list(set(rst))
内容三:构造每一个非终结符的FOLLOW集合
首先我们了解一下FOLLOW集合的求法,对比了许多网上的方法,基本大同小异,这里贴一个给大家参考:链接
大致过程如下:
扫描所有产生式.对于文法G的每一个非终结符A构造FOLLOW(A)可以连续使用以下规则,直至每一个FOLLOW集合不在增大为止:
1.对于文法的开始符号S,置 # 于FOLLOW(S)中.
2.若有产生式 A → αBβ,其中α和β是非终结符和终结符构成的串,B为非终结符,则把FIRST(β) \ {ε}加入到FOLLOW(B)中.
3.若有产生式 A → αB 或 A → αBβ ε∈FIRST(β) ,则把FOLLOW(A)加入到FOLLOW(B)中.
注意: 一个产生式可能匹配算法的好几个步骤。
这一部分的逻辑代码我构思了挺久,没有想到什么好的方法,我的想法是:
按照产生式右部的长度分类讨论,长度为1、2、3以及大于3。其中长度小于等于3的逻辑代码我已经测试完毕,大于3部分的代码还在测试,还有一些小问题,所以暂时只分析(逻辑代码写起来也不复杂),之后完成后更新。
接下来是具体代码分析,下载源码见 FOLLOW集合
首先是和获取FIRST集合差不多的获取数据的部分:
其中形参lst为嵌套列表,第一层为所有产生式,第二层为每一条产生式的具体元素。
## 形参lst为嵌套列表,内容为每一条产生式.返回值为一个字典,记录所有FOLLOW集合的数据
## 产生式的条数和非终结符的个数相同,这里我通过分类讨论每一条产生式中的非终结符个数来确定这一产生式匹配几个算法
## 2020.5.11 代码中运算只包括小于等于三个非终结符/终结符的串,并且在等于三的情况中,有一些情况等待coding
## 计算一个指定串的FIRST集合代码已经写好,可以方便在待coding部分调用
## 2020.5.11 修改部分判定条件。我觉得这整个求FOLLOW集的算法有点太过于繁琐,是不是因为思考方向?
def get_follow(lst):
rst = {}
flag = 1
punc = string.punctuation # 引入标点符号库
for i in lst:
rst["".join(i[:i.index('→')])] = [] # 将每一个非终结符存入字典
while flag == 1:
flag = 0
for j in lst:
first = get_first(lst) # 获取first字典
temp_split = [] # 将每一个产生式分割以便求的长度来进行分类讨论
# 获取这一条产生式的所有'|'的索引值
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
获取 → 和 | 的索引,并且根据这些索引,把一条产生式所有的右部都分离出来方便调用:
这里temp_split中存储的就是当前产生式所有的右部,比如,若产生式为
A → Ea|bE’|abc , 则存储的是[[‘E’,‘a’],[‘b’,‘E’,"’"],[‘a’,‘b’,‘c’]]
# 把一个产生式以'→'和'|'为分隔符分割,将分割后的数据存储在嵌套列表temp_split里
for m in range(len(temp_search)):
if len(temp_search) == 1:
temp_split = [j[temp_search[m] + 1:]]
else:
if m == (len(temp_search) - 1):
temp_split.append(j[temp_search[m] + 1:])
else:
temp_split.append(j[temp_search[m] + 1:temp_search[m + 1]])
接下来按条件判断:
处理开始符号:
if lst.index(j) == 0:
if '#' not in rst["".join(j[:j.index('→')])]:
rst["".join(j[:j.index('→')])].append('#')
flag = 1
按右部的长度进行分类处理:
这边需要说明的就是,有注释待编写代码的地方,其实是一些小的分支,具体就是跟其他处理方式一致,不过要用调用get_first_bunch方法求符号串的FIRST集合。另外长度大于3的情况也是如此,用一个循环,来匹配三个步骤里的几个条件,只不过需要调用的就是get_first_bunch,而并非只有get_first方法了。
for n in temp_split:
length = len(n)
if length == 2 and ("'" not in n):
# 两个非终结符
if "".join(n).isupper():
if 'ε' in first[n[1]]:
temp_judge = first[n[1]]
temp_judge.remove('ε')
if not set(rst[n[0]]) >= set(temp_judge):
rst[n[0]].extend(temp_judge)
flag = 1
if not set(rst[n[0]]) >= set(first[n[1]]):
rst[n[0]].extend(first[n[1]])
flag = 1
if not set(rst[n[1]]) >= set(rst["".join(j[:temp_get])]):
rst[n[1]].extend(rst["".join(j[:temp_get])])
flag = 1
if 'ε' in first[n[1]]:
if not set(rst[n[0]]) >= set(rst["".join(j[:temp_get])]):
rst[n[0]].extend(rst["".join(j[:temp_get])])
flag =1
# 一个非终结符和一个终结符
if "".join(n[0]).islower() or ("".join(n[0]) in punc) and "".join(n[1]).isupper():
if not set(rst[n[1]]) >= set(rst["".join(j[:temp_get])]):
rst[n[1]].extend(rst["".join(j[:temp_get])])
flag = 1
if "".join(n[1]).islower() or ("".join(n[1]) in punc) and "".join(n[0]).isupper():
if n[1] not in rst[n[0]]:
rst[n[0]].append(n[1])
flag = 1
if length == 3 and ("'" in n):
if n.index("'") == 1:
if n[2].isupper():
if 'ε' in first[n[2]]:
temp_judge = first[n[2]]
temp_judge.remove('ε')
if not set(rst["".join(n[:2])]) >= set(temp_judge):
rst["".join(n[:2])].extend(temp_judge)
flag = 1
if not set(rst["".join(n[:2])]) >= set(rst["".join(j[:temp_get])]):
rst["".join(n[:2])].extend(rst["".join(j[:temp_get])])
flag =1
if not set(rst["".join(n[:2])]) >= set(first[n[2]]):
rst["".join(n[:2])].extend(first[n[2]])
flag = 1
if not set(rst[n[2]]) >= set(rst["".join(j[:temp_get])]):
rst[n[2]].extend(rst["".join(j[:temp_get])])
flag = 1
# if 'ε' in first[n[2]]:
# if not set(rst["".join(n[:2])]) >= set(rst["".join(j[:temp_get])]):
# rst["".join(n[:2])].extend(rst["".join(j[:temp_get])])
# flag =1
else:
if n[2] not in rst["".join(n[:2])]:
rst["".join(n[:2])].append(n[2])
flag = 1
if n.index("'") == 2:
if n[0].isupper():
if 'ε' in first["".join(n[1:])]:
temp_judge = first["".join(n[1:])]
temp_judge.remove('ε')
if not set(rst[n[0]]) >= set(temp_judge):
rst[n[0]].extend(temp_judge)
flag = 1
if not set(rst[n[0]]) >= set(rst["".join(j[:temp_get])]):
rst[n[0]].extend(rst["".join(j[:temp_get])])
flag =1
if not set(rst[n[0]]) >= set(first["".join(n[1:])]):
rst[n[0]].extend(first["".join(n[1:])])
flag = 1
if not set(rst["".join(n[1:])]) >= set(rst["".join(j[:temp_get])]):
rst["".join(n[1:])].extend(rst["".join(j[:temp_get])])
flag = 1
# if 'ε' in first["".join(n[1:])]:
# print("ok2")
# if not set(rst[n[0]]) >= set(rst["".join(j[:temp_get])]):
# rst[n[0]].extend(rst["".join(j[:temp_get])])
# flag =1
if length == 3 and ("'" not in n):
if n[0].islower() or (n[0] in punc):
if n[1].islower() or (n[1] in punc):
if n[2].isupper():
if not set(rst[n[2]]) >= set(rst["".join(j[:temp_get])]):
rst[n[2]].extend(rst["".join(j[:temp_get])])
flag = 1
if n[1].isupper():
if n[2].isupper():
if 'ε' in first[n[2]]:
temp_judge = first[n[2]]
temp_judge.remove('ε')
if not set(rst[n[1]]) >= set(temp_judge):
rst[n[1]].extend(temp_judge)
flag = 1
if not set(rst[n[1]]) >= set(rst["".join(j[:temp_get])]):
rst[n[1]].extend(rst["".join(j[:temp_get])])
flag =1
if not set(rst[n[1]]) >= set(first[n[2]]):
rst[n[1]].extend(first[n[2]])
flag = 1
# if 'ε' in first[n[2]]:
# if not set(rst[n[1]]) >= set(rst["".join(j[:temp_get])]):
# rst[n[1]].extend(rst["".join(j[:temp_get])])
# flag =1
else:
if n[2] not in rst[n[1]]:
rst[n[1]].append(n[2])
flag = 1
if n[0].isupper():
if n[1].isupper():
if n[2].isupper():
if 'ε' in first[n[2]]:
temp_judge = first[n[2]]
temp_judge.remove('ε')
if not set(rst[n[1]]) >= set(temp_judge):
rst[n[1]].extend(temp_judge)
flag = 1
if not set(rst[n[1]]) >= set(rst["".join(j[:temp_get])]):
rst[n[1]].extend(rst["".join(j[:temp_get])])
flag = 1
if not set(rst[n[1]]) >= set(first[n[2]]):
rst[n[1]].extend(first[n[2]])
flag = 1
# code record εAAA
## ...
# if 'ε' in first[n[2]]:
# if not set(rst[n[1]]) >= set(rst["".join(j[:temp_get])]):
# rst[n[1]].extend(rst["".join(j[:temp_get])])
# flag = 1
else:
if n[2] not in rst[n[1]]:
rst[n[1]].append(n[2])
flag = 1
# code record εAAa
## ...
else:
# AaA
if n[2].isupper():
if not set(rst[n[2]]) >= set(rst["".join(j[:temp_get])]):
rst[n[2]].extend(rst["".join(j[:temp_get])])
flag = 1
# code record εAaA
## ...
# code record Aaa
## ...
if length == 4 and ("'" in n):
if n[1] == "'":
if n[2].isupper():
if n[3].isupper():
if 'ε' in first[n[3]]:
temp_judge = first[n[3]]
temp_judge.remove('ε')
if not set(rst[n[2]]) >= set(temp_judge):
rst[n[2]].extend(temp_judge)
flag = 1
if not set(rst[n[2]]) >= set(rst["".join(j[:temp_get])]):
rst[n[2]].extend(rst["".join(j[:temp_get])])
flag = 1
if not set(rst[n[2]]) >= set(first[n[3]]):
rst[n[2]].extend(first[n[3]])
flag = 1
# code record εA'AA
## ...
# if 'ε' in first[n[3]]:
# if not set(rst[n[2]]) >= set(rst["".join(j[:temp_get])]):
# rst[n[2]].extend(rst["".join(j[:temp_get])])
# flag = 1
else:
if n[3] not in rst[n[2]]:
rst[n[2]].append(n[3])
flag = 1
# code record εA'Aa
## ...
else:
# A'aA
if n[3].isupper():
if not set(rst[n[3]]) >= set(rst["".join(j[:temp_get])]):
rst[n[3]].extend(rst["".join(j[:temp_get])])
flag = 1
# code record εA'aA
## ...
# code record A'aa
## ...
if n[2] == "'":
if n[0].isupper():
if n[3].isupper():
if 'ε' in first[n[3]]:
temp_judge = first[n[3]]
temp_judge.remove('ε')
if not set(rst["".join(n[1:3])]) >= set(temp_judge):
rst["".join(n[1:3])].extend(temp_judge)
flag = 1
if not set(rst["".join(n[1:3])]) >= set(rst["".join(j[:temp_get])]):
rst["".join(n[1:3])].extend(rst["".join(j[:temp_get])])
flag = 1
if not set(rst["".join(n[1:3])]) >= set(first[n[3]]):
rst["".join(n[1:3])].extend(first[n[3]])
flag = 1
# code record εAA'A
## ...
# if 'ε' in first[n[3]]:
# if not set(rst["".join(n[1:3])]) >= set(rst["".join(j[:temp_get])]):
# rst["".join(n[1:3])].extend(rst["".join(j[:temp_get])])
# flag = 1
else:
if n[3] not in rst["".join(n[1:3])]:
rst["".join(n[1:3])].append(n[3])
flag = 1
# code record εAA'a
## ...
else:
# aA'A
if n[3].isupper():
if 'ε' in first[n[3]]:
temp_judge = first[n[3]]
temp_judge.remove('ε')
if not set(rst["".join(n[1:3])]) >= set(temp_judge):
rst["".join(n[1:3])].extend(temp_judge)
flag = 1
if not set(rst["".join(n[1:3])]) >= set(rst["".join(j[:temp_get])]):
rst["".join(n[1:3])].extend(rst["".join(j[:temp_get])])
flag = 1
if not set(rst["".join(n[1:3])]) >= set(first[n[3]]):
rst["".join(n[1:3])].extend(first[n[3]])
flag = 1
# code record εaA'A
## ...
# if 'ε' in first[n[3]]:
# if not set(rst["".join(n[1:3])]) >= set(rst["".join(j[:temp_get])]):
# rst["".join(n[1:3])].extend(rst["".join(j[:temp_get])])
# flag = 1
else:
if n[3] not in rst["".join(n[1:3])]:
rst["".join(n[1:3])].append(n[3])
flag = 1
# code record εaA'a
## ...
if n[3] == "'":
if n[0].isupper():
if n[1].isupper():
# AAA'
if 'ε' in first["".join(n[2:])]:
temp_judge = first["".join(n[2:])]
temp_judge.remove('ε')
if not set(rst[n[1]]) >= set(temp_judge):
rst[n[1]].extend(temp_judge)
flag = 1
if not set(rst[n[1]]) >= set(rst["".join(j[:temp_get])]):
rst[n[1]].extend(rst["".join(j[:temp_get])])
flag = 1
if not set(rst[n[1]]) >= set(first["".join(n[2:])]):
rst[n[1]].extend(first["".join(n[2:])])
flag = 1
# code record εAAA'
## ...
# if 'ε' in first["".join(n[2:])]:
# if not set(rst[n[1]]) >= set(rst["".join(j[:temp_get])]):
# rst[n[1]].extend(rst["".join(j[:temp_get])])
# flag = 1
else:
if n[0].isupper():
# AaA'
if not set(rst["".join(n[2:3])]) >= set(rst["".join(j[:temp_get])]):
rst["".join(n[2:3])].extend(rst["".join(j[:temp_get])])
flag = 1
else:
if n[1].isupper():
# aAA'
if 'ε' in first["".join(n[2:])]:
temp_judge = first["".join(n[2:])]
temp_judge.remove('ε')
if not set(rst[n[1]]) >= set(temp_judge):
rst[n[1]].extend(temp_judge)
flag = 1
if not set(rst[n[1]]) >= set(rst["".join(j[:temp_get])]):
rst[n[1]].extend(rst["".join(j[:temp_get])])
flag = 1
if not set(rst[n[1]]) >= set(first["".join(n[2:])]):
rst[n[1]].extend(first["".join(n[2:])])
flag = 1
# code record εAAA'
## ...
# if 'ε' in first["".join(n[2:])]:
# if not set(rst[n[1]]) >= set(rst["".join(j[:temp_get])]):
# rst[n[1]].extend(rst["".join(j[:temp_get])])
# flag = 1
else:
if not set(rst["".join(n[2:3])]) >= set(rst["".join(j[:temp_get])]):
rst["".join(n[2:3])].extend(rst["".join(j[:temp_get])])
flag = 1
# code record
## AAA...
利用集合的特性删除重复的数据
# 利用集合特性删除重复的数据
for q in rst:
rst[q] = list(set(rst[q]))
return rst
供测试的代码:
if __name__ == '__main__':
lst_demo = [["E","→","T","E","'"],["E","'","→","+","T","E","'","|","ε"],["T","→","F","T","'"],\
["T","'","→","*","F","T","'","|","ε"],["F","→","(","E",")","|","i"]]
lst_demo1 = ["E'","E"]
lst_demo1 = ["q"]
print("代码测试1:求FIRST集、FOLLOW集和符号串的FIRST集.")
result = {}
result = get_first(lst_demo)
print("FIRST:",result)
result = get_follow(lst_demo)
print("FOLLOW:",result)
result1 = get_first_bunch(lst_demo1,lst_demo)
print("FIRST(BUNCH):",result1)
总结
- 逻辑代码可以处理大部分简单的文法结构,FOLLOW集合中的其他部分可以自行编写,和前几个判断差不多。