first集合及follow集合
前面那片文章生成的语法分析表并不是最优的,因为有些项在遇到错误输入的时候,并不是采取报错,而是执行规约,直到不能再规约的时候才报错。这是不科学的,我们需要在得到错误输入的时候立马报错,为了实现这个功能,我们需要知道某个文法符号的后面可能接的终结文法符号的集合,只有当前的输入终结文法符号处在栈顶文法符号的后缀集合中时,我们才去采取接受规约移进这三个操作,否则报错。
为了得到后缀集合,我们首先需要得到每一个文法符号的开始符号集合。为了得到这个集合,我们需要将这些符号进行拓扑排序,以保证生成体的第一个符号在生成体的头部符号处理前处理。注意这里一定不会出现环,即出现E:->T +R ,T:->E+F这种情况,至于问什么不会出现,我只是凭直觉。还需要说明的一点是,对于左递归文法,则不需要添加自己到自己的边。
在生成开始集合之后,我们再去生成直接后缀集合,即对于A:->B.C这种的产生式,我们可以直接把c的开始符号集合添加到b的后缀集合之中。这里就不需要拓扑排序了,因为我们的first集合已经完全生成了。
生成了直接后缀集合之后,我们再生成间接后缀集合,即 A:->B.这种的,b的后缀依赖于a的后缀,我们又需要去做一次拓扑排序。直觉上是没有环。。。,又是直觉。
注意拓扑排序出来的第一个项一定是开始符号,而开始符号的直接后缀已经得到了,间接后缀需要手工添加输入结束符。后面的处理就是不停的迭代。
下面是代码。
- #include "decl_tackle.h"
- typedef struct _first_graph_node
- {
- int first_symbol;//代表当前文法符号的某个产生式的第一个节点
- struct* _first_graph_node next;
- }first_graph_node,*pfirst_graph_node;//这里可以当作邻接表来使用
- typedef struct _end_graph_node
- {
- int symbol_head;//代表以当前文法符号为生成式结尾符号的产生式头部符号
- struct _end_graph_node* next;
- }end_graph_node,pend_graph_node;//这里也是当作邻接表串联起来
- int** first_graph_set;//first集
- int** follow_graph_set;//follow集
- pfirst_graph_node** first_graph;//这里是整个图的邻接表的开始节点
- pend_graph_node** end_graph;//这里是整个图的邻接表的开始节点
- int number_edge_first=;//记录开始图里面有多少条边
- int number_edge_end=;//记录结束图里面有多少条边
- //上面两个变量都是为了拓扑排序使用的
- //注意这里是间接后缀而不是直接后缀,直接后缀的处理我们在第二遍遍历图的时候处理
- //而间接后缀处理需要在直接后缀处理的基础上执行
- //而直接后缀处理需要在前缀处理的基础上执行,因此我们需要三趟遍历
- void add_first_edge(int index_from,int index_to)//添加一条边到前缀引用图中
- {
- pfirst_graph_node temp_node_add,temp_node;
- if((*(*(first_graph_set+index_from)+index_to)!=))//如果这个符号还没有添加
- {
- temp_node=*(first_graph+index_from);
- temp_node_add=malloc(sizeof(struct _first_graph_node));
- temp_node_add->first_symbol=index_to;
- temp_node_add->next=temp_node;
- *(first_graph+index_from)=temp_node_add;//添加到邻接表
- *(*(first_graph_set+index_from)+index_to)=;//标记为已经添加过了
- number_edge_first++;
- }
- }
- void add_end_edge(int index_from,int index_to)//添加一条边到后缀引用图中
- {
- pend_graph_node temp_node_add,temp_node;
- if((*(*(end_graph_set+index_from)+index_to)!=))//如果这个符号还没有添加
- {
- temp_node=*(end_graph+index_from);
- temp_node_add=malloc(sizeof(struct _end_graph_node));
- temp_node_add->symbol_head=index_to;
- temp_node_add->next=temp_node;
- *(end_graph+index_from)=temp_node_add;//添加到邻接表
- *(*(end_graph_set+index_from)+index_to)=;//标记为已经添加过了
- number_edge_end++;
- }
- }
- void generate_graph(void)//生成first集合,利用数组来表示
- //注意这个函数调用是在反转链表之后的
- {
- int for_i,for_j;
- decl_node temp_decl_node;
- int* temp_set;
- phrase* temp_phrase;
- pdecl_edge temp_decl_edge;
- first_graph_set=malloc(sizeof(int)*(node_index+));
- end_graph_set=malloc(sizeof(int)*(node_index+));
- for(for_i=;for_i<node_size+;for_i++)
- {
- temp_set=malloc(sizeof(int)*(node_index+));
- for(for_j=;for_j<node_index+;for_j++)
- {
- temp_set[for_j]=;
- }
- first_graph_set[for_i]=temp_set;
- temp_set=malloc(sizeof(int)*(node_index+));
- for(for_j=;for_j<node_index+;for_j++)
- {
- temp_set[for_j]=;
- }
- end_graph_set[for_i]=temp_set;
- }//初始化所有的矩阵
- first_graph=malloc(sizeof(struct _first_graph_node*)*(node_index+));//因为这里考虑了偏移量和结束符
- end_graph=malloc(sizeof(struct _end_graph_node*)*(node_index+));//因为这里考虑了偏移量和结束符
- for(for_i=;for_i<node_index+;for_i++)
- {
- first_graph[for_i]=NULL;
- end_graph[for_i]=NULL;
- }//初始化为空,省得犯以前的错误
- //现在开始建立这两个图
- for(for_i=;for_i<=node_index;for_i++)
- {
- temp_decl_node=decl_node_table[for_i];
- temp_phrase=temp_decl_node->phrase_link;
- while(temp_phrase!=NULL)
- {
- temp_edge=temp_phrase->begin_of_phrase;
- if(temp_edge->node_of_dest!=for_i)//对于左递归进行忽略
- {
- add_first_edge(temp_edge->node_of_dest,for_i);//添加一条边到开始图中
- }
- while(temp_edge->next!=NULL)
- {
- temp_edge=temp_edge->next;//寻找到最后一个节点
- }
- if(temp_edge->node_of_dest!=for_i)//对于右递归进行忽略
- {
- add_end_edge(for_i,temp_edge->node_of_dest);//添加到后缀引用图中
- }
- temp_phrase=temp_phrase->next;
- }//对于有产生式的文法单元,处理所有的产生式
- }//所有文法单元处理完毕
- }//前缀引用图和后缀引用图处理完毕
- void generate_first_set(void)//对于引用图进行拓扑排序,然后按照拓扑排序来生成first集合
- {
- //first矩阵里面真正有意义的是那些终结文法符号所占据的位,非终结文法符号没啥意思
- int number_of_edge;
- int begin_stack_pointer;
- int* begin_stack;
- int end_stack_pointer;
- int* end_stack;
- int* temp_row,temp_row_add;//用来遍历矩阵的变量
- pfirst_graph_node temp_node;
- int* already_out_stack;//代表已经处理过了,已经出栈
- int for_i,for_j;
- already_out_stack=malloc(sizeof(int)*(node_index+));
- for(for_i=;for_i<=node_index+;for_i++)
- {
- already_out_stack[for_i]=;
- }//初始化
- begin_stack=malloc(sizeof(int)*(node_index+));//多申请几个又不会怀孕
- end_stack=malloc(sizeof(int)*(node_index+));
- begin_stack_pointer=end_stack_pointer=;
- number_of_edge=number_edge_first;
- while(number_of_edge>)//只要还有一条边没有处理
- {
- for_i=;
- while(first_graph[for_i]==NULL)
- {
- for_i++;
- }//找到一个还有边存在的点
- begin_stack_pointer++;
- begin_stack[begin_stack_pointer]=for_i;
- while(begin_stack_pointer>)//好像写的有点问题
- {
- temp_node=first_graph[begin_stack[begin_stack_pointer]];
- if(temp_node!=NULL)//如果栈顶的点还有边
- {
- if(already_out_stack[temp_node->symbol_head]==)//如果这个点已经处理过了
- {
- first_graph[begin_stack[begin_stack_pointer]]=temp_node->next;//将这条边摘掉
- number_of_edge--;
- free(temp_node);
- }
- else//如果还没处理,那就直接入栈
- {
- begin_stack_pointer++;
- begin_stack[begin_stack_pointer]=temp_node->symbol;
- }
- }
- else//如果没有边,则需要考虑是不是最后一个点
- {
- if(begin_stack_pointer>)//如果不是栈底
- {
- end_stack_pointer++;
- end_stack[end_stack_pointer]=begin_stack[begin_stack_pointer];//换栈
- already_out_stack[begin_stack[begin_stack_pointer]]=;//表示已经处理完了
- begin_stack_pointer--;
- temp_node=first_graph[begin_stack[begin_stack_pointer]];
- number_of_edge--;
- first_graph[begin_stack[begin_stack_pointer]]=temp_node->next;//把这条边摘除
- free(temp);//出栈
- }
- else//如果这是栈底
- {
- begin_stack_pointer=;//直接设置为栈空,不需要另外的措施
- }
- }
- }//一直循环到当前节点可达的节点都被处理了
- }//所有的边都处理完毕了
- end_stack_pointer++;
- end_stack[end_stack_pointer]=begin_stack[];//别忘了最后一个节点
- already_out_stack[begin_stack[]]=;
- //现在按照栈里面的顺序来处理位图
- while(end_stack_pointer>)
- {
- temp_row=first_graph+end_stack[end_stack_pointer];
- if(decl_node_table[end_stack[end_stack_pointer]].phrase_link!=NULL)//如果当前的是非终结文法符号
- {
- for(for_i=;for_i<=node_index;for_i++)
- {
- if(decl_node_table[temp_row[for_i]].phrase_link!=NULL)//如果引用的是一个非终结符,则开始合并终结符号
- {
- temp_row_add=first_graph+temp_row[for_i];
- for(for_j=;for_j<node_index;for_j++)
- {
- temp_row[for_j]=temp_row[for_j]|temp_row_add[for_j];
- //这里我就不去判断是不是终结文法符号了,因为我们保证了这里是一个拓扑排序
- }
- }
- else
- {
- //对应终结符的时候,则不需要处理
- }
- }//搜索完成
- end_stack_pointer--;
- }
- else//对于终结文法符号,在自己所在的位标注为1
- {
- temp_row[end_stack[end_stack_pointer]]=;
- end_stack_pointer--;
- }
- }//堆栈中的点都处理完成
- free(begin_stack);
- free(end_stack);
- free(already_out_stack);
- //释放内存的是好孩子
- }//前缀集合处理完成
- //现在开始处理后缀集合
- //下面这个函数来处理直接后缀
- void direct_end(void)
- {
- int* before_row;
- int* after_row;
- pdecl_edge before_edge,after_edge;
- phrase* current_phrase;
- int for_i,for_j;
- for(for_i=;for_i<node_index;for_i++)
- {
- current_phrase=decl_node_table[for_i].phrase_link
- if(current_phrase!=NULL)//如果这里有生成式 ,则进行处理
- {
- before_edge=current_phrase->begin_of_phrase;
- after_edge=before_edge->next;
- while(after_edge!=NULL)//这个不是空的,则我们需要去处理一个直接后缀
- {
- before_row=follow_graph_set[before_edge->node_of_dest];
- if(decl_node_table[after_edge->node_of_dest].phrase_link!=NULL)//如果不是终结文法符号
- {
- after_row=first_graph_set[after_edge->node_of_dest];//取出后面文法符号的first集合
- for(for_j=;for_j<=node_index;for_j++)//因为是直接后缀,所以先不考虑输入终结符
- {
- before_row[for_j]=before_row[for_j]|after_row[for_j];//取并集
- }
- //并集处理完成
- }
- else
- {
- before_row[after_edge->node_of_dest]=;//将这一位置为1就行了
- }
- before_edge=after_edge;
- after_edge=after_edge->next;
- }//当前产生式处理完成
- current_phrase=current_phrase->next;//处理下一个产生式
- }//当前产生式头的所有产生式处理完成
- }//处理下一个产生式头
- //所有的产生式处理完毕
- }//直接后缀处理完毕
- void indirect_follow(int begin_node_index )
- {
- //这里又需要走一遍拓扑排序
- //注意这趟处理的时候需要考虑输入终结符号
- //由于开始符号可以生成任何符号,所以在拓扑排序完成之后,处于栈顶的一般是开始符号
- //注意这只是一般情况下,我们并未用理论来证实这个结论,所以还是老老实实的先找到开始符号吧
- //这个我们采用参数传递的方法
- *(follow_graph_set[begin_node_index]+node_index+)=;//将输入结束符作为开始符号的后缀符号
- //下面的内容基本就是复制前面生成first集合的代码
- int number_of_edge;
- int begin_stack_pointer;
- int* begin_stack;
- int end_stack_pointer;
- int* end_stack;
- int* temp_row,temp_row_add;//用来遍历矩阵的变量
- pend_graph_node temp_node;//这里改变了节点类型
- int* already_out_stack;//代表已经处理过了,已经出栈
- int for_i,for_j;
- already_out_stack=malloc(sizeof(int)*(node_index+));
- for(for_i=;for_i<=node_index+;for_i++)
- {
- already_out_stack[for_i]=;
- }//初始化
- begin_stack=malloc(sizeof(int)*(node_index+));//多申请几个又不会怀孕
- end_stack=malloc(sizeof(int)*(node_index+));
- begin_stack_pointer=end_stack_pointer=;
- number_of_edge=number_edge_end;
- while(number_of_edge>)//只要还有一条边没有处理
- {
- for_i=;
- while(first_graph[for_i]==NULL)
- {
- for_i++;
- }//找到一个还有边存在的点
- begin_stack_pointer++;
- begin_stack[begin_stack_pointer]=for_i;
- while(begin_stack_pointer>)//好像写的有点问题
- {
- temp_node=end_graph[begin_stack[begin_stack_pointer]];
- if(temp_node!=NULL)//如果栈顶的点还有边
- {
- if(already_out_stack[temp_node->symbol_head]==)//如果这个点已经处理过了
- {
- end_graph[begin_stack[begin_stack_pointer]]=temp_node->next;//将这条边摘掉
- number_of_edge--;
- free(temp_node);
- }
- else//如果还没处理,那就直接入栈
- {
- begin_stack_pointer++;
- begin_stack[begin_stack_pointer]=temp_node->symbol;
- }
- }
- else//如果没有边,则需要考虑是不是最后一个点
- {
- if(begin_stack_pointer>)//如果不是栈底
- {
- end_stack_pointer++;
- end_stack[end_stack_pointer]=begin_stack[begin_stack_pointer];//换栈
- already_out_stack[begin_stack[begin_stack_pointer]]=;//表示已经处理完了
- begin_stack_pointer--;
- temp_node=first_graph[begin_stack[begin_stack_pointer]];
- number_of_edge--;
- first_graph[begin_stack[begin_stack_pointer]]=temp_node->next;//把这条边摘除
- free(temp);//出栈
- }
- else//如果这是栈底
- {
- begin_stack_pointer=;//直接设置为栈空,不需要另外的措施
- }
- }
- }//一直循环到当前节点可达的节点都被处理了
- }//所有的边都处理完毕了
- end_stack_pointer++;
- end_stack[end_stack_pointer]=begin_stack[];//别忘了最后一个节点
- already_out_stack[begin_stack[]]=;
- //现在按照栈里面的顺序来处理位图
- while(end_stack_pointer>)
- {
- temp_row=follow_graph_set+end_stack[end_stack_pointer];//
- for(for_i=;for_i<=node_index;for_i++)
- {
- if(temp_row[for_i]==&&decl_node_table[for_i].phrase_link!=NULL)//如果这里依赖一个非终结符
- {
- temp_row_add=follow_graph_set+for_i;
- for(for_j=;for_j<=node_index+;for_j++)//注意这里需要考虑输入终结符
- {
- temp_row[for_j]=temp_row[for_j]|temp_row_add[for_j];
- }//合并完成
- }//此符号处理完成
- }//所有的处理完成
- end_stack_pointer--;//堆栈减1
- }//堆栈空
- //现在所有的后缀信息已经处理完毕
- free(begin_stack);
- free(end_stack);
- free(already_out_stack);
- //释放内存的是好孩子
- }//
first集合及follow集合的更多相关文章
- FIRST集合、FOLLOW集合、SELECT集合以及预测分析表地构造
FIRST集合.FOLLOW集合.SELECT集合以及预测分析表地构造 FIRST集合的简单理解就是推导出的字符串的开头终结符的集合. FOLLOW集合简单的理解就对于非终结符后面接的第一个终结符. ...
- FIRST集合、FOLLOW集合及LL(1)文法求法
FIRST集合 定义 可从α推导得到的串的首符号的集合,其中α是任意的文法符号串. 规则 计算文法符号 X 的 FIRST(X),不断运用以下规则直到没有新终结符号或 ε可以被加入为止 : (1)如果 ...
- 高可用Redis(四):列表,集合与有序集合
1.列表类型 1.1 列表数据结构 左边为key,是字符串类型 右边为value,是一个有序的队列,与python的列表结构相同 可以在Redis中对列表的value进行如下操作 从左边添加元素 从右 ...
- Java常用的几种集合, Map集合,Set集合,List集合
Java中 Object是所有类的根 Java集合常用的集合List集合.Set集合.Map集合 Map接口常用的一些方法 size() 获取集合中名值对的数量 put(key k, value v ...
- C#语言基础——集合(ArrayList集合)
集合及特殊集合 集合的基本信息: System.Collections 命名空间包含接口和类,这些接口和类定义各种对象(如列表.队列.位数组.哈希表和字典)的集合.System.Collections ...
- JAVASE02-Unit04: 集合框架 、 集合操作 —— 线性表
Unit04: 集合框架 . 集合操作 -- 线性表 操作集合元素相关方法 package day04; import java.util.ArrayList; import java.util.Co ...
- ArrayList集合 、特殊集合
一.ArrayList集合 集合内可以放不同类型的元素 另:object类型为所有数据类型的基类 添加元素:.add(); 清空集合:al.clear(); 克隆集合:.clone(); 判断是否包含 ...
- 2016年10月16日--ArrayList集合、特殊集合
ArrayList集合 使用前引用 using System.Collections; ArrayList集合 实例化.初始化 ArrayList al = new ArrayList(); Arra ...
- java集合 之 Map集合
Map用于保存具有映射关系的数据,具有两组值:一组用于保存Map中的key:另一组用于保存Map中的value,形成key-value的存储形式. Map集合中包含的一些方法: void clear( ...
随机推荐
- java使用jdbc对sqlite 添加、删除、修改的操作
package com.jb.jubmis.Dao.DaoImpl; import java.io.File;import java.io.FileInputStream;import java.io ...
- HTML5中script的async属性异步加载JS
HTML5中script的async属性异步加载JS HTML4.01为script标签定义了5个属性: charset 可选.指定src引入代码的字符集,大多数浏览器忽略该值.defer 可 ...
- OC: 类的扩展、类的延展、协议、 NSDate
NSDateFormatter 指定⽇日期格式: NSDateFormatter * formatter = [[NSDateFormatter alloc] init]; [formatter ...
- 无责任Windows Azure SDK .NET开发入门篇二[使用Azure AD 进行身份验证-2.2身份验证开发]
2.2身份验证开发 在我们的案例中,我们是用户通过Web应用程序进行身份识别. 上面的图示说明了如下的一些概念 l Azure AD 是标识提供程序,负责对组织的目录中存在的用户和应用程序的标识进行验 ...
- thinkphp 3+ 观后详解 (5)
static public function dispatch() { $varPath = C('VAR_PATHINFO'); $varAddon = C('VAR_ADDON'); $varMo ...
- 操作无法完成,因为文件夹已在另一个程序中打开(the action can't be completed because the folder or a file in it is open in another program)
解决方法: 启动任务管理器——性能——资源监视器——CPU选项卡——关联的句柄——搜索句柄 ——(输入)要删除的文件夹名——搜索到与文件夹名句柄相关联的进程 (由于此程序进程正在调用文件夹,才造成了对 ...
- NAT类型与穿透 及 STUN TURN 协议
STUN : Simple Traversal of User Datagram Protocol [UDP] Through Network Address Translators [NATs] S ...
- 排版系统Latex傻瓜方式使用(论文排版)
0. 什么是Latex? LaTEX(英语发音:/ˈleɪtɛk/ lay-tek或英语发音:/ˈlɑːtɛk/ lah-tek,音译"拉泰赫").文字形式写作LaTeX.是一种基 ...
- iOS开发——动画总结OC篇&所有常用动画总结
所有常用动画总结 先来装下B,看不懂没关系,其实我也看不懂-
- 使用openssl工具生成证书
第一步. 生成rsa私钥文件 :\> openssl genrsa -out bexio.pem 1024 : 若要加密生成的rsa私钥文件(des3加密) :\> openssl gen ...