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

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

任意符号α的FIRST集求法:
. α为终结符,则把它自身加入FIRSRT(α)
. α为非终结符,则:
()若存在产生式α->a...,则把a加入FIRST(α),其中a可以为ε
()若存在一串非终结符Y1,Y2, ..., Yk-,且它们的FIRST集都含空串,且有产生式α->Y1Y2...Yk...,那么把FIRST(Yk)-{ε}加入FIRST(α)。如果k-1抵达产生式末尾,那么把ε加入FIRST(α)
   注意(2)要连续进行,通俗地描述就是:沿途的Yi都能推出空串,则把这一路遇到的Yi的FIRST集都加进来,直到遇到第一个不能推出空串的Yk为止。
重复1,2步骤直至每个FIRST集都不再增大为止。
任意非终结符A的FOLLOW集求法:
. A为开始符号,则把#加入FOLLOW(A)
. 对于产生式A-->αBβ:
  (1)把FIRST(β)-{ε}加到FOLLOW(B)
  (2)若β为ε或者ε属于FIRST(β),则把FOLLOW(A)加到FOLLOW(B)
重复1,2步骤直至每个FOLLOW集都不再增大为止。

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

数据元素的定义:

 const int MAX_N = ;//产生式体的最大长度
const char nullStr = '$';//空串的字面值
typedef int Type;//符号类型 const Type NON = -;//非法类型
const Type T = ;//终结符
const Type N = ;//非终结符
const Type NUL = ;//空串 struct Production//产生式
{
char head;
char* body;
Production(){}
Production(char h, char b[]){
head = h;
body = (char*)malloc(strlen(b)*sizeof(char));
strcpy(body, b);
}
bool operator<(const Production& p)const{//内部const则外部也为const
if(head == p.head) return body[] < p.body[];//注意此处只适用于LL(1)文法,即同一VN各候选的首符不能有相同的,否则这里的小于符号还要向前多看几个字符,就不是LL(1)文法了
return head < p.head;
}
void print() const{//要加const
printf("%c -- > %s\n", head, body);
}
}; //以下几个集合可以再封装为一个大结构体--文法
set<Production> P;//产生式集
set<char> VN, VT;//非终结符号集,终结符号集
char S;//开始符号
map<char, set<char> > FIRST;//FIRST集
map<char, set<char> > FOLLOW;//FOLLOW集 set<char>::iterator first;//全局共享的迭代器,其实觉得应该用局部变量
set<char>::iterator follow;
set<char>::iterator vn;
set<char>::iterator vt;
set<Production>::iterator p; Type get_type(char alpha){//判读符号类型
if(alpha == '$') return NUL;//空串
else if(VT.find(alpha) != VT.end()) return T;//终结符
else if(VN.find(alpha) != VN.end()) return N;//非终结符
else return NON;//非法字符
}

主函数的流程很简单,从文件读入指定格式的文法,然后依次求文法的FIRST集、FOLLOW集

 int main()
{
FREAD("grammar2.txt");//从文件读取文法
int numN = ;
int numT = ;
char c = ' ';
S = getchar();//开始符号
printf("%c", S);
VN.insert(S);
numN++;
while((c=getchar()) != '\n'){//读入非终结符
printf("%c", c);
VN.insert(c);
numN++;
}
pn();
while((c=getchar()) != '\n'){//读入终结符
printf("%c", c);
VT.insert(c);
numT++;
}
pn();
REP(numN){//读入产生式
c = getchar();
int n; RINT(n);
while(n--){
char body[MAX_N];
scanf("%s", body);
printf("%c --> %s\n", c, body);
P.insert(Production(c, body));
}
getchar();
} get_first();//生成FIRST集
for(vn = VN.begin(); vn != VN.end(); vn++){//打印非终结符的FIRST集
printf("FIRST(%c) = { ", *vn);
for(first = FIRST[*vn].begin(); first != FIRST[*vn].end(); first++){
printf("%c, ", *first);
}
printf("}\n");
} get_follow();//生成非终结符的FOLLOW集
for(vn = VN.begin(); vn != VN.end(); vn++){//打印非终结符的FOLLOW集
printf("FOLLOW(%c) = { ", *vn);
for(follow = FOLLOW[*vn].begin(); follow != FOLLOW[*vn].end(); follow++){
printf("%c, ", *follow);
}
printf("}\n");
}
return ;
}

主函数

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

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

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

剩余行:每行描述了一个非终结符的所有产生式,第一个字符为产生式头(非终结符),后跟一个整数位候选式的个数n,之后是n个以空格分隔的字符串为产生式体。

示例文件如下:(注:非终结符本应都用大写字母,原题用的是加上标的方法,如E′,但我用char型存每个符号,所以用的是相应的小写字母,如e)

 EeTtFfP
+*()^ab
E Te
e +E $
T Ft
t T $
F Pf
f *f $
P (E) ^ a b

求FIRST集的部分:

 void get_first(){//生成FIRST集
for(vt = VT.begin(); vt != VT.end(); vt++)
FIRST[*vt].insert(*vt);//终结符的FIRST集包含它自身
FIRST[nullStr].insert(nullStr);//空串的FIRST集包含它自身
bool flag = true;
while(flag){//上一轮迭代中集合有扩大
flag = false;
for(vn = VN.begin(); vn != VN.end(); vn++){//对于每个非终结符
for(p = P.begin(); p != P.end(); p++){
//(*p).print();
if(p->head == *vn){//找所有左部为A的产生式
int before = FIRST[*vn].size();
put_body(*vn, &(p->body)[]);
if(FIRST[*vn].size() > before)//集合有扩大
flag = true;
//printf("%c size %d -> %d\n", *vn, before, FIRST[*vn].size());
}
}
}
}
}

与FIRST集相关的几个辅助函数:

 void put_first_first(char A, char B){//把FIRST[B]-{$}加到FIRST[A]
first = FIRST[B].begin();
for(; first != FIRST[B].end(); first++){
if(*first != nullStr)
FIRST[A].insert(*first);
}
}

put_first_first

 void put_body(char A, char* pb){//用产生式体从pb开始往后的部分扩充A的FIRST集
if(*pb == '\0'){//抵达产生式体的末尾
FIRST[A].insert(nullStr);//向FIRST(A)加入空串
return ;
}
switch(get_type(*pb)){
case ://pb[0]为非终结符,把pb[0]的FIRST集加到A的FIRST集
put_first_first(A, *pb);
if(FIRST[*pb].find(nullStr) != FIRST[*pb].end())
put_body(A, pb+);
break;
case ://pb[0]位终结符,把pb[0]加到A的FIRST集
FIRST[A].insert(*pb);
break;
case : //pb[0]为空,把空串加入A的FIRST集
FIRST[A].insert(nullStr);
break;
default: return ;
}
}

put_body

求FOLLOW集的部分

 void get_follow(){//生成FOLLOW集
FOLLOW[S].insert('#');//结束符放入文法开始符号的FOLLOW集
bool flag = true;
while(flag){
flag = false;
for(vn = VN.begin(); vn != VN.end(); vn++){//对于每个非终结符
for(p = P.begin(); p != P.end(); p++){
//(*p).print();
char A = p->head;
int i;
for(i=; (p->body)[i+] != '\0'; i++){
char B = (p->body)[i];
char beta = (p->body)[i+];
int before = FOLLOW[B].size();
if(get_type(B) == N){//跟在B后面的可以扩充B的FOLLOW集
put_follow_first(B, beta);
if(get_type(beta) == NUL)//beta为空串
put_follow_follow(B, A);
else if(FIRST[beta].find(nullStr) != FIRST[beta].end())
put_follow_follow(B, A);
if(FOLLOW[B].size() > before) flag = true;
//printf("%c size %d -> %d\n", B, before, FOLLOW[B].size());
}
}
put_follow_follow((p->body)[i], A);
}
}
}
}

与FOLLOW集相关的几个辅助函数:

 void put_follow_first(char B, char beta){//把FIRST[beta]加到FOLLOW[B]
first = FIRST[beta].begin();
for(; first != FIRST[beta].end(); first++){
if(*first != nullStr)
FOLLOW[B].insert(*first);
}
}

put_follow_first

 void put_follow_follow(char B, char A){//把FOLLOW[A]加到FOLLOW[B]
follow = FOLLOW[A].begin();
for(; follow != FOLLOW[A].end(); follow++){
FOLLOW[B].insert(*follow);
}
}

put_follow_follow

运行结果(请忽略集合最后一个元素后的逗号。。。):

注:

1. 语法分析的每个终结符号实际上代表一个单词,是从词法分析器获取的,这里为了简化问题所以只用了一个char型表示;而每个非终结符号则是一个语法单元,这里同样用char型表示了;

2. 感觉我的实现稍显复杂,C++的集合操作不太会用(没有找到原生的类似.addAll这样的方法,所以是自己用迭代器一个个加的),考完试用其他语言实现一个更简洁的。

3. 这样的算法用程序实现并不复杂,但是它规则比较多,且退出的条件是“集合不再增大”,手算起来一轮一轮的容易乱。祝我期末好运吧。

【编译原理】语法分析LL(1)分析法的FIRST和FOLLOW集的更多相关文章

  1. 编译原理实验之SLR1文法分析

    ---内容开始--- 这是一份编译原理实验报告,分析表是手动造的,可以作为借鉴. 基于  SLR(1) 分析法的语法制导翻译及中间代码生成程序设计原理与实现1 .理论传授语法制导的基本概念,目标代码结 ...

  2. 编译原理学习笔记·语法分析(LL(1)分析法/算符优先分析法OPG)及例子详解

    语法分析(自顶向下/自底向上) 自顶向下 递归下降分析法 这种带回溯的自顶向下的分析方法实际上是一种穷举的不断试探的过程,分析效率极低,在实际的编译程序中极少使用. LL(1)分析法 又称预测分析法, ...

  3. 编译原理实习(应用预测分析法LL(1)实现语法分析)

    #include<iostream> #include<fstream> #include<iomanip> #include<cstdio> #inc ...

  4. 编译原理(六)自底向上分析之LR分析法

    自底向上分析之LR分析法 说明:以老师PPT为标准,借鉴部分教材内容,AlvinZH学习笔记. 基本概念 1. LR分析:从左到右扫描(L)自底向上进行规约(R),是规范规约,也即最右推导(规范推导) ...

  5. 《编译原理》LR 分析法与构造 LR(1) 分析表的步骤 - 例题解析

    <编译原理>LR 分析法与构造 LR(1) 分析表的步骤 - 例题解析 笔记 直接做题是有一些特定步骤,有技巧.但也必须先了解一些基本概念,本篇会通过例题形式解释概念,会容易理解和记忆,以 ...

  6. 跟vczh看实例学编译原理——三:Tinymoe与无歧义语法分析

    文章中引用的代码均来自https://github.com/vczh/tinymoe.   看了前面的三篇文章,大家应该基本对Tinymoe的代码有一个初步的感觉了.在正确分析"print ...

  7. 《编译原理》-用例题理解-自顶向下语法分析及 FIRST,FOLLOW,SELECT集,LL(1)文法

    <编译原理>-用例题理解-自顶向下语法分析及 FIRST,FOLLOW,SELECT集,LL(1)文法 此编译原理确定某高级程序设计语言编译原理,理论基础,学习笔记 本笔记是对教材< ...

  8. 《编译原理》-用例题理解-自底向上的语法分析,FIRSTVT,LASTVT集

    <编译原理>-用例题理解-自底向上的语法分析,FIRSTVT,LASTVT集 上一篇:编译原理-用例题理解-自顶向下语法分析及 FIRST,FOLLOW,SELECT集,LL(1)文法 本 ...

  9. TINY语言采用递归下降分析法编写语法分析程序

    目录 自顶向下分析方法 TINY文法 消左提左.构造first follow 基本思想 python构造源码 运行结果 参考来源:聊聊编译原理(二) - 语法分析 自顶向下分析方法 自顶向下分析方法: ...

随机推荐

  1. Codeforce 217 div2

    C 假设每种颜色的个数都相同,可以用轮换的方式,让答案达到最大n,当不同的时候,可以每次从每种颜色中取出相同个数的手套来操作; 一直迭代下去直到只剩下1种颜色; 再将这一种颜色与之前交换过的交换就行了 ...

  2. oracle数据库 ORA-12560: 协议适配器错误

    ORA-12560:  协议适配器错误 造成ORA-12560: TNS: 协议适配器错误的问题的原因有三个: 1.监听服务没有起起来.windows平台个一如下操作:开始---程序---管理工具-- ...

  3. cumber + selenium +java自动化测试

    1.新建一个maven项目,pom文件为: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=&quo ...

  4. 【转载自i春秋】图片马合成方法

    1.将图片和一句话木马放在同一个文件夹 2.创建快捷方式,将起始位置修改为图片和txt文本的路径. 3.进行合成,命令如下 copy .png /b + .txt /a .png 4.成功!自行测试. ...

  5. 改进的newlisp编译脚本,只需要配置

    前面有一篇Say bye to CMake and Makefile我开始用自己编写的newlisp脚本替代CMake,今天对前面的进行改进. 改进部分是: 1. newlisp armory模块的引 ...

  6. [Cycle.js] Generalizing run() function for more types of sources

    Our application was able to produce write effects, through sinks, and was able to receive read effec ...

  7. Android 编程下模拟 HOME 键效果

    Android 应用开发中,有一种场景,就是我们不希望用户按 Back 键直接退出 Activity,而是希望应用隐藏到后台,类似于按 Home 键的效果.参考如下代码即可实现这样的效果: publi ...

  8. ORacle 复制表

    create table r_register_company as select companyid,companyname,from grdata.r_register_company inser ...

  9. Android-图标

    首先需要申明一点,系统图标并不存在于项目资源中,而是存在于设备中. 这就带来一个大问题,界面风格的控制权交到了不同的设备手中.这是我们不愿意看到的. 如何解决这个问题?有两种方法: 1.创建自己的图标 ...

  10. POJ 1743 - Musical Theme 最长不重叠重复子串

    题意:    给出一列数据,问你其中重复的最长连续子串的长度    但是有要求:        1. 长度至少为 5 .        2. 两串可以不相等,但两串每个对应位置的数字相减差值固定 (即 ...