LEMON源码分析笔记——压缩分析表
2011-04-01 18:21
393 查看
LEMON源码分析笔记——压缩分析表
2011-4-1
陕师大长安区
小雨
在几乎没有注释的情况下分析此段代码,是一件令人头疼的事!分析表本来是一个二维数组。为了节省空间,LEMON对分析表进行了巧妙的压缩。
压缩过程
一、
线性化二维表
分析表的行标为状态,列标为文法符号的编号。假设有m个状态,与n个文法符号。则每个状态对应一个长度为n的一维数组,这里把它叫状态数组,分析表为二维数组:
压缩最后要得到一个一维数组。这里称之为线性表。压缩第一步将二维数组线性代。
二、
削两端
状态数组存放的是可接收文法符号的动作。显然,状态不可能一定能接收所有文法符号。也就是说状态数组中,可能有没有动作的元素,这里称之为空档(嗜打球,惯用空档一词,呵呵)。削两端的意思是,在状态数组并入线性表之前,削去两端空档。要注意的时,中间还可能存在空档。
比如某一状态数组如下,蓝色表示已经存放了动作,白的表示空档。
削去两端之后为:
削去两端之后,带来索引麻烦是显然的。经过第三步处理之后,我们才着手解决这个问题。
三、
犬牙交错或融入其中
削完两端之后,每个状态数组中依然可能存在着空档。不能让这些空档在那耗着呀。为了利用这些空档,在状态数组依次并入过程中,如果存在一个块区域,其中的空档可以放入削去两端后的状态数组,那么这个数组就放入这块区域,使得状态数组之间形成犬牙交错的状态。
下面是并入过程中,线性表的某一状态
此时要加入前面示例中的状态数组,此时标有null的区域刚好可以放下并入数组,于是并入此区域。
若当状态为:
若标有V字样的区域与待并入状态数组完全一致,就将状态数组融入其中。
问题及解决方案
一、
从何下手
削去了状态数组两端空档后,将状态将如何索引线性表呢?用一个变量mnLookahead记下状态数组前端削去的空档个数。再记下状态数组插入点的位置i,那么状态数组中的元素在线性表中就从i-mnLookahead数起了。可以将这个偏移量i-mnLookahead作为状态的属性。当要在线性表中索引状态数组文法符号j的动作时,只需将j加上状态偏移量作索引即可。
二、
来者不拒
状态数组中的空档并不是没有用的,空档表示文法符号不被状态接收。于是状态数组中的空档不占用则罢了,一旦被占用,万一碰到被占用空档所对应文法符号,岂不是误解成可接收符号!导致一些本来状态拒绝的文法符号被接收了。解决的方法是,在动作中增加一个属性——lookahead。用来表示该动作处理文法符号的编号。这个属性在没有压缩的二维表中是没须要的。因为动作的索引号就是文法符号的编号。当任取一个文法符号j塞给一状态时,可以由状态取得偏移量,再加上文法符号编号,再到线性表中索引得动作,若动作的lookahead域与j不同,说明这个位置不是此状态赋值的,是由别的状态填入的。这个文法符号是不能接收的。每次“问取”某个文法符号动作时,都会进行这样的判断,下面是yy_find_reduce_action中的一个语句:
assert(
yy_lookahead[i]==iLookAhead );
问题还是显然的,万一别的状态填入的动作的lookahead也跟j一样,那么不一样出错吗?
三、
鸠占鹊巢
自家的空档被它人占用了,别人还假冒成自家人,这是新问题所在。
先看看状态数组融入线性表中的情况。融入不会对已经并入线性表中的元素产生任何影响,因为没有添加任何新元素,也就不可能占用别人家的空档。此时需要考虑的是,没有没可能被线性表已有的动作占了自己的空档。查找线性表中,是否还有动作会被当成自己人,只要有,那就不能融入,换个位置再试。
这段代码反映了这个事实(acttab_insert函数中):
for(j=0;
j<p->nAction;
j++)
{
if( p->aAction[j].lookahead<0 )
continue;
if( p->aAction[j].lookahead==j+p->mnLookahead-i
) n++; }
if( n==p->nLookahead )
{
break; /* Same as a prior transaction set */
}
以犬牙交错方式嵌入时,是考虑的是自己是否会破坏线性表,还是线性表是否会欺骗自己呢?如果考虑自己是否会破坏线性表的话,你将会不堪其扰。因为你得为前面并入的每个状态数组考虑。于是,我们转向考虑,线性表是否会有动作欺骗自己。若有的话,换下一个位置试试。但这样做,对已并入状态是否会产生影响呢?答案是否定的。因为若新来状态数组中有一个位置占用并欺骗了另一个状态数组。那么这两个状态一定重叠,也就是新状态一定不会挑中这个位置的。所以新状态数组不可能欺骗到线性表中已有的任何一个状态数组。
关键源码如下(acttab_insert函数中):
for(j=0;
j<p->nAction;
j++)
{
if( p->aAction[j].lookahead==j+p->mnLookahead-i
) break;
}
if( j==p->nAction )
{
break; /* Fits in empty slots */
}
最头疼的莫过于这句话:
p->aAction[j].lookahead==j+p->mnLookahead-i
这块代码就就连《LEMON语法分析生成器源代码情景分析》作者虞森林,也没有解释好,还说是作者留下的“盲肠”。这句话其实是在判断线性表中是否有会欺骗状态数组的动作。这样写可能好理解一些:
yy_lookahead[i-p->mnLookahead+k]==k//状态数组的第k个元素是k的话就会有欺骗
但要遍历的不是状态数组呀,而是线性表。于是作一个“简单的”变量替换:
令j = i-p->mnLookahead+k则k
= j+p->mnLookahead-i,再代入上式,不就明白了吗?
好一个变量替换,整整弄了两天才想通。看来代数确实推动了数学的发展,这么
“复杂”的问题用几个式子就搞定了。
2011-4-1
陕师大长安区
小雨
在几乎没有注释的情况下分析此段代码,是一件令人头疼的事!分析表本来是一个二维数组。为了节省空间,LEMON对分析表进行了巧妙的压缩。
压缩过程
一、
线性化二维表
分析表的行标为状态,列标为文法符号的编号。假设有m个状态,与n个文法符号。则每个状态对应一个长度为n的一维数组,这里把它叫状态数组,分析表为二维数组:
S0 S1 Sm-1 | D0 | … | Dn-1 |
D0 | … | Dn-1 | |
… | … | … | |
D0 | … | Dn-1 |
S0 S2 … Sm-1 | |||||||||
D0 | … | Dn-1 | D0 | … | Dn-1 | … | D0 | … | Dn-1 |
削两端
状态数组存放的是可接收文法符号的动作。显然,状态不可能一定能接收所有文法符号。也就是说状态数组中,可能有没有动作的元素,这里称之为空档(嗜打球,惯用空档一词,呵呵)。削两端的意思是,在状态数组并入线性表之前,削去两端空档。要注意的时,中间还可能存在空档。
比如某一状态数组如下,蓝色表示已经存放了动作,白的表示空档。
三、
犬牙交错或融入其中
削完两端之后,每个状态数组中依然可能存在着空档。不能让这些空档在那耗着呀。为了利用这些空档,在状态数组依次并入过程中,如果存在一个块区域,其中的空档可以放入削去两端后的状态数组,那么这个数组就放入这块区域,使得状态数组之间形成犬牙交错的状态。
下面是并入过程中,线性表的某一状态
null | null | null |
若当状态为:
V | V | V |
问题及解决方案
一、
从何下手
削去了状态数组两端空档后,将状态将如何索引线性表呢?用一个变量mnLookahead记下状态数组前端削去的空档个数。再记下状态数组插入点的位置i,那么状态数组中的元素在线性表中就从i-mnLookahead数起了。可以将这个偏移量i-mnLookahead作为状态的属性。当要在线性表中索引状态数组文法符号j的动作时,只需将j加上状态偏移量作索引即可。
二、
来者不拒
状态数组中的空档并不是没有用的,空档表示文法符号不被状态接收。于是状态数组中的空档不占用则罢了,一旦被占用,万一碰到被占用空档所对应文法符号,岂不是误解成可接收符号!导致一些本来状态拒绝的文法符号被接收了。解决的方法是,在动作中增加一个属性——lookahead。用来表示该动作处理文法符号的编号。这个属性在没有压缩的二维表中是没须要的。因为动作的索引号就是文法符号的编号。当任取一个文法符号j塞给一状态时,可以由状态取得偏移量,再加上文法符号编号,再到线性表中索引得动作,若动作的lookahead域与j不同,说明这个位置不是此状态赋值的,是由别的状态填入的。这个文法符号是不能接收的。每次“问取”某个文法符号动作时,都会进行这样的判断,下面是yy_find_reduce_action中的一个语句:
assert(
yy_lookahead[i]==iLookAhead );
问题还是显然的,万一别的状态填入的动作的lookahead也跟j一样,那么不一样出错吗?
三、
鸠占鹊巢
自家的空档被它人占用了,别人还假冒成自家人,这是新问题所在。
先看看状态数组融入线性表中的情况。融入不会对已经并入线性表中的元素产生任何影响,因为没有添加任何新元素,也就不可能占用别人家的空档。此时需要考虑的是,没有没可能被线性表已有的动作占了自己的空档。查找线性表中,是否还有动作会被当成自己人,只要有,那就不能融入,换个位置再试。
这段代码反映了这个事实(acttab_insert函数中):
for(j=0;
j<p->nAction;
j++)
{
if( p->aAction[j].lookahead<0 )
continue;
if( p->aAction[j].lookahead==j+p->mnLookahead-i
) n++; }
if( n==p->nLookahead )
{
break; /* Same as a prior transaction set */
}
以犬牙交错方式嵌入时,是考虑的是自己是否会破坏线性表,还是线性表是否会欺骗自己呢?如果考虑自己是否会破坏线性表的话,你将会不堪其扰。因为你得为前面并入的每个状态数组考虑。于是,我们转向考虑,线性表是否会有动作欺骗自己。若有的话,换下一个位置试试。但这样做,对已并入状态是否会产生影响呢?答案是否定的。因为若新来状态数组中有一个位置占用并欺骗了另一个状态数组。那么这两个状态一定重叠,也就是新状态一定不会挑中这个位置的。所以新状态数组不可能欺骗到线性表中已有的任何一个状态数组。
关键源码如下(acttab_insert函数中):
for(j=0;
j<p->nAction;
j++)
{
if( p->aAction[j].lookahead==j+p->mnLookahead-i
) break;
}
if( j==p->nAction )
{
break; /* Fits in empty slots */
}
最头疼的莫过于这句话:
p->aAction[j].lookahead==j+p->mnLookahead-i
这块代码就就连《LEMON语法分析生成器源代码情景分析》作者虞森林,也没有解释好,还说是作者留下的“盲肠”。这句话其实是在判断线性表中是否有会欺骗状态数组的动作。这样写可能好理解一些:
yy_lookahead[i-p->mnLookahead+k]==k//状态数组的第k个元素是k的话就会有欺骗
但要遍历的不是状态数组呀,而是线性表。于是作一个“简单的”变量替换:
令j = i-p->mnLookahead+k则k
= j+p->mnLookahead-i,再代入上式,不就明白了吗?
好一个变量替换,整整弄了两天才想通。看来代数确实推动了数学的发展,这么
“复杂”的问题用几个式子就搞定了。
相关文章推荐
- LEMON源码分析笔记——分割源码
- LEMON源码分析笔记——状态默认动作
- redis源码分析(八)、redis数据结构之压缩ziplist--------ziplist.c ziplist.h学习笔记
- Hadoop源码分析笔记(二):Hadoop序列化与压缩
- jQuery的Deferred对象测试笔记以及源码分析
- 看书 Python 源码分析笔记 (八) 读源码
- PHP源码阅读笔记一(explode和implode函数分析)
- 看书 Python 源码分析笔记 (九) 类机制二
- Android笔记--LayoutInflator源码和使用分析
- Android内核源码bionic目录下的子目录arch-arm源码分析笔记
- jQuery源码研究分析学习笔记-jQuery.deferred()(12)
- JDK 1.7源码阅读笔记(四)集合类之Arrays 源码分析
- Javascript笔记:(实践篇)从jQuery插件技术说起-分析extend方法的源码(发现extend方法里有bug)(下篇)
- springMVC学习笔记之源码分析
- Lighttpd1.4.20源码分析 笔记 通用数组array.c(h)
- jQuery 源码分析笔记(5) jQuery.support
- zeromq源码分析笔记之线程间收发命令(2)
- jQuery 源码分析笔记(2) 变量列表
- librtmp 源码分析笔记 RTMP_ReadPacket
- Ogre源码分析与学习笔记-1