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

时间:2022-06-08 02:27:15

  近来复习编译原理,语法分析中的自上而下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

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

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

注:

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

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

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

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

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

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

  2. 编译原理学习笔记&CenterDot;语法分析(LL&lpar;1&rpar;分析法&sol;算符优先分析法OPG)及例子详解

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

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

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

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

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

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

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

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

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

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

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

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

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

  9. 【编译原理】c&plus;&plus;实现自下而上语法分析及中间代码&lpar;四元式&rpar;生成

    写在前面:本博客为本人原创,严禁任何形式的转载!本博客只允许放在博客园(.cnblogs.com),如果您在其他网站看到这篇博文,请通过下面这个唯一的合法链接转到原文! 本博客全网唯一合法URL:ht ...

随机推荐

  1. Myeclipse怎么连接MySQL数据库?

    1.打开  >>  Myeclipse 2.Window  >>  Open Perspective  >>  Myeclipse Database Explore ...

  2. 从客户端&lpar;Content&equals;&quot&semi;&lt&semi;EM &gt&semi;&lt&semi;STRONG &gt&semi;&lt&semi;U &gt&semi;这是测试这&period;&period;&period;&quot&semi;&rpar;中检测到有潜在危险的Request&period;Form 值。

    说明: 请求验证过程检测到有潜在危险的客户端输入值,对请求的处理已经中止.该值可能指示存在危及应用程序安全的尝试,如跨站点脚本攻击.若要允许页面重写应用程序请求验证设置,请将 httpRuntime  ...

  3. 服务器添加ipa MIME 类型,防止ipa下载后变zip后缀

    客户反映apk文件下载 后缀会变为zip   打开mime.types文件   application/iphone pxl ipa application/vnd.android.package-a ...

  4. How to take partial screenshot with Selenium WebDriver in python

    from selenium import webdriver from PIL import Image fox = webdriver.Firefox() fox.get('http://stack ...

  5. java 反射机制探究

    一 反射机制操作类的成员变量 二 操作类的方法 三 利用反射实例化类 四 利用反射访问一个类的私有成员  五 利用反射覆盖数据对象的toString 方法

  6. Ubuntu 中查看内核版本和系统版本的三个命令

    一.查看内核版本:cat /proc/version 二.查看内核版本:uname -a 三.查看系统版本:lsb_release -a 四.查看发行版类型:cat /etc/issue

  7. dbus-glib 和 GDBus 的区别

    http://people.freedesktop.org/~david/gio-gdbus-codegen-20110412/ch29.html Conceptual differences(概念上 ...

  8. 伪类实现特殊图形,一个span加三角形

    题目如图: 实现思路: 伪类+三边透明的三角形实现 代码: <span class="wei">wei</span> .wei{ display: inli ...

  9. Device does not seem to be present &lbrack;常见错误解决&rsqb;

    一.故障现象: [root@c1node01 ~]# service network restart Shutting down loopback insterface:                ...

  10. &period;NetCore中EFCore的使用整理

    EntirtyFramework框架是一个轻量级的可扩展版本的流行实体框架数据访问技术. 其中的.NetCore版本对应EntityFrameworkCore Git源代码地址:https://git ...