阅读博客的朋友可以到我的网易云课堂中,通过视频的方式查看代码的调试和执行过程:
http://study.163.com/course/courseMain.htm?courseId=1002830012

一节我们讨论了Thompson构造的算法后,从这节开始,我们通过代码着手实现该算法,本节主要讨论算法要用到的数据结构,同时我们从简单入手,先对简单的正则表达式构建其对应的NFA状态机,以后我们再一步一步的向构建更复杂的状态机延伸。任何复杂的形态都建立在多种简单形态的组合之上。

NFA节点的数据结构表示:
通过Thompson构造法产生的状态机有如下特点:
1. 状态机一定只有一个初始状态节点和一个结束状态节点。
2. 任何一个状态,最多只有两条出去的转换边。
3. 每个状态节点所拥有的边最多只有三种可能:
(1) 有一条边对应的是单个输入字符,例如:

(2) 有一条边,对应的是ε:

(3) 有两条出去的边,对应的都是ε:

对应上述几个特点,我们在程序中,通过下面的数据结构来描述一个NFA的节点(eclipse):

Nfa 对应于状态机的一个节点,如果该节点的边对应情况1,那么next2 等于null, next 指向下一个状态,同时edge的值对应该边的输入字符。如果该边对应的输入是一个字符集类,例如[0-9],那么所有可接受的字符都存放在inputSet中, 同时,edge的值等于CCL。

如果该节点只有一条出去的ε边,那么next 指向下一个状态节点, next2 等于 null, edge 的值等于 EPSILON.

如果该节点有两条出去的ε边,那么next 和 next2 分别指向要转换的下一个节点,同时edge的值等于 EPSILON.

如果该节点没有出去的边,那么next 和 next2 都是null, edge 的值就是EMPTY.

inputSet 专门用来存放字符集类,由于我们只处理ASCII输入,因此iputSet最多包含127个元素。 例如表达式[a-z]接收任何小写字母,这样inputSet 就用来存储a 到 z 的26个字母的ASCII 值。对这个集合,我在代码中专门定义了一个取补操作Nfa::setComplement,例如 [^a-z] 接收的是任何不是小写字母的ASCII 字符,要使得inputSet中存放所有不是小写字母的ASCII码符号,需要的操作是先将a到z这26个字符加入到inputSet, 然后调用setComplement接口,这样inputSet存储的就是所有非小写字母的ASCII字符的集合了,我们从代码上可以理解上面讲的逻辑:

节点的内存管理
当程序要生成一个NFA节点对象时,它不会直接调用new, 在程序中,我们专门使用一个类叫NfaManager来负责NFA节点的构造和回收。

当它初始化时,先构造足够多的NFA节点对象放在内存池中,当外界需要获取一个NFA节点时,调用它的newNfa 接口,该接口先查看节点是否已经分配光了,如果没有可用节点,则显示内存错误。要不然在堆栈上看有没有原先被回收的节点可以使用,如果有,则将栈上的节点返回,如果没有,那么再从内存池中分配一个节点。
当外界不再需要NFA节点对象后,调用它的discardNfa接口,该接口将回收的节点放到堆栈上,以待将来复用。

大家回忆一下前面提到的有Thompson 构造出来的Nfa限状态机(打开相应文档), 它有一个特点就是,通过Thompson 构造算法构建的状态机,它必有一个起始状态,和一个结束状态,因此我们专门定义了一个类,来存储这两个状态:

该类很简单,也就是两个指针,一个指向头,一个指向尾。

NFA状态机构造实现:

在代码中,NfaMachineConstructor.java 专门负责通过读入正则表达式来构造状态机。我们通过简单到复杂的方式,一步一步剖析Thompson构造算法。
首先,我们先看最简单的正则表达式,单字符匹配 :c 是如何构造的。在代码中,接口 NfaMachineConstructor:constructSingleCharacter.

首先,我们判断当前正则表达式是否是单字符匹配,如果是,那么我们分别分配两个Nfa节点作为头和尾。头节点是pairOut.startNode, 尾节点是pairOut.endNode. 接着把头结点的next 指向尾节点。同时将头节点的edge设置为匹配字符的ascii 值。总体流程如下:

接下来我们看看,正则表达式 “.” 也就是匹配任意单字符,是如何构造Nfa状态机的:

首先,判断当前表达式是否匹配 . , 如果匹配,我们往下走构建单字符匹配的Nfa状态机器。第一步任然是先分配起始节点和结束节点。第二步,将起始节点的next指针指向结束节点。接下来要注意的是,匹配任意字符,意味着当输入为任何ASCII 字符时,除了回车换行两个字符外,我们的状态机都会接收。因此我们的状态机接收的是一个字符集类,[…] 中括号里面的三点表示除了\r \n 外的任何ASCII 字符, 因此我们把起始节点的edge 设置为 CCL. 然后把\r \n 加入集合,并对结合做补操作,也就是把集合当前的元素去掉,将不是当前元素的其他元素加入集合,总体流程如下:

接下来我们看看,字符集类的正则表达式如何构建Nfa状态机,例如:[abcd], 该字符集类匹配a,b,c,d4个字符, [0-9] 匹配0到9九个数字:

一开始,我们任然分配两个节点作为起始和结束。由于识别的是字符集类,所有edge设置为CCL, 接下来调用dodash 函数将字符集类对应的字符放入到Nfa节点的inputSet 中,然后再检查正则表达式有没有以 ] 作为结束,如果有 [ 做为开头,但没有以 ] 作为结束,那么正则表达式是错误的,要通过ErrorHandler输出错误,我们再看看dodash的实现:

如果字符集类是[abcd]这种没有横杆的情况,那么 if (lexer.MatchToken(Lexer.Token.DASH) == false) 里面的代码会被执行,这样a,b,c,d四个字符,或者说[ 和 ]之间的字符会被加入到inputSet中, 如果有横杆,例如[0-9] 那么下面的for 循环会执行,它会把0 到 9 之间的数字字符加入到inputSet中, 总体流程如下:

最后我们看看对字符集类进行取反的情况,例如形如[^0-9], 也就是匹配所有不是数字字符,它的实现简单,首先完成上面所做的步骤,然后调用setComplement对inputSet做取补操作就可以了:

if(lexer.MatchToken(Lexer.Token.AT_BOL)) 用于判断[之后是不是跟着^,如果是,那意味着取反操作,于是先将字符集加入到inputSet,然后再对inputSet调用setComlement, 从而实现取反结果。

最后,我们把前面这几种操作结合起来,放入到一个函数叫term中:

只要调用term, 我们便可以根据具体情况,构造上面说过的几种Nfa有限状态自动机。接下来,我将以单行调试的方式向大家展示相关代码的实现逻辑

代码实现Thompson构造:由简单到复杂的构建NFA状态机相关推荐

  1. 有限状态自动机java实现_用java开发编译器之:Thompson构造,将正则表达式转换为有限状态自动机...

    阅读博客的朋友可以到我的网易云课堂中,通过视频的方式查看代码的调试和执行过程: 上一节,我们通过代码,实现了一个有限状态自动机,并将其应用于对整形和浮点数的识别.构造有限状态自动机,并驱动它,从而实现 ...

  2. 用java开发编译器之:Thompson构造,将正则表达式转换为有限状态自动机

    阅读博客的朋友可以到我的网易云课堂中,通过视频的方式查看代码的调试和执行过程: http://study.163.com/course/courseMain.htm?courseId=10028300 ...

  3. 代码和普通的java_Java中普通代码块,构造代码块,静态代码块区别及代码示例...

    对静态代码块以及构造函数的执行先后顺序,一直很迷惑,看了孙伟琴老师的<java面向对象编程>又结合毕老师的java基础视频中的讲解,现用下面的这段小程序来简明扼要的叙述一下,以加深理解,与 ...

  4. 牛客网Java刷题知识点之什么是代码块、普通代码块、静态代码块、同步代码块、构造代码块以及执行顺序...

    不多说,直接上干货! 这种形式的程序段我们将其称之为代码块,所谓代码块就是用大括号({})将多行代码封装在一起,形成一个独立的数据体,用于实现特定的算法.一般来说代码块是不能单独运行的,它必须要有运行 ...

  5. Java普通代码块,构造代码块,静态代码块区别,执行顺序的代码实例

    Java普通代码块,构造代码块,静态代码块区别,执行顺序的代码实例 2018年05月08日 08:12:48 WJN不忘初心 阅读数:34 除了说普通代码块,静态代码块,构造代码块的执行顺序外,还有静 ...

  6. java子类代码块_java中父类子类静态代码块、构造代码块执行顺序

    父类静态(代码块,变量赋值二者按顺序执行) 子类静态 父类构造代码块 父类构造方法 子类构造代码块 子类构造方法 普通方法在实列调用的时候执行,肯定位于上面之后了 //父类A public class ...

  7. 将Linux代码移植到Windows的简单方法

    将Linux代码移植到Windows的简单方法 一.前言 Linux拥有丰富各种源代码资源,但是大部分代码在Windows平台情况是无法正常编译的.Windows平台根本无法直接利用这些源代码资源.如 ...

  8. 构造一个简单的神经网络,以DQN方式实现小游戏的自动控制

    在之前的文章中,我们做了如下工作: 如何设计一个类flappy-bird小游戏:[python实战]使用pygame写一个flappy-bird类小游戏 | 设计思路+项目结构+代码详解|新手向 DF ...

  9. filter hid_如何构造一个简单的USB过滤驱动程序

    本文分三部分来介绍如何构造一个简单的USB过滤驱动程序,包括"基本原理"."程序的实现"."使用INF安装".此文的目的在于希望读者了解基本 ...

  10. 构造一个简单的操作系统内核,详解进程切换细节

    (1)基本功能介绍 如题,本文将介绍如何构造一个简单的操作系统内核(基于内核版本3.9.4 ).它有以下功能: 1:进程的管理 2:进程的初始化 3 : 进程基于时间片的调度 (2)实操步骤 1 安装 ...

最新文章

  1. [洛谷3811]【模板】乘法逆元
  2. python虚拟环境的使用
  3. Java中super与this
  4. python中字典不自动排序/删除指定类型文件/执行可执行文件的返回值
  5. idea控制台搜索功能
  6. 怎么换c语言程序窗口背景图,vc++如何给窗体添加背景图片
  7. 中小型企业网络IP地址规划案例
  8. 《编程珠玑》-- 开篇:BitMap算法用于磁盘文件排序的原理与实现
  9. 夜神模拟器的安装与使用
  10. C语言《程序设计基础课程设计》题库
  11. 遭遇Win32.Loader.c,Trojan.PSW.Win32.GameOnline,Trojan.PSW.Win32.AskTao等1
  12. 迅为开发板4412开发板-ANROID系统的烧写方法分享
  13. C# 实例解释面向对象编程中的单一职责原则
  14. python 按键精灵脚本_[620]使用Python实现一个按键精灵
  15. hdu 1078 FatMouse Chees
  16. SSM理发店会员管理系统
  17. Tableau4——标靶图,甘特图,瀑布图
  18. 链表的二路归并排序 Sort List
  19. Microsoft Office for Mac 2021 (Office 365) 16.55 Universal
  20. 记录小吴的电脑维修记录

热门文章

  1. shader里的uv是什么
  2. Entegris EUV 1010光罩盒展现极低的缺陷率,已获ASML认证
  3. 用vim解压各种格式
  4. BigDecimal的equals方法做等值比较问题
  5. Hadoop伪分布和全分布部署指南
  6. 5G NR 下行同步SSB(1)-- SSB时频资源
  7. NR-DL PDSCH Resource allocation 下行资源分配
  8. 中国电信无限流量卡服务器,拒绝套路!中国电信再出新版“无限量”卡,流量不限速!...
  9. linux so 加壳,[android] 从加壳的so文件中抽出symbols
  10. Shell和Python中的“正三角”和“倒三角”