1.1.1 Epsilon闭包计算
同样使用文法
S’ - > S
S – > ( S ) S
S - >
4.2.1节给出了8个项目状态,构成的NFA也有8个状态。如下图:
图表 46
通过NFA的eposilon转换,我们可以得到该文法的DFA。如下图:
图表 47
我们可以看到,在NFA向DFA进行eposilon转换的过程中,DFA中的状态实际上是将NFA的状态中的项作eposilon闭包后合并入同一状态中得到的。因此产生epsilon闭包的项称为核心项(Kernel Item),而被产生的就是闭包项(closure Item),而且闭包项还可以产生新的闭包项。如S’ - > •S是状态0的核心项,S->•(S)S 和S->• 就是闭包项。依据这个特点,实际上生成DFA可以直接从文法中生成,而不必先构造NFA而后在构造DFA,YACC是这样做的,WACC也是这样做的。
在WACC的代码中,见4.2.1节CItem定义的15行给出的函数void ComputerEClosure(CLanguage &L, vector<CItem> &Ivec),该函数就是计算Item的闭包项。下面给出一个测试函数,这个测试函数中的Item项是根据4.2.1节(1)所示的方法手工构造出来的。后面的章节会介绍如何自动生产Item哦。计算文法
A1->A
A->( A )
A->a
的闭包项。
void test4()
{
CLanguage lang;
//定义一个符号集合V
CSymbol A1(1,"A1");//v1
CSymbol A(2, "A"); //v2
CSymbol LP(3,"("); //v3
CSymbol RP(4,")");//v4
CSymbol a (5,"a");//v5
//一.获得CLanguage之vector<CSymbol>
lang.InsertSymbol(A1); //1
lang.InsertSymbol(A);
lang.InsertSymbol(LP); //3
lang.InsertSymbol(RP); //4
lang.InsertSymbol(a); //5
showsym(lang.symbol); //显示之
//二.获得CLanguage之vector<Rules>
//1.获得rightpart之Vector<CSymbol*>
#define vlang(x) (&lang.symbol[x])
//rule1
vector<CSymbol*> rpart1;
rpart1.push_back(vlang( 2 ));
//2。获得leftpart--取得rules
CRules r1(vlang(1),rpart1);
//3.插入vector<Rules>
showrule( r1 );
//r2
vector<CSymbol*> rpart2;
rpart2.push_back(vlang(3));
rpart2.push_back(vlang(2));
rpart2.push_back(vlang(4));
CRules r2(vlang(2),rpart2);
showrule( r2 );
//r3
vector<CSymbol*> rpart3;
rpart3.push_back(vlang(5));
CRules r3(vlang(2),rpart3);
showrule( r3 );
cout<<" \nThe language's symbol is "<<endl;
showsym( lang.symbol );
lang.InsertRules(r1);
lang.InsertRules(r2);
lang.InsertRules(r3);
cout << "\nThe languge's rules is"<<endl;
for ( int i = 0; i<lang.rules.size(); i++)
showrule(lang.rules[i]);
#undef vlang
//三.获得开始符号
lang.SetStartSymbol(lang.symbol[1]);
cout<<"\nlanguage's startsymbol is "<<lang.startsym->name
<<endl;
//language初始化结束
//计算first集合
//1.计算所有终结符的first集合
//lang.FirstAllTerm();
//2.从rules中计算first集合
lang.ComputerFirstSet();
for ( i = 0; i<lang.symbol.size(); i++)
showFirst(lang.symbol[i],lang);
//设置一个item 并计算其closure
CItem item;
vector < CItem > Ivec;
//I0
item.setDot( 0 );
item.setRuleInd( 0 );
item.ComputerEClosure(lang ,Ivec);
//其中Ivec[0] 是核心项目,切不可直接使用item 计算
Ivec[0].precs.insert(0);//插入"$"
Ivec[0].ComputerEPrecs(lang, Ivec);
cout<<"\nI0:"<<endl;
for ( i = 0; i < Ivec.size(); i++)
{
dispItem( lang, Ivec[i]);
}
//I1
Ivec[0].Go(item,lang);
Ivec.clear();
item.ComputerEClosure(lang,Ivec);
Ivec[0].ComputerEPrecs(lang, Ivec);
cout<<"\nI1:"<<endl;
for ( i = 0; i < Ivec.size(); i++)
{
dispItem( lang, Ivec[i]);
}
//I2
Ivec.clear();
item.setDot(1);
item.setRuleInd(2);
item.ComputerEClosure(lang,Ivec);
Ivec[0].precs.insert(0);//插入"$"
Ivec[0].precs.insert(4);//插入)
Ivec[0].ComputerEPrecs(lang, Ivec);
cout<<"\nI2:"<<endl;
for ( i = 0; i < Ivec.size(); i++)
{
dispItem( lang, Ivec[i]);
}
//I3
Ivec.clear();
item.setDot(1);
item.setRuleInd(1);
item.ComputerEClosure(lang,Ivec);
Ivec[0].precs.insert(0);//插入"$"
Ivec[0].precs.insert(4);//插入)
Ivec[0].ComputerEPrecs(lang, Ivec);
cout<<"\nI3:"<<endl;
for ( i = 0; i < Ivec.size(); i++)
{
dispItem( lang, Ivec[i]);
}
//I4
Ivec.clear();
item.setDot(2);
item.setRuleInd(1);
item.ComputerEClosure(lang,Ivec);
Ivec[0].precs.insert(0);//插入"$"
Ivec[0].precs.insert(4);//插入)
Ivec[0].ComputerEPrecs(lang, Ivec);
cout<<"\nI4:"<<endl;
for ( i = 0; i < Ivec.size(); i++)
{
dispItem( lang, Ivec[i]);
}
//I5
Ivec.clear();
item.setDot(3);
item.setRuleInd(1);
item.ComputerEClosure(lang,Ivec);
Ivec[0].precs.insert(0);//插入"$"
Ivec[0].precs.insert(4);//插入)
Ivec[0].ComputerEPrecs(lang, Ivec);
cout<<"\nI5:"<<endl;
for ( i = 0; i < Ivec.size(); i++)
{
dispItem( lang, Ivec[i]);
}
}
计算结果如下
*******===Test4====***
$: eposilon is 0 nt is 0 value is 0
A1: eposilon is 0 nt is 0 value is 1
A: eposilon is 0 nt is 0 value is 2
(: eposilon is 0 nt is 0 value is 3
): eposilon is 0 nt is 0 value is 4
a: eposilon is 0 nt is 0 value is 5
Rule: A1->A
Rule: A->( A )
Rule: A->a
The language's symbol is
$: eposilon is 0 nt is 0 value is 0
A1: eposilon is 0 nt is 1 value is 1
A: eposilon is 0 nt is 1 value is 2
(: eposilon is 0 nt is 0 value is 3
): eposilon is 0 nt is 0 value is 4
a: eposilon is 0 nt is 0 value is 5
The languge's rules is
Rule: A1->A
Rule: A->( A )
Rule: A->a
language's startsymbol is A1
$:firstset value is $
A1:firstset value is ( a
A:firstset value is ( a
(:firstset value is (
):firstset value is )
a:firstset value is a
I0:
A1->@A
The precs is :$
A->@( A )
The precs is :$
A->@a
The precs is :$
I1:
A1->A @
The precs is :$
I2:
A->a @
The precs is :$ )
I3:
A->( @A )
The precs is :$ )
A->@( A )
The precs is :)
A->@a
The precs is :)
I4:
A->( A @)
The precs is :$ )
I5:
A->( A ) @
The precs is :$ )
需要指出的是,precs是先行符号集,这里的先行符号是自己构造出来的,这个测试函数除了调用ComputerEClosure函数之外,还调用了ComputerEPrecs,这个函数是将核心集的先行符号集扩散到闭包项中,也是LALR(1)的一个重要函数。计算结果中的@相当于圆点。现在我们看看ComputerEClosure函数是怎么工作的。
void CItem::ComputerEClosure(CLanguage & L, vector < CItem > & Ivec)
{
int changes = 1;
/*/-------------------算法--------*/
//a) I 中项目在closesure 中
Ivec.push_back(*this);// I 即为核心项目 A->α.Bβ
//b)如果A->α.Bβ属于closure(I),则B->.γ项目也属于closure(I)
//c)重复b)直到不出现新项目为止
while (changes)
{
changes = 0;
changes += UnionClosure(Ivec,L);//直到现有的没有变化
}
}
注意底纹为灰色的注释,请将注释与4.2.1节(2)所指出的ε状态图对应起来,在看看代码的实现,明白了吧。其中产生的核心项和闭包项均填充在Ivec中。
进入UnionClosure函数,可以知道如何从核心项目生产闭包项目,以及再由闭包项目生成新的闭包项目。这段代码的注释很清楚,不再逐条解释了。
int CItem::UnionClosure(vector < CItem > & Ivec, CLanguage &L)
{
int changes = 0;
int section = 0;
int ruleInd = -1;
CItem aItem;
//如果找到一条item如果新鲜,插入,并且changes自加1
//否则继续
//直到再也找不到
for ( int i = 0; i < Ivec.size(); i++)
{
if ( Ivec[i].Dot >= L.rules[Ivec[i].RuleInd].RightPart.size()) //Dot是规约句子
{ continue;}
//或rules所在的dot 位置不是非终结符
else if ( !(L.rules[Ivec[i].RuleInd].RightPart[Ivec[i].Dot]->isNT))
{continue;}
else //其它情况均要针对i项插入新鲜item
{
while(-1!= (ruleInd=getRule(L.rules[Ivec[i].RuleInd].RightPart[Ivec[i].Dot],L,
section)))
{
aItem.setRuleInd(ruleInd);
aItem.setDot( 0 );
if (checkFresh(aItem,Ivec))//检查是否为新鲜玩意
{
changes++;
Ivec.push_back(aItem);
}
}
}
}
return changes;
}
其中,灰色底纹包含getRule的那一行比较长,它实际上就是取A->α.Bβ中以B为左部的规则,section是一个控制开关,初始值必须为0,让getRule每次取出来的规则均不相同。
如果将上面文法给出的Item计算的闭包项结果绘成状态图的话,就是下面这个图了。
根据这个图,可以生成该文法的分析表,如下:
状 态 | 动 作 | 规 则 | 输 入 | Goto | ||
( | a | ) | A | |||
0 | 移进 | 3 | 2 | 1 | ||
1 | 归约 | A’->A | ||||
2 | 归约 | A->a | ||||
3 | 移进 | 3 | 2 | 4 | ||
4 | 移进 | 5 | ||||
5 | 归约 | A->(A) |
在图表 48与图表 49中我们可以看到,在状态1或状态2中只能有一个规约式,如果出现多个规约式,LR(0)分析就不知道应该选择哪个规约式进行规约,此时LR(0)的文法出现了二义性,这种情况被称为规约-规约冲突(reduce-reduce conflict)。如果是无法知道应该是移进还是规约,那么就成为移进-规约冲突(shift-reduce conflict)。实际上,我们通常使用的语言,均不是LR(0)文法,因此,LR(0)文法的分析能力是很弱的。但是,使用反向先行传播算法,可以直接从LR(0)状态生成LALR(1)的状态,远比从LR(1)合并简单。因此,在这里花了很长的篇幅来讲LR(0)。
如果在LR(0)上添加先行符号的指导,那么就可以解决规约-规约和移进-规约冲突。LALR(1)就是这种思想的产物。根据LR(1)文法会将LR(0)状态分裂出很多状态来。而LALR(1)就是LR(1)的改进,它保持了LR(0)的小规模优点,又保持了LR(1)的先行符号所带来的能力。