【编译原理】语法分析LL(1)分析法的FIRST和FOLLOW集

简介:   近来复习编译原理,语法分析中的自上而下LL(1)分析法,需要构造求出一个文法的FIRST和FOLLOW集,然后构造分析表,利用分析表+一个栈来做自上而下的语法分析(递归下降/预测分析),可是这个FIRST集合FOLLOW集看得我头大。

  近来复习编译原理,语法分析中的自上而下LL(1)分析法,需要构造求出一个文法的FIRST和FOLLOW集,然后构造分析表,利用分析表+一个栈来做自上而下的语法分析(递归下降/预测分析),可是这个FIRST集合FOLLOW集看得我头大。。。

  教课书上的规则如下,用我理解的语言描述的:

任意符号α的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集都不再增大为止。

老师和同学能很敏锐地求出来,而我只能按照规则,像程序一样一条条执行。于是我把这个过程写成了程序,如下:

数据元素的定义:

 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 }
主函数

其中文法文件的数据格式为(按照平时做题的输入格式设计的):

第一行:所有非终结符,无空格,第一个为开始符号;

第二行:所有终结符,无空格;

剩余行:每行描述了一个非终结符的所有产生式,第一个字符为产生式头(非终结符),后跟一个整数位候选式的个数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. 这样的算法用程序实现并不复杂,但是它规则比较多,且退出的条件是“集合不再增大”,手算起来一轮一轮的容易乱。祝我期末好运吧。

目录
相关文章
【编译原理】语法分析:从顶向下、最左推导
【编译原理】语法分析:从顶向下、最左推导
|
8月前
|
存储 编译器 测试技术
【编译原理】LL(1)分析法:C/C++实现
【编译原理】LL(1)分析法:C/C++实现
831 0
|
8月前
编译原理复习六:依赖图、注释语法树上节点的求值讲解(附题目与答案 超详细)
编译原理复习六:依赖图、注释语法树上节点的求值讲解(附题目与答案 超详细)
406 0
|
C++
C++ Primer Plus 第六章答案 分支语句和逻辑运算符
只有聪明人才能看见的摘要~( ̄▽ ̄~)~
80 0
|
C++
C++ Primer Plus 第五章答案 循环和关系表达式
只有聪明人才能看见的摘要~( ̄▽ ̄~)~
71 0
|
存储 自然语言处理 Unix
编译原理 实验一:词法分析器的自动实现(Lex词法分析)
编译原理 实验一:词法分析器的自动实现(Lex词法分析)
1127 0
编译原理 实验一:词法分析器的自动实现(Lex词法分析)
|
存储 Go C语言
编译原理,C语言实现LR(0)分析(扩展文法的生成、项目集规范簇的生成、ACTION GOTO表的生成、句子的分析)
注:代码生成的项目集规范簇、ACTION GOTO表的顺序可能和课本、教材、参考答案的顺序不同,但这并不影响分析过程的正确性,毕竟机器是按规律办事的😄
595 0
编译原理,C语言实现LR(0)分析(扩展文法的生成、项目集规范簇的生成、ACTION GOTO表的生成、句子的分析)
|
存储 自然语言处理 算法
语法设计——基于LL(1)文法的预测分析表法
语法设计——基于LL(1)文法的预测分析表法
313 0
语法设计——基于LL(1)文法的预测分析表法
|
5G
[NCPC2021] Antenna Analysis | 思维递推
题目描述 Åke has heard that there may be some suspicious 5G radiation in his city. To test this, he uses the antenna on his roof to measure the 5G level each day. However, he does not know how he should analyze the data.
264 0