更新于
2024-05-08 00:40:59
1.确定的自顶向下分析方法
1.0 求字符是否可以推导出空
对于非空终结符,不能推导出空。
接着扫描产生式:
- 如果产生式右部中有终结符,直接删除该产生式,如果此操作导致有一个非终结符的所有产生式都被删除,则认为不能推导出空。
- 如果该产生式是一个空产生式即,则直接认为能够推导出空,并且删除所有左部为的产生式。
TIP剩下来的产生式的右部都是由非终结符组成的非空产生式。
循环松弛:
- 接着扫描每一个产生式的右部的每一个非终结符:
- 如果该非终结符已经被认为不能推导出空,则直接删除对应的产生式。如果此操作导致有一个非终结符的所有产生式都被删除,则认为不能推导出空。
- 如果该非终结符已经被认为能够推导出空,则直接删除对应产生式中该非终结符。如果该操作导致对应产生式的右部全被删除完,则认为非终结符能够推导出空串。
- 如果该非终结符还没有被认定,就
continue
。
- 如果已经没有非终结符再继续能被认定,则退出松弛循环。
就能得到每一个符号是否可以推导出空。
TIP对于一个串 ,能推导出空, ,当且仅当 。
1.1 : 对串运算
定义一个串的运算:
当一个上下文无关文法没有空产生式时,如果相同左部的产生式的右部的开头要么是不同的非终结符要么是不同的终结符。能保证每一个串使用不同产生式得到的串的不相交。
则只需要对着当前字符串的第一个待匹配字符,确定当前字符串对应不同产生式的集合,就能确定使用哪一个产生式继续推导。这就是确定的自顶向下分析的第一种情况。
CAUTION求集合注意最后一步的.
在有空产生式且产生式的右部第一个字符有非终结符的情况时,集合依旧可以被定义。实际情况中使用算法来求解字符串的集合。
1.1.1 循环松弛算法求每一个符号的[与课本上不太一致]
前置步骤(可以减少之后循环产生式的数量 )
- 对于终结符,。
- 对于非终结符,如果有产生式,则。
- 对于产生式,则直接丢掉。
接着把上述两种产生式[开头是终结符/空产生式]都可以丢掉,只剩下右部以非终结符开头的产生式,循环扫描所有剩下的产生式:
- 对于非终结符,如果有产生式 ,找到第一个不能推导出空的符号,即第一个使得,则
- 查看每一个非终结符的有无变化,如果每一个都没有变化,则退出循环。
- 最后一步,为所有能够导出空的非终结符符号的集合都添加上元素!
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 建图求每一个符号的集合
设该上下文无关文法(Context Free Grammar)有个非终结符(),有个终结符(),建立一张个节点的新图,每一个节点代表一个字符:
遍历每一条产生式给图加边:
- 如果该产生式为,,找到其中第一个使得,对每一个,连接一条从字符连向的有向边。
- 对于空产生式,跳过
在这张有向图上,每一个字符的可达集合就是该字符的集合。
最后,为能够推导出的非终结符的集合加入
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通过以上步骤可以得到每一个字符的。
对于串,写为符号的拼接 ,找到第一个不能推导出空的符号,即第一个使得,则
1.2 : 对一个非终结符运算
当一个上下文无关文法有空产生式时,当当前推导有时可以选择使用该产生式,把匹配字符的任务交给当前推导的下一个字符。
定义 定义对一个**非终结符**的运算:
运算的结果是所有可能存在于A后的合法终结符的集合,当且仅当有一个合法句型形如(即存在时),,为句子结束符。
CAUTION在FOLLOW中很重要。
FOLLOW集合中没有。
1.2.1 使用循环松弛法求非终结符的集
第一步,把加入到起始符号的集合中去。
接着循环松弛:
- 遍历每一个产生式:
- 对于产生式,。对于:
- 如果,则把加入到中。如果,需要把中!
- 对于产生式,。对于:
- 如果每一个非终结符的集合都不再变化,则结束循环。
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 使用建图法求非终结符的集
该算法把求SELECT的大部分事情也都做了。
设该上下文无关文法(Context Free Grammar)有个非终结符(),有个终结符(),建立一张点的新图,分别代表非终结符的集合、非终结符的集合,简称集合,终结符,以及
首先,从初始符号的节点连一条边向节点。
遍历每一个产生式:
- 遇到空产生式,跳过
- 对于产生式,。对于:
- 对每一个,如果,找到第一个,使得,接着从连向所有的,。如果,就从连一条边向,否则从连接一条边向。如果子串,注意空子串也可以导出空串。,就从连接一条边向。 [FOLLOW的事情]
- 找到第一个使得,则从连向每一个,如果,则从连向,否则从连向(FIRST的事情)
则每一个FOLLOW节点的可达集合就是该集合。
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 : 对一个产生式的运算
如果可以推导出,那么
如过推导不出,那么。
由此规则遍历所有的产生式就可以得到每一条产生式的集合。
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 文法的判别
判定一个文法是文法的充要条件是,相同左部的任意两个产生式的集合没有交集。
按照左部扫描一次产生式集合,两两比较集合是否有交集。