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

本文详细解析了LEMON源码中的状态默认动作机制,包括如何设置状态默认动作、如何使用状态默认动作,以及这一机制如何帮助压缩分析表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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_MAXyy_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;//nTknActResortStates被赋值

ax[i*2+1].stp = stp;

ax[i*2+1].isTkn = 0;

ax[i*2+1].nAction = stp->nNtAct;

}

state::nTknActstate::nNtActResortStates函数中初使化了。

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].nAction0,那么后面的一定也为零,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中的类似,不再做分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值