【編譯原理】語法分析LL(1)分析法的FIRST和FOLLOW集

  近來複習編譯原理,語法分析中的自上而下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 }
put_first_first
 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 }
put_body

 

求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 }
put_follow_first
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 }
put_follow_follow

運行結果(請忽略集合最後一個元素後的逗號。。。):

注:

1. 語法分析的每一個終結符號實際上表明一個單詞,是從詞法分析器獲取的,這裏爲了簡化問題因此只用了一個char型表示;而每一個非終結符號則是一個語法單元,這裏一樣用char型表示了;

2. 感受個人實現稍顯複雜,C++的集合操做不太會用(沒有找到原生的相似.addAll這樣的方法,因此是本身用迭代器一個個加的),考完試用其餘語言實現一個更簡潔的。

3. 這樣的算法用程序實現並不複雜,可是它規則比較多,且退出的條件是「集合再也不增大」,手算起來一輪一輪的容易亂。祝我期末好運吧。

相關文章
相關標籤/搜索