大概和我不以程序员为职业有关吧,我本人是比较喜欢算法的那种,固然是比不了科班出身的。 html
好比我就写过不少版本的算术表达式解析器,优先级堆栈的;二叉树的;分治策略的;修正计算顺序的…… python
正则表达式两个我也写过两个,一个采用的回溯算法,另外一个则是二叉树版本的,虽然这俩却是都能用吧,可是对分组的处理(分组是采用递归调用的方法实现的,至关于另外一个正则表达式)实在是不太好,这个缺点致使不少时候搜索的结果会遗漏不少,而二叉树版本的还有个缺点是选择操做“|”只能得到第一个匹配………… 程序员
就效果看,这俩都是残次品。 正则表达式
后来,我在vczh的博客里看到了他的一个科普贴子,在那里面,我才了解到还有有限自动机这种方法。 算法
以后我花了很多时间去思考,由于总以为这个方法太麻烦了,断断续续有2-3个月吧,想来想去也没想出什么更好的法子,最后仍是以为只有用有限自动机才合适。 数据结构
不过多少我仍是作了些修改的,倒不是为了效率,是为了减小打字量…… 函数
主要修改以下: spa
(1)不每一个字符转移一次状态,而是尽量的把连续的字符做为一个总体进行比对。 设计
(2)不弄什么字母分组表,数据结构就使用指针链表,节省工做量 指针
(3)“{a,b}”这种有次数限定的重复,vczh是采用复制节点实现的,我采用增长状态边来解决(效率会降低)
(4)分组命名,向前向后预查这些花哨而实用的功能,俺就丢了
那么,总体上看,正则表达式引擎的设计我就划分为以下几个步骤:
1)对规则字符串作预处理,主要是换行这类要使用转义符的字符,这个预处理能够把这些字符还原
2)将规则字符串进行划分,修正成操做符、操做数的列表形式
3)对这个列表进行处理,得到NFA状态转移图
4)将NFA状态转移图里的ε边消去,减小状态转移数
5)利用获得的状态转移图,对字符串进行匹配
第一步很容易解决,无非就是找到'\',而后将后跟't'、'n'、'r'等字符的修正为相应的制表、换行……
第二步也相对简单,无非是识别几个操做符:
'*'、'+'、'?'、'*?'、'+?'、'??'、'('、'(:'、')'、'|'、'['、']'、'{'、'}'
比较特殊的是:
由于把连续的字符看作一个总体,因此在后跟重复或者可选运算符时,要检查一下是否须要断开字符串;
'['后面要一直读取到前面不是'\'的']',期间不做分析,结果做为一个总体
'{'后面有'{a}'、'{a,}'、'{,a}'、'{a,b}'这几种形式,一样是读取为一个总体
还有一个隐藏的操做符,它的做用是将两个操做数链接起来,相似算术里的“*”,关于它另有介绍[*]
如此一来,列表里的成员记录的内容就会有好几种,python里这不算啥,C++里就麻烦一些,我采用的是struct和union来回嵌套+标志字节的办法来处理这个问题。
(若是使用正则表达式这一步很是轻松,但是若是用了就真成了笑话了)
第三步见下一帖
[*]被隐藏了的“链接”运算符,好比“ab[cd]”这个字符串,分析后的两个元素“ab”和“[cd]”正是经过这个操做符链接的。这个隐藏运算符我是用后面的Operator类的一个补足函数来填充修复的,否则就得另外写一个函数了。