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中的类似,不再做分析。
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中的类似,不再做分析。
相关文章推荐
- LEMON源码分析笔记——分割源码
- LEMON源码分析笔记——压缩分析表
- 高并发服务器架构笔记(3)——muduo_base 源码分析
- rstplib源码分析---快速生成树之端口状态转移状态机
- MyXML源码分析系列:我自己写的一个XML解析-基于状态图
- Android系统默认Home应用程序(Launcher)的启动过程源码分析
- nginx 源码分析阅读笔记-进程管理
- SaltStack源码分析之group状态模块
- 伟东山视频自学笔记——第9课第3节 u-boot分析之源码第1阶段(1)
- jQuery 源码分析笔记(6)
- jQuery 源码分析笔记(6) jQuery.data
- 看书 Python 源码分析笔记 (十) Python的初始化
- 蔡军生先生第二人生的源码分析(七十五)启动状态详细说明
- Vue.js 源码学习笔记 -- 分析前准备2 -- Object.defineProperty
- Lighttpd1.4.20源码分析 笔记 fdevent系统-连接socket及超时处理
- Master主备切换机制原理及源码分析(笔记)
- Lighttpd1.4.20源码分析 笔记 状态机与插件
- Hadoop源码分析笔记(四):Hadoop文件系统简介
- OpenCV学习笔记(29)KAZE 算法原理与源码分析(三)特征检测与描述
- Android系统默认Home应用程序(Launcher)的启动过程源码分析