近来复习编译原理,语法分析中的自上而下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. paip.hadoop的应用研究总结

    paip.hadoop的应用研究总结 作者Attilax ,  EMAIL:1466519819@qq.com  来源:attilax的专栏 地址:http://blog.csdn.net/attil ...

  2. HDU 1027 Ignatius and the Princess II 选择序列题解

    直接选择序列的方法解本题,可是最坏时间效率是O(n*n),故此不能达到0MS. 使用删除优化,那么就能够达到0MS了. 删除优化就是当须要删除数组中的元素为第一个元素的时候,那么就直接移动数组的头指针 ...

  3. ewebeditor下利用ckplayer增加html5 (mp4)全平台的支持

    学校数字化平台富文本编辑器一直用的ewebeditor,应该说非常的好,支持常用office文档的直接导入,极大的方便了老师们资料的上传,最近在规划整个数字化校园向全平台改版,框架采用bootstra ...

  4. EClipse开发NDK流程

    EClipse开发NDK流程(现在studio也在2.2之后支持了非常简单,只要创建项目的时候勾选c++支持就可以了)   什么情况下使用ndk,1.保护代码,java很容易反编译,c/c++反汇编比 ...

  5. VC防止程序被多次运行 互斥体方法

    BOOL CXXXApp::InitInstance() //函数内添加代码 HANDLE hMutex=CreateMutex(NULL,TRUE,"test"); // 用于检 ...

  6. Mac系统cocos2dx + android 开发环境配置

    Mac系统cocos2dx + android 开发环境配置 /****************************************************** 这遍文章主要转载自:htt ...

  7. 【转】10 个迅速提升你 Git 水平的提示

    最近我们推出了两个教程:熟悉Git的基本功能和让你在开发团队中熟练的使用Git . 我们所讨论的命令足够一个开发者在Git使用方面游刃有余.在这篇文章中,我们试图探索怎样有效的管理你的时间和充分的使用 ...

  8. 解决Sublime-Text-3在ubuntu下中文输入的问题

    在ubuntu下使用ST这神器已经一段日子了,但是一直有个纠结的问题,就是中文输入非常坑爹,曾经一段时间,使用inputHelper这个插件来解决, 但是……每次都要按个快捷键,弹出一个小小小框来输入 ...

  9. Remove掉Request.QueryString

    好久上博客来了,最近有点忙,有点懒. 今天在解决一个Request.QueryString 传值的问题上遇到了,当不是第一次加载时需要把Request.QueryString的值赋值为null,刚开始 ...

  10. 浅谈Hive vs HBase

     Hive是什么? Apache Hive是一个构建于Hadoop(分布式系统基础架构)顶层的数据仓库,注意这里不是数据库.Hive可以看作是用户编程接口,它本身不存储和计算数据:它依赖于HDFS(H ...