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( ...
随机推荐
- weblogic日志小结
weblogic日志小结 http://blog.csdn.net/forest_hou/article/details/5468222
- c++中的signal机制
简介 signal是为了解决类之间通信的问题而出现的,更深入的原因是面向对象讲究封装,但是封装必然导致类之间沟通困难,但是使用接口的方式又太重量级--需要写很多代码,而且会导致接口爆炸 比如你需要把一 ...
- js 原型的内存分析
使用构造器的弊端:http://www.cnblogs.com/a757956132/p/5258897.html 示例 将行为设置为全局的行为,如果将所有的方法都设计为全局函数的时候, 这个函数就可 ...
- Oracle 建表常用数据类型的详解
创建表时,必须为表的各个列指定数据类型.如果实际的数据与该列的数据类型不相匹配,则数据库会拒绝保存.如为学生指定出生日期为“1980-13-31”. 在Oracle中,常见的数据类型有: 字符串:字符 ...
- Synchronization in Delphi TThread class : Synchronize, Queue
http://embarcadero.newsgroups.archived.at/public.delphi.rtl/201112/1112035763.html > Hi,>> ...
- (高精度运算4.7.27)UVA 10494 If We Were a Child Again(大数除法&&大数取余)
package com.njupt.acm; import java.math.BigInteger; import java.util.Scanner; public class UVA_10494 ...
- openldap 安装 配置 使用
1.安装 #安装 yum install -y openldap-servers openldap-clients openldap-devel 2.复制配置文件 #复制配置文件 cp /usr/sh ...
- static使用方法小结
static使用方法小结 statickeyword是C, C++中都存在的keyword, 它主要有三种使用方式, 当中前两种仅仅指在C语言中使用, 第三种在C++中使用(C,C++中详细细微操作不 ...
- Effective C++ Item 37 绝不又一次定义继承而来的缺省參数值
本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:绝对不要又一次而来的缺省參数值.由于缺省參数值都是静态绑定,而 virtual 函数 ...
- 通达OA 公共文件柜二次开发添加管理信息(图文)
公共文件柜当内容较多时,管理起来非常easy乱,特别是当有多个名字相近的目录时.这里通过简单的开发添加了两个管理信息,能够通过添加备注的方式加以区分. watermark/2/text/aHR0cDo ...