编译原理----手写编译器(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)函数.