简易正则表达式引擎的实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简易正则表达式引擎的实现相关的知识,希望对你有一定的参考价值。

  正则表达式基本每个程序员都会用到,实现正则表达式引擎却似乎是一个很难的任务。实际上,掌握《编译原理》前端的词法分析部分知识就能够实现一个简单的正则表达式引擎。这里推荐一下网易云课堂的课程。http://mooc.study.163.com/course/USTC-1000002001?tid=1000003000#/info

基本的正则表达式  正则表达式由字符与元字符组成,整个表达式用于描述符合某些特定特征的一类字符串,比如说表达式:abc,它表示 "abc" 这个字符串,由 ‘a‘, ‘b‘, ‘c‘ 三个字符按顺序连接在一起。本文要实现的正则表达式比较简单,只实现连接、选择、闭包的功能。定义直接从ppt里截图:

技术分享

实现的大概步骤如下:

技术分享

NFA指的是非确定自动机,对任意的字符,有多个状态可以转换。DFA指的是确定自动机,对任意的字符,最多只有一个状态可以转换。

Thompson算法是一个递归算法,先将单个字符转换为nfa,再根据规则将nfa组合起来。单个字符(比如c)的转化如下

技术分享

 

两个字符(比如e1e2)则中间用ε连接

技术分享

接下来是选择(比如e1|e2)

技术分享

闭包(比如e1*)比较复杂

技术分享

知道了如何将小的nfa组合成大的nfa,那怎么处理正则表达式,将其转换为nfa呢?我们可以像处理四则运算那样,用两个栈来解决,但这只能应对简单情况。还可以用递归下降分析构建抽象语法树,a|b的语法树如下,递归下降分析的具体做法可以看前面推荐的视频或直接看源码

技术分享

由于nfa对任意的字符,有多个状态可以转换,我们需要将其转换为dfa。我们的nfa实际上是ε-NFA,有很多ε边,而dfa是没有ε边的,所以我们可以通过子集构造算法去除这些ε边。子集构造算法的大概思路是将从状态A出发接收某个字符能到达的所有状态(包括接收字符后再通过ε边到达)构成一个子集,这个子集所能到达的所有状态又构成一个子集,到最后就能消除ε边,得到dfa。

对于下图的nfa

技术分享

子集构造算法步骤如下

技术分享

第一列第一行I的意思是从NFA的起始节点经过任意个ε所能到达的结点集合。Ia表示从该集合开始接收一个a所能到达的集合,Ib也就是接收一个b所能到达的状态的集合。

如果Ia和Ib还没出现在I,就把它们填在接下的I里。结果如下

技术分享

技术分享

得到dfa后再用Hopcroft算法最小化dfa,这个算法的思想是将等价的状态浓缩为一个结点。比如对于以下dfa

技术分享

可以简化为

技术分享

这样我们就完成了整个步骤,对于输入的字符串,如果能沿着dfa走到接收状态,就说明能够匹配。具体源码看这里 可能有bug,最后的最小化dfa也没有实现,轻喷。

最后推荐几个相关链接

轮子哥的教程 http://www.cppblog.com/vczh/archive/2008/05/22/50763.html

http://www.cnblogs.com/cute/p/4021689.html 这个人写得比较清楚

再推荐一下网易公开课的课程 http://mooc.study.163.com/learn/USTC-1000002001?tid=1000003000#/learn/content

以上是关于简易正则表达式引擎的实现的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式

干货|正则表达式引擎实现

正则表达式

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

leetcode如何实现 regex 正则表达式引擎

正则表达式如何只匹配一个中文字符