您的位置:首页 > 其它

LEMON源码分析笔记——状态默认动作

2011-04-06 10:31 405 查看
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中的类似,不再做分析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: