前面那片文章生成的语法分析表并不是最优的,因为有些项在遇到错误输入的时候,并不是采取报错,而是执行规约,直到不能再规约的时候才报错。这是不科学的,我们需要在得到错误输入的时候立马报错,为了实现这个功能,我们需要知道某个文法符号的后面可能接的终结文法符号的集合,只有当前的输入终结文法符号处在栈顶文法符号的后缀集合中时,我们才去采取接受规约移进这三个操作,否则报错。

为了得到后缀集合,我们首先需要得到每一个文法符号的开始符号集合。为了得到这个集合,我们需要将这些符号进行拓扑排序,以保证生成体的第一个符号在生成体的头部符号处理前处理。注意这里一定不会出现环,即出现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集合的更多相关文章

  1. FIRST集合、FOLLOW集合、SELECT集合以及预测分析表地构造

    FIRST集合.FOLLOW集合.SELECT集合以及预测分析表地构造 FIRST集合的简单理解就是推导出的字符串的开头终结符的集合. FOLLOW集合简单的理解就对于非终结符后面接的第一个终结符. ...

  2. FIRST集合、FOLLOW集合及LL(1)文法求法

    FIRST集合 定义 可从α推导得到的串的首符号的集合,其中α是任意的文法符号串. 规则 计算文法符号 X 的 FIRST(X),不断运用以下规则直到没有新终结符号或 ε可以被加入为止 : (1)如果 ...

  3. 高可用Redis(四):列表,集合与有序集合

    1.列表类型 1.1 列表数据结构 左边为key,是字符串类型 右边为value,是一个有序的队列,与python的列表结构相同 可以在Redis中对列表的value进行如下操作 从左边添加元素 从右 ...

  4. Java常用的几种集合, Map集合,Set集合,List集合

    Java中  Object是所有类的根 Java集合常用的集合List集合.Set集合.Map集合 Map接口常用的一些方法 size() 获取集合中名值对的数量 put(key k, value v ...

  5. C#语言基础——集合(ArrayList集合)

    集合及特殊集合 集合的基本信息: System.Collections 命名空间包含接口和类,这些接口和类定义各种对象(如列表.队列.位数组.哈希表和字典)的集合.System.Collections ...

  6. JAVASE02-Unit04: 集合框架 、 集合操作 —— 线性表

    Unit04: 集合框架 . 集合操作 -- 线性表 操作集合元素相关方法 package day04; import java.util.ArrayList; import java.util.Co ...

  7. ArrayList集合 、特殊集合

    一.ArrayList集合 集合内可以放不同类型的元素 另:object类型为所有数据类型的基类 添加元素:.add(); 清空集合:al.clear(); 克隆集合:.clone(); 判断是否包含 ...

  8. 2016年10月16日--ArrayList集合、特殊集合

    ArrayList集合 使用前引用 using System.Collections; ArrayList集合 实例化.初始化 ArrayList al = new ArrayList(); Arra ...

  9. java集合 之 Map集合

    Map用于保存具有映射关系的数据,具有两组值:一组用于保存Map中的key:另一组用于保存Map中的value,形成key-value的存储形式. Map集合中包含的一些方法: void clear( ...

随机推荐

  1. mongodb索引操作

    创建索引 db.table.ensureIndex({name:1}) 创建联合索引 db.table.ensureIndex({"table.name":1,"tabl ...

  2. SecureCRT上传、下载文件(使用sz与rz命令)

    1.简述 借助securtCRT,使用linux命令sz可以很方便的将服务器上的文件下载到本地,使用rz命令则是把本地文件上传到服务器. 2.理解记忆 其中,对于sz和rz的理解与记忆我用了如下的方法 ...

  3. C++成员变量、构造函数的初始化顺序 [转]

    C++成员变量.构造函数的初始化顺序 一.C++成员变量初始化 1.普通的变量:一般不考虑啥效率的情况下 可以在构造函数中进行赋值.考虑一下效率的可以再构造函数的初始化列表中进行 2.static 静 ...

  4. A debugger is already attached

    Today is the last day that all the laptops of winXP OS should be upgrade to WIN7. After updated. whe ...

  5. Flex4 DataGrid ItemRenderer内嵌方式

    Flex4 DataGrid ItemRenderer像Flex3一直内嵌ItemRenderer会报空对象引用的错误,如: <s:GridColumn dataField="titl ...

  6. USB DATA Toggle

    For bulk and interrupt transfers, the data toggle resets <0> only on Set Configuration, Set In ...

  7. PostgreSQL停止动作观察

    实验过程如下: 启动一个客户端: [postgres@cnrd56 bin]$ ./psql psql () Type "help" for help. postgres=# be ...

  8. jquery 新建的元素事件绑定问题

    js的事件监听跟css不一样,css只要设定好了样式,不论是原来就有的还是新添加的,都有一样的表现.而事件监听不是,你必须给每一个元素单独绑定事件. 常见的例子是处理表格的时候.每行行末有个删除按钮, ...

  9. Understanding Linux Kernel version 3 读书笔记

    P30, preemptive  kernel .kernel threading 和Multithreaded application support没太好理解,我想如果设计个多线程的程序来运行运行 ...

  10. PHP 自动生成导航网址的最佳方法 v20130826

    经常制作开发不同的网站的后台,写过很多种不同的后台导航写法. 最终积累了这种最写法,算是最好的吧.附上截图和代码如下(PHP+HTML) <?php $linkArr = array( 'ind ...