Fork me on GitHub

编译原理----手写编译器(2)----LR(1)分析表的构造

上一篇文章已经对LR分析法做了比较详细的介绍,本篇将主要介绍LR(1)分析表的构造过程:

完整的项目请参考:https://github.com/luofei2011/jslr1

首先有一些基本概念.

LR(1)项目

  就是对于一个上下文无关文法G=(V,T,P,S),若存在规范推导:S=>...=>δAω=>δαβω,则称[A->α.β,a]对活前缀 γ=δα是有效的.其中α,β,δ是属于V&T并集闭包的元素.a是终结符T.且a是ω的首字符,若ω=ε,则a=#.[A->α.β,a]称为文法G的LR(1)项目,a称为搜索符.

例如:如下文法

T->xyz

具体的LR(1)项目是什么呢?我的理解是在产生式右部的不同位置加上'.',然后加上相应的搜索符a:

T->.xyz,#

T->x.yz,#

T->xy.z,#

T->xyz.,#

在具体的语法分析过程中,这些状态将对应不同的操作(移进,归约,接受,出错等).咱们的重点还是项目集的构造.

什么是项目集呢?

识别文法全部活前缀的DFA的每一个状态就用LR(1)项目集来表示.至于什么是活前缀,前面一篇文章有提到.

为了构造文法的LR(1)项目集,需要用到两个函数CLOSURE(I)和GO(I,X).CLOSURE(I)用来计算LR(1)项目集I的LR(1)闭包.以下的G'均为G的拓广文法

CLOSURE(I)的算法描述如下:

 1 function CLOSURE(I);
 2 begin
 3     C := I;
 4     repeat
 5         for [A->α.Bβ,a] in C do
 6             for B->η in G' && b in FIRST(βa) do
 7                 if [B->.η,b] not in C then 
 8                     C := C+{[B->.η,b]};
 9     until C.length no change;
10     return C;
11 end;

下面给出Javascript的具体算法实现(非递归实现):

 1 /*
 2  *    闭包函数
 3  *    是非递归实现
 4  * @param array I 传递的需要求闭包的项目
 5  * @param array C 记录产生的闭包集合
 6  * @return array C    最终的闭包集合
 7  *
 8  * */
 9 function closure(I) {
10     //初始化闭包
11     var C = I || [];    
12     /*记录闭包中的项目数*/
13     var len = C.length;
14     while(1){
15         for(var item in C) {
16             var str = C[item].slice(C[item].indexOf('.')+1,C[item].indexOf('.')+2);
17             /*满足这种产生式:A->a.Bp,a*/
18             if(str.length && str != ','){
19                 /*'.'后面是终结符则停止*/
20                 if(is_inArray(str,T))
21                     continue;
22                 var first_arr = C[item].slice(C[item].indexOf('.')+2,C[item].length).replace(/,/g,'');
23                 var first = getFirstAll(first_arr);
24                 /*遍历拓广文法G'中产生式的左部*/
25                 for(var i in pro){
26                     /*找到以B开始的项目*/
27                     if(str == i){
28                         /*遍历出以B开始的产生式,并把他们加'.'以后加入闭包中*/
29                         for(var j in pro[i]){
30                             /*还得对当前产生式的FIRST集合遍历一次*/
31                             for(var n in first){
32                                 var yeta = i + '->.' + pro[i][j] + ',' + first[n];
33                                 /*循环处理C中的每项,去重.直到C的大小不再改变*/
34                                 if(!is_inArray(yeta,C))
35                                     C.push(yeta);
36                             }
37                         }
38                     }
39                 }
40             }
41         }
42         /*大小不再改变则停止寻找闭包*/
43         if(C.length > len){
44             len = C.length;
45         }else{
46             break;
47         }
48     }
49     return C;
50 }

从算法描述里面我看见了,就是需要求解FIRST(βa),所以得自己实现一个FIRST集的算法(我这里的文法不需要手动消除左递归的情况):

 1 /*
 2  *    求FIRST集合
 3  *    若出现以下情况(存在左递归):
 4  *        T->T*F
 5  *        T->T/F会产生死循环
 6  *    则程序自动处理,不需要手动消除.
 7  *    @param    array    pro_G    存储自己的文法.如:S->0S1等
 8  *    @param    array    V        所有的非终结符集合
 9  *    @param    array    T        所有的终结符集合
10  *    @return array    first    返回first集合
11  *
12  * */
13 function getFirstByOne(value) {
14     var first = [];    
15     if(is_inArray(value,T) || value == '#')
16         first.push(value);
17     if(is_inArray(value,V)){
18         //找出所有的X->a/X->Y型产生式
19         var all_x = [];
20         for(var item in pro_G){
21             //产生式的右部
22             if(pro_G[item][0] == value){
23                 //右侧是终结符并且没有加入first集合的情况下
24                 if(is_inArray(pro_G[item][3],T) && !is_inArray(pro_G[item][3],first))
25                     first.push(pro_G[item][3]);
26                 //右侧第一个是非终结符
27                 /*像这种T->T/F的产生式会发生死递归.
28                   能想到的有两种方法能解决:
29                         1.循环的过程中,像遍历二叉树一样弄一个hash表记录是否被访问过
30                         2.强制规定不能有这种类型的产生式出现,若出现则忽略其FIRST集合
31                   本实验我采取第二种方法,牺牲精确度,提高效率.
32                 */
33                 else if(is_inArray(pro_G[item][3],V) && pro_G[item][3] != pro_G[item][0]){
34                     var all_v = getFirstByOne(pro_G[item][3]);
35                     if(!is_inArray(all_v,first))
36                         for(var j in all_v)
37                             first.push(all_v[j]);
38                 }
39             }
40         }
41     }
42     return first;
43 }
44 
45 /*符号串的FIRST集合*/
46 function getFirstAll(str){
47     var first = [];
48     /*for(var i=0; i<str.length; i++){
49         var _val = getFirstByOne(str[i]);
50         for(var j in _val)
51             if(!is_inArray(_val[j],first))
52                 first.push(_val[j]);
53         if(is_inArray(str[0],T))
54             break;
55     }*/
56    //感觉这里只能这样写,不知是FIRST集求错还是怎么.循环会有更多的FIRST集合
57     var _val = getFirstByOne(str[0]);
58     for(var j in _val)
59         if(!is_inArray(_val[j],first))
60             first.push(_val[j]);
61     return first;
62 }

 

到此LR(1)分析法的CLOSURE(I)函数就实现,下一节将讲解GO(I,X)函数.

posted @ 2013-04-13 23:23  Poised_flw  阅读(1564)  评论(0编辑  收藏  举报