近來複習編譯原理,語法分析中的自上而下LL(1)分析法,須要構造求出一個文法的FIRST和FOLLOW集,而後構造分析表,利用分析表+一個棧來作自上而下的語法分析(遞歸降低/預測分析),但是這個FIRST集合FOLLOW集看得我頭大。。。算法
教課書上的規則以下,用我理解的語言描述的:ide
任意符號α的FIRST集求法: 1. α爲終結符,則把它自身加入FIRSRT(α) 2. α爲非終結符,則: (1)若存在產生式α->a...,則把a加入FIRST(α),其中a能夠爲ε (2)若存在一串非終結符Y1,Y2, ..., Yk-1,且它們的FIRST集都含空串,且有產生式α->Y1Y2...Yk...,那麼把FIRST(Yk)-{ε}加入FIRST(α)。若是k-1抵達產生式末尾,那麼把ε加入FIRST(α)
注意(2)要連續進行,通俗地描述就是:沿途的Yi都能推出空串,則把這一路遇到的Yi的FIRST集都加進來,直到遇到第一個不能推出空串的Yk爲止。
重複1,2步驟直至每一個FIRST集都再也不增大爲止。
任意非終結符A的FOLLOW集求法: 1. A爲開始符號,則把#加入FOLLOW(A) 2. 對於產生式A-->αBβ:
(1)把FIRST(β)-{ε}加到FOLLOW(B)
(2)若β爲ε或者ε屬於FIRST(β),則把FOLLOW(A)加到FOLLOW(B)
重複1,2步驟直至每一個FOLLOW集都再也不增大爲止。
老師和同窗能很敏銳地求出來,而我只能按照規則,像程序同樣一條條執行。因而我把這個過程寫成了程序,以下:函數
數據元素的定義:spa
1 const int MAX_N = 20;//產生式體的最大長度 2 const char nullStr = '$';//空串的字面值 3 typedef int Type;//符號類型 4 5 const Type NON = -1;//非法類型 6 const Type T = 0;//終結符 7 const Type N = 1;//非終結符 8 const Type NUL = 2;//空串 9 10 struct Production//產生式 11 { 12 char head; 13 char* body; 14 Production(){} 15 Production(char h, char b[]){ 16 head = h; 17 body = (char*)malloc(strlen(b)*sizeof(char)); 18 strcpy(body, b); 19 } 20 bool operator<(const Production& p)const{//內部const則外部也爲const 21 if(head == p.head) return body[0] < p.body[0];//注意此處只適用於LL(1)文法,即同一VN各候選的首符不能有相同的,不然這裏的小於符號還要向前多看幾個字符,就不是LL(1)文法了 22 return head < p.head; 23 } 24 void print() const{//要加const 25 printf("%c -- > %s\n", head, body); 26 } 27 }; 28 29 //如下幾個集合能夠再封裝爲一個大結構體--文法 30 set<Production> P;//產生式集 31 set<char> VN, VT;//非終結符號集,終結符號集 32 char S;//開始符號 33 map<char, set<char> > FIRST;//FIRST集 34 map<char, set<char> > FOLLOW;//FOLLOW集 35 36 set<char>::iterator first;//全局共享的迭代器,其實以爲應該用局部變量 37 set<char>::iterator follow; 38 set<char>::iterator vn; 39 set<char>::iterator vt; 40 set<Production>::iterator p; 41 42 Type get_type(char alpha){//判讀符號類型 43 if(alpha == '$') return NUL;//空串 44 else if(VT.find(alpha) != VT.end()) return T;//終結符 45 else if(VN.find(alpha) != VN.end()) return N;//非終結符 46 else return NON;//非法字符 47 }
主函數的流程很簡單,從文件讀入指定格式的文法,而後依次求文法的FIRST集、FOLLOW集設計
1 int main() 2 { 3 FREAD("grammar2.txt");//從文件讀取文法 4 int numN = 0; 5 int numT = 0; 6 char c = ' '; 7 S = getchar();//開始符號 8 printf("%c", S); 9 VN.insert(S); 10 numN++; 11 while((c=getchar()) != '\n'){//讀入非終結符 12 printf("%c", c); 13 VN.insert(c); 14 numN++; 15 } 16 pn(); 17 while((c=getchar()) != '\n'){//讀入終結符 18 printf("%c", c); 19 VT.insert(c); 20 numT++; 21 } 22 pn(); 23 REP(numN){//讀入產生式 24 c = getchar(); 25 int n; RINT(n); 26 while(n--){ 27 char body[MAX_N]; 28 scanf("%s", body); 29 printf("%c --> %s\n", c, body); 30 P.insert(Production(c, body)); 31 } 32 getchar(); 33 } 34 35 get_first();//生成FIRST集 36 for(vn = VN.begin(); vn != VN.end(); vn++){//打印非終結符的FIRST集 37 printf("FIRST(%c) = { ", *vn); 38 for(first = FIRST[*vn].begin(); first != FIRST[*vn].end(); first++){ 39 printf("%c, ", *first); 40 } 41 printf("}\n"); 42 } 43 44 get_follow();//生成非終結符的FOLLOW集 45 for(vn = VN.begin(); vn != VN.end(); vn++){//打印非終結符的FOLLOW集 46 printf("FOLLOW(%c) = { ", *vn); 47 for(follow = FOLLOW[*vn].begin(); follow != FOLLOW[*vn].end(); follow++){ 48 printf("%c, ", *follow); 49 } 50 printf("}\n"); 51 } 52 return 0; 53 }
其中文法文件的數據格式爲(按照平時作題的輸入格式設計的):3d
第一行:全部非終結符,無空格,第一個爲開始符號;code
第二行:全部終結符,無空格;blog
剩餘行:每行描述了一個非終結符的全部產生式,第一個字符爲產生式頭(非終結符),後跟一個整數位候選式的個數n,以後是n個以空格分隔的字符串爲產生式體。遞歸
示例文件以下:(注:非終結符本應都用大寫字母,原題用的是加上標的方法,如E′,但我用char型存每一個符號,因此用的是相應的小寫字母,如e)字符串
1 EeTtFfP 2 +*()^ab 3 E 1 Te 4 e 2 +E $ 5 T 1 Ft 6 t 2 T $ 7 F 1 Pf 8 f 2 *f $ 9 P 4 (E) ^ a b
求FIRST集的部分:
1 void get_first(){//生成FIRST集 2 for(vt = VT.begin(); vt != VT.end(); vt++) 3 FIRST[*vt].insert(*vt);//終結符的FIRST集包含它自身 4 FIRST[nullStr].insert(nullStr);//空串的FIRST集包含它自身 5 bool flag = true; 6 while(flag){//上一輪迭代中集合有擴大 7 flag = false; 8 for(vn = VN.begin(); vn != VN.end(); vn++){//對於每一個非終結符 9 for(p = P.begin(); p != P.end(); p++){ 10 //(*p).print(); 11 if(p->head == *vn){//找全部左部爲A的產生式 12 int before = FIRST[*vn].size(); 13 put_body(*vn, &(p->body)[0]); 14 if(FIRST[*vn].size() > before)//集合有擴大 15 flag = true; 16 //printf("%c size %d -> %d\n", *vn, before, FIRST[*vn].size()); 17 } 18 } 19 } 20 } 21 }
與FIRST集相關的幾個輔助函數:
1 void put_first_first(char A, char B){//把FIRST[B]-{$}加到FIRST[A] 2 first = FIRST[B].begin(); 3 for(; first != FIRST[B].end(); first++){ 4 if(*first != nullStr) 5 FIRST[A].insert(*first); 6 } 7 }
1 void put_body(char A, char* pb){//用產生式體從pb開始日後的部分擴充A的FIRST集 2 if(*pb == '\0'){//抵達產生式體的末尾 3 FIRST[A].insert(nullStr);//向FIRST(A)加入空串 4 return ; 5 } 6 switch(get_type(*pb)){ 7 case 1://pb[0]爲非終結符,把pb[0]的FIRST集加到A的FIRST集 8 put_first_first(A, *pb); 9 if(FIRST[*pb].find(nullStr) != FIRST[*pb].end()) 10 put_body(A, pb+1); 11 break; 12 case 0://pb[0]位終結符,把pb[0]加到A的FIRST集 13 FIRST[A].insert(*pb); 14 break; 15 case 2: //pb[0]爲空,把空串加入A的FIRST集 16 FIRST[A].insert(nullStr); 17 break; 18 default: return ; 19 } 20 }
求FOLLOW集的部分
1 void get_follow(){//生成FOLLOW集 2 FOLLOW[S].insert('#');//結束符放入文法開始符號的FOLLOW集 3 bool flag = true; 4 while(flag){ 5 flag = false; 6 for(vn = VN.begin(); vn != VN.end(); vn++){//對於每一個非終結符 7 for(p = P.begin(); p != P.end(); p++){ 8 //(*p).print(); 9 char A = p->head; 10 int i; 11 for(i=0; (p->body)[i+1] != '\0'; i++){ 12 char B = (p->body)[i]; 13 char beta = (p->body)[i+1]; 14 int before = FOLLOW[B].size(); 15 if(get_type(B) == N){//跟在B後面的能夠擴充B的FOLLOW集 16 put_follow_first(B, beta); 17 if(get_type(beta) == NUL)//beta爲空串 18 put_follow_follow(B, A); 19 else if(FIRST[beta].find(nullStr) != FIRST[beta].end()) 20 put_follow_follow(B, A); 21 if(FOLLOW[B].size() > before) flag = true; 22 //printf("%c size %d -> %d\n", B, before, FOLLOW[B].size()); 23 } 24 } 25 put_follow_follow((p->body)[i], A); 26 } 27 } 28 } 29 }
與FOLLOW集相關的幾個輔助函數:
1 void put_follow_first(char B, char beta){//把FIRST[beta]加到FOLLOW[B] 2 first = FIRST[beta].begin(); 3 for(; first != FIRST[beta].end(); first++){ 4 if(*first != nullStr) 5 FOLLOW[B].insert(*first); 6 } 7 }
1 void put_follow_follow(char B, char A){//把FOLLOW[A]加到FOLLOW[B] 2 follow = FOLLOW[A].begin(); 3 for(; follow != FOLLOW[A].end(); follow++){ 4 FOLLOW[B].insert(*follow); 5 } 6 }
運行結果(請忽略集合最後一個元素後的逗號。。。):
注:
1. 語法分析的每一個終結符號實際上表明一個單詞,是從詞法分析器獲取的,這裏爲了簡化問題因此只用了一個char型表示;而每一個非終結符號則是一個語法單元,這裏一樣用char型表示了;
2. 感受個人實現稍顯複雜,C++的集合操做不太會用(沒有找到原生的相似.addAll這樣的方法,因此是本身用迭代器一個個加的),考完試用其餘語言實現一個更簡潔的。
3. 這樣的算法用程序實現並不複雜,可是它規則比較多,且退出的條件是「集合再也不增大」,手算起來一輪一輪的容易亂。祝我期末好運吧。