最初步的正则表达式引擎:生成nfa
2013-06-24 13:23
225 查看
这个版本修改了前面版本的两个个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,但不是按照教科书进行的,具体方法可以看代码,待会我会写一篇文章来讲我的方法。
第一个:识别到字符集的时候,只是将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,但不是按照教科书进行的,具体方法可以看代码,待会我会写一篇文章来讲我的方法。
#include "regular_preprocess.h" //这个版本终于要上nfa了,好兴奋啊 //由于连个节点之间可能有多条边,所以只能用邻接表来存储了 //注意这里是有向图 //对于每一个token,这里都会生成一个或多个图节点 //但是每个token会附带另外的两个域,即这个token的开始节点和结束节点 //因为内部节点对于外部来说是不可连接的,所以不需要暴露 //这里有一个难题,就是空转换如何表示,这里我们必须找一个不可打印字符来代表空转换 //楼主自己查了一下asc码表,选择了17号字符,因为 //鬼才知道那个字符啥意思,而且看描述c语言里面也不会出现这个字符吧, //我看了看键盘,非常欣慰,因为我不知道怎么打出17号字符 //好,就选它了。 //对于nfa图,这里维护了一个图节点的数组,而这个数组组成了邻接表 typedef struct _graph_edge_list { struct _graph_edge_list* next_edge;//下一条边的指针 char lable_of_edge;//这个是转换条件 int destination_number;//有向边的终点,直接用这个点在图节点数组中的索引来表示 }graph_edge_list,*p_edge_list; typedef struct _node_for_token//对于每一个token,我们记录他的进入节点和终止节点,为了连接成大的nfa图用 { int begin; int end; }node_for_token,pnode_for_token; node_for_token token_node[100]; //每一个token对应一个节点,所以100就够了,当然现在处理的是小的输入 //注意这里有一个特殊的地方,对于括号运算符,他的内容与他的子节点的内容是一样的 //而对于假名操作符,他的内容与他的子节点的内容也是一样的,但是他的内容永远都不会被其他节点所利用 //因为在生成token的过程中,入栈的是他所代表的子节点,所以他的token是不会被其他的token所引用的 //还有一个最需要注意的地方,就是每一个token都有其相对应的token_node,而且这两者的索引都相同 //这个设定便利了nfa的处理,同时也就造就了上面说的括号与中括号的特殊性 int token_node_number=1;//这里是用来遍历整个token表的,每增加1,则token_node的内容增加1 p_edge_list nfa_node[400];//因为生成nfa的过程中可能生成四倍于输入的节点,所以定为这么大 int nfa_node_number=0;//这个是nfa中的节点的标号 void add_edge(int nfa_node_begin,int nfa_node_end,char label)//添加边的函数 { p_edge_list temp_pnode=malloc(sizeof(struct _graph_edge_list)); temp_pnode->lable_of_edge=label; temp_pnode->destination_number=nfa_node_end; temp_pnode->next_edge=nfa_node[nfa_node_begin]; nfa_node[nfa_node_begin]=temp_pnode; } void generate_nfa_node(void) { int reg_pattern_left; int reg_pattern_right; int reg_pattern_origin; int for_i,for_j; int add_edge_from,add_edge_to; //这里建立节点的时候,是从低往高来遍历标号,来生成节点的 //因为我们在生成token的时候,保证了低标号的不会引用高标号的token,因此是一个拓扑排序 while(token_node_number<name_number) { switch(reg_pattern_table[token_node_number].type) { case closure: //对于闭包运算,我们可以直接将子节点的开始节点与结束节点之间添加两条空边 //不过这两条边的方向相反 ,偷懒哈哈 reg_pattern_origin=reg_pattern_table[token_node_number].sub; add_edge_from=token_node[reg_pattern_origin].begin; add_edge_to=token_node[reg_pattern_origin].end; add_edge(add_edge_from,add_edge_to,(char)17); add_edge(add_edge_to,add_edge_from,(char)17); token_node[token_node_number].begin=add_edge_from; token_node[token_node_number].end=add_edge_to; token_node_number++; //处理下一个token_node break; case cat: //对于cat节点,那就非常简单了,只需要在原来的左节点的结束点与右节点的开始点之间连一条边 //然后设置一下当前token_node的开始节点和结束节点 //然后token_node_number加一,由于这里没有生成新的nfa节点,所以nfa_node_number不变 reg_pattern_left=reg_pattern_table[token_node_number].left; reg_pattern_right=reg_pattern_table[token_node_number].right; token_node[token_node_number].begin=token_node[reg_pattern_left].begin; token_node[token_node_number].end=token_node[reg_pattern_right].end; add_edge_from=token_node[reg_pattern_left].end; add_edge_to=token_node[reg_pattern_right].begin; add_edge(add_edge_from,add_edge_to,(char)17); token_node_number++; break; case or: //对于or节点,需要增加两个节点和四条边,郁闷啊 reg_pattern_left=reg_pattern_table[token_node_number].left; reg_pattern_right=reg_pattern_table[token_node_number].right; nfa_node_number++; //建立这个token_node的头节点,以及初始化他的邻接表 token_node[token_node_number].begin=nfa_node_number; nfa_node[nfa_node_number]=NULL; add_edge_from=nfa_node_number; add_edge_to=token_node[reg_pattern_left].begin; add_edge(add_edge_from,add_edge_to,(char)17); add_edge_to=token_node[reg_pattern_right].begin; add_edge(add_edge_from,add_edge_to,(char)17); nfa_node_number++; //建立这个token_node的尾节点,以及增加两条指向他的边 token_node[token_node_number].end=nfa_node_number; nfa_node[nfa_node_number]=NULL; add_edge_to=nfa_node_number; add_edge_from=token_node[reg_pattern_left].end; add_edge(add_edge_from,add_edge_to,(char)17); add_edge_from=token_node[reg_pattern_right].begin; add_edge(add_edge_from,add_edge_to,(char)17); token_node_number++; break; case parenthesis: token_node[token_node_number].begin=token_node[reg_pattern_table[token_node_number].sub].begin; token_node[token_node_number].end=token_node[reg_pattern_table[token_node_number].sub].end; token_node_number++; break; case alias: //对于假名,直接初始化他的开始节点和结束节点就行了,反正也没人会用它了 token_node[token_node_number].begin=token_node[reg_pattern_table[token_node_number].origin_number].begin; token_node[token_node_number].end=token_node[reg_pattern_table[token_node_number].origin_number].end; token_node_number++; break; case literal_char: //对于单字符,直接新建两个节点,然后在这两个节点中建立一条边 //然后初始化token_node nfa_node_number++; nfa_node[nfa_node_number]=NULL; token_node[token_node_number].end=nfa_node_number; add_edge_to=nfa_node_number; nfa_node_number++; nfa_node[nfa_node_number]=NULL; token_node[token_node_number].begin=nfa_node_number; add_edge_from=nfa_node_number; add_edge(add_edge_from,add_edge_to,reg_pattern_table[token_node_number].value); token_node_number++; break; case set_of_char: for_i=reg_pattern_table[token_node_number].begin; for_j=reg_pattern_table[token_node_number].end; nfa_node_number++; //增加一个节点,当前是作为尾节点 token_node[token_node_number].end=nfa_node_number; nfa_node[nfa_node_number]=NULL; add_edge_to=nfa_node_number; nfa_node_number++; //增加一个节点,作为头节点 add_edge_from=nfa_node_number; token_node[nfa_node_number].begin=nfa_node_number; nfa_node[nfa_node_number]=NULL; for(for_i;for_i<=for_j;for_i++) { //对于字符集里面的每个字符,都需要增加一条边 add_edge(add_edge_from,add_edge_to,(char)for_i); } token_node_number++; break; case maybe_exist: //处理问号运算符,其实这个就比较简单了,只需要在子表达式的头节点与尾节点之间加一条空边 reg_pattern_origin=reg_pattern_table[token_node_number].sub; add_edge_from=token_node[reg_pattern_origin].begin; add_edge_to=token_node[reg_pattern_origin].end; add_edge(add_edge_from,add_edge_to,(char)17); token_node_number++; break; case one_or_more: //这种情况下,我另外建立一个节点当作本token的尾节点 //然后添加两条空边,起点都是子节点的尾节点,终点一个是子节点的开始节点 //另外一个就是当前节点的尾节点 nfa_node_number++; //这个节点是作为当前节点的尾节点 nfa_node[nfa_node_number]=NULL; token_node[token_node_number].end=nfa_node_number; add_edge_to=nfa_node_number; reg_pattern_origin=reg_pattern_table[token_node_number].sub; add_edge_from=token_node[reg_pattern_origin].end; add_edge(add_edge_from,add_edge_to,(char)17); add_edge_to=token_node[reg_pattern_origin].begin; add_edge(add_edge_from,add_edge_to,(char)17); token_node_number++; break; default: printf("a type can't be recognised, please check\n"); token_node_number++; break; } } }
相关文章推荐
- Vczh Library++3.0之正则表达式引擎(生成epsilon-NFA)
- 最初步的正则表达式引擎:nfa的转换规则。
- 最初步的正则表达式引擎:将显示的连接符改为了非显示的连接符
- 理解DFA和NFA正则表达式引擎
- 正则表达式: NFA引擎匹配原理
- 最初步的正则表达式引擎:增加了字符集表示和?运算符和+运算符
- 实现一个 DFA 正则表达式引擎 - 2. NFA 的构建
- 正则表达式: NFA引擎匹配原理
- 一个正则表达式引擎的设计和实施1-如何通过NFA识别字符串
- 正则表达式引擎:nfa的转换规则。
- 正则表达式: NFA引擎匹配原理
- 正则表达式:NFA引擎匹配原理
- 最初步的正则表达式引擎:在上个版本的基础上增加了转义字符
- 基于ε-NFA的正则表达式引擎
- 最初步的正则表达式引擎
- 最初步的正则表达式引擎:支持子表达式替换
- 根据正则表达式生成随机字符串
- js 正则学习小记之NFA引擎
- [C#][固定格式网页解析]使用正则表达式处理网页的初步体会
- [C#][固定格式网页解析]使用正则表达式处理网页的初步体会