这个版本修改了前面版本的两个个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,但不是按照教科书进行的,具体方法可以看代码,待会我会写一篇文章来讲我的方法。

 #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[];
//每一个token对应一个节点,所以100就够了,当然现在处理的是小的输入
//注意这里有一个特殊的地方,对于括号运算符,他的内容与他的子节点的内容是一样的
//而对于假名操作符,他的内容与他的子节点的内容也是一样的,但是他的内容永远都不会被其他节点所利用
//因为在生成token的过程中,入栈的是他所代表的子节点,所以他的token是不会被其他的token所引用的
//还有一个最需要注意的地方,就是每一个token都有其相对应的token_node,而且这两者的索引都相同
//这个设定便利了nfa的处理,同时也就造就了上面说的括号与中括号的特殊性
int token_node_number=;//这里是用来遍历整个token表的,每增加1,则token_node的内容增加1
p_edge_list nfa_node[];//因为生成nfa的过程中可能生成四倍于输入的节点,所以定为这么大
int nfa_node_number=;//这个是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));
add_edge(add_edge_to,add_edge_from,(char));
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));
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));
add_edge_to=token_node[reg_pattern_right].begin;
add_edge(add_edge_from,add_edge_to,(char));
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));
add_edge_from=token_node[reg_pattern_right].begin;
add_edge(add_edge_from,add_edge_to,(char));
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));
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));
add_edge_to=token_node[reg_pattern_origin].begin;
add_edge(add_edge_from,add_edge_to,(char));
token_node_number++;
break;
default:
printf("a type can't be recognised, please check\n");
token_node_number++;
break;
}
}
}

最初步的正则表达式引擎:生成nfa的更多相关文章

  1. 最初步的正则表达式引擎:nfa的转换规则。

    [在此处输入文章标题] 正则到nfa 前言 在写代码的过程中,本来还想根据龙书上的说明来实现re到nfa的转换.可是写代码的时候发现,根据课本来会生成很多的无用过渡节点和空转换边,需要许多的代码.为了 ...

  2. 正则表达式引擎:nfa的转换规则。

    正则表达式引擎:nfa的转换规则. 正则到nfa 前言 在写代码的过程中,本来还想根据龙书上的说明来实现re到nfa的转换.可是写代码的时候发现,根据课本来会生成很多的无用过渡节点和空转换边,需要许多 ...

  3. 基于ε-NFA的正则表达式引擎

    正则表达式几乎每个程序员都会用到,对于这么常见的一个语言,有没有想过怎么去实现一个呢?乍一想,也许觉得困难,实际上实现一个正则表达式的引擎并没有想像中的复杂,<编译原理>一书中有一章专门讲 ...

  4. 实现一个正则表达式引擎in Python(一)

    前言 项目地址:Regex in Python 开学摸鱼了几个礼拜,最近几天用Python造了一个正则表达式引擎的轮子,在这里记录分享一下. 实现目标 实现了所有基本语法 st = 'AS342abc ...

  5. 实现一个正则表达式引擎in Python(二)

    项目地址:Regex in Python 在看一下之前正则的语法的 BNF 范式 group ::= ("(" expr ")")* expr ::= fact ...

  6. 实现一个正则表达式引擎in Python(三)

    项目地址:Regex in Python 前两篇已经完成的写了一个基于NFA的正则表达式引擎了,下面要做的就是更近一步,把NFA转换为DFA,并对DFA最小化 DFA的定义 对于NFA转换为DFA的算 ...

  7. 1000行代码徒手写正则表达式引擎【1】--JAVA中正则表达式的使用

    简介: 本文是系列博客的第一篇,主要讲解和分析正则表达式规则以及JAVA中原生正则表达式引擎的使用.在后续的文章中会涉及基于NFA的正则表达式引擎内部的工作原理,并在此基础上用1000行左右的JAVA ...

  8. 【C++】正则表达式引擎学习心得

    最近参照一些资料实现了一个非常简易的正则表达式引擎,支持基本的正则语法 | + * ()等. 实现思路是最基本的:正则式->AST->NFA->DFA. 以下是具体步骤: 一. 正则 ...

  9. (2015大作业)茹何优雅的手写正则表达式引擎(regular expression engine

    貌似刚开学的时候装了个逼,和老师立了个flag说我要写个正则表达式引擎,然后学期末估计老师早就忘了这茬了,在历时3个月的懒癌发作下,终于在这学期末deadline的时候花了一个下午加晚上在没有网的房间 ...

随机推荐

  1. Java 关于 == 和 equal()的区别

    因为用new创建了两个对象,所以a和b指向两个不同的内存地址,所以返回false equal()是object的方法,所以只适用于对象,不使用于基本类型.不过equal()默认是用“==”比较两个对象 ...

  2. android手机ping不通linux的ip地址

    我的linux是装载虚拟机里的,修改虚拟机的网络连接方式为桥接模式即可.

  3. Android LIstView初次创建getview方法执行多次问题

    写listview优化的时候,发现Listview初次创建的时候会多次执行getView方法. <?xml version="1.0" encoding="utf- ...

  4. OAB配置

    OAB管理: http://blogs.technet.com/b/exchange_chs/archive/2013/01/31/exchange-server-2013-oab-managing- ...

  5. C# DateTime 日期加1天 减一天 加一月 减一月 等方法

    //今天 DateTime.Now.Date.ToShortDateString(); //昨天,就是今天的日期减一 DateTime.Now.AddDays(-).ToShortDateString ...

  6. windows 32位系统中进程最大可用内存空间为3GB (转)

    http://msdn.microsoft.com/zh-cn/library/ms189334.aspx 进程地址空间 所有 32 位应用程序都有 4 GB 的进程地址空间(32 位地址最多可以映射 ...

  7. 利用FluorineFX录制音频与视频

    要做一个完整的录制程序,处理RPC请求的类不仅要继承ApplicationAdapter,还要继承IStreamService接口,该接口定义了play(),pause(),publish(),cre ...

  8. windows 支持curl命令

    curl 是一般linux发行版中都带有的小工具,利用这个工具可以很方便的下载文件, 我一般使用这个工具来查看某个页面相应的HTTP头信息,在Windows系统中我们也一样可以使用这个工具,如果不需要 ...

  9. 0c-40-ARC下多对象内存管理

    1个人拥有1条狗. 问题1:人拥有狗作为成员变量,此时使用weak,释放过程是什么样? Person *p = [Person new]; Dog *d = [Dog new]; //设置人拥有dog ...

  10. selenium python 定位一组对象

    为什么定位一组对象? 定位一组对象的思想    在定位一组对象的过程中我们如何实现?以前的都是通过具体的对象定位,那么定位一组我们就需要通过css来定位   在单个定位对象中使用的是find_elem ...