LL(1)文法分析法
一、基本思路:
- 计算可推出 ϵ \epsilon ϵ的非终结符表
- 计算各非终结符的FIRST集
- 计算出各产生式右侧的FIRST集
- 计算各非终结符的FOLLOW集
- 计算各产生式的SELECT集
- 构造预测分析表
- 分析输入串
二、实现过程
文法实例:
E->TA
A->+TA
A->=
T->FB
B->*FB
B->=
F->i
F->(E)
此处用=
代替
ϵ
\epsilon
ϵ,方便编程。
1、对文法进行预处理
- 首先我们先把该文法的终结符和非终结符取出,用
map<char,int>
储存,方便查找 - 将产生式左右两侧分开,用
map<char,string>
存储,如文法E->TA
存为{E,TA}
。 - 将左侧相同的产生式计数,用于求第一步,可用
map<char,int>
存储,如A->+TA,A->=
左侧为A
的产生式有两个,可存为{A,2}
预处理所用到的存储结构:
//--------------------预处理
unordered_map<char,int>VT;//终结符
unordered_map<char,int>VN;//非终结符
unordered_map<char, vector<string>>LR;//文法分开存储
unordered_map<char, int>record;//记录左侧相同的产生式数量
代码实现如下:
vector<string>str;//存储输入的产生式
string curr;
cin >> curr;
while (curr != ".") {
str.push_back(curr);
cin >> curr;
}
for (int i = 0; i < str.size(); i++) {
int len = str[i].size();
LR[str[i][0]].push_back(str[i].substr(3));//A->BC,key:A,value:BC
record[str[i][0]]++;//记录右侧为此非终结符的产生式的数量,用于ComputeEpsilon
for (int j = 0; j < str[i].size(); j++) {
if ((str[i][j] == '-'&&str[i][j + 1] == '>')) {//碰到箭头直接跳过
j += 1;
continue;
}
if (VT.count(str[i][j]) == 0 &&VN.count(str[i][j]) == 0) {//判断是否已经存储
int s = str[i][j] - 'A';
if (s >= 0 && s <= 25) {
VN[str[i][j]]=1;
}
else {
VT[str[i][j]]=1;
}
}
}
}
2、求出能推出 ϵ \epsilon ϵ的非终结符
-
使用
map<char,bool>
来标志能推出 ϵ \epsilon ϵ的非终结符,能为true
,不能为false
。 -
扫描文法中的产生式。
① 删除所有右部含有终结符的产生式,如果使得以某一非终结符为左部的所有产生式都删除,则此非终结符为
false
,即不能推出 ϵ \epsilon ϵ。Ex: 上述文法中以
F
为左部的产生式有两个:F->i,F->(E)
,遍历其产生式右部都含有非终结符,将它们都删除后,F
的产生式为0
,所以为false
。② 若某一产生式的右侧只有一个 ϵ \epsilon ϵ,则将左部的非终结符标志位
true
,并删除该非终结符的所有产生式。如:B->=$
,B直接置为true
,并将B
的所有产生式置为空。 -
扫描产生式右部的每一符号。
① 若扫描到的非终结符已经标志为
true
,则删去该非终结符,若使得该产生式右部为空,即全部被删了,则将该产生式左部的非终结符标志为true
,并删除该非终结符的所有产生式。② 若扫描到的非终结符已经被标志
false
,则删除该产生式,若这使得该产生式左部的非终结符的所有产生式都被删除了,则将该非终结符也置为false
。Ex: 极端情况只剩一条产生式
A->BC
,B
已经被标志为false
,则把A->BC
也删除,则左部为A
的产生式没有了,则A
也标志为false
。 -
重复步骤3,直到非终结符的标志不再变换。
用到的存储结构:
//刚开始代码中用的是Ne,现在改为toEpsilon
unordered_map<char, bool>toEpsilon;//是否能推出epsilon
代码实现(稍显冗余😏):
void ComputeEpsilon() {//第一步求出能推出epsilon的非终结符
unordered_map<char, vector<string>>curr = LR;
unordered_map<char, int>record2 = record;//记录以某一非终结符为左部的产生式的数量
for (auto s : curr) {
//产生式:A->BC,s1:BC s2:A
vector<string> s1 = s.second;
char s2 = s.first;
for (int i = 0; i < s1.size(); i++) {
if (s1[i].size() == 1 && s1[i][0] == '=') {//如果产生式右侧只有epsilon
toEpsilon[s2] = true;
curr[s2] = { "" };//将所有此非终结符的产生式置空
break;
}
for (int j = 0; j < s1[i].size(); j++) {
if (VT.count(s1[i][j]) != 0) {//为非终结符
curr[s2][i] = "";//此产生式删除
record2[s2]--;//s2的产生式减1
if (record2[s2] == 0) {//如果s2的产生式都被删除,则为false
toEpsilon[s2] = false;
}
}
}
}
}
int judge = 0;
while (judge<=20) {//第三步,迭代20次
for (auto s : curr) {
vector<string>s1 = s.second;
char s2 = s.first;
for (int i = 0; i < s1.size(); i++) {
int len = s1[i].size();//用len来记录该产生式右部剩下的字符数
for (int j = 0; j < s1[i].size(); j++) {
if (toEpsilon.count(s1[i][j]) != 0&&VN.count(s1[i][j])!=0) {//为非终结符
if (toEpsilon[s1[i][j]]) {
len--;//删去该终结符,右部字符数-1
if (len == 0) {//全部删了,则为true
toEpsilon[s2] = true;
break;
}
}
else {
curr[s2][i] = "";//删去该产生式
record2[s2]--;
if (record2[s2] == 0) {
toEpsilon[s2] = false;
break;
}
}
}
}
}
}
judge++;
}
}
3、计算非终结符的FIRST集
-
若
X
属于终结符,则 F I R S T ( X ) = FIRST(X)= FIRST(X)={ X X X}。 -
若
X
属于非终结符,且有产生式 X → a ⋅ ⋅ ⋅ , a ∈ V T X\to a···,a\in V_T X→a⋅⋅⋅,a∈VT,则 a ∈ F I R S T ( X ) a\in FIRST(X) a∈FIRST(X)。 -
若 X ∈ V N , X → ϵ , X\in V_N,X\to\epsilon, X∈VN,X→ϵ,则 ϵ ∈ F I R S T ( X ) \epsilon \in FIRST(X) ϵ∈FIRST(X)。
这三条比较简单🐸 -
若有产生式 X → Y 1 , Y 2 , Y 3 , ⋅ ⋅ ⋅ Y n X\to Y_1,Y_2,Y_3,···Y_n X→Y1,Y2,Y3,⋅⋅⋅Yn,且 X , Y 1 , Y 2 , Y 3 , ⋅ ⋅ ⋅ Y n X,Y_1,Y_2,Y_3,···Y_n X,Y1,Y2,Y3,⋅⋅⋅Yn都属于非终结符。
①若 Y 1 , Y 2 Y_1,Y_2 Y1,Y2都可推出 ϵ \epsilon ϵ, Y 3 Y_3 Y3不能,则 F I R S T ( Y 1 ) − ϵ , F I R S T ( Y 2 ) − ϵ , F I R S T ( Y 3 ) FIRST(Y_1)-\epsilon,FIRST(Y_2)-\epsilon,FIRST(Y_3) FIRST(Y1)−ϵ,FIRST(Y2)−ϵ,FIRST(Y3)都属于FIRST(X), Y 3 Y_3 Y3后面的可忽略,可推广
3
到i
。Ex: 有产生式
A->BCD
,当B
的first集为 a , ϵ a,\epsilon a,ϵ,C
的first集为 b , ϵ b,\epsilon b,ϵ,D
的first集为 c c c,当B
推导为 ϵ \epsilon ϵ时(相当于把B
删除),产生式可化为A->CD
,则只需看first(C)
,若C
也推导为 ϵ \epsilon ϵ,产生式变为A->D
则只需看first(D)
,所以first(A)={first(B)-e,first(C)-e,first(D)}={a,b,c}
。② 若 Y 1 Y_1 Y1到 Y n Y_n Yn都可推导为 ϵ \epsilon ϵ,则只需在上步的基础上加 ϵ \epsilon ϵ到 F I R S T ( X ) FIRST(X) FIRST(X)。此时我们发现每个符号的first集的 ϵ \epsilon ϵ刚开始都可以去掉,直到最后如果满足此条件再加上即可,反正是取并集
-
重复步骤3,4,即可求出每个文法符号的first集。
用到的存储结构:
unordered_map<char, unordered_map<char, int>>p;
//存储每个文法符号的first集,嵌套的map即为first集,存在标志为1
代码实现:
for (auto s : VT) {//若文法符号为终结符
p[s.first][s.first]=1;
}
for (auto s : toEpsilon) {
if (s.second == true) {//若能推出epsilon
p[s.first]['=']=1;
}
}
int count = 0;
//计算每个文法符号的first集
while (count <= 20) {//迭代20次
for (auto s : curr) {
//产生式:A->BC,s1:BC s2:A
vector<string>s1 = s.second;
char s2 = s.first;
for (int i = 0; i < s1.size(); i++) {
if (VT.count(s1[i][0]) != 0) {//终结符
p[s2][s1[i][0]]=1;
continue;
}
int judge = 0;//用于判断是否产生式右边都能推到空
for (int j = 0; j < s1[i].size(); j++) {
if (VT.count(s1[i][j]) == 0) {
if (toEpsilon[s1[i][j]] == true) {//若此文法符号能推到epsilon
for (auto t : p[s1[i][j]]) {//将first集都减去epsilon再加入
if (p[s2].count(t.first) == 0&&t.first!='=') {
p[s2][t.first] = 1;
}
}
}
else {
for (auto t : p[s1[i][j]]) {//将first集都减去epsilon再加入
if (p[s2].count(t.first) == 0) {
p[s2][t.first] = 1;
}
}
judge = 1;//表示不能都推到epsilon
break;
}
}
}
if (judge == 0) {//若都能推到epsilon
p[s2]['='] = 1;
}
}
}
count++;
}
计算产生式右侧的FIRST集
与上面3,4跳类似,依赖于上面求出的first集。😮
- 遍历该产生式的右侧。
- 若不能都推导到
ϵ
\epsilon
ϵ,则将该位置(不能推导出
ϵ
\epsilon
ϵ的符号的位置)之前的**所有符号的
FIRST
集减去 ϵ \epsilon ϵ **加入到该产生式右侧的first
集中。 - 若都能推导到
ϵ
\epsilon
ϵ,则将该右侧所有符号的
FIRST
集减去 ϵ \epsilon ϵ加入到该产生式右侧的first
集中
用到的存储结构:
unordered_map<string, unordered_map<char, int>>bStr;//key:产生式右侧,value:first集
代码实现:
//计算每个产生式右边的first集
for (auto s : curr) {
vector<string>s1 = s.second;//产生式右侧集合
for (int i = 0; i < s1.size(); i++) {
string s2 = s1[i];//产生式右侧
int judge = 0;//用于判断是否都能推到出空
for (int j = 0; j < s2.size(); j++) {
if (p.count(s2[0]) != 0 &&p[s2[0]].count('=') ==0) {//第一个字符不能推出空
for (auto t1 : p[s2[j]]) {//将该符号的first集加入产生式的first集
if (bStr[s2].count(t1.first) == 0) {
bStr[s2][t1.first] = 1;
}
}
judge = 1;//标志为1,表示不能都推导出epsilon
break;
}
if (VT.count(s2[j]) == 0) {//非终结符
if (toEpsilon[s2[j]] == true) {//能推出空,first(s2[j])-{=}属于first(s2)
for (auto t : p[s2[j]]) {//将该符号的first集加入产生式的first集
if (bStr[s2].count(t.first) == 0 && t.first != '=') {
bStr[s2][t.first] = 1;
}
}
}
else {
for (auto t : p[s2[j]]) {
if (bStr[s2].count(t.first) == 0) {//first(s2[j])属于first(s2)
bStr[s2][t.first] = 1;
}
}
judge = 1;//不能全部推到空
break;
}
}
}//若为终结符可当做不能推导出epsilon的非终结符处理,将其加入后break即可
if (judge == 0) {//若能全部推到空
bStr[s2]['='] = 1;
}
}
}
求出FIRST集后,后面的步骤就是水到渠成了。🌚
4、计算各非终结符的FOLLOW集
求Follow集,简单理解就是求该非终结符后面能够
紧挨着的终结符。
- 对于文法中每个非终结符
A
,根据定义计算FOLLOW(A)。 - 若
S
为文法的开始符号,我们首先将#
加入FOLLOW(S)中。#为句子括号,可看做结束符
- 若 A → α B β A\to\alpha B\beta A→αBβ是一个产生式, β \beta β是由终结符和非终结符组成的串且 β \beta β不能推导到 ϵ \epsilon ϵ,则把FIRST( β \beta β)加入FOLLOW(B)中。
- 若 A → α B β A \to \alpha B\beta A→αBβ是一个产生式, β \beta β能推导到 ϵ \epsilon ϵ,则把FOLLOW(A)也加入FOLLOW(B)中。
解释: 有产生式A->BCDE
,现在求C
的FOLLOW集,此时DE
就是上面所说的
β
\beta
β,求
β
\beta
β的First
集与第三步产生式右侧类似。
- 第一步我们得判断
DE
是否都能推导到 ϵ \epsilon ϵ,这可以用第二步求出的toEpsilon
(判断每个非终结符是否可以推到 ϵ \epsilon ϵ),若D
为true
,E
为false
,则只需将FIRST(D)
加入FOLLOW( C)中,即First(DE)
=First(D)
,这是求产生式右侧First
集的方法。 - 若
D
,E
都为true
,则不仅要将First(DE)
加入到Follow(B)
中,还要将Follow(A)
也加入。假设还有产生式F-AG
,因为DE
都可以推到空,所以原式可化为A->BC
,结合这两式可得F->BCG
,所以Follow(A)
也属于Follow(B)
。
用到的存储结构:
unordered_map<char, unordered_map<char, int>>follow;
//key:非终结符,value:follow集
代码实现(虽然很长但逻辑很简单🐳):
//计算Follow集
void ComputeFollow() {
char start = (VN.begin())->first;
//cout << start << endl;
follow[start]['#'] = 1;
unordered_map<char, vector<string>>curr = LR;
int judge = 0;
while (judge <= 5) {
for (auto s : curr) {
vector<string>s1 = s.second;//产生式右侧
char s2 = s.first;//产生式左侧
for (int i = 0; i < s1.size(); i++) {
for (int j = 0; j < s1[i].size(); j++) {//遍历产生式右侧
if (VN.count(s1[i][j]) != 0) {//为非终结符
int pos = 0;//标志,若为1都不能都推到空
for (int k = j + 1; k < s1[i].size(); k++) {//查询其后面的非终结符是否都可推导到空
if (VN.count(s1[i][k]) != 0) {//若为非终结符
if (toEpsilon.count(s1[i][k]) != 0 && toEpsilon[s1[i][k]] == false) {//不能推导为空
pos = 1;
for (auto s3 : p[s1[i][k]]) {//将First集加入Follow集中
if (s3.first != '=') {
follow[s1[i][j]][s3.first] = 1;
}
}
break;
}
else if (toEpsilon.count(s1[i][k]) != 0 && Ne[s1[i][k]] == true) {//推导为空
for (auto s3 : p[s1[i][k]]) {//将First集加入Follow集中
if (s3.first != '=') {
follow[s1[i][j]][s3.first] = 1;
}
}
}
}
else {//非终结符
follow[s1[i][j]][s1[i][k]] = 1;//加入Follow集
pos = 1;
break;
}
}
if (pos == 0 && follow[s2].size() != 0) {//该非终结符为最后一个或者后面全部能推到空
for (auto s3 : follow[s2]) {//Follow集
follow[s1[i][j]][s3.first] = 1;
}
}
}
}
}
}
judge++;
}
}
5、求各非终结符的SELECT集
Select集较简单,与上面Follow集类似。
- 有产生式
S->AB
,若产生式右侧AB
不能推导为空,则将First(AB)
加入Select(S->AB)
。 - 若都能推导为空,则还需将
First(AB)-epsilon和Follow(A)
加入Select(S->AB)
。 - 总之,看右侧能不能全为空就完事了。🍄
用到的存储结构:
unordered_map<string, unordered_map<char, int>>select;
//key:产生式,value:select集
代码实现:
void ComputeSelect() {
unordered_map<char, vector<string>>curr = LR;
int judge = 0;
for (auto s : curr) {
vector<string>s1 = s.second;//产生式右侧
char s2 = s.first;//产生式左侧
for (int i = 0; i < s1.size(); i++) {
string str1(1,s2);
str1 += "->";
str1 += s1[i];//构建产生式
int pos = 0;
for (int j = 0; j < s1[i].size(); j++) {//判断产生式右边是否能全推到空
if (s1[i].size() == 1 && s1[i][j] == '=') {//产生式右边只有一个且为空,将Follow加入select
break;
}
if (VN.count(s1[i][j]) != 0) {//非终结符
if (toEpsilon.count(s1[i][j]) != 0) {
if (toEpsilon[s1[i][j]] == false) {//不能推出空
pos = 1;//标志为1,表示不能全推到空
break;
}
}
}
else {
pos = 1;
break;
}
}
for (auto st : bStr[s1[i]]) {//把产生式右侧的First集
if (st.first != '=') {
select[str1][st.first] = 1;
}
}
if (pos == 0) {
for (auto st : follow[s2]) {//产生式左侧的FOLLOW集
select[str1][st.first] = 1;
}
}
}
}
}
还有一步检测是否为LL(1)文法,只需判断有相同左侧的产生式的Select集是否有交集
Select(A->B)={a,b},Select(A->C)={a,c}
这两个产生式有相同左侧且Select有交集所以不是LL(1)
文法。
6、构建预测分析表
预测分析表可看做是一个二维数组。
i | |
---|---|
A | BC |
第一行为终结符,第一列为非终结符,表中元素为当A
推出i
时的下一步
- 存在
a
∈ \in ∈Select(A->B)
,预测分析表M[A,a]=B
- 已经求出
select
集,只需遍历每个产生式的select
集,对于每个非终结符若属于此select
集,则在分析表中加一项即可 - 如,
select(A->B)={a}
,终结符有a,b
,此时a
在select
集中,所以M[A,a]=B
。
用到的存储结构:
unordered_map<char, unordered_map<char, string>>predict;
//两个char分别为产生式左侧和终结符,string为产生式右侧,如M[A][a]=B。
代码实现:
//构建预测分析表
void buildPredict() {
unordered_map<string, unordered_map<char, int>>curr = select;
unordered_map<char, int>V=VT;
V['#'] = 1;
for (auto s : curr) {//遍历select表
char t1 = (s.first)[0];//产生式左侧
string str = s.first;
int len = str.size();
auto t2 = s.second;
for (auto s1 : V) {//对于每个终结符
if (t2.count(s1.first) != 0) {//如果属于select表
predict[t1][s1.first]=str.substr(3);//将产生式右侧加入预测表
}
}
}
/*for (auto s : predict) {
char t1 = s.first;
for (auto s1 : s.second) {
cout << "(" << t1 << "," <<s1.first<< ")" << ":" << s1.second << endl;
}
}*/
}
7、分析
分析需用到栈结构。
步骤 | 分析栈 | 剩余输入串 | 推导所用产生式和匹配 |
---|---|---|---|
1 | #A | a# | A->B |
2 | #B | a# | B->a |
3 | #a | a# | 匹配’a’ |
4 | # | # | 匹配’#’,ACCEPT |
首先将#
和开始符号入栈,与输入串进行匹配:
- 若栈顶和输入串当前字符匹配,如
3
,则将栈顶弹出,输入串指向下一个 - 若不匹配,且栈顶元素为
非终结符
则将预测分析表中M[A,a]
入栈,Note:需逆序入栈,如A->BC,则C先入栈
,如1
,若分析表中不存在则报错。 - 若为
终结符
,则报错。
代码实现:
bool Analyse(string ans) {
stack<char>st;
char start = (VN.begin())->first;
st.push('#');
st.push(start);
bool judge=true;
for (int i = 0; i < ans.size(); i++) {
while (!st.empty()&&st.top() != ans[i]) {//若栈顶元素不等于当前符号,则入栈
char curr = st.top();
if (VT.count(curr) != 0) {
judge = false;
error.push_back("栈顶终结符与当前符号不匹配!!");
break;
}
st.pop();
string s = predict[curr][ans[i]];
if (s.size() == 0) {//预测分析表中不存在
judge = false;
//错误语句
string t = "非终结符";
t.push_back(ans[i]);
t += "位于栈顶,面临的输入符号为a,但分析表M的表项M[";
t.push_back(curr);
t += ",";
t.push_back(ans[i]);
t += "]为空";
//
error.push_back(t);
break;
}
for (int i = s.size()-1; i >=0 ; i--) {//将当前项入栈
if(s[i]!='=')
st.push(s[i]);
}
}
if(!st.empty())
st.pop();//弹出栈顶元素
if (!judge)
break;
}
return judge;
}
总结
不想总结了!🐸