这个版本修改了前面版本的两个个bug。
第一个:识别到字符集的时候,只是将name_number加1,却并不对reg_pattern_table[name_number]进行初始化。
第二个:识别到假名的时候,并不为他分配一个name_number,而只是在hash表中为其分配一个表项。
现在,当识别到这两个的时候,都会为之分配一个name_number,并在reg_pattern_table中正确的初始化。
相关的修改的代码都在tackle_particle()函数中。
还有对tackle_cat()函数的定义移动到了tackle_invisible_cat()函数的前面。
另外一个重大的修改就是,将当前文件分割为多文件了,原来的正则处理部分划分为reg_preprocess.h。
而新的nfa生成部分为nfa_preocess.h。
nfa_process.h基本生成了nfa,但不是按照教科书进行的,具体方法可以看代码,待会我会写一篇文章来讲我的方法。
1 #include "regular_preprocess.h" 2 //这个版本终于要上nfa了,好兴奋啊 3 //由于连个节点之间可能有多条边,所以只能用邻接表来存储了 4 //注意这里是有向图 5 //对于每一个token,这里都会生成一个或多个图节点 6 //但是每个token会附带另外的两个域,即这个token的开始节点和结束节点 7 //因为内部节点对于外部来说是不可连接的,所以不需要暴露 8 //这里有一个难题,就是空转换如何表示,这里我们必须找一个不可打印字符来代表空转换 9 //楼主自己查了一下asc码表,选择了17号字符,因为 10 //鬼才知道那个字符啥意思,而且看描述c语言里面也不会出现这个字符吧, 11 //我看了看键盘,非常欣慰,因为我不知道怎么打出17号字符 12 //好,就选它了。 13 //对于nfa图,这里维护了一个图节点的数组,而这个数组组成了邻接表 14 typedef struct _graph_edge_list 15 { 16 struct _graph_edge_list* next_edge;//下一条边的指针 17 char lable_of_edge;//这个是转换条件 18 int destination_number;//有向边的终点,直接用这个点在图节点数组中的索引来表示 19 }graph_edge_list,*p_edge_list; 20 typedef struct _node_for_token//对于每一个token,我们记录他的进入节点和终止节点,为了连接成大的nfa图用 21 { 22 int begin; 23 int end; 24 }node_for_token,pnode_for_token; 25 node_for_token token_node[100]; 26 //每一个token对应一个节点,所以100就够了,当然现在处理的是小的输入 27 //注意这里有一个特殊的地方,对于括号运算符,他的内容与他的子节点的内容是一样的 28 //而对于假名操作符,他的内容与他的子节点的内容也是一样的,但是他的内容永远都不会被其他节点所利用 29 //因为在生成token的过程中,入栈的是他所代表的子节点,所以他的token是不会被其他的token所引用的 30 //还有一个最需要注意的地方,就是每一个token都有其相对应的token_node,而且这两者的索引都相同 31 //这个设定便利了nfa的处理,同时也就造就了上面说的括号与中括号的特殊性 32 int token_node_number=1;//这里是用来遍历整个token表的,每增加1,则token_node的内容增加1 33 p_edge_list nfa_node[400];//因为生成nfa的过程中可能生成四倍于输入的节点,所以定为这么大 34 int nfa_node_number=0;//这个是nfa中的节点的标号 35 void add_edge(int nfa_node_begin,int nfa_node_end,char label)//添加边的函数 36 { 37 p_edge_list temp_pnode=malloc(sizeof(struct _graph_edge_list)); 38 temp_pnode->lable_of_edge=label; 39 temp_pnode->destination_number=nfa_node_end; 40 temp_pnode->next_edge=nfa_node[nfa_node_begin]; 41 nfa_node[nfa_node_begin]=temp_pnode; 42 } 43 void generate_nfa_node(void) 44 { 45 int reg_pattern_left; 46 int reg_pattern_right; 47 int reg_pattern_origin; 48 int for_i,for_j; 49 int add_edge_from,add_edge_to; 50 //这里建立节点的时候,是从低往高来遍历标号,来生成节点的 51 //因为我们在生成token的时候,保证了低标号的不会引用高标号的token,因此是一个拓扑排序 52 while(token_node_number<name_number) 53 { 54 switch(reg_pattern_table[token_node_number].type) 55 { 56 case closure: 57 //对于闭包运算,我们可以直接将子节点的开始节点与结束节点之间添加两条空边 58 //不过这两条边的方向相反 ,偷懒哈哈 59 reg_pattern_origin=reg_pattern_table[token_node_number].sub; 60 add_edge_from=token_node[reg_pattern_origin].begin; 61 add_edge_to=token_node[reg_pattern_origin].end; 62 add_edge(add_edge_from,add_edge_to,(char)17); 63 add_edge(add_edge_to,add_edge_from,(char)17); 64 token_node[token_node_number].begin=add_edge_from; 65 token_node[token_node_number].end=add_edge_to; 66 token_node_number++; 67 //处理下一个token_node 68 break; 69 case cat: 70 //对于cat节点,那就非常简单了,只需要在原来的左节点的结束点与右节点的开始点之间连一条边 71 //然后设置一下当前token_node的开始节点和结束节点 72 //然后token_node_number加一,由于这里没有生成新的nfa节点,所以nfa_node_number不变 73 reg_pattern_left=reg_pattern_table[token_node_number].left; 74 reg_pattern_right=reg_pattern_table[token_node_number].right; 75 token_node[token_node_number].begin=token_node[reg_pattern_left].begin; 76 token_node[token_node_number].end=token_node[reg_pattern_right].end; 77 add_edge_from=token_node[reg_pattern_left].end; 78 add_edge_to=token_node[reg_pattern_right].begin; 79 add_edge(add_edge_from,add_edge_to,(char)17); 80 token_node_number++; 81 break; 82 case or: 83 //对于or节点,需要增加两个节点和四条边,郁闷啊 84 reg_pattern_left=reg_pattern_table[token_node_number].left; 85 reg_pattern_right=reg_pattern_table[token_node_number].right; 86 nfa_node_number++; 87 //建立这个token_node的头节点,以及初始化他的邻接表 88 token_node[token_node_number].begin=nfa_node_number; 89 nfa_node[nfa_node_number]=NULL; 90 add_edge_from=nfa_node_number; 91 add_edge_to=token_node[reg_pattern_left].begin; 92 add_edge(add_edge_from,add_edge_to,(char)17); 93 add_edge_to=token_node[reg_pattern_right].begin; 94 add_edge(add_edge_from,add_edge_to,(char)17); 95 nfa_node_number++; 96 //建立这个token_node的尾节点,以及增加两条指向他的边 97 token_node[token_node_number].end=nfa_node_number; 98 nfa_node[nfa_node_number]=NULL; 99 add_edge_to=nfa_node_number; 100 add_edge_from=token_node[reg_pattern_left].end; 101 add_edge(add_edge_from,add_edge_to,(char)17); 102 add_edge_from=token_node[reg_pattern_right].begin; 103 add_edge(add_edge_from,add_edge_to,(char)17); 104 token_node_number++; 105 break; 106 case parenthesis: 107 token_node[token_node_number].begin=token_node[reg_pattern_table[token_node_number].sub].begin; 108 token_node[token_node_number].end=token_node[reg_pattern_table[token_node_number].sub].end; 109 token_node_number++; 110 break; 111 case alias: 112 //对于假名,直接初始化他的开始节点和结束节点就行了,反正也没人会用它了 113 token_node[token_node_number].begin=token_node[reg_pattern_table[token_node_number].origin_number].begin; 114 token_node[token_node_number].end=token_node[reg_pattern_table[token_node_number].origin_number].end; 115 token_node_number++; 116 break; 117 case literal_char: 118 //对于单字符,直接新建两个节点,然后在这两个节点中建立一条边 119 //然后初始化token_node 120 nfa_node_number++; 121 nfa_node[nfa_node_number]=NULL; 122 token_node[token_node_number].end=nfa_node_number; 123 add_edge_to=nfa_node_number; 124 nfa_node_number++; 125 nfa_node[nfa_node_number]=NULL; 126 token_node[token_node_number].begin=nfa_node_number; 127 add_edge_from=nfa_node_number; 128 add_edge(add_edge_from,add_edge_to,reg_pattern_table[token_node_number].value); 129 token_node_number++; 130 break; 131 case set_of_char: 132 for_i=reg_pattern_table[token_node_number].begin; 133 for_j=reg_pattern_table[token_node_number].end; 134 nfa_node_number++; 135 //增加一个节点,当前是作为尾节点 136 token_node[token_node_number].end=nfa_node_number; 137 nfa_node[nfa_node_number]=NULL; 138 add_edge_to=nfa_node_number; 139 nfa_node_number++; 140 //增加一个节点,作为头节点 141 add_edge_from=nfa_node_number; 142 token_node[nfa_node_number].begin=nfa_node_number; 143 nfa_node[nfa_node_number]=NULL; 144 for(for_i;for_i<=for_j;for_i++) 145 { 146 //对于字符集里面的每个字符,都需要增加一条边 147 add_edge(add_edge_from,add_edge_to,(char)for_i); 148 } 149 token_node_number++; 150 break; 151 case maybe_exist: 152 //处理问号运算符,其实这个就比较简单了,只需要在子表达式的头节点与尾节点之间加一条空边 153 reg_pattern_origin=reg_pattern_table[token_node_number].sub; 154 add_edge_from=token_node[reg_pattern_origin].begin; 155 add_edge_to=token_node[reg_pattern_origin].end; 156 add_edge(add_edge_from,add_edge_to,(char)17); 157 token_node_number++; 158 break; 159 case one_or_more: 160 //这种情况下,我另外建立一个节点当作本token的尾节点 161 //然后添加两条空边,起点都是子节点的尾节点,终点一个是子节点的开始节点 162 //另外一个就是当前节点的尾节点 163 nfa_node_number++; 164 //这个节点是作为当前节点的尾节点 165 nfa_node[nfa_node_number]=NULL; 166 token_node[token_node_number].end=nfa_node_number; 167 add_edge_to=nfa_node_number; 168 reg_pattern_origin=reg_pattern_table[token_node_number].sub; 169 add_edge_from=token_node[reg_pattern_origin].end; 170 add_edge(add_edge_from,add_edge_to,(char)17); 171 add_edge_to=token_node[reg_pattern_origin].begin; 172 add_edge(add_edge_from,add_edge_to,(char)17); 173 token_node_number++; 174 break; 175 default: 176 printf("a type can't be recognised, please check "); 177 token_node_number++; 178 break; 179 } 180 } 181 }