写在前面
由于代码较长,csdn对文章总长度有字数限制,想只看完整代码的请移步另一篇博客。
https://blog.csdn.net/qq_46640863/article/details/125705891
理论与习题讲解移步视频
https://www.bilibili.com/video/BV1zu4y1C7SL
一、实验内容
1.实现LL(1)分析算法
2.输入:教材中的算术表达式文法;待分析的语句(如i+i*i)
3.输出:语句的分析过程(参见ppt例题)
4.要求:LL(1)分析表程序自动生成。如果使用已知的分析表,实验分数会降低。
二、实验目的
通过设计、编制、调试一个具体的文法分析程序,深入理解LL(1)预测分析法的基本分析原理,理解FIRST集、FOLLOW集的构造方法并对其加以实现,构造LL(1)预测分析表并利用分析表对语句、文法进行分析。
三、实验分析
书中算术表达式文法示例如下:
G
[
E
]
:
(
1.1
)
E
→
E
+
T
∣
T
T
→
T
∗
F
∣
F
F
→
(
E
)
∣
i
G[E]:(1.1)\\ E\to E+T|T\\ T\to T*F|F\\ F\to(E)|i
G[E]:(1.1)E→E+T∣TT→T∗F∣FF→(E)∣i
这是一个算术表达式文法,满足*优先于+,且*和+都服从左结合。这个文法不存在二义性,因为运算符优先级被分为了三个层次:“+”为一层,“*”为一层,括号“()”与运算对象“
i
i
i”为另一层,这三层优先级依次递增。用非终结符
E
E
E表示文法的开始符号,再增加
T
T
T、
F
F
F表示不同的层次。即:离开始符越近的产生式优先级越低,离开始符越远的产生式优先级越高。
使用一个消除了二义性的文法可以进行自顶向下的语法分析如递归下降分析与LL(1)分析法。但是,还需要先消除左递归与回溯。
设关于非终结符
A
A
A的直接左递归产生式为
A
→
A
α
∣
β
A\to A\alpha |\beta
A→Aα∣β,对其消除左递归,可以将其改写为:
{
A
→
β
A
′
A
′
→
α
A
′
∣
ϵ
\begin{cases} A\to \beta A'\\ A'\to \alpha A'|\epsilon \end{cases}
{A→βA′A′→αA′∣ϵ
其中,
A
′
A'
A′为新引入的非终结符,
ϵ
\epsilon
ϵ为空字不可缺少。
对文法
G
[
E
]
(
1.1
)
G[E](1.1)
G[E](1.1)消除左递归可以得到文法
G
[
E
]
(
1.2
)
G[E](1.2)
G[E](1.2)
G
[
E
]
(
1.2
)
E
→
T
E
′
E
′
→
+
T
E
′
∣
ϵ
T
→
F
T
′
T
′
→
∗
F
T
′
∣
ϵ
F
→
(
E
)
∣
i
G[E](1.2)\\ E\to TE'\\ E'\to+TE'|\epsilon\\ T\to FT'\\ T'\to *FT'|\epsilon\\ F\to(E)|i
G[E](1.2)E→TE′E′→+TE′∣ϵT→FT′T′→∗FT′∣ϵF→(E)∣i
回溯发生的原因在于候选式存在共同的左公因子。如关于
A
A
A产生式为
A
→
δ
β
1
∣
δ
β
2
∣
.
.
.
∣
δ
β
i
∣
β
i
+
1
∣
.
.
.
∣
β
j
A\to \delta\beta_1|\delta\beta_2|...|\delta\beta_i|\beta_{i+1}|...|\beta_j
A→δβ1∣δβ2∣...∣δβi∣βi+1∣...∣βj,则可以将其改写为:
{
A
→
δ
A
′
∣
β
i
+
1
∣
.
.
.
∣
β
j
A
′
→
β
1
∣
.
.
.
∣
β
i
\begin{cases} A\to\delta A'|\beta_{i+1}|...|\beta_j\\ A'\to \beta_1|...|\beta_i \end{cases}
{A→δA′∣βi+1∣...∣βjA′→β1∣...∣βi
由于算术表达式文法不存在左公因子,所以无需消除回溯。
在程序实现时,可以消除文法中的或符号,即对其进行处理:
G
[
E
]
(
1.3
)
E
→
T
E
′
E
′
→
+
T
E
′
E
′
→
ϵ
T
→
F
T
′
T
′
→
∗
F
T
′
T
′
→
ϵ
F
→
(
E
)
F
→
i
G[E](1.3)\\ E\to TE'\\ E'\to+TE'\\ E'\to\epsilon\\ T\to FT'\\ T'\to *FT'\\ T'\to\epsilon\\ F\to(E)\\ F\to i
G[E](1.3)E→TE′E′→+TE′E′→ϵT→FT′T′→∗FT′T′→ϵF→(E)F→i
为了LL(1)构造分析表
M
M
M,需要构造与文法相关的集合FIRST与FOLLOW。
FIRST(
α
\alpha
α)表示
α
\alpha
α的所有可能推导的开头终结符或可能的空字
ϵ
\epsilon
ϵ。FIRST构造方法为反复运用下述规则,直到每个集合的FIRST集合不再变化为止。
1.若存在产生式
X
→
a
X\to a
X→a,且
a
a
a为终结符,则把
a
a
a加入FIRST(
X
X
X)中。若存在
X
→
ϵ
X\to\epsilon
X→ϵ,则
ϵ
\epsilon
ϵ也加到FIRST(
X
X
X)中。
2.若存在
X
→
Y
.
.
.
X\to Y...
X→Y...,且
Y
Y
Y为非终结符,则将FIRST(
Y
Y
Y)中所有非
ϵ
\epsilon
ϵ元素加入到FIRST(
X
X
X)中。若
X
→
Y
1
Y
2
.
.
.
Y
k
X\to Y_1Y_2...Y_k
X→Y1Y2...Yk且
Y
1
Y_1
Y1~
Y
i
Y_i
Yi都是非终结符且产生式中存在
ϵ
\epsilon
ϵ,则将FIRST(
Y
j
Y_j
Yj)
(
j
=
1
,
2
,
.
.
.
,
i
)
(j=1,2,...,i)
(j=1,2,...,i)的所有非
ϵ
\epsilon
ϵ元素加入到FIRST(
X
X
X)中,当
Y
1
Y_1
Y1~
Y
k
Y_k
Yk都含有
ϵ
\epsilon
ϵ产生式时,把
ϵ
\epsilon
ϵ加入到FIRST(
X
X
X)中。
对本实验中涉及的算术表达式文法构造FIRST集合。FIRST(
E
′
E'
E′)=
{
+
,
ϵ
}
\{+,\epsilon\}
{+,ϵ},FIRST(
T
′
T'
T′)=
{
∗
,
ϵ
}
\{*,\epsilon\}
{∗,ϵ},FIRST(
F
F
F)=
{
(
,
i
}
\{(,i\}
{(,i}。由
T
→
F
.
.
.
T\to F...
T→F...和
E
→
T
.
.
.
E\to T...
E→T...,可得FIRST(
T
T
T)=FIRST(
E
E
E)=FIRST(
F
F
F)=
{
(
,
i
}
\{(,i\}
{(,i}:
对每个非终结符构造FOLLOW集合,需要连续使用以下规则:
1.对文法开始符号
S
S
S,将
#
\#
#加入FOLLOW(
S
S
S)。
2.若存在产生式
A
→
.
.
.
B
β
A\to ...B\beta
A→...Bβ,将FIRST(
β
\beta
β)中除
ϵ
\epsilon
ϵ的元素加入FOLLOW(
B
B
B)。
3.若存在产生式
A
→
.
.
.
B
A\to ...B
A→...B或
A
→
.
.
.
B
β
A\to ...B\beta
A→...Bβ且
ϵ
∈
\epsilon\in
ϵ∈FIRST(
β
\beta
β),则把所有元素加到FOLLOW(
B
B
B)。
构造FOLLOW集合时,按规则1,FOLLOW(
E
E
E)=
{
#
}
\{ \#\}
{#}。按规则2,由
E
→
T
E
′
E\to TE'
E→TE′,FOLLOW(
T
T
T)=
{
+
}
\{+\}
{+}。由
T
→
F
T
′
T\to FT'
T→FT′,FOLLOW(
F
F
F)=
{
∗
}
\{*\}
{∗},由
F
→
(
E
)
F\to (E)
F→(E),FOLLOW(
E
E
E)=
{
)
,
#
}
\{),\# \}
{),#}。按规则3,由
E
→
T
E
′
E\to TE'
E→TE′,FOLLOW(
E
′
E'
E′)=
{
)
,
#
}
\{),\#\}
{),#}。由
E
→
T
E
′
E\to TE'
E→TE′且
E
→
ϵ
E\to\epsilon
E→ϵ,FOLLOW(
T
T
T)=
{
+
,
)
,
#
}
\{+,),\#\}
{+,),#}。由
T
→
F
T
′
T\to FT'
T→FT′,FOLLOW(
T
′
T'
T′)=
{
+
,
)
,
#
}
\{+,),\# \}
{+,),#}。由
T
→
F
T
′
T\to FT'
T→FT′且
T
′
→
ϵ
T'\to \epsilon
T′→ϵ,FOLLOW(
F
F
F)=
{
∗
,
+
,
)
,
#
}
\{*,+,),\# \}
{∗,+,),#}
在构造完FIRST集合与FOLLOW集合后,可以构造分析表
M
M
M。对于文法中的每一个产生式,对每个终结符
a
∈
a\in
a∈FIRST(
A
A
A),把
A
→
α
A\to \alpha
A→α加入
M
[
A
,
a
]
M[A,a]
M[A,a]中,其中
a
∈
a\in
a∈FIRST(
α
\alpha
α)。若
ϵ
∈
\epsilon\in
ϵ∈FIRST(
A
A
A),则对任何属于FOLLOW(
A
A
A)的终结符
b
b
b,将
A
→
ϵ
A\to\epsilon
A→ϵ加入到
M
[
A
,
b
]
M[A,b]
M[A,b]。将其他表项都认为是出错。算术表达式文法的分析表如下:
i i i | + + + | ∗ * ∗ | ( ( ( | ) ) ) | # \# # | |
---|---|---|---|---|---|---|
E E E | E → T E ′ E\to TE' E→TE′ | E → T E ′ E\to TE' E→TE′ | ||||
E ′ E' E′ | E ′ → + T E ′ E'\to +TE' E′→+TE′ | E ′ → ϵ E'\to\epsilon E′→ϵ | E ′ → ϵ E'\to\epsilon E′→ϵ | |||
T T T | T → F T ′ T\to FT' T→FT′ | T → F T ′ T\to FT' T→FT′ | ||||
T ′ T' T′ | T ′ → ϵ T'\to\epsilon T′→ϵ | T ′ → ∗ F T ′ T'\to *FT' T′→∗FT′ | T ′ → ϵ T'\to\epsilon T′→ϵ | T ′ → ϵ T'\to\epsilon T′→ϵ | ||
F F F | F → i F\to i F→i | F → ( E ) F\to (E) F→(E) |
输入串以 # \# #作为结束标志,分析栈栈底先放入 # \# #。分析程序根据分析栈栈顶符号 x x x和当前输入符号 a a a决定分析器动作。若 x = a = # x=a=\# x=a=#,分析成功。若 x = a ≠ # x=a\ne \# x=a=#,栈顶符号与输入符号匹配,则 x x x出栈,继续对下一个字符进行分析。若栈顶为非终结符 A A A,则查表。若为一个 A A A的产生式,则将 A A A出栈,将产生式右部符号逆序压入栈。若产生式为 A → ϵ A\to\epsilon A→ϵ,则只将 A A A出栈。若表中该表项为空,则出错。
四、实验流程
4.1 整体流程
首先从文件中读入文法,识别出文法的终结符与非终结符,对文法进行消除左递归、消除回溯、消除或。再次识别出文法的终结符与非终结符,对每个符号与短语求出其FIRST集合与FOLLOW集合,自动分析生成建立分析表。输入待分析的文法,对其进行分析。整体流程如下图所示。
4.2 求FIRST
对字符或短语求FIRST集合时,往往需要用到递归。若在递归时发现
c
c
c字符的FIRST集合已经求解完毕,则直接返回。若当前字符
c
c
c是终结符,则FIRST集合为自己,返回。若不是终结符,则对其产生式进行遍历。若产生式中就是空字,则将空字加入FIRST集合。否则,遍历产生式的字符。递归的求解当前字符的FIRST集合,将该字符FIRST集合除了空字以外的元素加入。若该字符的FIRST集合存在空字,则遍历下一字符,如此循环。求解短语的FIRST集合的过程与求解某一非终结符的产生式的FIRST集合的流程是类似的。求FIRST集合的流程如下图。
4.3 求FOLLOW
对字符或短语求FOLLOW集合时,也需要用到递归。对于文法开始符号,先将#加入其FOLLOW集合。对于每个非终结符,判断每条产生式,若存在
A
→
.
.
.
B
b
A\to ...Bb
A→...Bb,则将
b
b
b加
B
B
B入的FOLLOW集合。对每个产生式从右往左遍历,对于当前标识符之后的字符,若是非终结符,则将这个非终结符的FIRST集加入当前字符的FOLLOW集,这一过程需要递归地进行。若
A
→
B
β
A\to B\beta
A→Bβ且
ϵ
∈
\epsilon\in
ϵ∈FIRST(
β
\beta
β),将FOLLOW(
A
A
A)加入FOLLOW(
B
B
B)。每次都要反复进行此过程。
4.4 求分析表并分析输入串
构造分析表的流程与上文是一致的:若 a a a属于FIRST( A A A),把 A → a A\to a A→a加入到 M [ A , a ] M[A,a] M[A,a]中。若 ϵ \epsilon ϵ属于FIRST( A A A),则对FOLLOW( A A A)的终结符 b b b加入 A → ϵ A\to\epsilon A→ϵ。分析输入串的流程与上文一致,在此也不赘述。
五、实验代码
5.1 数据结构
利用S
表示文法的开始符,利用VN
与VT
分别表示非终结符与终结符。
字符的FIRST集合用map
存储。map
的键为字符,值为FIRST集合,用set
存储。字符的FIRST集表被定义为:map<char, set<char>> firstSet
短语的FIRST集合也用map
存储。map
的键为字符串,即短语。值为FIRST集合,用set
存储。其被定义为:map<string, set<char>> firstSetS
字符的FOLLOW集合用map
存储。map
的键为字符,值为FOLLOW集合,用set
存储。字符的FOLLOW集表被定义为:map<char, set<char>> followSet;
利用MAP
表示产生式表,利用map
存储。map
的键为产生式的左部,值为产生式右部。因为一个非终结符可能有多个右部,所以值用vector<string>
存储,可以存储多个右部字符串。map<char, vector<string>> expressionSet;
预测分析表本体利用二维数组存储。vector<vector<string>> table;
文件读取到的文法、消左递归的文法、消除回溯的文法、增广的文法,可以用于求解FIRST与FOLLOW的文法存储在不同的变量名中。
vector<string> filebuffer;//文件读取的缓冲区
vector<string> expression_withoutrecursion;//消左递归
vector<string> expression_withoutback;//消完左递归和回溯
vector<string> inputExpression;//最后增广完成,用于进行分析的文法
5.2 部分核心算法设计
5.2.1 消除左递归与回溯并消除或
需要着重介绍的就是此部分的代码。在读取到的文件中,用字符”->”
代替箭头。对文法消除左递归时,判断文法某一个产生式的第0项与第3项即”->”
后的字符是否相同。若相同,则发现左递归。利用下式对
A
→
A
α
∣
β
A\to A\alpha|\beta
A→Aα∣β进行改写。为了方便程序的编写,所有符号都只用一个字符表示。从大写字母表中找一个没有被使用过的字母作为非终结符表示下式中的
A
′
A'
A′,用字母
e
e
e代替
ϵ
\epsilon
ϵ,表示空字。每次利用find()
函数找到”|”,利用substr()
截取子字符串,找到产生式中的
α
\alpha
α短语与
β
\beta
β短语。
{
A
→
β
A
′
A
′
→
α
A
′
∣
ϵ
\begin{cases} A\to\beta A'\\ A'\to\alpha A'|\epsilon \end{cases}
{A→βA′A′→αA′∣ϵ
在消除回溯时,判断第3个字符与每处”|”后的字符是否相同。若相同,则此产生式存在回溯,存在共同的左公因子。用同样的方法,找到每处”|”,用下式中的文法替换原有文法。同样的,用没有使用过的字符代替
A
′
A'
A′。
{
A
→
δ
A
′
∣
β
i
+
1
∣
.
.
.
∣
β
j
A
′
→
β
1
∣
.
.
.
∣
β
i
\begin{cases} A\to\delta A'|\beta_{i+1}|...|\beta_j\\ A'\to \beta_1|...|\beta_i \end{cases}
{A→δA′∣βi+1∣...∣βjA′→β1∣...∣βi
消除“或”时,同样需要找到每处”|”,并对含有”|”符号的字符串进行重新拼接处理。
void leftRecursion(){
for (string str : filebuffer){
if (str[0] == str[str.find('>') + 1]){
char newch = 'A';
for (newch = 'A'; newch <= 'Z'; newch++)
if (VN.count(newch) == 0)
break;
VN.insert(newch);
string newstr = "";
string alpha = str.substr(4, str.find('|') - 4);
// cout << "\nalpha" << alpha << endl;
//A->betaA'
newstr = newstr + str[0] + (string)"->" + str.substr(str.find('|') + 1) + newch;
expression_withoutrecursion.push_back(newstr);
newstr = "";
//A'->alphaA'|e
newstr = newstr + newch + "->" + alpha + newch + "|" + 'e';
//e表示空字符episilon
VT.insert('e');
expression_withoutrecursion.push_back(newstr);
}
else
expression_withoutrecursion.push_back(str);
}
cout << "\n消除左递归:\n";
for (string str : expression_withoutrecursion)
cout << str << endl;
}
//消除回溯
void leftTraceback(){
for (string str : expression_withoutrecursion){
//发现回溯
if (str[3] == str[str.find("|") + 1]){
char newch = 'A';
for (newch = 'A'; newch <= 'Z'; newch++)
if (VN.count(newch) == 0)
break;
VN.insert(newch);
string delta = "" + str[3];
string newstr = "";
//A->alphaA'
newstr += str[0] + "->" + delta + newch;
expression_withoutback.push_back(newstr);
newstr = "";
//A'->beta1|beta2
string beta1 = str.substr(4, str.find('|') - 4);
string beta2 = str.substr(str.find('|') + 1);
newstr += newch + "->" + beta1 + '|' + beta2;
expression_withoutback.push_back(newstr);
}
else
expression_withoutback.push_back(str);
}
cout << "\n消除回溯:\n";
for (string str : expression_withoutback)
cout << str << endl;
}
//增广,删除竖线|
void deleteor(){
for (string str : expression_withoutback){
if (str.find('|') != string::npos){
//将A->beta1|beta2拆成A->beta1和A->beta2
string right1 = str.substr(3, str.find('|') - 3);
// cout << "right1" << right1 << endl;
string newstr1 = "";
newstr1 = str[0] + (string)"->" + right1;
// cout << "newstr1" << newstr1 << endl;
inputExpression.push_back(newstr1);
string right2 = str.substr(str.find('|') + 1);
// cout << "right2" << right2 << endl;
string newstr2 = "";
newstr2 += str[0] + (string)"->" + right2;
// cout << "newstr2" << newstr2 << endl;
inputExpression.push_back(newstr2);
}
else
inputExpression.push_back(str);
}
cout << "\n增广\n";
for (string str : inputExpression)
cout << str << endl;
}
5.2.2 求解FIRST
求字符与短语的FIRST集合的过程与4.2章节中的流程图是一致的。这是一个递归的过程。因为涉及到了求单个字符与短语,所以需要对函数进行重载。
//字符的求first集
void getFirst(char c){
//已经求完了当前字符的first集
if (firstSet.count(c))
return;
set<char> newset;
//若c为终结符,则first为自己,结束
if (VT.count(c)){
newset.insert(c);
firstSet[c] = newset;
return;
}
for (string s : expressionSet[c]){
//若存在X->e,将e加入
if ('e' == c)
newset.insert('e');
else{
for (char cur : s){
if (!firstSet.count(cur))
getFirst(cur);
set<char> curFirst = firstSet[cur];
for (auto curf : curFirst)
newset.insert(curf);
//将Y的first集合中的元素中不是e的加入
if (!curFirst.count('e'))
break;
//如果加到了e,则循环回去,即X->Y1Y2..Yk时,Y1产生e,就需要加入Y2的first集合
}
}
}
firstSet[c] = newset;
}
//多个字符(字符串、短语)first集求法
void getFirst(string s){
if (firstSetS.count(s))
return;
set<char> newset;
int i = 0;
while (i < s.length()){
char cur = s[i];
if (!firstSet.count(cur))
getFirst(cur);
//将当前字符的first集作为短语的first集
set<char> rightSet = firstSet[cur];
for (auto rs : rightSet)
newset.insert(rs);
//类似的,如果加到了e,则循环回去,即X->Y1Y2..Yk时,Y1产生e,就需要加入Y2的first集合
if (rightSet.count('e'))
i++;
else
break;
//遍历完了当前语句后,将空字加入当前语句first集
if (i == s.length())
newset.insert('e');
}
firstSetS[s] = newset;
}
5.2.3 求解FOLLOW
求解FOLLOW集合的流程是迭代的。在调用初始化函数时,需要针对每一个字符反复的对其求解FOLLOW集合。
//求每个非终结符的follow
for (char c : VN)
getFollow(c);
求解某一字符FOLLOW集合的流程与章节4.3的流程图是一致的。
//某个字符的follow集合
void getFollow(char c){
vector<string> list = expressionSet[c];
set<char> leftFollowSet;
if (followSet.count(c))
leftFollowSet = followSet[c];
//对文法开始符号,将#加入follow集合
if (c == S)
leftFollowSet.insert('#');
for (char ch : VN) {
for (string s : expressionSet[ch]) {
for (int i = 0; i < s.length(); i++) {
//若有A->aBb(a可为空),则将b加入B的follow集合
if (c == s[i] && i + 1 < s.length() && VT.count(s[i + 1]))
leftFollowSet.insert(s[i + 1]);
}
}
}
followSet[c] = leftFollowSet;
for (string s : list){
int i = s.length() - 1;
//对每个产生式从右往左遍历
while (i >= 0)
{
char cur = s[i];
//对于当前标识符B
//对于当前标识符之后的字符,若是非终结符,则将这个非终结符的first集加入当前字符的follow集
if (VN.count(cur)){
string right = s.substr(i + 1);
set<char> rightFirstSet;
if (!followSet.count(cur))
getFollow(cur);
set<char> curFollowSet = followSet[cur];
//若A->aB或A->aB\beta且\beta->e
//则把follow(A)加到follow(B)
if (right.length() == 0)
for (auto lfs : leftFollowSet)
curFollowSet.insert(lfs);
else
//将若A->Bbeta,将B后的字符的first(除e)加入
if (right.length() == 1)
if (!firstSet.count(right[0]))
getFirst(right[0]);
rightFirstSet = firstSet[right[0]];
else
if (!firstSetS.count(right))
getFirst(right);
rightFirstSet = firstSetS[right];
for (char var : rightFirstSet)
if (var != 'e')
curFollowSet.insert(var);
//若A->Bbeta且e属于first(beta),将follow(A)加入follow(B)
if (rightFirstSet.count('e'))
for (auto lfs : leftFollowSet)
curFollowSet.insert(lfs);
}
followSet[cur] = curFollowSet;
}
i--;
}
}
}
5.2.4 文法分析
分析的过程与第三章节是一致的。首先,放 # \# #入分析栈,再放文法开始符 S S S入分析栈。当栈顶元素不是 # \# #时,如果栈顶元素与输入字符是一致的,则将栈顶弹出,扫描指针向后移动。若栈顶为其他终结符,则出错。若分析表中当前表项为空,则出错。若当前表项为空字,则出栈。否则,从分析表中找到当前非终结符 X X X表项 M [ X , a ] M[X,a] M[X,a],倒序将当前表项中产生式的右部入栈。其他情况均为出错情况。
void analyzeLL(){
cout << "\nLL分析过程\n";
cout << setw(20) << "stack" << setw(10) << "input" << setw(20) << "expression";
cout << endl;
analyzeStack.push('#');
//#入栈
analyzeStack.push(S);
//开始符入栈
displayLL();
char X = analyzeStack.top();
while (X != '#'){
char a = strInput[index];
if (X == a){
action = "";
analyzeStack.pop();
index++;
}
else if (VT.count(X))
return;//找到终结符,出错
else if (find(X, a) == "")
return;
else if (find(X, a) == "e"){
analyzeStack.pop();
action = "";
action += X;
action += "->e";
}//空字,出栈
else{//其余情况,需要按分析表对栈顶元素进行推导
string str = find(X, a);
if (str != ""){
action = "";
action += X;
action += "->";
action += str;
analyzeStack.pop();
int len = str.length();
for (int i = len - 1; i >= 0; i--)
analyzeStack.push(str[i]);//栈顶元素出栈,按分析表和下一字符将分析表中元素入栈
}
else
{
cout << "\nERROR!\n";//出错,直接返回
return;
}
}
X = analyzeStack.top();
displayLL();
}
}
5.3 完整程序
https://blog.csdn.net/qq_46640863/article/details/125705891文章来源:https://www.toymoban.com/news/detail-456395.html
六、运行结果
从文件中读取输入文法为算术表达式文法如下,这一文法存在左递归,且没有增广。
因此,需要识别终结符与非终结符,对其进行消除左递归、消除回溯、增广处理,运行结果如下。为了简化程序,用A
代替
E
′
E'
E′,用B
代替
T
′
T'
T′,用e
代替
ϵ
\epsilon
ϵ,其结果与第三章节推导的结果是完全一致的。
对于消除了”|”完成的文法,再次识别终结符与非终结符,并对每个非终结符输出其产生式右部。
在预处理工作完成后,即可对每个终结符与非终结符求解FIRST集合,求解非终结符的FOLLOW集合。其结果与第三章节的结果是完全一致的。
求解FIRST集合和FOLLOW集合完成后,构建分析表,其结果与第三章节的分析是一致的。
输入表达式
i
∗
i
+
i
i*i+i
i∗i+i,自动给出分析过程。
首先,将
#
\#
#与开始符
E
E
E压栈;根据
M
[
E
,
i
]
=
E
→
T
A
M[E,i]=E\to TA
M[E,i]=E→TA,将
E
E
E出栈,将
T
A
TA
TA逆序后入栈;根据
M
[
T
,
i
]
=
T
→
F
B
M[T,i]=T\to FB
M[T,i]=T→FB,将
T
T
T出栈,将
F
B
FB
FB逆序后入栈;根据
M
[
F
,
i
]
=
F
→
i
M[F,i]=F\to i
M[F,i]=F→i,将
F
F
F出栈,将
i
i
i入栈;此时,栈顶与输入串匹配,弹出;此时栈顶为
B
B
B,根据
M
[
B
,
+
]
=
B
→
ϵ
M[B,+]=B\to\epsilon
M[B,+]=B→ϵ,空字符不入栈;此时栈顶为
A
A
A,根据
M
[
A
,
+
]
=
A
→
+
T
A
M[A,+]=A\to +TA
M[A,+]=A→+TA,将
+
T
A
+TA
+TA逆序后入栈。此时栈顶的+与输入串的+匹配,出栈。之后的分析步骤与流程与前文类似,在此不再赘述。
在对正确地文法分析完毕后,分析过程正确地停止,程序退出。文章来源地址https://www.toymoban.com/news/detail-456395.html
七、实验感悟
到了这里,关于【编译原理】 实验三 LL(1)分析法(LL1分析表的自动生成)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!