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