2718 字
14 分钟
编译原理: 自顶向下的语法分析方法

更新于 2024-05-08 00:40:59

1.确定的自顶向下分析方法#

1.0 求字符aa是否可以推导出空#

对于非空终结符aVTaεa\in V_T \wedge a \ne \varepsilon​,不能推导出空。

接着扫描产生式p:Aβp: A \to \beta

  1. 如果产生式右部β\beta中有终结符,直接删除该产生式,如果此操作导致有一个非终结符AA的所有产生式都被删除,则认为AA不能推导出空。
  2. 如果该产生式是一个空产生式即β=ε\beta = \varepsilon,则直接认为AA能够推导出空,并且删除所有左部为AA的产生式。
TIP

剩下来的产生式的右部都是由非终结符组成的非空产生式。

循环松弛:

  • 接着扫描每一个产生式的右部的每一个非终结符BB:
    1. 如果该非终结符已经被认为不能推导出空,则直接删除对应的产生式AαBβA\to \alpha B\beta。如果此操作导致有一个非终结符AA的所有产生式都被删除,则认为AA​不能推导出空。
    2. 如果该非终结符已经被认为能够推导出空,则直接删除对应产生式中该非终结符BB。如果该操作导致对应产生式ABA\to B的右部全被删除完,则认为非终结符能够推导出空串。
    3. 如果该非终结符还没有被认定,就continue
  • 如果已经没有非终结符再继续能被认定,则退出松弛循环。

就能得到每一个符号a(VTVN)a\in (V_T \cup V_N)是否可以推导出空。

TIP

对于一个串β=A1A2A3An,\beta = A_1A_2A_3\cdots A_n, Ai(VTVN)A_i \in (V_T \cup V_N),能推导出空,βε\beta\mathop{\Rightarrow}\limits^* \varepsilon当且仅当Aiε,i(1in)A_i \mathop{\Rightarrow}\limits^* \varepsilon, \forall i(1\le i \le n)

1.1 FIRST(α)FIRST(\alpha) : 对串运算​#

定义1.1.1定义1.1.1 定义一个串α\alphaFirstFirst运算:

FIRST(α)=    {bαbβ,bVTβ(VTVN)}({ε} if αε otherwise )\begin{align} FIRST(\alpha) &= \\ &\ \ \ \ \{b|\alpha\xRightarrow{*}b\beta, b\in V_T \wedge \beta \in (V_T \cup V_N)^*\} \\ & \cup (\{\varepsilon \}\ if \ \alpha \xRightarrow{*} \varepsilon \ otherwise \ \varnothing) \end{align}

当一个上下文无关文法没有空产生式AεA \to \varepsilon时,如果相同左部的产生式的右部的开头要么是不同的非终结符要么是不同的终结符。能保证每一个串α\alpha使用不同产生式得到的串β\betaFIRST(β)FIRST(\beta)不相交

则只需要对着当前字符串的第一个待匹配字符,确定当前字符串对应不同产生式的FIRSTFIRST​集合,就能确定使用哪一个产生式继续推导。这就是确定的自顶向下分析的第一种情况。

CAUTION

FIRSTFIRST集合注意最后一步的ε\varepsilon.

在有空产生式且产生式的右部第一个字符有非终结符的情况时,FIRSTFIRST集合依旧可以被定义。实际情况中使用算法来求解字符串的FIRSTFIRST集合。

1.1.1 循环松弛算法求每一个符号FIRSTFIRST[与课本上不太一致]#

前置步骤(可以减少之后循环产生式的数量 )

  1. 对于终结符aVTa \in V_TFIRST(a)={a}FIRST(a) = \{a\}
  2. 对于非终结符AVNA \in V_N,如果有产生式Aaβ,aVT,β(VTVN)A \to a\beta,a\in V_T, \beta \in (V_T \cup V_N)^*,则aFIRST(A)a \in FIRST(A)​​。
  3. 对于产生式AϵA\to \epsilon,则直接丢掉

接着把上述两种产生式[开头是终结符/空产生式]都可以丢掉,只剩下右部以非终结符开头的产生式,循环扫描所有剩下的产生式:

  1. 对于非终结符AVNA\in V_N,如果有产生式AA1A2A3An,A \to A_1A_2A_3\cdots A_n, Ai(VTVN)A_i \in (V_T \cup V_N),找到第一个不能推导出空的符号AiA_i,即第一个ii使得AiϵA_i \mathop{\nRightarrow}\limits^* \epsilon,则FIRST(A)FIRST(Ai)(j=1i1(FIRST(Aj){ϵ}))FIRST(A) \supseteq FIRST(A_i) \cup (\mathop{\cup}\limits_{j=1}^{i-1} (FIRST(A_j) - \{\epsilon\}))
  2. 查看每一个非终结符的FIRST(A)FIRST(A)​有无变化,如果每一个都没有变化,则退出循环。
  3. 最后一步,为所有能够导出空的非终结符符号的FIRSTFIRST集合都添加上ε\varepsilon元素!
void ContextFreeGrammar::getFIRST_loop()
{
	vector<int> used(P.size(), 0);
	for (int i = 0; i < P.size(); i++)
	{
		int idx = P[i].first; string beta = P[i].second;
		if (beta.length() == 0) {
			used[i] = 1; continue;
		}
		if (ys_T.find(beta[0]) != ys_T.end())
		{
			FIRST[idx][ys_T[beta[0]]] = 1;
			used[i] = 1;
		}
	}
	bool flag = 1;
	while (flag)
	{
		flag = 0;
		for (int i = 0; i < P.size(); i++)
		{
			if (used[i])	continue;
			int idx = P[i].first; string beta = P[i].second;
			for (int j = 0; j < beta.size(); j++)
			{
				if (ifToEpsilon(beta[j]))
				{
					int idx2 = ys_N[beta[j]];
					vector<int> tmp = FIRST[idx];
					FIRST[idx] = merge(FIRST[idx], FIRST[idx2]);
					flag = isUpdated(FIRST[idx], tmp);
				}
				else
				{
					if (isTerminaled(beta[j]))
					{
						if (FIRST[idx][ys_T[beta[j]]] == 0)
						{
							flag = 1;
							FIRST[idx][ys_T[beta[j]]] = 1;
						}
					}
					else {
						int idx2 = ys_N[beta[j]];
						vector<int> tmp = FIRST[idx];
						FIRST[idx] = merge(FIRST[idx], FIRST[idx2]);
						flag = isUpdated(FIRST[idx], tmp);
					}
					break;
				}
			}
		}
	}
	for (int i = 0; i < toEpsilon.size(); i++)
	{
		if (toEpsilon[i] == 1)
		{
			FIRST[i][ys_T['\0']] = 1;
		}
	}
}

1.1.2 建图求每一个符号的FIRSTFIRST集合#

设该上下文无关文法(Context Free Grammar)有nn个非终结符(VN=n|V_N| = n),有mm个终结符(VT=m|V_T| = m),建立一张n+mn+m个节点的新图,每一个节点代表一个字符:

遍历每一条产生式pp​给图加边:

  1. 如果该产生式为AA1A2A3AnA \to A_1A_2A_3\cdots A_nAi(VTVN)A_i \in (V_T \cup V_N),找到其中第一个ii使得AiεA_i \mathop{\nRightarrow}\limits^* \varepsilon,对每一个1ji1\le j\le i,连接一条从字符AA连向AjA_j​的有向边。
  2. 对于空产生式AεA\to \varepsilon,跳过

在这张有向图GG上,每一个字符的可达集合SS就是该字符的FIRST(A){ε}FIRST(A) - \{\varepsilon\}集合。

最后,为能够推导出ε\varepsilon的非终结符的FIRSTFIRST集合加入ε\varepsilon

void ContextFreeGrammar::getFIRST_graph()
{
	int n = VN.size(),m = VT.size();
	vector<vector<int>> g(n+m);
	for (int i = 0; i < P.size(); i++)
	{
		int idx = P[i].first; string beta = P[i].second;
		if (beta.length() == 0)	continue;
		for (int j = 0; j < beta.size(); j++)
		{
			if (ys_T.find(beta[j]) != ys_T.end())
			{
				g[idx].push_back(ys_T[beta[j]] + n);
			}
			else
			{
				g[idx].push_back(ys_N[beta[j]]);
			}
			if (!ifToEpsilon(beta[j]))	break;
		}
	}

	for (int i = 0; i < n; i++)
	{
		vector<int> ans = bfs({ i }, g);
		for (int j = 0; j < ans.size(); j++)
		{
			int idx = ans[j];
			if (idx >= n)	FIRST[i][idx - n] = 1;
		}
		if(toEpsilon[i])	FIRST[i][ys_T['\0']] = 1;
	}
}
TIP

通过以上步骤可以得到每一个字符a(VTVN)a\in (V_T \cup V_N)FIRST(a)FIRST(a)

对于串β(VTVN)\beta \in (V_T \cup V_N)^*,写为符号的拼接β=A1A2A3An,\beta = A_1A_2A_3\cdots A_n, Ai(VTVN)A_i \in (V_T \cup V_N),找到第一个不能推导出空的符号AiA_i,即第一个ii使得AiεA_i \mathop{\nRightarrow}\limits^* \varepsilon,则FIRST(β)=FIRST(Ai)(j=1i1(FIRST(Aj){ϵ}))FIRST(\beta) = FIRST(A_i) \cup (\mathop{\cup}\limits_{j=1}^{i-1} (FIRST(A_j) - \{\epsilon\}))

1.2 FOLLOW(A)FOLLOW(A) : 对一个非终结符运算#

当一个上下文无关文法有空产生式AεA \to \varepsilon时,当当前推导有AA时可以选择使用该产生式,把匹配字符的任务交给当前推导的下一个字符。

定义1.2.11.2.1 定义对一个**非终结符AA**的FOLLOWFOLLOW运算:

FOLLOW(A)=    {bSαAβ,bFIRST(β)}({#} if βε otherwise )\begin{align} FOLLOW(A) &= \\ &\ \ \ \ \{b|S\xRightarrow{*}\alpha A \beta, b\in FIRST(\beta)\} \\ & \cup (\{\# \}\ if \ \beta \xRightarrow{*} \varepsilon \ otherwise \ \varnothing) \end{align}

FOLLOW(A)FOLLOW(A)运算的结果是所有可能存在于A后的合法终结符bb的集合,当且仅当有一个合法句型形如αA\alpha A(即存在SαAS\xRightarrow{*}\alpha A时),#FOLLOW(A)\# \in FOLLOW(A)#\#​​​​为句子结束符。

CAUTION

#\#在FOLLOW中很重要。

FOLLOW集合中没有ε\varepsilon

1.2.1 使用循环松弛法求非终结符AAFOLLOWFOLLOW#

第一步,把#\#加入到起始符号的FOLLOWFOLLOW集合中去。

接着循环松弛:

  1. 遍历每一个产生式pp:
    • 对于产生式AA1A2A3AnA \to A_1A_2A_3\cdots A_n,Ai(VTVN)A_i \in (V_T \cup V_N)。对于1in1\le i \le n:
      • 如果AiVNA_i \in V_N,则把(FIRST(Ai+1Ai+2AN){ε})(FIRST(A_{i+1}A_{i+2}\cdots A_N) - \{\varepsilon\})加入到FOLLOW(Ai)FOLLOW(A_i)中。如果Ai+1Ai+2AnεA_{i+1}A_{i+2}\cdots A_n \xRightarrow{*} \varepsilon需要把FOLLOW(A)加入到FOLLOW(Ai)FOLLOW(A)加入到FOLLOW(A_i)中!
  2. 如果每一个非终结符AAFOLLOWFOLLOW集合都不再变化,则结束循环。
void ContextFreeGrammar::getFOLLOW_loop()
{
	FOLLOW[ys_N[S]][VT.size()] = 1;	// '#'
	bool flag = 1;
	while (flag)
	{
		flag = 0;
		for (int i = 0; i < P.size(); i++)
		{
			int idx = P[i].first; string beta = P[i].second;
			for (int j = 0; j < beta.length(); j++)
			{
				if (isNonTerminaled(beta[j]))
				{
					int idx2 = ys_N[beta[j]];
					// vector<int> tmp1 = FOLLOW[idx];
					vector<int> tmp2 = FOLLOW[idx2];
					vector<char> cs = getFIRST(beta.substr(j + 1));
					for (char c : cs)
					{
						if (c == '\0')	continue;			// epsilon不在FOLLOW中
						FOLLOW[idx2][ys_T[c]] = 1;
					}
					if (ifToEpsilon(beta.substr(j + 1)))
					{
						FOLLOW[idx2] = merge(FOLLOW[idx2], FOLLOW[idx]);
					}
					flag = flag || isUpdated(tmp2, FOLLOW[idx2]);
				}
			}
		}
	}
}

1.2.2 使用建图法求非终结符AAFOLLOWFOLLOW​集#

该算法把求SELECT的大部分事情也都做了。

设该上下文无关文法(Context Free Grammar)有nn个非终结符(VN=n|V_N| = n),有mm个终结符(VT=m|V_T| = m),建立一张n+n+m+1n+n+m+1点的新图,分别代表非终结符AVTA \in V_TFOLLOWFOLLOW集合、非终结符AVTA\in V_TFIRST(A){ε}FIRST(A) - \{\varepsilon\}集合,简称FF'集合,终结符aVNa \in V_N以及#\#

首先,从初始符号SSFOLLOW(S)FOLLOW(S)节点连一条边向#\#节点。

遍历每一个产生式pp:

  1. 遇到空产生式,跳过
  2. 对于产生式AA1A2A3AnA \to A_1A_2A_3\cdots A_n,Ai(VTVN)A_i \in (V_T \cup V_N)。对于1in1\le i \le n:
    • 对每一个1in1\le i \le n,如果AiVNA_i \in V_N,找到第一个j>ij > i,使得AjεA_j \mathop{\nRightarrow}\limits^* \varepsilon,接着从AiA_i连向所有的F(Ak)F’(A_k),i<k<ji < k < j。如果AjVTA_j \in V_T,就从AiA_i连一条边向AjA_j,否则从AiA_i连接一条边向F(Aj)F'(A_j)如果子串Ai+1Ai+2AnεA_{i+1}A_{i+2}\cdots A_n \xRightarrow{*} \varepsilon,注意空子串也可以导出空串。,就从FOLLOW(Ai)FOLLOW(A_i)连接一条边向FOLLOW(A)FOLLOW(A)。 [FOLLOW的事情]
    • 找到第一个ii使得AiεA_i \mathop{\nRightarrow}\limits^* \varepsilon,则从F(A)F'(A)连向每一个F(Aj),j<iF'(A_j),j<i,如果AiVTA_i \in V_T,则从F(A)F'(A)连向AiA_i,否则从F(A)F'(A)连向F(Ai)F'(A_i)(FIRST的事情)

则每一个FOLLOW节点的可达VT{#}V_T \cup \{\#\}集合就是该FOLLOWFOLLOW​集合。

void ContextFreeGrammar::getFOLLOW_graph1()
{
	int n = VN.size(), m = VT.size();
	vector<vector<int>> g(n + n + m + 1);
	g[ys_N[S]].push_back(2 * n + m);
	for (auto p : P)
	{
		int idx = p.first; string beta = p.second;
		if (beta.length() == 0)	continue;
		for (int i = 0; i < beta.length(); i++)			//FOLLOW 的连边
		{
			if (isNonTerminaled(beta[i]))
			{
				int idx2 = ys_N[beta[i]];
				bool flag = 1;
				for (int j = i + 1; j < beta.length(); j++)
				{
					if (beta[j] == '\0')	continue;
					if (isTerminaled(beta[j])) {
						g[idx2].push_back(ys_T[beta[j]] + 2 * n);
					}
					else if (isNonTerminaled(beta[j]))
					{
						g[idx2].push_back(ys_N[beta[j]] + n);
					}
					if (!ifToEpsilon(beta[j])) {
						flag = 0;
						break;
					}
				}
				if (flag)
				{
					g[idx2].push_back(idx);
				}
			}
		}
		for (int i = 0; i < beta.length(); i++)
		{
			if (isNonTerminaled(beta[i]))
			{
				g[idx + n].push_back(ys_N[beta[i]] + n);
			}
			else if (isTerminaled(beta[i]))
			{
				g[idx + n].push_back(ys_T[beta[i]] + 2 * n);
			}
			if (!ifToEpsilon(beta[i])) {
				break;
			}
		}
	}
	for (int i = 0; i < n; i++)
	{
		vector<int> ans = bfs({ i }, g);
		for (int v : ans)
		{
			if (v >= 2 * n)
			{
				FOLLOW[i][v - 2 * n] = 1;
			}
		}
	}
}

1.3 SELECT(Aβ)SELECT(A \to \beta): 对一个产生式的运算#

如果β\beta可以推导出ε\varepsilon,那么SELECT(Aβ)=(FIRST(β){ε})(FOLLOW(A))SELECT(A \to \beta) = (FIRST(\beta) - \{\varepsilon\}) \cup (FOLLOW(A))

如过β\beta推导不出ε\varepsilon,那么SELECT(Aβ)=FIRST(β)SELECT(A \to \beta) = FIRST(\beta)

由此规则遍历所有的产生式就可以得到每一条产生式的SELECTSELECT集合。

void ContextFreeGrammar::getSELECT_loop()
{
	int m = VT.size();

	for (int i = 0; i < P.size(); i++)
	{
		int idx = P[i].first; string beta = P[i].second;
		vector<char> first = getFIRST(beta);
		for (char c : first)
		{
			if(c != '\0')
				SELECT[i][ys_T[c]] = 1;
		}
		if (ifToEpsilon(beta))
		{
			vector<char> follow = getFOLLOW(VN[idx]);
			for (char c : follow)
			{
				if (c == '#')
					SELECT[i][m] = 1;
				else SELECT[i][ys_T[c]] = 1;
			}

		}

	}
}

1.4 LL(1)LL(1)​文法的判别#

判定一个文法是LL(1)LL(1)文法的充要条件是,相同左部的任意两个产生式的SELECTSELECT集合没有交集。

按照左部扫描一次产生式集合,两两比较SELECTSELECT集合是否有交集。

1.5 LL(1)文法的实现#

编译原理: 自顶向下的语法分析方法
http://blog.fragments.work/posts/principleofcompiling/ch4/
作者
Lixin WANG
发布于
2024-04-23
许可协议
CC BY-NC-SA 4.0