您的位置:首页 > 其它

LEMON源码分析笔记——压缩分析表

2011-04-01 18:21 393 查看
LEMON源码分析笔记——压缩分析表

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
此时要加入前面示例中的状态数组,此时标有null的区域刚好可以放下并入数组,于是并入此区域。

若当状态为:

V
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,再代入上式,不就明白了吗?
好一个变量替换,整整弄了两天才想通。看来代数确实推动了数学的发展,这么
“复杂”的问题用几个式子就搞定了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: