编译原理中LR(0)项目集规范族的构造

  此文略长。我也没想到这写起来这么多,但对构造过程绝对清楚,一步步慢慢看吧。
  
  LR的第一个L和LL的第一个L含义相同,即从左到右扫描句子 ,第二个R表示Right most最右推导。
  
  在通常的描述中,后面还有一个括号里面的数字如,LR(0)、LR(1)这样,括号里面的数字表示用于决策所需的后续token分词数。
  
  首先看一下LR分析器的模型图
  
  


  
  可惜看出,LR分析器最关键的部分就是 LR分析表了,而LR分析表的构建是由已构造出的LR(0)项目集规范族来进行构造的。LR分析法貌似是不要求掌握的,而且这部分比我想象的还要复杂,今天看了好多。才勉强搞清楚这个项目集规范族的构造,但是用来锻炼思维确实不错啊。
  
  项目集,那么字面上看就是项目的集合了,项目是什么呢。这个也确实不好说,书上是说在文法G中每个产生式的右部适当位置添加一个圆点构成LR(0)项目,举个例子吧。
  
  比如对于
  
  A->xyz
  
  这条产生式可以构造的LR(0)项目就有4个
  
  A->.xyz    A->x.yz    A->xy.z     A->xyz.
  
  这样很清楚了吧,就是用.分割。这个分割产生的四个项目在进行真正的语法分析的时候对应不同的操作,比如规约还是移位。这里不讨论。重点是项目集规范族的构造,
  
  在知道了LR(0)项目后,可以来看看项目集规范族的定义,
  
  对于构成识别一个文法活前缀的DFA项目集(状态)的全体我们称之为这个文法的LR(0)项目集规范族。至于什么是活前缀呢,定义如下
  
  对于任一文法G[S],若S’经过任意次推导得到αAω,继续经过一次推导得到αβω,若γ是αβ的前缀,则称γ是G的一个活前缀。
  
  现在知道了LR(0)项目,了解了活前缀,和项目集规范族的定义,还须引入LR(0)项目集的闭包函数CLOSURE和状态转换函数GO两个概念,先给出数学上的定义,如果你觉得麻烦可以跳过,后面会给出一道例题。
  
  ① 闭包函数CLOSURE(I)的定义如下:
  
  a)I的项目均在CLOSURE(I)中。
  
  b)若A→α·Bβ属于CLOSURE(I),则每一形如B→·γ的项目也属于CLOSURE(I)。
  
  c)重复b)直到不出现新的项目为止。即CLOSURE(I)不再扩大。
  
  ② 转换函数GO(I,X)的定义:
  
  GO(I,X)=CLOSURE(J)
  
  其中:I为包含某一项目的状态,就是前面我们说的那四个了。,X为一文法符号,X∈(VN∪VT),J={任何形如A→αX·β的项目| A→α·Xβ属于I}。
  
  这样就可以使用闭包函数和转换函数构造文法G′的LR(0)项目集规范族,其步骤如下:
  
   a)置项目S′→·S为初态集的核,然后对核求闭包,CLOSURE({S′→·S})得到初态的项目集。
   b)对初态集或其它所构造的项目集应用转换函数GO(I,X)=CLOSURE(J),求出新状态J的项目集。
   c)重复b)直到不出现新的项目为止。
  
  开始拿个例题来说明,定义没例题看起来看难了。
  
  <strong><em>例题:对于下列文法,S→aS|bS|a,构造该文法的LR(0)项目集规范族</em></strong>
  
  思路就是利用闭包函数CLOSURE和转换函数GO来构造。通过计算函数CLOSURE和GO得到文法的LR(0)项目集规范族,而GO函数则把LR(0)项目集规范族连成一个识别该文法所产生的活前缀的DFA。DFA大家都知道,有穷自动机。
  
  (1)将文法G(S)拓广为G(S’)也就是该文法的增广文法,目的是使语法分析器知道何时应该停止并接受该串,也就是说当使用S'-&gt;S进行规约的时候,就结束。
  
  (0)S’→S
  (1)S→aS  
  (2)S→bS  
  (3)S→a
  
  构造该文法的LR(0)项目集规范族,I就是产生式,至于I0 I1就是往下递增就可以了。没什么特别的意思。:
  
  I0=CLOSURE({S' →•S})={S’ →•S, S→•aS, S→•bS, S→•a}
  
  //第一条是固定的,都是求S' →•S的闭包。而因为右侧的S又可以推导出后面三个,所以后面三个式子是属于该闭包的。在闭包的规则中可以看出,若A→α·Bβ属于CLOSURE(I),此时S' →•S属于闭包,S相当于规则中的B,则每一形如B→·γ的项目也属于CLOSURE(I),此处相当于S-&gt;后面的三个推导式。加上.就可以了
  
  I1=GO( I0 , a)=CLOSURE({S→a•S , S→a•})={S→a•S , S→a• , S→•aS, S→•bS, S→•a }
  
  //第二条你可能已经看出来了,其实就是把转换函数GO反过来用,在GO函数中,X为一文法符号,J={任何形如A→αX·β的项目| A→α·Xβ属于I}。也就是在I0中,找到右侧包含a的项目,然后将.右移一位来求他们的闭包函数,此处,I0中包含.a的项目为 S→•aS和 S→•a,.右移一位变成S→a•S , S→a•,求闭包函数的规则和上面那条是一样的,继续找推导式右边可以推导出来的式子就可以了,此处,右边同样是S可以推导出三个式子,前面加上.就可以了。
  
  I2=GO(I0 , b)=CLOSURE({S→b•S })={ S→b•S, S→•aS, S→•bS, S→•a }
  
  //第三条仿照第二条进行推导,先在I0中找有.b的,然后右移一位,然后推导式右侧的S继续用题目给出的推导,然后前面加上.
  
  I3=GO(I0 , S)=CLOSURE({S’ →S•})={ S’ →S•}
  
  //第四条因为右侧包含.S的只有一项。必须是.S。所以只有一个,右移后求闭包即可。因为S是右侧的第一位,所以不用再向下推导了,规则是:A→α·Bβ属于CLOSURE(I),此处是S’ →S•,则B→·γ的项目也属于CLOSURE(I),此处S相当于规则中的α,无B。。。。
  
  因为GO(I1, a)=CLOSURE({S→a•S , S→a•})=I1,
  
  //第五条同理,在I1中找有右侧有.a的项目,右移一位。进行求闭包,发现和I1求闭包的变量是一样的。所以结果必然也和I1是一样的。
  
  GO(I1, b)=CLOSURE(S→b•S)=I2.
  
  //第六条没有新的I产生。
  
  I4=GO(I1, S)=CLOSURE({S→aS•})={S→aS•}
  
  //这第七条在I1找右侧包含.S的项目,只有S→a•S,右移后求闭包,同第四条,无B,所以直接如此。
  
  GO(I2, a)= CLOSURE({S→a•S , S→a•})=I1
  
  GO(I2, b)=CLOSURE({S→b•S})=I2
  
  I5=GO(I2, S)=CLOSURE({S→bS•})={S→bS•}
  
  此时应该继续求GO(I3, a),GO(I3, b)和,GO(I3, S),但显然I3中没有.a,没有.b也没有.S,所以不用多此一举了,
  
  继续求GO(I4, a),GO(I4, b)和,GO(I4, S),显然同上,也没有。所以也不用求了,
  
  继续求GO(I5, a),GO(I5, b)和,GO(I5, S),理由同上,没有任何必要了
  
  最终,项目集I0,I1,I2,I3,I4和I5构成了该文法的LR(0)项目集规范族。
  
  编译原理真是博大精深啊。就一个简单的三个推导式就得这么多步骤才构造了一个规范族,还要利用规范族来构造LR分析表,这。。手工果断不是事啊。今天看了几个小时才完全弄清楚LR(0)项目集规范族的构造。例题的步骤是我自己写的。这篇文章写了2个小时。。太费脑子了。。慢慢再写产生活前缀的DFA和LR分析表吧。
  我的网站中的原文链接:http://leaver.me/archives/548.html
  参考:
  
  http://metc.gdut.edu.cn/compile/nandian/n-7.htm

posted @ 2012-05-12 10:46  lazycoding  阅读(30206)  评论(5编辑  收藏  举报