LEMON源码分析笔记——状态默认动作
2011-4-6 陕师大 阴
为压缩分析表,将状态中使用最频的生产式所对应的动作,作为状态默认动作。状态默认动作保存在状态的iDflt域中。
设置状态默认动作
1. 对每个状态,找出使用最频的生产式,然后标识它对应的动作。标识的办法是:将第一个动作的先行符改为{default},而其它的只将其类型由REDUCE改成NOT_USED.(CompressTables)
2. 遍历每一个状态,先将iDflt域设成lemon::nstate + lemon::nrule(就是ERROR).再将有归约动作(归约意味着可以挑出最频生产式)的状态的iDflt设为默认动作。(ResortStates)
3. 将状态动作制成分析表,将默认动作排除在外。从而达到压缩分析表的目的。(ReportTable)
使用状态默认动作
每一个状态的默认动作都打印在yy_default[]表中,可以根据状态编号来索引。接下来看看这个数组在什么情况下使用:
第一处调用(yy_find_shift_action):
if( stateno>YY_SHIFT_MAX || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT )
{
return yy_default[stateno];
}
没有偏移量的状态。YY_SHIFT_MAX指yy_shift_ofst的最大下标。yy_shift_ofst的最大长度不就就是lemon::nstate吗?从理论上说是对的,但lemon为了最大限度地压榨缩小用使用空间,就连yy_shift_ofst的长度也经过精心计算。
while( n>0 && lemp->sorted[n-1]->iTknOfst==NO_OFFSET ) n--;
fprintf(out, "#define YY_SHIFT_MAX %d/n", n-1);
lemp->sorted[n-1]->iTknOfst==NO_OFFSET表示状态没有偏移量。但一个状态只要有一个先行符为终结符,在不考虑ax[i].nAction>0的情况下,就会执行到下面代码中的stp->iTknOfst = acttab_insert(pActtab);。而一个状态的先行符(指动作中的文法符号)中,不可能没有终结符,若没有一个文法符号,系统会状态加上符号”$”垫底。先行符中也不可能出现只有非终结符,而没有终结符,因为如果可以接受非终结符,那么它的First集一定能被状态接受。
for(i=0; i<lemp->nstate*2 && ax[i].nAction>0; i++)
{
stp = ax[i].stp;
if( ax[i].isTkn )
{
for(ap=stp->ap; ap; ap=ap->next)
{
int action;
if( ap->sp->index>=lemp->nterminal ) continue;
action = compute_action(lemp, ap);
if( action<0 ) continue;
acttab_action(pActtab, ap->sp->index, action);
}
stp->iTknOfst = acttab_insert(pActtab);
if( stp->iTknOfst<mnTknOfst ) mnTknOfst = stp->iTknOfst;
if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst;
}
else
{
...
}
}
答案就在ax[i].nAction的计算方法中。
for(i=0; i<lemp->nstate; i++)
{
stp = lemp->sorted[i];
ax[i*2].stp = stp;
ax[i*2].isTkn = 1;
ax[i*2].nAction = stp->nTknAct;//nTknAct在ResortStates被赋值
ax[i*2+1].stp = stp;
ax[i*2+1].isTkn = 0;
ax[i*2+1].nAction = stp->nNtAct;
}
而state::nTknAct与state::nNtAct在ResortStates函数中初使化了。
for(i=0; i<lemp->nstate; i++)
{
stp = lemp->sorted[i];
stp->nTknAct = stp->nNtAct = 0;
stp->iDflt = lemp->nstate + lemp->nrule;//ERROR
stp->iTknOfst = NO_OFFSET;
stp->iNtOfst = NO_OFFSET;
for(ap=stp->ap; ap; ap=ap->next)
{
if( compute_action(lemp,ap)>=0 )
{
if( ap->sp->index<lemp->nterminal )
{
stp->nTknAct++;
}
else if( ap->sp->index<lemp->nsymbol )
{
stp->nNtAct++;
}
else//难道有符号不在三界五行之内!有——{default}
{
stp->iDflt = compute_action(lemp, ap);
}
}
}
}
if( compute_action(lemp,ap)>=0 )使得标有NOT_USED的动作得不到统计,而标识{default}的先行符也没有记入state::nTknAct之中(只可能是终结符)。也就是说如果一个状态所有的终结符都与默认动作有关,那么这些终结符将全军覆没。其对应的ax[i].nAction就为零,所有不可能进入for循环并把iTknOfst修改。在计算iTknOfst之前,还有一句:
qsort(ax, lemp->nstate*2, sizeof(ax[0]), axset_compare);//先行符少的垫底
这样使得先行符少的垫底,所以for中只要碰到ax[i].nAction为0,那么后面的一定也为零,for没有必要再执行了。但是要明确的是,先行符少的垫底是指在ax数组中,对于lemon::sorted并不一定。也就是说这些空着iTknOfst的状态散落在lemon::sorted的不同位置。对于在尾端的,截去之后,才是yy_shift_ofst的真正大小。这样stateno>YY_SHIFT_MAX就有可能成立了。经过上面的分析(i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT的含义也清楚了。ReportTable中将散落在lemon::sorted中,没有偏移量的状态,赋值为YY_SHIFT_USE_DFLT。而这两个条件的任何一个成立都是与默认动作有关的,返回yy_default[stateno]一定是默认动作。
第二处调用(yy_find_shift_action):
i += iLookAhead;
if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead )
{
return yy_default[stateno];
}
延迟报错。主要解释yy_lookahead[i]!=iLookAhead的情形。当状态不接受iLookAhead时,会出现这种情况,但如果状态偏移量不为NO_OFFSET,那么它会通过第一个关卡。到了这里i可以是不接受的符号也可能是被丢掉了的归约先行符,但它们统一使用默认动作,要知道此时的默认动作是归约动作。这样做对于丢掉的可归约先行符来说是没有问题的,但对于不可接受的符号,会出现问题吗?答案是否定的。因为默认动作是归约,并没有移进这个符号,它还是在“外面”等待是否接受,当栈里的句柄被归约后,进入了一个新的状态,如果这么巧,新的状态还是这样,那继续等待,直到碰到一个没有归约动作的状态。此时,yy_default[]中的值就不是归约动作了,而是ERROR了,这时就等着报错吧。所以碰到不接受符号时,虽然不会立马检查出来,但报错是迟早的事。
另外在yy_find_reduce_action中,还有两处对yy_default的引用,原理与yy_find_shift_action中的类似,不再做分析。