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

为了得到后缀集合,我们首先需要得到每一个文法符号的开始符号集合。为了得到这个集合,我们需要将这些符号进行拓扑排序,以保证生成体的第一个符号在生成体的头部符号处理前处理。注意这里一定不会出现环,即出现E:->T +R ,T:->E+F这种情况,至于问什么不会出现,我只是凭直觉。还需要说明的一点是,对于左递归文法,则不需要添加自己到自己的边。

在生成开始集合之后,我们再去生成直接后缀集合,即对于A:->B.C这种的产生式,我们可以直接把c的开始符号集合添加到b的后缀集合之中。这里就不需要拓扑排序了,因为我们的first集合已经完全生成了。

生成了直接后缀集合之后,我们再生成间接后缀集合,即 A:->B.这种的,b的后缀依赖于a的后缀,我们又需要去做一次拓扑排序。直觉上是没有环。。。,又是直觉。

注意拓扑排序出来的第一个项一定是开始符号,而开始符号的直接后缀已经得到了,间接后缀需要手工添加输入结束符。后面的处理就是不停的迭代。

下面是代码。

  1. #include "decl_tackle.h"
  2. typedef struct _first_graph_node
  3. {
  4. int first_symbol;//代表当前文法符号的某个产生式的第一个节点
  5. struct* _first_graph_node next;
  6. }first_graph_node,*pfirst_graph_node;//这里可以当作邻接表来使用
  7. typedef struct _end_graph_node
  8. {
  9. int symbol_head;//代表以当前文法符号为生成式结尾符号的产生式头部符号
  10. struct _end_graph_node* next;
  11. }end_graph_node,pend_graph_node;//这里也是当作邻接表串联起来
  12. int** first_graph_set;//first集
  13. int** follow_graph_set;//follow集
  14. pfirst_graph_node** first_graph;//这里是整个图的邻接表的开始节点
  15. pend_graph_node** end_graph;//这里是整个图的邻接表的开始节点
  16. int number_edge_first=;//记录开始图里面有多少条边
  17. int number_edge_end=;//记录结束图里面有多少条边
  18. //上面两个变量都是为了拓扑排序使用的
  19. //注意这里是间接后缀而不是直接后缀,直接后缀的处理我们在第二遍遍历图的时候处理
  20. //而间接后缀处理需要在直接后缀处理的基础上执行
  21. //而直接后缀处理需要在前缀处理的基础上执行,因此我们需要三趟遍历
  22. void add_first_edge(int index_from,int index_to)//添加一条边到前缀引用图中
  23. {
  24. pfirst_graph_node temp_node_add,temp_node;
  25. if((*(*(first_graph_set+index_from)+index_to)!=))//如果这个符号还没有添加
  26. {
  27. temp_node=*(first_graph+index_from);
  28. temp_node_add=malloc(sizeof(struct _first_graph_node));
  29. temp_node_add->first_symbol=index_to;
  30. temp_node_add->next=temp_node;
  31. *(first_graph+index_from)=temp_node_add;//添加到邻接表
  32. *(*(first_graph_set+index_from)+index_to)=;//标记为已经添加过了
  33. number_edge_first++;
  34. }
  35. }
  36. void add_end_edge(int index_from,int index_to)//添加一条边到后缀引用图中
  37. {
  38. pend_graph_node temp_node_add,temp_node;
  39. if((*(*(end_graph_set+index_from)+index_to)!=))//如果这个符号还没有添加
  40. {
  41. temp_node=*(end_graph+index_from);
  42. temp_node_add=malloc(sizeof(struct _end_graph_node));
  43. temp_node_add->symbol_head=index_to;
  44. temp_node_add->next=temp_node;
  45. *(end_graph+index_from)=temp_node_add;//添加到邻接表
  46. *(*(end_graph_set+index_from)+index_to)=;//标记为已经添加过了
  47. number_edge_end++;
  48. }
  49. }
  50.  
  51. void generate_graph(void)//生成first集合,利用数组来表示
  52. //注意这个函数调用是在反转链表之后的
  53. {
  54. int for_i,for_j;
  55. decl_node temp_decl_node;
  56. int* temp_set;
  57. phrase* temp_phrase;
  58. pdecl_edge temp_decl_edge;
  59. first_graph_set=malloc(sizeof(int)*(node_index+));
  60. end_graph_set=malloc(sizeof(int)*(node_index+));
  61. for(for_i=;for_i<node_size+;for_i++)
  62. {
  63. temp_set=malloc(sizeof(int)*(node_index+));
  64. for(for_j=;for_j<node_index+;for_j++)
  65. {
  66. temp_set[for_j]=;
  67. }
  68. first_graph_set[for_i]=temp_set;
  69. temp_set=malloc(sizeof(int)*(node_index+));
  70. for(for_j=;for_j<node_index+;for_j++)
  71. {
  72. temp_set[for_j]=;
  73. }
  74. end_graph_set[for_i]=temp_set;
  75. }//初始化所有的矩阵
  76. first_graph=malloc(sizeof(struct _first_graph_node*)*(node_index+));//因为这里考虑了偏移量和结束符
  77. end_graph=malloc(sizeof(struct _end_graph_node*)*(node_index+));//因为这里考虑了偏移量和结束符
  78. for(for_i=;for_i<node_index+;for_i++)
  79. {
  80. first_graph[for_i]=NULL;
  81. end_graph[for_i]=NULL;
  82. }//初始化为空,省得犯以前的错误
  83. //现在开始建立这两个图
  84. for(for_i=;for_i<=node_index;for_i++)
  85. {
  86. temp_decl_node=decl_node_table[for_i];
  87. temp_phrase=temp_decl_node->phrase_link;
  88. while(temp_phrase!=NULL)
  89. {
  90. temp_edge=temp_phrase->begin_of_phrase;
  91. if(temp_edge->node_of_dest!=for_i)//对于左递归进行忽略
  92. {
  93. add_first_edge(temp_edge->node_of_dest,for_i);//添加一条边到开始图中
  94. }
  95. while(temp_edge->next!=NULL)
  96. {
  97. temp_edge=temp_edge->next;//寻找到最后一个节点
  98. }
  99. if(temp_edge->node_of_dest!=for_i)//对于右递归进行忽略
  100. {
  101. add_end_edge(for_i,temp_edge->node_of_dest);//添加到后缀引用图中
  102. }
  103. temp_phrase=temp_phrase->next;
  104. }//对于有产生式的文法单元,处理所有的产生式
  105. }//所有文法单元处理完毕
  106. }//前缀引用图和后缀引用图处理完毕
  107.  
  108. void generate_first_set(void)//对于引用图进行拓扑排序,然后按照拓扑排序来生成first集合
  109. {
  110. //first矩阵里面真正有意义的是那些终结文法符号所占据的位,非终结文法符号没啥意思
  111. int number_of_edge;
  112. int begin_stack_pointer;
  113. int* begin_stack;
  114. int end_stack_pointer;
  115. int* end_stack;
  116. int* temp_row,temp_row_add;//用来遍历矩阵的变量
  117. pfirst_graph_node temp_node;
  118. int* already_out_stack;//代表已经处理过了,已经出栈
  119. int for_i,for_j;
  120. already_out_stack=malloc(sizeof(int)*(node_index+));
  121. for(for_i=;for_i<=node_index+;for_i++)
  122. {
  123. already_out_stack[for_i]=;
  124. }//初始化
  125. begin_stack=malloc(sizeof(int)*(node_index+));//多申请几个又不会怀孕
  126. end_stack=malloc(sizeof(int)*(node_index+));
  127. begin_stack_pointer=end_stack_pointer=;
  128. number_of_edge=number_edge_first;
  129. while(number_of_edge>)//只要还有一条边没有处理
  130. {
  131. for_i=;
  132. while(first_graph[for_i]==NULL)
  133. {
  134. for_i++;
  135. }//找到一个还有边存在的点
  136. begin_stack_pointer++;
  137. begin_stack[begin_stack_pointer]=for_i;
  138. while(begin_stack_pointer>)//好像写的有点问题
  139. {
  140. temp_node=first_graph[begin_stack[begin_stack_pointer]];
  141. if(temp_node!=NULL)//如果栈顶的点还有边
  142. {
  143. if(already_out_stack[temp_node->symbol_head]==)//如果这个点已经处理过了
  144. {
  145. first_graph[begin_stack[begin_stack_pointer]]=temp_node->next;//将这条边摘掉
  146. number_of_edge--;
  147. free(temp_node);
  148. }
  149. else//如果还没处理,那就直接入栈
  150. {
  151. begin_stack_pointer++;
  152. begin_stack[begin_stack_pointer]=temp_node->symbol;
  153. }
  154. }
  155. else//如果没有边,则需要考虑是不是最后一个点
  156. {
  157.  
  158. if(begin_stack_pointer>)//如果不是栈底
  159. {
  160. end_stack_pointer++;
  161. end_stack[end_stack_pointer]=begin_stack[begin_stack_pointer];//换栈
  162. already_out_stack[begin_stack[begin_stack_pointer]]=;//表示已经处理完了
  163. begin_stack_pointer--;
  164. temp_node=first_graph[begin_stack[begin_stack_pointer]];
  165. number_of_edge--;
  166. first_graph[begin_stack[begin_stack_pointer]]=temp_node->next;//把这条边摘除
  167. free(temp);//出栈
  168. }
  169. else//如果这是栈底
  170. {
  171. begin_stack_pointer=;//直接设置为栈空,不需要另外的措施
  172. }
  173. }
  174. }//一直循环到当前节点可达的节点都被处理了
  175. }//所有的边都处理完毕了
  176. end_stack_pointer++;
  177. end_stack[end_stack_pointer]=begin_stack[];//别忘了最后一个节点
  178. already_out_stack[begin_stack[]]=;
  179. //现在按照栈里面的顺序来处理位图
  180. while(end_stack_pointer>)
  181. {
  182. temp_row=first_graph+end_stack[end_stack_pointer];
  183. if(decl_node_table[end_stack[end_stack_pointer]].phrase_link!=NULL)//如果当前的是非终结文法符号
  184. {
  185. for(for_i=;for_i<=node_index;for_i++)
  186. {
  187. if(decl_node_table[temp_row[for_i]].phrase_link!=NULL)//如果引用的是一个非终结符,则开始合并终结符号
  188. {
  189. temp_row_add=first_graph+temp_row[for_i];
  190. for(for_j=;for_j<node_index;for_j++)
  191. {
  192. temp_row[for_j]=temp_row[for_j]|temp_row_add[for_j];
  193. //这里我就不去判断是不是终结文法符号了,因为我们保证了这里是一个拓扑排序
  194. }
  195. }
  196. else
  197. {
  198. //对应终结符的时候,则不需要处理
  199. }
  200. }//搜索完成
  201. end_stack_pointer--;
  202. }
  203. else//对于终结文法符号,在自己所在的位标注为1
  204. {
  205. temp_row[end_stack[end_stack_pointer]]=;
  206. end_stack_pointer--;
  207. }
  208. }//堆栈中的点都处理完成
  209. free(begin_stack);
  210. free(end_stack);
  211. free(already_out_stack);
  212. //释放内存的是好孩子
  213. }//前缀集合处理完成
  214. //现在开始处理后缀集合
  215. //下面这个函数来处理直接后缀
  216. void direct_end(void)
  217. {
  218. int* before_row;
  219. int* after_row;
  220. pdecl_edge before_edge,after_edge;
  221. phrase* current_phrase;
  222. int for_i,for_j;
  223. for(for_i=;for_i<node_index;for_i++)
  224. {
  225. current_phrase=decl_node_table[for_i].phrase_link
  226. if(current_phrase!=NULL)//如果这里有生成式 ,则进行处理
  227. {
  228. before_edge=current_phrase->begin_of_phrase;
  229. after_edge=before_edge->next;
  230. while(after_edge!=NULL)//这个不是空的,则我们需要去处理一个直接后缀
  231. {
  232. before_row=follow_graph_set[before_edge->node_of_dest];
  233. if(decl_node_table[after_edge->node_of_dest].phrase_link!=NULL)//如果不是终结文法符号
  234. {
  235. after_row=first_graph_set[after_edge->node_of_dest];//取出后面文法符号的first集合
  236. for(for_j=;for_j<=node_index;for_j++)//因为是直接后缀,所以先不考虑输入终结符
  237. {
  238. before_row[for_j]=before_row[for_j]|after_row[for_j];//取并集
  239. }
  240. //并集处理完成
  241. }
  242. else
  243. {
  244. before_row[after_edge->node_of_dest]=;//将这一位置为1就行了
  245. }
  246. before_edge=after_edge;
  247. after_edge=after_edge->next;
  248. }//当前产生式处理完成
  249. current_phrase=current_phrase->next;//处理下一个产生式
  250. }//当前产生式头的所有产生式处理完成
  251. }//处理下一个产生式头
  252. //所有的产生式处理完毕
  253. }//直接后缀处理完毕
  254. void indirect_follow(int begin_node_index )
  255. {
  256. //这里又需要走一遍拓扑排序
  257. //注意这趟处理的时候需要考虑输入终结符号
  258. //由于开始符号可以生成任何符号,所以在拓扑排序完成之后,处于栈顶的一般是开始符号
  259. //注意这只是一般情况下,我们并未用理论来证实这个结论,所以还是老老实实的先找到开始符号吧
  260. //这个我们采用参数传递的方法
  261. *(follow_graph_set[begin_node_index]+node_index+)=;//将输入结束符作为开始符号的后缀符号
  262. //下面的内容基本就是复制前面生成first集合的代码
  263. int number_of_edge;
  264. int begin_stack_pointer;
  265. int* begin_stack;
  266. int end_stack_pointer;
  267. int* end_stack;
  268. int* temp_row,temp_row_add;//用来遍历矩阵的变量
  269. pend_graph_node temp_node;//这里改变了节点类型
  270. int* already_out_stack;//代表已经处理过了,已经出栈
  271. int for_i,for_j;
  272. already_out_stack=malloc(sizeof(int)*(node_index+));
  273. for(for_i=;for_i<=node_index+;for_i++)
  274. {
  275. already_out_stack[for_i]=;
  276. }//初始化
  277. begin_stack=malloc(sizeof(int)*(node_index+));//多申请几个又不会怀孕
  278. end_stack=malloc(sizeof(int)*(node_index+));
  279. begin_stack_pointer=end_stack_pointer=;
  280. number_of_edge=number_edge_end;
  281. while(number_of_edge>)//只要还有一条边没有处理
  282. {
  283. for_i=;
  284. while(first_graph[for_i]==NULL)
  285. {
  286. for_i++;
  287. }//找到一个还有边存在的点
  288. begin_stack_pointer++;
  289. begin_stack[begin_stack_pointer]=for_i;
  290. while(begin_stack_pointer>)//好像写的有点问题
  291. {
  292. temp_node=end_graph[begin_stack[begin_stack_pointer]];
  293. if(temp_node!=NULL)//如果栈顶的点还有边
  294. {
  295. if(already_out_stack[temp_node->symbol_head]==)//如果这个点已经处理过了
  296. {
  297. end_graph[begin_stack[begin_stack_pointer]]=temp_node->next;//将这条边摘掉
  298. number_of_edge--;
  299. free(temp_node);
  300. }
  301. else//如果还没处理,那就直接入栈
  302. {
  303. begin_stack_pointer++;
  304. begin_stack[begin_stack_pointer]=temp_node->symbol;
  305. }
  306. }
  307. else//如果没有边,则需要考虑是不是最后一个点
  308. {
  309.  
  310. if(begin_stack_pointer>)//如果不是栈底
  311. {
  312. end_stack_pointer++;
  313. end_stack[end_stack_pointer]=begin_stack[begin_stack_pointer];//换栈
  314. already_out_stack[begin_stack[begin_stack_pointer]]=;//表示已经处理完了
  315. begin_stack_pointer--;
  316. temp_node=first_graph[begin_stack[begin_stack_pointer]];
  317. number_of_edge--;
  318. first_graph[begin_stack[begin_stack_pointer]]=temp_node->next;//把这条边摘除
  319. free(temp);//出栈
  320. }
  321. else//如果这是栈底
  322. {
  323. begin_stack_pointer=;//直接设置为栈空,不需要另外的措施
  324. }
  325. }
  326. }//一直循环到当前节点可达的节点都被处理了
  327. }//所有的边都处理完毕了
  328. end_stack_pointer++;
  329. end_stack[end_stack_pointer]=begin_stack[];//别忘了最后一个节点
  330. already_out_stack[begin_stack[]]=;
  331. //现在按照栈里面的顺序来处理位图
  332. while(end_stack_pointer>)
  333. {
  334. temp_row=follow_graph_set+end_stack[end_stack_pointer];//
  335. for(for_i=;for_i<=node_index;for_i++)
  336. {
  337. if(temp_row[for_i]==&&decl_node_table[for_i].phrase_link!=NULL)//如果这里依赖一个非终结符
  338. {
  339. temp_row_add=follow_graph_set+for_i;
  340. for(for_j=;for_j<=node_index+;for_j++)//注意这里需要考虑输入终结符
  341. {
  342. temp_row[for_j]=temp_row[for_j]|temp_row_add[for_j];
  343. }//合并完成
  344. }//此符号处理完成
  345. }//所有的处理完成
  346. end_stack_pointer--;//堆栈减1
  347. }//堆栈空
  348. //现在所有的后缀信息已经处理完毕
  349. free(begin_stack);
  350. free(end_stack);
  351. free(already_out_stack);
  352. //释放内存的是好孩子
  353. }//

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. java使用jdbc对sqlite 添加、删除、修改的操作

    package com.jb.jubmis.Dao.DaoImpl; import java.io.File;import java.io.FileInputStream;import java.io ...

  2. HTML5中script的async属性异步加载JS

    HTML5中script的async属性异步加载JS     HTML4.01为script标签定义了5个属性: charset 可选.指定src引入代码的字符集,大多数浏览器忽略该值.defer 可 ...

  3. OC: 类的扩展、类的延展、协议、 NSDate

      NSDateFormatter 指定⽇日期格式: NSDateFormatter * formatter = [[NSDateFormatter alloc] init]; [formatter ...

  4. 无责任Windows Azure SDK .NET开发入门篇二[使用Azure AD 进行身份验证-2.2身份验证开发]

    2.2身份验证开发 在我们的案例中,我们是用户通过Web应用程序进行身份识别. 上面的图示说明了如下的一些概念 l Azure AD 是标识提供程序,负责对组织的目录中存在的用户和应用程序的标识进行验 ...

  5. thinkphp 3+ 观后详解 (5)

    static public function dispatch() { $varPath = C('VAR_PATHINFO'); $varAddon = C('VAR_ADDON'); $varMo ...

  6. 操作无法完成,因为文件夹已在另一个程序中打开(the action can't be completed because the folder or a file in it is open in another program)

    解决方法: 启动任务管理器——性能——资源监视器——CPU选项卡——关联的句柄——搜索句柄 ——(输入)要删除的文件夹名——搜索到与文件夹名句柄相关联的进程 (由于此程序进程正在调用文件夹,才造成了对 ...

  7. NAT类型与穿透 及 STUN TURN 协议

    STUN : Simple Traversal of User Datagram Protocol [UDP] Through Network Address Translators [NATs] S ...

  8. 排版系统Latex傻瓜方式使用(论文排版)

    0. 什么是Latex? LaTEX(英语发音:/ˈleɪtɛk/ lay-tek或英语发音:/ˈlɑːtɛk/ lah-tek,音译"拉泰赫").文字形式写作LaTeX.是一种基 ...

  9. iOS开发——动画总结OC篇&所有常用动画总结

    所有常用动画总结 先来装下B,看不懂没关系,其实我也看不懂-

  10. 使用openssl工具生成证书

    第一步. 生成rsa私钥文件 :\> openssl genrsa -out bexio.pem 1024 : 若要加密生成的rsa私钥文件(des3加密) :\> openssl gen ...