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

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+kk = j+p->mnLookahead-i,再代入上式,不就明白了吗?

好一个变量替换,整整弄了两天才想通。看来代数确实推动了数学的发展,这么 “复杂”的问题用几个式子就搞定了。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值