编译原理JAVA求First集Follow集

合集下载

转First集合和Follow集合的求法(修改含例子)

转First集合和Follow集合的求法(修改含例子)

转First集合和Follow集合的求法(修改含例⼦)对于终结符和⾮终结符的理解:终结符:通俗的说就是不能单独出现在推导式左边的符号,也就是说终结符不能再进⾏推导。

⾮终结符:不是终结符的都是⾮终结符。

如:A->B,则A是⾮终结符;A->id,则id是终结符。

(⼀般书上终结符⽤⼩写,⾮终结符⽤⼤写。

)⽂法产⽣语⾔句⼦的基本思想:从识别符号(开始符)开始,把当前产⽣的符号串中的⾮终结符替换为相应规则右部的符号串,直到全部由终结符组成。

所以⽂法产⽣句⼦的基本思想就是基于产⽣式(例如A->num)的替换,当所有的⾮终结符都被终结符替换时,推导结束。

FIRST集求法:我对First集的理解:first集应该就是求⼀个表⽰⽂法的字串(⼀般指⾮终结符,终结符的first集就是它⾃⾝)开头的所有可能出现的字符的集合。

例如A->aC | bB | cD,根据这个产⽣式,就可以知道,⾮终结符A,被替换后,它开头可能出现字符有a、b 、c, 所以 {a,b,c}是First(A)的⼀个⼦集。

求First集的步骤:1. 若X->a..,则将终结符a加⼊FIRST(X)中;(注意⾮终结符的情况)2. 若X->e ,则将终结符e加⼊FIRST(X)中(e表⽰空集);3. 若 X->BC..D,则将First(B)所有元素(除了空集)加⼊First(A),然后检测First(B),若First(B)中不存在空集, 即e,则停⽌,若存在则向B的后⾯查看,将First(C)中所有元素(除了空集)加⼊First(A),然后再检测First(C)中是否有e...直到最后,若D之前的所有⾮终结符的First集中都含有e,则检测到D时,将First(D)也加⼊First(A),若First(D)中含有e,则将 e加⼊First(A)。

对于第三条,其实也很好理解,就是说当X推导出⼀个字串时,D前⾯的⾮终结符都可能推出空串,这个时候,X推出的串的⾸部,就不是那些推出空串的⾮终结符了,⽽是这些推出空串的⾮终结符后⾯的⽂法符号所推导出的字串。

编译原理(求First集)

编译原理(求First集)

4、运行结果分析 5、总结
2、分析、设计、实现 通过对产生式的顺序扫描,运用上述规则,把每种规则都转换为相应的算法,经过规则后 将产生的 First 集存下来。 流程图 3、函数与过程的功能(MFC) CString CFirstAndFollow::First(char Vn,char PVn) { CString result,tmpresult; char tmpmid; int tmpflag; if(Vns.Find(Vn,0)==-1&&Vn!=';'&&Vn!='|') { result.Insert(result.GetLength(),Vn); return result; } for(int ix=0;ix<Vpro.GetLength();ix++) { ix=Vpro.Find(Vn,ix); if(Vpro.GetAt(ix-1)==';') { tmpflag=ix; break; } } tmpflag+=3; for(ቤተ መጻሕፍቲ ባይዱnt index=tmpflag;index<Vpro.GetLength();index++) { char tmp=Vpro.GetAt(index); tmpmid=tmp; if(Vns.Find(tmp,0)>=0) // Vn { if(tmp==PVn || tmp==Vn) { do
1、问题描述
题目: First 集和 Follow 集生成算法模拟 设计一个由正规文法生成 First 集和 Follow 集并进行简化的算法动态模拟。 动态模拟算法的基本功能是: ⅰ. 输入一个文法 G; ⅱ. 输出由文法 G 构造 FIRST 集的算法; ⅲ. 输出 First 集; ⅳ. 输出由文法 G 构造 FOLLOW 集的算法; ⅴ. 输出 FOLLOW 集。

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

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

【编译原理】语法分析LL(1)分析法的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集都不再增⼤为⽌。

⽼师和同学能很敏锐地求出来,⽽我只能按照规则,像程序⼀样⼀条条执⾏。

于是我把这个过程写成了程序,如下:数据元素的定义:1const int MAX_N = 20;//产⽣式体的最⼤长度2const char nullStr = '$';//空串的字⾯值3 typedef int Type;//符号类型45const Type NON = -1;//⾮法类型6const Type T = 0;//终结符7const Type N = 1;//⾮终结符8const Type NUL = 2;//空串910struct Production//产⽣式11 {12char head;13char* 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 }20bool operator<(const Production& p)const{//内部const则外部也为const21if(head == p.head) return body[0] < p.body[0];//注意此处只适⽤于LL(1)⽂法,即同⼀VN各候选的⾸符不能有相同的,否则这⾥的⼩于符号还要向前多看⼏个字符,就不是LL(1)⽂法了22return head < p.head;23 }24void print() const{//要加const25 printf("%c -- > %s\n", head, body);26 }27 };2829//以下⼏个集合可以再封装为⼀个⼤结构体--⽂法30set<Production> P;//产⽣式集31set<char> VN, VT;//⾮终结符号集,终结符号集32char S;//开始符号33 map<char, set<char> > FIRST;//FIRST集34 map<char, set<char> > FOLLOW;//FOLLOW集3536set<char>::iterator first;//全局共享的迭代器,其实觉得应该⽤局部变量37set<char>::iterator follow;38set<char>::iterator vn;39set<char>::iterator vt;40set<Production>::iterator p;4142 Type get_type(char alpha){//判读符号类型43if(alpha == '$') return NUL;//空串44else if(VT.find(alpha) != VT.end()) return T;//终结符45else if(VN.find(alpha) != VN.end()) return N;//⾮终结符46else return NON;//⾮法字符47 }主函数的流程很简单,从⽂件读⼊指定格式的⽂法,然后依次求⽂法的FIRST集、FOLLOW集1int main()2 {3 FREAD("grammar2.txt");//从⽂件读取⽂法4int numN = 0;5int numT = 0;6char c = '';7 S = getchar();//开始符号8 printf("%c", S);9 VN.insert(S);10 numN++;11while((c=getchar()) != '\n'){//读⼊⾮终结符12 printf("%c", c);13 VN.insert(c);14 numN++;15 }16 pn();17while((c=getchar()) != '\n'){//读⼊终结符18 printf("%c", c);19 VT.insert(c);20 numT++;21 }22 pn();23 REP(numN){//读⼊产⽣式24 c = getchar();25int n; RINT(n);26while(n--){27char body[MAX_N];28 scanf("%s", body);29 printf("%c --> %s\n", c, body);30 P.insert(Production(c, body));31 }32 getchar();33 }3435 get_first();//⽣成FIRST集36for(vn = VN.begin(); vn != VN.end(); vn++){//打印⾮终结符的FIRST集37 printf("FIRST(%c) = { ", *vn);38for(first = FIRST[*vn].begin(); first != FIRST[*vn].end(); first++){39 printf("%c, ", *first);40 }41 printf("}\n");42 }4344 get_follow();//⽣成⾮终结符的FOLLOW集45for(vn = VN.begin(); vn != VN.end(); vn++){//打印⾮终结符的FOLLOW集46 printf("FOLLOW(%c) = { ", *vn);47for(follow = FOLLOW[*vn].begin(); follow != FOLLOW[*vn].end(); follow++){48 printf("%c, ", *follow);49 }50 printf("}\n");51 }52return0;53 }主函数其中⽂法⽂件的数据格式为(按照平时做题的输⼊格式设计的):第⼀⾏:所有⾮终结符,⽆空格,第⼀个为开始符号;第⼆⾏:所有终结符,⽆空格;剩余⾏:每⾏描述了⼀个⾮终结符的所有产⽣式,第⼀个字符为产⽣式头(⾮终结符),后跟⼀个整数位候选式的个数n,之后是n个以空格分隔的字符串为产⽣式体。

构造FIRST集和FOLLOW集的方法

构造FIRST集和FOLLOW集的方法
构造 FIRST 集和 FOLLOW 集的方法
1、构造 FIRST 集的算法 (1) 对于 G 中的每个文法符号 X,为求 FIRST(X),反复应用如下规则,直到集合不再增大: ① 若 X∈VT,则 FIRST(X)是{X} ② 若 X∈VN ,且 X→aα (a∈VT ),则{ a } FIRST(X) X→ε, 则{ε} FIRST(X) ③ 若 X->Y1Y2 … Yi-1 Yi … YK∈P,Y1∈VN ,则 FIRST(Y1)-{ε} FIRST(X)
则 FIRST(Xi) -{} FIRST() 特别是,若所有的 FIRST(Xj)均含有,1jn,则{} FIRST()。 显然,若=则 FIRST()={}。
2、构造 FOLLOW 集的算法 对于 G 中的每一 AFOLLOW 集不再增大为止: ① 对于文法的开始符号 S,令# ∈ FOLLOW(S)。 ② 对于每一 A→αBβ∈P, 令 FIRST(β) - {ε} FOLLOW(B) 。 ③ 对于每一 A→αB∈P, 或 A→αBβ∈P,且ε∈FIRST(β), 则令 FOLLOW(A) FOLLOW(B) 。

而对所有的 j(1≤j ≤i-1), Yj ∈VN,且 Yj⇒ ε,则令 FIRST(Yj)-{ε} FIRST(X) (1≤j ≤i) 特别,当ε∈FIRST(Yj) (1≤j ≤k)时,令ε∈FIRST(X)
(2) 对文法 G 的任何符号串=X1X2…Xn 构造集合 FIRST() ① 置 FIRST(X1)-{} FIRST() ② 若对任何 1ji-1,FIRST(Xj),

《编译原理》课后习题答案第5章

《编译原理》课后习题答案第5章

《编译原理》课后习题答案第5章《编译原理》课后习题答案第5章.pdf《编译原理》课后习题答案第5章.pdf第5章自顶向下语法分析方法第1题对文法G[S] S→a|∧|(T) T→T,S|S(1) 给出(a,(a,a))和(((a,a),∧,(a)),a)的最左推导。

(2) 对文法G,进行改写,然后对每个非终结符写出不带回溯的递归子程序。

(3) 经改写后的文法是否是LL(1)的?给出它的预测分析表。

(4) 给出输入串(a,a)#的分析过程,并说明该串是否为G的句子。

答案:(1) 对(a,(a,a)的最左推导为:S(T) (T,S) (S,S) (a,S) (a,(T)) (a,(T,S)) (a,(S,S)) (a,(a,S)) (a,(a,a))对(((a,a),∧,(a)),a) 的最左推导为:S(T) (T,S) (S,S) ((T),S) ((T,S),S) ((T,S,S),S) ((S,S,S),S) (((T),S,S),S) (((T,S),S,S),S) (((S,S),S,S),S) (((a,S),S,S),S) (((a,a),S,S),S) (((a,a),∧,S),S) (((a,a),∧,(T)),S)(((a,a),∧,(S)),S)《编译原理》课后习题答案第5章.pdf《编译原理》课后习题答案第5章.pdf(((a,a),∧,(a)),S) (((a,a),∧,(a)),a)(2) 改写文法为:0) S→a 1) S→∧ 2) S→( T ) 3) T→S N 4) N→, S N 5) N→ε非终结符FIRST集FOLLOW集S {a,∧,(} {#,,,)} T {a,∧,(} {)} N {,,ε} {)}对左部为N的产生式可知:FIRST (→, S N)={,} FIRST (→ε)={ε} FOLLOW (N)={)}由于SELECT(N →, S N)∩SELECT(N →ε) ={,}∩ { )}= 所以文法是LL(1)的。

first集合和follow集合的求法

first集合和follow集合的求法

first集合和follow集合的求法
FIRST集合和FOLLOW集合的求法如下:
1、FIRST集合的求法:
直接收取:如果X是终结符或为空,则First(X) = {X}。

反复传送:如果X是非终结符,则First集合一直传送下去,直到遇到终结符。

第一个状态减去ε(即空字符串)后加入到First集合中。

注意传送时非终结符是否可以为空,如果可以为空,则看下一个字符。

对于形如“…UP…”(P是非终结符)的组合,把First(P)直接收入到First集合中。

遇到形如E →TE’这样的产生式时,先把First(T)放入First(E),然后查看T是否能推导出ε(即空字符串)。

如果能,则把First(E’)放入First(E),以此类推。

若T不能推出ε,则First(E)求完。

2、FOLLOW集合的求法:
对于文法的开始符号S,将识别符号“#”置于FOLLOW(S)中。

若存在产生式A →αBβ,则将First(β) - {ε}加至FOLLOW(B)中。

这里,First(β)表示β能推导出的第一个终结符或非终结符的集合,但要去掉ε。

如果β可以推导出ε,则将FOLLOW(A)加至FOLLOW(B)中。

这意味着,如果B有可能是最后一个符号,那么A的FOLLOW集合应该加入到B的FOLLOW集合中。

反复使用上述规则,直到所求FOLLOW集合不再增大为止。

以上是对FIRST集合和FOLLOW集合求法的简要概述。

在实际应用中,需要根据具体的文法和产生式进行具体的分析和计算。

编译原理实验+求first集和follow集+代码

编译原理实验+求first集和follow集+代码

编译原理实验+求first集和follow集+代码/*说明:输入格式:每行输入一个产生式,左部右部中间的→用空格代替。

非终结符等价于大写字母^ 表示空输入到文件结束,或用 0 0 结尾。

Sample Input:(习题5·3):S MHS aH LSoH ^K dMLK ^L eHfM KM bLM0 0*/#include#include#include#include#includeusing namespace std;char l;string r;multimap sentence; //存储产生式multimap senRever; //产生式逆转set ter; //非终结符集合map toEmpty; //非终结符能否推出空bool flag;set fir; // 保存单个元素的first集set follow; //保存单个元素的follow集vector rightSide; //右部char Begin;bool capL(char c) //字母是否大写{if(c<='Z' && c>='A')return true;return false;}/*bool lowerL(char c) //小写字母{if(c<='z' && c>='a')return true;return false;}*/bool CapLString(string s) // 大写字符串{for(int i=0; iif(!capL(s[i])) {return false;}}return true;}bool isToEmpty(char ch) // 判断终结符能否推出空{bool flag;// for(set::iterator sIter = ter.begin(); sIter!=ter.end(); ++sIter) {flag = false;multimap::iterator mIter = sentence.find(ch);int cnt = sentence.count(ch);for(int i=0; iif(mIter->second=="^") {return true;// toEmpty[ch] = true;}else if(CapLString(mIter->second)){string s(mIter->second);bool flag2 = true;for(int j=0; jif(!isToEmpty(s[j]) || s[j]==ch) {flag2 = false;break;}}if(flag2) { // 右部全为终结符,全能推出空return true;}}}// }return false;}void getFirst(char ch, set &First) //求单个元素的 FIRST集{// if(flag)// return;multimap::iterator imul = sentence.find(ch);if(imul==sentence.end())return;int sum = sentence.count(imul->first);// cout<first<<endl;for(int i=0; i// cout<second<<endl;string s(imul->second);for(int j=0; jif(!capL(s[j])) {// cout<<" "<<s[j]<<endl;First.insert(s[j]);// flag = true;break;}else if(capL(s[j])) {if(s[j]==ch) { //有左递归,跳出循环break;;}getFirst(s[j], First);if(toEmpty[s[j] ]==false) {break;}}}}flag = true;}bool isLast(string s, char ch) //ch 是否是 s 的直接或间接的最后一个非终结符{if(!capL(ch))return false;for(int i=s.size()-1; i>=0; i--) {if(ch==s[i])return true;if(!capL(s[i]) || toEmpty[s[i] ]==false) {return false;}}return false;}void getFollow(char ch, set<cha</cha</s[j]<<endl;</endl;</endl;r> &follow) //求单个元素的 FOLLOW集{if(!capL(ch))return;for(vector::iterator iter=rightSide.begin(); iter!=rightSide.end(); ++iter) {for(int i=0; i<(*iter).size(); i++) {if(ch==(*iter)[i] && i!=(*iter).size()-1) {if(!capL((*iter)[i+1])) {follow.insert((*iter)[i+1]);}else {getFirst((*iter)[i+1], follow);}}if(ch==(*iter)[i] && i==(*iter).size()-1) { //判断是否是右部的最后一个非终结符 follow +#follow.insert('#');}else if(ch==(*iter)[i] && i<(*iter).size()-1){ //不是最后一个但之后全是非终结符且都能推出空 follow +#bool flag1=true;for(int j=i+1;j<(*iter).size(); j++) {if(!capL((*iter)[j]) || toEmpty[(*iter)[j]]==false) {flag1 = false;if(!capL((*iter)[j])) {follow.insert((*iter)[j]);}break;}}if(flag1 == true) {follow.insert('#');}}}if(isLast(*iter, ch)) { //ch是*iter的最后一个符号(直接或间接)int n = senRever.count(*iter);multimap::iterator mIter = senRever.find(*iter);for(int i=0 ;iif(mIter->second!=ch )getFollow(mIter->second, follow);}}}}int main(){int cnt=0;while(cin>>l>>r) {if(cnt==0) {Begin = l;cnt++;}if(l=='0')break;sentence.insert(make_pair(l, r)); //产生式senRever.insert(make_pair(r,l));ter.insert(l); //非终结符集合(左部)rightSide.push_back(r); //右部的集合/* if(r=="^") { // 判断是否有非终结符->^toEmpty[l] = true;}else {if(toEmpty.find(l)==toEmpty.end()) {toEmpty[l] = false;}} */}for(set::iterator sIter = ter.begin(); sIter!=ter.end(); ++sIter) { // 判断是否有非终结符->^if(isToEmpty(*sIter) ) {toEmpty[*sIter] = true;}else {toEmpty[*sIter] = false;}}for(set::iterator iter=ter.begin(); iter!=ter.end(); iter++) {flag = false;cout<<*iter<<" FIRST集 :";fir.clear();getFirst(*iter, fir);for(set::iterator iterF=fir.begin(); iterF!=fir.end(); iterF++) {cout<<" "<<*iterF;}cout<<endl;follow.clear();getFollow(*iter, follow);cout<<" FOLLOW集:";if(*iter==Begin) {cout<<" #";}for(set::iterator iterF=follow.begin(); iterF!=follow.end(); ++iterF) {if(*iterF!='^')cout<<" "<<*iterF;}cout<<endl<<endl;}system("pause");return 0;}</endl<<endl; </endl;。

编译原理:FIRST(x)FOLLOW(x)SELECT(x)的计算

编译原理:FIRST(x)FOLLOW(x)SELECT(x)的计算

编译原理:FIRST(x)FOLLOW(x)SELECT(x)的计算⽬录已知⽂法G[S]:S→MH|aH→LSo|εK→dML|εL→eHfM→K|bLM判断G是否是LL(1)⽂法。

First计算First集合的定义就是某个⾮终结符表达式可以推导出来的第⼀个字符可以是什么⽐如表达式S --> abb,它的First(S)={a}First(S)={a}+First(M)+First(H)+{ε}={a,b,d,e,ε}# S表达式存在单个的终结符a,添加{a}# S表达式以M开头,添加First(M)# 根据后⾯的表达式判断,M可以推出K,K可以推出空,所以M可以为空,此时S以H开头,添加First(H)# 由于H也可以推出空,所以S最终也会指向空,添加空集First(M)={b}+First(K)+{ε}={b,d,ε}# M表达式以终结符b开头,添加{b}# M表达式可以推导出单个的K表达式,所以添加First(K)# K有可能推导出空集,即M可以推导出空,所以添加空集First(K)={d}+{ε}={d,ε}# K可以以终结符d开头,添加{d}# K可以推导出空,添加空集First(H)=First(L)+{ε}={e,ε}# H可以推导出以L开头的表达式,所以添加First(L)# H可以推导出空,所以添加空集First(L)={e}# L只能推导出⼀个表达式,并且开头是终结符,所以添加{e}## 最后将已知的表达式代⼊到未知的表达式当中去,即可求出全部First集合Follow计算Follow表⽰某个⾮终结符后⾯可以跟着什么样的字符Follow集不存在空集为表达式编号1: S→MH|a2: H→LSo|ε3: K→dML|ε4: L→eHf5: M→K|bLMFollow(S)={$}+{o}={o,$}# 在表达式1中,S是⼀个⾮终结符,S是孤⽴的⼀个表达式,其Follow可以添加$,即添加{$}# 在表达式2中,S后⾯跟上了o,所以添加{o}Follow(H)=Follow(S)+{f}={f,o,$}# 在表达式1中,S后⾯跟了什么,MH后⾯就跟了什么,所以添加Follow(S)# 在表达式4中,H后⾯跟了f,所以添加{f}Follow(M)=First(H)+Follow(S)+First(L)={e,o,$}# 在表达式1中,M后⾯跟了H,所以添加First(H)# 在表达式2中可知,H可以推导出空,所以回到表达式1,S后⾯跟了什么,M后⾯就跟了什么,所以添加Follow(S)# 在表达式3中,M后⾯跟了⾮终结符L,所以添加First(L)# 在表达式5中,M后⾯跟了什么,bLM后⾯就跟什么,都是Follow(M),表达式不变Follow(L)=First(S)+Follow(K)+{o}+{$}+First(M)+Follow(M)={a,b,d,e,o,$}# 在表达式2中,L后⾯跟了⾮终结符S,所以添加First(S)# 在表达式2中,First(S)可以推出空,所以此时L后⾯跟着o,添加{o}# 在表达式3中,K后⾯跟了什么,dML后⾯就跟了什么,所以添加Follow(K)# 在表达式4中,L属于单⼀元素,所以添加$# 在表达式5中,L后⾯跟上了⾮终结符M,所以添加First(M)# 在表达式5中,从上得知,First(M)可以推导出空,所以此时M后⾯跟着什么,L后⾯就要跟着什么,所以添加Follow(M) Follow(K)={$}+Follow(M)={e,o,$}# 在表达式3中,K是单⼀字符,添加{$}# 在表达式5中,M后⾯跟着什么,K后⾯就跟着什么,所以添加Follow(M)注意:在书写Follow集中要时刻检查First集是否可以为空.Select计算分割表达式,如果⾮空则是First集,是空则为Follow集Select(S→MH)=First(M)+First(H)+Follow(S)={b,d,e,o,$}# S以M开头,加⼊First(M)# First(M)可以为空,加⼊First(H)# M和H都可以为空,加⼊Follow(S)Select(S→a)={a}# S只能推导出a,加⼊{a}Select(H→LSo)=First(L)={e}# H以L开头,并且First(L)不可以为空,即加⼊First(L)Select(H→ε)=Follow(H)={f,o,$}# H推导出空,加⼊Follow(H)Select(K→dML)={d}# K以终结符d开头,加⼊{d}Select(K→ε)=Follow(K)={e,o,$}# K可以为空,加⼊Follow(K)Select(L→eHf)={e}# L以终结符e开头,加⼊{e}Select(M→K)=First(K)+Follow(M)={d,e,o,$}# M可以推出K,加⼊First(K)# First(K)可以为空,即M可以加⼊Follow(M)Select(M→bLM)={b}# M可以推出以终结符b开头,加⼊{b}判断是否是LL(1)⽂法LL(1)⽂法就是同⼀⾮终结符推出来的Select集合相交为空,现在开始逐⼀判断Select(S→MH)∩Select(S→a)Select(H→LSo)∩Select(H→ε)Select(K→dML)∩Select(K→ε)# L只有⼀个表达式,省略LSelect(M→K)∩Select(M→bLM)易知,上述表达式都为空,所以这个表达式是LL(1)⽂法预测分析表的书写先列出First集和Follow集的表格First FollowS{a,d,b,e, ε}{$,o}H{e,ε}{f,o,$}K{d,ε}{e,o,$}L{e}{a,b,d,e,o,$}M{b,d,ε}{e,o,$}然后将⾮终结符、终结符作为横纵⾏,填⼊表达式。

编译原理 FIRST集和FOLLOW集的求法

编译原理  FIRST集和FOLLOW集的求法

First集合的求法:First集合最终是对产生式右部的字符串而言的,但其关键是求出非终结符的First集合,由于终结符的First集合就是它自己,所以求出非终结符的First集合后,就可很直观地得到每个字符串的First集合。

1. 直接收取:对形如U-a…的产生式(其中a是终结符),把a收入到First(U)中2. 反复传送:对形入U-P…的产生式(其中P是非终结符),应把First(P)中的全部内容传送到First(U)中。

Follow集合的求法:Follow集合是针对非终结符而言的,Follow(U)所表达的是句型中非终结符U所有可能的后随终结符号的集合,特别地,“#”是识别符号的后随符。

1. 直接收取:注意产生式右部的每一个形如“…Ua…”的组合,把a直接收入到Follow(U)中。

2.直接收取:对形如“…UP…”(P是非终结符)的组合,把First(P)除ε直接收入到Follow(U)中。

3.反复传送:对形如P-…U的产生式(其中U是非终结符),应把Follow(P)中的全部内容传送到Follow(U)中。

(或 P-…UB且First(B)包含ε,则把First(B)除ε直接收入到Follow(U)中,并把Follow(P)中的全部内容传送到Follow(U)中)例1:判断该文法是不是LL(1)文法,说明理由 S→ABc A→a|ε B→b|ε?First集合求法就是:能由非终结符号推出的所有的开头符号或可能的ε,但要求这个开头符号是终结符号。

如此题A可以推导出a和ε,所以FIRST(A)={a,ε};同理FIRST (B)={b,ε};S可以推导出aBc,还可以推导出bc,还可以推导出c,所以FIRST(S)={a,b,c}。

Follow集合的求法是:紧跟随其后面的终结符号或#。

但文法的识别符号包含#,在求的时候还要考虑到ε。

具体做法是把所有包含你要求的符号的产生式都找出来,再看哪个有用。

编译原理一些习题答案

编译原理一些习题答案

编译原理⼀些习题答案第2章形式语⾔基础2.2 设有⽂法G[N]: N -> D | NDD -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9(1)G[N]定义的语⾔是什么?(2)给出句⼦0123和268的最左推导和最右推导。

解答:(1)L(G[N])={(0|1|2|3|4|5|6|7|8|9)+} 或L(G[N])={α| α为可带前导0的正整数}(2)0123的最左推导:N ? ND ? NDD ? NDDD ? DDDD ? 0DDD ? 01DD ? 012D ? 0123 0123的最右推导:N ? ND ? N3 ? ND3 N23 ND23 N123 D123 0123268的最左推导:N ? ND ? NDD ? DDD ? 2DDD ? 26D ? 268268的最右推导:N ? ND ? N8 ? ND8 ? N68 ? D68 ? 2682.4 写⼀个⽂法,使其语⾔是奇数的集合,且每个奇数不以0开头。

解答:⾸先分析题意,本题是希望构造⼀个⽂法,由它产⽣的句⼦是奇数,并且不以0开头,也就是说它的每个句⼦都是以1、3、5、7、9中的某个数结尾。

如果数字只有⼀位,则1、3、5、7、9就满⾜要求,如果有多位,则要求第1位不能是0,⽽中间有多少位,每位是什么数字(必须是数字)则没什么要求,因此,我们可以把这个⽂法分3部分来完成。

分别⽤3个⾮终结符来产⽣句⼦的第1位、中间部分和最后⼀位。

引⼊⼏个⾮终结符,其中,⼀个⽤作产⽣句⼦的开头,可以是1-9之间的数,不包括0,⼀个⽤来产⽣句⼦的结尾,为奇数,另⼀个则⽤来产⽣以⾮0整数开头后⾯跟任意多个数字的数字串,进⾏分解之后,这个⽂法就很好写了。

N -> 1 | 3 | 5 | 7 | 9 | BNB -> 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | B02.7 下⾯⽂法⽣成的语⾔是什么?G1:S->ABA->aA| εB->bc|bBc G2:S->aA|a A->aS解答:B ? bcB ? bBc? bbccB ? bBc? bbBcc ? bbbccc……A ?εA ? aA ? aA ? aA ? aaA ? aa……∴S ? AB ? a m b n c n , 其中m≥0,n≥1即L(G1)={ a m b n c n | m≥0,n≥1} S ? a S ? aA ? aaS ? aaaS ? aA ? aaS ? aaaA ?aaaaS ? aaaaa ……∴S ? a2n+1 , 其中n≥0即L(G2)={ a2n+1 | n≥0}2.11 已知⽂法G[S]: S->(AS)|(b)A->(SaA)|(a)请找出符号串(a)和(A((SaA)(b)))的短语、简单短语和句柄。

FIRST、FOLLOW求解编译原理课程设计报告

FIRST、FOLLOW求解编译原理课程设计报告

摘要:编译原理是计算机科学与技术专业最重要的一门专业基础课程,内容庞大,涉及面广,知识点多。

由于该课程教、学难度都非常大,往往费了大量时间而达不到预期教学效果俗语说:学习的最好方法是实践。

本次课程设计的目的正是基于此,力求为学生提供一个理论联系实际的机会,通过布置一定难度的课题,要求学生独立完成。

我们这次课程设计的主要任务是编程实现对给定文法的FIRST 集和FOLLOW集的求解。

通过实践,建立系统设计的整体思想,锻炼编写程序、调试程序的能力,学习文档编写规范,培养独立学习、吸取他人经验、探索前言知识的习惯,树立团队协作精神。

同时,课程设计可以充分弥补课堂教学及普通实验中知识深度与广度有限的缺陷,更好地帮助学生从全局角度把握课程体系。

关键词:编译原理;FIRST集;FOLLOW集目录1 课题综述 (1)1.1 课题来源 (1)1.2 课题意义 (1)1.3 预期目标 (1)1.4 面对的问题 (1)1.5 需解决的关键技术 (1)2 系统分析 (2)2.1 基础知识 (2)2.1.1 FIRST集定义 (2)2.1.2FIRST集求解算法.................................................................... 错误!未定义书签。

2.1.3FOLLOW集的定义 (4)2.1.4 FOLLOW集算法 (4)2.2 解决问题的基本思路 (4)2.3 总体方案 (4)3 系统设计 (5)3.1 算法实现 (5)3.2 流程图 (6)4 代码编写 (10)5 程序调试 (15)6 运行与测试 (15)1 课题综述1.1 课题来源文法:包含若干终结符,非终结符,由终结符与非终结符组成的产生式。

本次课程设计就是对产生式进行左递归分析,待无左递归现象后进行FIRST集与FOLLOW集的求解。

1.2 课题意义由文法产生的若干个句子有可能是合法的或者不合法的,也有可能产生歧义,所以要消除歧义先消除文法左递归,然后根据求得的FIRST集与FOLLOW 集构造分析表,分析给定句子的合法性。

LL1语法分析,first集,follow集,分析表Java实现

LL1语法分析,first集,follow集,分析表Java实现

import java.awt.*;import java.awt.event.*;import javax.swing.*;import javax.swing.table.DefaultTableModel;import java.sql.*;import java.util.Vector;public class LL1 extends JFrame implements ActionListener { /****/private static final long serialVersionUID = 1L;JTextField tf1;JTextField tf2;JLabel l;JButton b0;JPanel p1, p2, p3;JTextArea t1, t2, t3;JButton b1, b2, b3;JLabel l0, l1, l2, l3, l4;JTable table;Statement sta;Connection conn;ResultSet rs;DefaultTableModel dtm;String Vn[] = null;Vector<String> P = null;int firstComplete[] = null;// 存储已判断过first的数据char first[][] = null;// 存储最后first结果int followComplete[] = null;// 存储已判断过follow的数据char follow[][] = null;// 存储最后follow结果char select[][] = null;// 存储最后select结果int LL = 0;// 标记是否为LL(1)String vt_tou[] = null;// 储存VtObject shuju[][] = null;// 存储表达式数据char yn_null[] = null;// 存储能否推出空LL1() {setLocation(100, 0);setSize(700, 780);tf1 = new JTextField(13);tf2 = new JTextField(13);l = new JLabel(">>");l0 = new JLabel("输入字符串:");l1 = new JLabel("输入的文法为:");l2 = new JLabel(" ");l3 = new JLabel("分析的结果:");l4 = new JLabel("预测分析表:");// p1=new JPanel();p2 = new JPanel();p3 = new JPanel();t1 = new JTextArea(24, 20);t2 = new JTextArea(1, 30);t3 = new JTextArea(24, 40);b0 = new JButton("确定(S为开始)");b1 = new JButton(" 判断文法 ");b2 = new JButton("输入");b3 = new JButton("清空");table = new JTable();JScrollPane jp1 = new JScrollPane(t1);JScrollPane jp2 = new JScrollPane(t2);JScrollPane jp3 = new JScrollPane(t3);p2.add(tf1);p2.add(l);p2.add(tf2);p2.add(b0);p2.add(b1);p2.add(l0);p2.add(l2);p2.add(jp2);p2.add(b2);p2.add(b3);p2.add(l1);p2.add(l3);p2.add(jp1);p2.add(jp3);p3.add(l4);p3.add(new JScrollPane(table));add(p2, "Center");add(p3, "South");b0.addActionListener(this);b1.addActionListener(this);b2.addActionListener(this);b3.addActionListener(this);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);table.setPreferredScrollableViewportSize(new Dimension(660, 200));setVisible(true);}////////////////////界面设计public void actionPerformed(ActionEvent e) {if (e.getSource() == b0) {String a = tf1.getText();String b = tf2.getText();t1.append(a + '→' + b + '\n');}if (e.getSource() == b1) {t3.setText("");int Vnnum = 0, k;Vn = new String[100];P = new Vector<String>();String s[] = t1.getText().split("\n");for (int i = 0; i < s.length; i++) {if (s.length < 2) {t3.setText("文法输入有误,请重新输入");// 判断长度是否符合return;}if (s[i].charAt(0) <= 'Z' && s[i].charAt(0) >= 'A'&& s[i].charAt(1) == '→') {for (k = 0; k < Vnnum; k++) {if (Vn[k].equals(s[i].substring(0, 1))) {break;}}if (Vnnum == 0 || k >= Vnnum) {Vn[Vnnum] = s[i].substring(0, 1);// 存入Vn数据Vnnum++;}P.add(s[i]);} else {t3.setText("文法输入有误,请重新输入");return;}}yn_null = new char[100];first = new char[Vnnum][100];int flag = 0;String firstVn[] = null;firstComplete = new int[Vnnum];for (int i = 0; Vn[i] != null; i++) // 依次求 FIRST**{flag = 0;firstVn = new String[20];if ((flag = add_First(first[i], Vn[i], firstVn, flag)) == -1) return;firstComplete[i] = 1;}t3.append("first集:" + "\n"); // 显示FIRST**for (int i = 0; Vn[i] != null; i++) {t3.append("first(" + Vn[i] + ")={ ");for (int j = 0; first[i][j] != '\0'; j++) {t3.append(first[i][j] + " , ");}t3.append("}" + "\n");}follow = new char[Vnnum][100];String followVn[] = null;followComplete = new int[Vnnum];for (int i = 0; Vn[i] != null; i++) // 求FOLLOW**{flag = 0;followVn = new String[20];if(i==0){}else if ((flag = tianjiaFollow(follow[i], Vn[i], followVn, flag)) == -1)return;followComplete[i] = 1;}t3.append("follow集:" + "\n"); // 显示FOLLOW**for (int i = 0; Vn[i] != null; i++) {t3.append("follow(" + Vn[i] + ")={ ");for (int j = 0; follow[i][j] != '\0'; j++) {t3.append(follow[i][j] + " , ");}t3.append("}" + "\n");}select = new char[P.size()][100];for (int i = 0; i < P.size(); i++) // 求SELECT**{flag = 0;tianjiaSelect(select[i], (String) P.elementAt(i), flag);}t3.append("select集:" + "\n"); // 显示SELECT**for (int i = 0; i < P.size(); i++) {t3.append("select(" + (String) P.elementAt(i) + ")={ ");for (int j = 0; select[i][j] != '\0'; j++) {t3.append(select[i][j] + " , ");}t3.append("}" + "\n");}for (int i = 0; Vn[i] != null; i++)// 判断select交集是否为空{int biaozhi = 0;char save[] = new char[100];for (int j = 0; j < P.size(); j++) {String t = (String) P.elementAt(j);if (t.substring(0, 1).equals(Vn[i])) {for (k = 0; select[j][k] != '\0'; k++) {if (puanduanChar(save, select[j][k])) {save[biaozhi] = select[j][k];biaozhi++;} else// 当有交集时,不为LL(1)文法{t3.append("不是LL(1)文法!!" + "\n");return;}}}}}char Vt[] = new char[100];int biaozhi = 0;for (int i = 0; i < P.size(); i++) {String t = (String) P.elementAt(i);for (int j = 2; j < t.length(); j++)// 提取表达式右侧的终结符存入Vt{if (t.charAt(j) > 'Z' || t.charAt(j) < 'A') {if (puanduanChar(Vt, t.charAt(j))) {Vt[biaozhi] = t.charAt(j);biaozhi++;}}}}if (puanduanChar(Vt, '#'))// 若可推出空集,则将#加入Vt。

FIRST集和FOLLOW集求法

FIRST集和FOLLOW集求法

FIRST集和FOLLOW集求法龙书算法:First:(1)、如果X是终结符,那么First(X) = X;(2)、如果X是⾮终结符,且XàY1Y2......Yk是⼀个产⽣式,其中k>=1;那么如果对于某个I, a在First(Yi)中,且#(空串)在所有的First(Y1)…..First(Yi-1)中,就吧a加⼊到First(X)中。

(3)、如果Xà#(空串)是⼀个产⽣式,那么将#加⼊到First(X)中。

Follow:(1)、将$放⼊到Follow(S)中,其中S是开始符号,⽽$是输⼊右端结束的标记。

(2)、如果存在⼀个产⽣式AàaBb,那么First(b)中除#(空串)外地所有符号都在Follow(B)中。

(3)、如果存在⼀个产⽣式AàaB, 或存在AàaBb且First(b)包含#(空串),那么Follow(A)中的所有符号都在Follow(B)中。

⾃⼰理解:First:(看X的产⽣式)(1)、如果X是终结符,那么First(X)= X;(2)、如果X是⾮终结符,且XàY1Y2......Yk,i=1;1)、将First(Yi)加⼊到First(X)中,2)、如果#包含着First(Yi)中,i++,重复1);3)、如果#不包含在First(Yi)中,First(X)计算完成;(3)、如果Xà#(空串)是⼀个产⽣式,那么将#加⼊到First(X)中。

Follow:(看在右边有B的产⽣式)(1)、将$放⼊到Follow(S)中,其中S是开始符号,⽽$是输⼊右端结束的标记。

(2)、如果存在⼀个产⽣式AàaBb,那么First(b)中除#(空串)外地所有符号都在Follow(B)中。

(3)、如果存在⼀个产⽣式AàaB, 或存在AàaBb且First(b)包含#(空串),那么Follow(A)中的所有符号都在Follow(B)中。

编译原理(3)语法_4(自顶向下语法分析:LL(1)分析法)

编译原理(3)语法_4(自顶向下语法分析:LL(1)分析法)
2first集确定了每一个非终结符在扫描输入串时所允许遇到的输入符号及所应采用的推导产生式集确定了每一个非终结符在扫描输入串时所允许遇到的输入符号及所应采用的推导产生式该非终结符所对应的产生式中的哪一个候选式33自顶向下的语法分析式中的哪一个候选式3follow集是针对文法中形如a这样的产生式的即在使用这样的产生式的即在使用a的产生式进行推导时面临输入串中哪些输入符号时有一空字的产生式进行推导时面临输入串中哪些输入符号时有一空字即匹配而不出错
课本例题3.8 第二步:计算非终结符的FOLLOW集合
G[E]: E→TE' E'→ + TE' | ε T→FT' T'→*FT' | ε F→(E) | i ③由E→TE' 知FOLLOW(E) ⊂ FOLLOW(E' ), 即FOLLOW(E' ) = {),#}; 由E→TE ' 且E ' → ε知FOLLOW(E)FOLLOW(T),即 FOLLOW(T) = {+,),#};
特别是当Y1~Yk均含有ε产生式时,应把ε也加到FIRST(X)中。
课本例题3.8 第一步:计算非终结符的FIRST集合 例3.8 试构造表达式文法G[E]的LL(1)分析表,其中: G[E]: E→TE' E'→ + TE' | ε T→FT' T'→*FT' | ε F→(E) | i
[解答] 首先构造FIRST集,步骤如下: ① FIRST(E') = {+, ε}; FIRST(T') = {*, ε}; FIRST(F) = {(, i}; ② T→F… 和E→T…知:FIRST(F) ⊂ FIRST(T) ⊂ FIRST(E) 即有FIRST(F) = FIRST(T) = FIRST(E) = {(,i}。

编译原理语法分析_自上而下__分析过程-FirstFollowSelect

编译原理语法分析_自上而下__分析过程-FirstFollowSelect
♨自上而下语法分析 Top-Down Parsing
G1: S → pA |qB A → a | cA B → b | dB 输入串 pca G2: S → Ap | Bq A → a | cA B → b | dB 输入串 cap
FIRST 集
FIRST集 / 开始符号集 / 终结首符集
* a…, a∈VT} FIRST(α) = {a|α * ε, 则规定 ε∈FIRST(α) 若α
G: S→ AB A→aA |ε B→bA |ε
自上而下语法分析面临的问题
G: S → cAd A → a|ab 输入串 cabd

- 回溯 Backtrack S
c A d
S
c A d a b
a A→ α1 |α2 |…|αn 如何决定用哪个候选式替换A? 不确定分析: 带回溯 (试探) 确定分析 : 不带回溯
练习: 求 First 集
G: S → ABC A → aA |ε B → bB |b C → cC |c First(S) = { a, b }
First(A) = { a, ε }
First(B) = { b}
First(C) = { c}
例: 自上而下语法分析
G5: E → TE' E'→ +TE'|ε T → FT' T'→ *FT'|ε F → (E) | i 输入串 i+i
Select 集
SELECT集 选择集合
给定上下文无关文法的产生式A→α, * ε, • 若α SELECT(A→α) = FIRST(α) * ε, • 若α SELECT(A→α) = (FIRST(α)-{ε})∪FOLLOW(A)

编译原理第四章答案

编译原理第四章答案

“编译技术”第四章作业4-1 已知文法G[C]:C→iEtS|iEtSeSE→ a|bS→ a+b|a*b(1)提取左公共因子;(2)计算修改后文法的每个非终结符的FIRST集和FOLLOW集;(3)给出递归下降分析程序。

(3)递归下降分析程序void match(token t){if(lookahead=='t')lookahead = nexttoken;else error;}void C(){if(lookahead=='i'){match('i');E();if(lookahead=='t'){match('t');S();Cprime();}else error;}else error;}void Cprime(){if(lookahead=='e'){match('e');S();}}void E(){if(lookahead=='a')match('a');else if(lookahead=='b')match('b');else error;}void S(){if(lookahead=='a'){match('a');Sprime();}}void Sprime(){if (lookahead=='+'){match('+');if(lookahed=='b'){match('b');}else error();}else if(lookahead=='*'){match('*');if(lookahead=='b'){match('b');}else error();}}4-2 已知文法G[Z]:Z→Az|bA→ Za|a(1)删除左递归;(2)计算修改后文法的每个非终结符的FIRST集和FOLLOW集;(3)给出递归下降分析程序。

【编译原理】FIRST集、FOLLOW集算法原理和实现

【编译原理】FIRST集、FOLLOW集算法原理和实现

【编译原理】FIRST集、FOLLOW集算法原理和实现书中⼀些话,不知是翻译的原因。

还是我个⼈理解的原因感觉不是⾮常好理解。

个⼈重新整理了⼀下。

不过相对于消除左递归和提取左公因,FIRST集和FOLLOW集的算法相对来说⽐较简单。

书中的重点给出:FIRST:⼀个⽂法符号的FIRST集就是这个符号能推导出的第⼀个终结符号的集合, 包括空串。

例: A -> abc | def | ε那么FIRST(A) 等于 { a, d, ε }。

FOLLOW:蓝线画的部分很重要。

特别是这句话:请注意,在这个推导的某个阶段,A和a之间可能存在⼀些⽂法符号。

单如果这样,这些符号会推导得到ε并消失。

这句话的意思就是好⽐说: S->ABa B->c | ε 这个⽂法 FOLLOW(A)的值应该是FIRST(B)所有的终结符的集合(不包含ε),但是FIRST(B)是包含ε的,说明B是可空的,既然B是可空的S->ABa 也可以看成 S->Aa。

那么a就可以跟在A的后⾯.所以在这种情况下,FOLLOW(A)的值是包含a的。

换句话说就是。

⼀个⽂法符号A的FOLLOW集合就是它的下⼀个⽂法符号B的FIRST集合。

如果下⼀个⽂法符号B的FIRST集合包含ε,那么我们就要获取下⼀个⽂法符号B的FOLLOW集添加到FOLLOW(A)中代码中的注释已经很详细// 提取First集合func First(cfg []*Production, sym *Symbolic) map[string] *Symbolic {result := make(map[string] *Symbolic)// 规则⼀如果符号是⼀个终结符号,那么他的FIRST集合就是它⾃⾝if sym.SymType() == SYM_TYPE_TERMINAL || sym.SymType() == SYM_TYPE_NIL {result[sym.Sym()] = symreturn result}// 规则⼆如果⼀个符号是⼀个⾮终结符号// (1) A -> XYZ 如果 X 可以推导出nil 那么就去查看Y是否可以推导出nil// 如果 Y 推导不出nil,那么把Y的First集合加⼊到A的First集合// 如果 Y 不能推导出nil,那么继续推导 Z 是否可以推导出nil,依次类推// (2) A -> XYZ 如果XYZ 都可以推导出 nil, 那么说明A这个产⽣式有可能就是nil,这个时候我们就把nil加⼊到FIRST(A)中for _, production := range cfg {if production.header == sym.Sym() {nilCount := 0for _, rightSymbolic := range production.body { // 对于⼀个产⽣式ret := First(cfg, rightSymbolic) // 获取这个产⽣式体的First集合hasNil := falsefor k, v := range ret {if v.SymType() == SYM_TYPE_NIL { // 如果推导出nil, 标识当前产⽣式体的符号可以推导出nilhasNil = true} else {result[k] = v}}if false == hasNil { // 当前符号不能推导出nil, 那么这个产⽣式的FIRST就计算结束了,开始计算下⼀个产⽣式break}// 当前符号可以推导出nil,那么开始推导下⼀个符号nilCount++if nilCount == len(production.body) { // 如果产⽣式体都可以推导出nil,那么这个产⽣式就可以推导出nilresult["@"] = &Symbolic{sym: "@", sym_type: SYM_TYPE_NIL}}}}}return result}// 提取FOLLOW集合func Follow(cfg []*Production, sym string) [] *Symbolic {fmt.Printf("Follow ------> %s\n", sym)result := make([] *Symbolic, 0)// ⼀个⽂法符号的FOLLOW集就是可能出现在这个⽂法符号后⾯的终结符// ⽐如 S->ABaD, 那么FOLLOW(B)的值就是a。

first集合和follow集合的求法

first集合和follow集合的求法

first集合和follow集合的求法编译原理是计算机专业中的重要学科,其中语法分析是编译原理的基础。

而语法分析器(Parser)的核心就是构建语法分析表格。

而在构建语法分析表格的过程中,first集合和follow集合的求法是一个非常重要的问题,本文就将详细介绍first集合和follow集合的求法。

一、first集合first集合指的是文法中每个非终结符号的经过一次推导得到的所有终结符号的集合,也就是最小前缀(First)的集合。

例如对于一个简单的文法表达式E→E+T|T,其中E和T是非终结符号,+是终极符号。

那么开始寻找E的first集合时,我们应该先判断E能够推导出哪些符号,根据文法表达式,E可以推导出E+T和T。

接着我们可以判断E+T和T 所能推导出的所有终结符号,并将这些终结符号加入到E的first集合中。

具体步骤可以参考下面的推导过程:E → E + TE → TT → a那么最终E的first集合就是{a,+}。

二、follow集合follow集合指的是文法中每个非终结符号在所有推导过程中后跟的符号的集合。

例如对于一个简单的文法表达式E→E+T|T,其中E和T是非终结符号,+是终极符号。

求解E的follow集合时,首先要考虑的是E出现在了哪些地方。

通过分析E在文法表达式中的位置,我们可以发现E出现在了三种不同的情况下:1. E是文法的起始符号,此时E的follow集合中必须包含结束符$。

2. E出现在某些规则的右侧,此时E的follow集合中必须包含右侧的符号的first集合,但是需要注意的是,如果推导出空串,则应该将右侧的非终结符号所在位置的follow集合添加进来。

3. E的右侧是其所在规则的最末尾,此时需要将E所在规则的左侧符号所在位置的follow集合添加到E的follow集合中。

根据以上三种情况,我们可以结合上面的文法表达式来推导出E的follow集合。

具体步骤可以参考下面的推导过程:S → EE → E + TE → TT → a1. $ ∈ follow(E)2. follow(T) = {+, $}follow(E) = first(T) ∪ {+, $}follow(E) = {+, a, $}3. follow(E) = {+, $}那么最终E的follow集合就是{+, a, $}。

编译原理实验报告FIRST集和FOLLOW集

编译原理实验报告FIRST集和FOLLOW集

编译原理实验报告实验名称计算first集合和follow集合实验时间院系计算机科学和技术班级软件工程1班学号姓名输入:任意的上下文无关文法。

输出:所输入的上下文无关文法一切非终结符的first 集合和follow 集合。

2. 实验原理设文法G[S]=(V N ,V T ,P ,S ),则首字符集为: FIRST (α)={a | α⇒*a β,a ∈V T ,α,β∈V *}。

若α⇒*ε,ε∈FIRST (α)。

由定义可以看出,FIRST (α)是指符号串α能够推导出的所有符号串中处于串首的终结符号组成的集合。

所以FIRST 集也称为首符号集。

设α=x 1x 2…x n ,FIRST (α)可按下列方法求得:令FIRST (α)=Φ,i =1;(1) 若x i ∈V T ,则x i ∈FIRST (α);(2) 若x i ∈V N ;① 若ε∉FIRST (x i ),则FIRST (x i )∈FIRST (α);② 若ε∈FIRST (x i ),则FIRST (x i )-{ε}∈FIRST (α);(3) i =i+1,重复(1)、(2),直到x i ∈V T ,(i =2,3,…,n )或x i∈V N 且若ε∉FIRST (x i )或i>n 为止。

当一个文法中存在ε产生式时,例如,存在A →ε,只有知道哪些符号可以合法地出现在非终结符A 之后,才能知道是否选择A →ε产生式。

这些合法地出现在非终结符A 之后的符号组成的集合被称为FOLLOW 集合。

下面我们给出文法的FOLLOW 集的定义。

设文法G[S]=(V N ,V T ,P ,S ),则FOLLOW (A )={a | S ⇒… Aa …,a ∈V T }。

若S ⇒*…A ,#∈FOLLOW (A )。

由定义可以看出,FOLLOW (A )是指在文法G[S]的所有句型中,紧跟在非终结符A 后的终结符号的集合。

FOLLOW 集可按下列方法求得:(1) 对于文法G[S]的开始符号S ,有#∈FOLLOW (S );(2) 若文法G[S]中有形如B →xAy 的规则,其中x ,y ∈V *,则FIRST(y )-{ε}∈FOLLOW (A );(3) 若文法G[S]中有形如B →xA 的规则,或形如B →xAy 的规则且ε∈FIRST (y ),其中x ,y ∈V *,则FOLLOW (B )∈FOLLOW (A );计算first集合和follow集合4.实验心得通过上机实验我对文法符号的FIRST集和FOLLOW集有了更深刻的理解,已经熟练的掌握了求解的思想和方法,同时也锻炼了自己的动手解决问题的能力,对编程能力也有所提高。

JAVA语言编写的编译原理FIRST和FOLLOW集

JAVA语言编写的编译原理FIRST和FOLLOW集

package cn.spy.action;import java.util.ArrayList;import java.util.Scanner;import java.util.StringTokenizer;*某一输入实例:* E->TE'* E'->+E|ε* T->FT'* T'->T|ε* F->PF'* F'->*F'|ε* P->(E)|a|b|^* end*/public class FirstFollow3 {public ArrayList<String[]> in=new ArrayList<String[]>();//这数据结构真是逼人绝路才去想到绝处逢生,哈哈,关键实现了可变长度文法接收,在这存放的是拆分后最简单的文法,也是由用户输入public ArrayList<String[]> first = new ArrayList<String[]>();//包括左推导符和其First集public ArrayList<String[]> follow = new ArrayList<String[]>(); public ArrayList<String[]> track = newArrayList<String[]>();//track有一条一条的非终结符串组成的路径数组public FirstFollow3(){Scanner sc = new Scanner(System.in);System.out.println("请分行输入一个完整文法:(end结束)");String sline="";sline=sc.nextLine();while(!sline.startsWith("end")){StringBuffer buffer=new StringBuffer(sline);int l=buffer.indexOf(" ");while(l>=0){//去空格buffer.delete(l,l+1);l=buffer.indexOf(" ");}sline=buffer.toString();String s[]=sline.split("->");//左推导符if(s.length==1)s=sline.split("→");//考虑到输入习惯和形式问题if(s.length==1)s=sline.split("=>");if(s.length==1){System.out.println("文法有误");System.exit(0);}StringTokenizer fx = new StringTokenizer(s[1],"|︱");//按英文隔符拆开产生式或按中文隔符拆开while(fx.hasMoreTokens()){String[] one = new String[2];//对于一个语句只需保存两个数据就可以了,语句左部和语句右部的一个简单导出式,假如有或符,就按多条存放one[0]=s[0];//头不变,0位置放非终结符one[1]=fx.nextToken();//1位置放导出的产生式,就是产生式右部的一个最简单导出式in.add(one);}sline=sc.nextLine();//求First集过程this.process("First");/** 打印First集算法和First集*/System.out.println("\nFirst集算法:");this.print(track);//打印First集算法System.out.println("\nFirst集:");for(int i=0;i<first.size();i++){String[] r=first.get(i);System.out.print("First("+r[0]+")={");for(int j=1;j<r.length;j++){System.out.print(r[j]);if(j<r.length-1)System.out.print(",");}System.out.println("}");}track.clear();//因为下面还要用,这里就先清空了//求Follow集过程this.process("Follow");System.out.println("\nFollow集算法:");for(int i=0;i<track.size();i++){String[] one = track.get(i);System.out.print("Follow("+follow.get(i)[0]+"):\t"); for(int j=0;j<one.length;j++)System.out.print(one[j]+"\t");System.out.println();}System.out.println("\nFollow集:");for(int i=0;i<follow.size();i++){String[] r=follow.get(i);System.out.print("Follow("+r[0]+")={");for(int j=1;j<r.length;j++){System.out.print(r[j]);if(j<r.length-1)System.out.print(",");}System.out.println("}");}}public void process(String firstORfollow){for(int i=0;i<in.size();i++){boolean bool=true;for(int j=0;j<i;j++)if(in.get(j)[0].equals(in.get(i)[0]))bool=false;if(bool){ArrayList<String> a=null;if(firstORfollow.equals("First"))a=this.getFirst(in.get(i)[0],"First("+in.get(i)[0]+")/"); else if(firstORfollow.equals("Follow"))a=this.getFollow(in.get(i)[0],in.get(i)[0],"");String[] sf=new String[a.size()/2+1];String[] st=new String[a.size()/2];sf[0]=in.get(i)[0];for(int j=0;j<a.size();j++){if(j%2==0)sf[j/2+1]=a.get(j);elsest[j/2]=a.get(j);}if(firstORfollow.equals("First"))first.add(sf);//first集else if(firstORfollow.equals("Follow"))follow.add(sf);track.add(st);//对应上面求得集的路径,在开始保存该非终结符了,因为已保存了该字符的First或Follow表示法}}}public ArrayList<String> getFirst(String s,String track1){//s表示左推导,track表示寻找路径,避免循环查找ArrayList<String> result = new ArrayList<String>();ArrayList<String> result1 = new ArrayList<String>();if(Character.isUpperCase(s.charAt(0))){//如果是非终结符,大写for(int i=0;i<in.size();i++){String[] one = in.get(i);if(s.equals(one[0])){if(track1.substring(0,track1.length()-9).indexOf("First("+s+")")>= 0)//假如在查找过程嵌套了这步,证明进入了无限循环,不需再找,此路径无结果;//有点要注意一下,本来一开始就把第一个开始推导符的First路径放进去了的,所以要避开这一次,不然已开始就结束了elseif(one[1].length()==1||one[1].charAt(1)!='\''&&one[1].charAt(1)!='’') result1=getFirst(one[1].charAt(0)+"",track1+"First("+one[1].char At(0)+")/");elseif(one[1].length()>1&&one[1].charAt(1)=='\''||one[1].charAt(1)=='’' )//假如接下来一个要求First的非终结符带了一撇,那一撇包括英文表示和中文表示result1=this.getFirst(one[1].substring(0,2),track1+"First("+one[1] .substring(0,2)+")/");result=addArrayString(result,result1);result1.clear();}}}else{//如果产生式首字符是终结字符if(s.equals("ε"))//注意:表示空的字符只能是这种了,其他形式在这个编译器中不能通过,还请原谅result1.add("#");elseresult1.add(s);result1.add(track1);//为了方便,把路径也加入了结果集,不然可能路径不匹配,没办法,因为中间有删去重复项result=result1;}return result;}public ArrayList<String> getFollow(String s,Stringelement,String track1){//从右至左反推,不是求Follow的等价Follow,因为推到后面的反而范围大ArrayList<String> result = new ArrayList<String>();ArrayList<String> result1 = new ArrayList<String>();if(Character.isUpperCase(s.charAt(0))){for(int i=0;i<in.size();i++){String[] one = in.get(i);int slen=s.length();int olen=one[1].length();if(element.equals(in.get(0)[0])){//如果是开始符号,或是可以反推到开始符号,证明也可以顺推导开始符号result1.add("#");result1.add(in.get(0)[0]+"→"+in.get(0)[0]+"\t");result = addArrayString(result,result1);result1.clear();}if(one[1].indexOf(s)>=0&&track1.indexOf((char)('a'+i)+"")>=0)//假如之前走过某一步,就不必再走了,那是死循环,之前在这语句前面加了个else,结果又部分内容显示不出来,总算发现了,就算反推到开始符号,也不一定就到结果了的,开始符号也可以反推,所以要继续;elseif(one[1].indexOf(s)>=0&&(olen-slen==one[1].indexOf(s)||slen==2 ||one[1].charAt(one[1].indexOf(s)+1)!='’'&&one[1].charAt(one[1].i ndexOf(s)+1)!='\'')){//如果在右产生式中真正存在需要求反推的字符,后面的条件控制它是真正存在,因为里面包含这个字符也不一定是真,就像E’中包含E,但这不是真正的包含int index=-1;index = one[1].indexOf(s,0);while(index>=0){//之前这没有用到循环,结果可能少点东西,仔细一想,必须要,就算是一个推导语句,也可能推出多个相同非终结符的组合,其实这也是一种特殊情况了,不考虑也可能正确了,也可能之前在其他地方把这样的结果求出来了,不求也没事,但就像假如要求T的Follow集,假如可以产生出T+a*T*b,这时还是有用的,万一吧if(olen-slen==index){//如果该非终结符在末尾,那么求导出该产生式的非终结符的倒推result1=getFollow(one[0], element,track1+(char)('a'+i));result=addArrayString(result,result1);result1.clear();}else{//如果后继非终结符在产生式中不是最后int t=index+slen;//指向在产生式非终结符s的后一个字符位置result1=returnFirstofFollow(s, element, track1, one[0], one[1], index, t);result=addArrayString(result,result1);//之前也没写这句话,结果把之前的内容覆盖了,就是之前的数据丢失result1.clear();}index = one[1].indexOf(s,index+1);}//endwhile}if(one[1].endsWith(element)){//如果最开始要求的Follow集非终结符在末尾result1.add("#");result1.add(in.get(0)[0]+"→"+one[1]+"\t");result=addArrayString(result,result1);//之前也没写这句话,结果把之前的内容覆盖了,就是之前的数据丢失result1.clear();}}//endfor}return result;}public ArrayList<String> returnFirstofFollow(String s,Stringelement,String track1,String one0,String one1,int index,int t){//返回求Follow集中要求的First集部分ArrayList<String> result = new ArrayList<String>();ArrayList<String> result1 = new ArrayList<String>();ArrayList<String > beckFirst;String lsh;//记录下一个字符if(t+1<one1.length()&&(one1.charAt(t+1)=='’'||one1.charAt(t+1)== '\''))//如果随后的非终结符还带了一撇符lsh=one1.substring(t,t+2);else//如果没带一撇,就只要截取一个字母就可以了lsh=one1.substring(t,t+1);String[] ls = null;int beflen=2;if(track1.length()>0){//这些都是为了算法输出容易理解点用的,其实要不输出这算法,要省下好多东西ls=in.get((int)(track1.charAt(track1.length()-1)-'a'));//得到上一步调用的语句if(Character.isUpperCase(ls[1].charAt(ls[1].length()-1)))beflen=1;}beckFirst=this.getFirst(lsh,"First("+lsh+")/");//相当于得到后继字符的First集for(int j=0;j<beckFirst.size()/2;j++){//调用求First集,返回的不一定只一个结果String lh="";if(beckFirst.get(j*2).equals("#")){result1.add(beckFirst.get(j*2));//这个加了是数据,下面一步就是把地址加上,就是一个结果,要两份数据if(ls==null)lh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+elemen t+"ε"+one1.substring(t+lsh.length(),one1.length());elselh=in.get(0)[0]+"→"+one1+"→"+one1.s ubstring(0,index)+ls[1]+o ne1.substring(index+s.length(),one1.length())+"→."+element+"ε" +one1.substring(t+lsh.length(),one1.length());result1.add(lh);result=addArrayString(result,result1);result1.clear();if(1+index+lsh.length()<one1.length())//证明后面还有字符,为什么要这一步,打个比方把,假如要求F的Follow集,而存在产生式FPQ,而P的有个First集为空,那么得还接着求Q的First,类推,假如最后一个字符Q还是返回空,那么求要求产生式左边的推导非终结符的Follow集了,必须把这些结果都算到F的Follow集中去result1=returnFirstofFollow(s, element, track1, one0,one1, index, t+lsh.length());else//到最后,那么求要求产生式左边的推导非终结符的Follow集了,其实这和上面一种情况都很特殊了,一般用不上了result1=getFollow(one0, element, track1);}else{//其实下面这一大坨都是为了易懂一点,Follow集算法清晰一点,好苦啊if(Character.isUpperCase(one1.charAt(t))){//如果是有随后的一个非终结符的First集求出的结果if(ls==null)lh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+element+beckFirst.get(j*2)+one1.substring(t+lsh.length(),one1.length()); elselh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+ls[1]+o ne1.substring(index+s.length(),one1.length())+"→."+element+be ckFirst.get(j*2)+one1.substring(t+lsh.length(),one1.length());}else{//如果不是大写,就是终结符了,那么用First集求出来的结果连接起来还是一样的,所以不要重复打印两次了if(ls==null){if(element==in.get(0)[0]||s.equals(element))lh=in.get(0)[0]+"→"+one1.substring(0,index)+element+one1.sub string(t,one1.length())+"\t";elselh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,in dex)+elemen t+one1.substring(t,one1.length())+"\t";}else{if(ls[1].length()==1||ls[1].length()==2&&!ls[1].endsWith("’")&&!ls[ 1].endsWith("\'"))lh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+elemen t+one1.substring(t,one1.length());elselh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+ls[1]+o ne1.substring(index+s.length(),one1.length())+"→."+element+on e1.substring(t,one1.length())+"!";}}result1.add(beckFirst.get(j*2));//这个加了是数据,下面一步就是把地址加上,就是一个结果,要两份数据result1.add(lh);}}result=addArrayString(result,result1);//之前也没写这句话,结果把之前的内容覆盖了,就是之前的数据丢失result1.clear();return result;}public ArrayList<String> addArrayString(ArrayList<String>a,ArrayList<String> b){//两个字符串数组相加ArrayList<String> result = new ArrayList<String>();for(int i=0;i<a.size();i+=2){//因为这每一个结果,都保存了两个数据,第一个是结果,第二个位置保存的是得到这结果的路径String s = a.get(i);if(result.contains(s)||s.equals("")){//如果结果集包含了这个字符串,就不加入结果集了,就是为了去掉重复项int index=result.indexOf(s);if(result.get(index+1).length()>a.get(i+1).length()){//如果新来的路径比现有的短result.set(index, s);result.set(index+1,a.get(i+1));}continue;}result.add(s);result.add(a.get(i+1));//还是要把路径继续保存在新的结果集中}for(int i=0;i<b.size();i+=2){String s = b.get(i);if(result.contains(s)||s.equals("")){int index=result.indexOf(s);if(result.get(index+1).length()>b.get(i+1).length()){//如果新来的路径比现有的短result.set(index, s);result.set(index+1,b.get(i+1));}continue;}result.add(s);//偶数地址存放的是数据result.add(b.get(i+1));//奇数地址存放的是该数据获得的路径}return result;}public void print(ArrayList<String[]> list){for(int i=0;i<list.size();i++){//循环非终结符个数次数String[] one = list.get(i);//得到某一个非终结符运行的所有路径String[][] strings= new String[one.length][];String[] finals = new String[one.length];//路径最终站点int number=0;//记录某一步最终有效站点个数,本来有几条路径,就因该有几个有效站点,但可能有些站点有重复的,即从同一站点发出int max=0;for(int j=0;j<one.length;j++){strings[j]=one[j].split("/");if(strings[j].length>max)max=strings[j].length;//求得某一非终结符路径最长一条}for(int j=0;j<max;j++){//循环最长站点次数number=0;for(int k=0;k<strings.length;k++){//有多少条路径就循环多少次String lsh="";if(j>=strings[k].length){lsh=strings[k][strings[k].length-1];}else {lsh=strings[k][j];}int m=0;for(m=0;m<number;m++){//记录有效站点if(lsh.equals(finals[m]))break;}if(m==number){finals[number]=lsh;number++;}}for(int k=0;k<number;k++){//打印每一条路径的某个站点System.out.print(finals[k]);if(k!=number-1)System.out.print(" + ");}if(j<max-1)System.out.print(" = ");}System.out.println();}}public static void main(String[] args){new FirstFollow3();}}。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
sf[0] = in.get(i)[0];
for (int j = 0; j < a.size(); j++) {
sf[j + 1] = a.get(j);
}
first.add(sf);// first集
}
}
}
public ArrayList<String> getFirst(String s) {// s表示左推导,track表示寻找路径,避免循环查找
if (in.get(j)[0].equals(in.get(i)[0]))
bool = false;
if (bool) {
ArrayList<String> a = null;
a = this.getFirst(in.get(i)[0]);
String[] sf = new String[a.size() + 1];
import java.util.ArrayList;
import java.util.Scanner;
import java.util.StringTokenizer;
/**
*@author 胡伟江<br/>
*
* 2012/5/20
*
*/
public class Firstop {
System.out.print("First(" + r[0] + ")={");
for (int j = 1; j < r.length; j++) {
System.out.print(r[j]);
if (j < r.length - 1)
System.out.print(",");
int k=0;
middle=getFirst(one[1].charAt(k) + "");
for (int j = 0; j < middle.size(); j++)
{
in.add(one);
}
sline = sc.nextLine();
}
this.process();
System.out.println("\nFirst集:");
for (int i = 0; i < first.size(); i++) {
String[] r = first.get(i);
}
System.out.println("}");
}
}
public void process() {
for (int i = 0; i < in.size(); i++) {
boolean bool = true;
for (int j = 0; j < i; j++)
public Firstop() {
Scanner sc = new Scanner(System.in);
System.out.println("请分行输入一个完整文法:(end结束)");
String sline = "";
sline = sc.nextLine();
if(!middle.get(o).equals("ε"))
result.add(middle.get(o));
}
}
}
}
}
} else {
result.add(s);
}
return result;
}
public static void main(String[] args) {
new Firstop();
}
}
System.out.println("文法有误");
System.exit(0nizer fx = new StringTokenizer(s[1], "|︱");// 按英文隔符拆开产生式或按中文隔符拆开
while (fx.hasMoreTokens()) {
s = sline.split("=>");
if (s.length == 1) {
System.out.println("文法有误");
System.exit(0);
}
if (!Character.isUpperCase(s[0].charAt(0))) {
ArrayList<String> result = new ArrayList<String>();
ArrayList<String> middle = new ArrayList<String>();
if (Character.isUpperCase(s.charAt(0))) {// 如果是非终结符,大写
String[] one = new String[2];// 对于一个语句只需保存两个数据就可以了,语句左部和语句右部的一个简单导出式,假如有或符,就按多条存放
one[0] = s[0];// 头不变,0位置放非终结符
one[1] = fx.nextToken();// 1位置放导出的产生式,就是产生式右部的一个最简单导出式
public ArrayList<String[]> in = new ArrayList<String[]>();// 这数据结构真是逼人绝路才去想到绝处逢生,哈哈,关键实现了可变长度文法接收,在这存放的是拆分后最简单的文法,也是由用户输入
public ArrayList<String[]> first = new ArrayList<String[]>();// 包括左推导符和其First集
result.add(middle.get(j));
while(middle.get(j).equals("ε")&&one[1].length()>++k){
middle=getFirst(one[1].charAt(k) + "");
for (int o = 0; o< middle.size(); o++){
while (!sline.startsWith("end")) {
String s[] = sline.split("->");// 左推导符
if (s.length == 1)
s = sline.split("→");// 考虑到输入习惯和形式问题
if (s.length == 1)
for (int i = 0; i < in.size(); i++) {
String[] one = in.get(i);
if (s.equals(one[0])) {
// if(one[1].equals("ε"))
// {
// result.add("ε");
// for (int j = 0; j < getFirst(one[1+1].charAt(0) + "").size(); j++)
// result.add(getFirst(one[1+1].charAt(0) + "").get(j));
//
// }
相关文档
最新文档