编译原理-LL(1)文法分析法实现(扳碎了给你看)C++

LL(1)文法分析法

一、基本思路:
  1. 计算可推出 ϵ \epsilon ϵ的非终结符表
  2. 计算各非终结符的FIRST集
  3. 计算出各产生式右侧的FIRST集
  4. 计算各非终结符的FOLLOW集
  5. 计算各产生式的SELECT集
  6. 构造预测分析表
  7. 分析输入串
二、实现过程

文法实例:

E->TA
A->+TA
A->=
T->FB
B->*FB
B->=
F->i
F->(E)

此处用=代替 ϵ \epsilon ϵ,方便编程。

1、对文法进行预处理
  1. 首先我们先把该文法的终结符非终结符取出,用map<char,int>储存,方便查找
  2. 将产生式左右两侧分开,用map<char,string>存储,如文法E->TA存为{E,TA}
  3. 将左侧相同的产生式计数,用于求第一步,可用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 ϵ的非终结符
  1. 使用map<char,bool>来标志能推出 ϵ \epsilon ϵ的非终结符,能为true,不能为false

  2. 扫描文法中的产生式。

    ① 删除所有右部含有终结符的产生式,如果使得以某一非终结符为左部的所有产生式都删除,则此非终结符为false,即不能推出 ϵ \epsilon ϵ

    Ex: 上述文法中以F为左部的产生式有两个F->i,F->(E),遍历其产生式右部都含有非终结符,将它们都删除后,F的产生式为0,所以为false

    ② 若某一产生式的右侧只有一个 ϵ \epsilon ϵ,则将左部的非终结符标志位true,并删除该非终结符的所有产生式。如:B->=$,B直接置为true,并将B的所有产生式置为空。

  3. 扫描产生式右部的每一符号。

    ① 若扫描到的非终结符已经标志为true,则删去该非终结符,若使得该产生式右部为空,即全部被删了,则将该产生式左部的非终结符标志为true,并删除该非终结符的所有产生式。

    ② 若扫描到的非终结符已经被标志false,则删除该产生式,若这使得该产生式左部的非终结符的所有产生式都被删除了,则将该非终结符也置为false

    Ex: 极端情况只剩一条产生式A->BCB已经被标志为false,则把A->BC也删除,则左部为A的产生式没有了,则A也标志为false

  4. 重复步骤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集
  1. X属于终结符,则 F I R S T ( X ) = FIRST(X)= FIRST(X)={ X X X}。

  2. X属于非终结符,且有产生式 X → a ⋅ ⋅ ⋅ , a ∈ V T X\to a···,a\in V_T Xa,aVT,则 a ∈ F I R S T ( X ) a\in FIRST(X) aFIRST(X)

  3. X ∈ V N , X → ϵ , X\in V_N,X\to\epsilon, XVNXϵ ϵ ∈ F I R S T ( X ) \epsilon \in FIRST(X) ϵFIRST(X)
    这三条比较简单🐸

  4. 若有产生式 X → Y 1 , Y 2 , Y 3 , ⋅ ⋅ ⋅ Y n X\to Y_1,Y_2,Y_3,···Y_n XY1,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后面的可忽略,可推广3i

    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 ϵ刚开始都可以去掉,直到最后如果满足此条件再加上即可,反正是取并集

  5. 重复步骤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集。😮

  1. 遍历该产生式的右侧。
  2. 若不能都推导到 ϵ \epsilon ϵ,则将该位置(不能推导出 ϵ \epsilon ϵ的符号的位置)之前的**所有符号的 FIRST集减去 ϵ \epsilon ϵ **加入到该产生式右侧的first集中。
  3. 若都能推导到 ϵ \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集,简单理解就是求该非终结符后面能够紧挨着的终结符

  1. 对于文法中每个非终结符A,根据定义计算FOLLOW(A)。
  2. S为文法的开始符号,我们首先将#加入FOLLOW(S)中。#为句子括号,可看做结束符
  3. A → α B β A\to\alpha B\beta AαBβ是一个产生式, β \beta β是由终结符和非终结符组成的串且 β \beta β不能推导到 ϵ \epsilon ϵ,则把FIRST( β \beta β)加入FOLLOW(B)中。
  4. A → α B β A \to \alpha B\beta AαBβ是一个产生式, β \beta β能推导到 ϵ \epsilon ϵ,则把FOLLOW(A)也加入FOLLOW(B)中。

解释: 有产生式A->BCDE,现在求C的FOLLOW集,此时DE就是上面所说的 β \beta β,求 β \beta βFirst集与第三步产生式右侧类似。

  1. 第一步我们得判断DE是否都能推导到 ϵ \epsilon ϵ,这可以用第二步求出的toEpsilon(判断每个非终结符是否可以推到 ϵ \epsilon ϵ),若DtrueEfalse,则只需将FIRST(D)加入FOLLOW( C)中,即First(DE)=First(D),这是求产生式右侧First集的方法。
  2. 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集类似。

  1. 有产生式S->AB,若产生式右侧AB不能推导为空,则将First(AB)加入Select(S->AB)
  2. 若都能推导为空,则还需将First(AB)-epsilon和Follow(A)加入Select(S->AB)
  3. 总之,看右侧能不能全为空就完事了。🍄

用到的存储结构:

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
ABC

第一行为终结符,第一列为非终结符,表中元素为当A推出i时的下一步

  1. 存在a ∈ \in Select(A->B),预测分析表M[A,a]=B
  2. 已经求出select集,只需遍历每个产生式的select集,对于每个非终结符若属于此select集,则在分析表中加一项即可
  3. 如,select(A->B)={a},终结符有a,b,此时aselect集中,所以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#Aa#A->B
2#Ba#B->a
3#aa#匹配’a’
4##匹配’#’,ACCEPT

首先将#和开始符号入栈,与输入串进行匹配:

  1. 若栈顶和输入串当前字符匹配,如3,则将栈顶弹出,输入串指向下一个
  2. 若不匹配,且栈顶元素为非终结符则将预测分析表中M[A,a]入栈,Note:需逆序入栈,如A->BC,则C先入栈,如1,若分析表中不存在则报错。
  3. 若为终结符,则报错。

代码实现:

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;
}
总结

不想总结了!🐸

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值