DEELX 正则引擎性能与特点
C++ 环境下的正则表达式引擎,RegExLab 的研究开发项目。
本页内容:
使用模板库编写
DEELX 正则引擎全部采用模板 template 编写,可将 DEELX 用于 char, wchar_t 以及其他基类型,比如 unsigned char, int 等。但只能是简单数据类型,不可以是 struct 或者 union 等复合类型。
比如:
#include "deelx.h"
int pattern[] = {65,
'+', 66, 0}; // 0 to indicate end int text[] = {180000,
65, 65, 66, 0};
CRegexpT <int> regexp(pattern); MatchResult result = regexp.Match(text);
if(result.IsMatched()) { printf("start at: %d\nlength: %d\n", result.GetStart(), result.GetEnd() - result.GetStart()); } |
更多详情可参考:
[ 编程帮助]
从右向左(RIGHTTOLEFT)匹配模式
DEELX 支持从右向左匹配模式,可使表达式从文本结束位置向前查找匹配。在表达式中,右侧的表达式比左侧的表达式先匹配。编写用来在“从右向左”模式下使用表达式与普通情况下使用的表达式并没有什么不同。匹配次数修饰符(*, +, {n}, ……)仍然位于被修饰部分的右侧;^ 仍然匹配文本开始;正向预搜索 (?=xxx) 仍然是向右搜索,而不是向左;分组(group)编号仍然是从左向右进行编号;等等。
需要注意的是,当使用了“从右向左”模式后,右侧的表达式会先进行匹配。这时如果使用了反向引用,那么被引用的分组(group)应该是在右侧。
比如:
“从右向左”模式下 |
正常模式下 |
\2(.*?)('|")
|
('|")(.*?)\1
|
递归匹配没有这个限制,不管是否是“从右向左”模式,下边的两种写法都是可以的:
(?2)(.*?)(")
|
(")(.*?)(?1)
|
反向预搜索(反向零宽度断言)
“反向预搜索”就是在匹配过程中,要求当前位置左侧的文本必须符合某个条件,格式为 (?<=xxx) 或者 (?<!xxx)。与 \b 类似,本身不匹配任何文本,只是对该当前位置设置一个条件。比如:
表达式 |
说明 |
\b |
要求当前位置为单词的边界,也就是说,左右两侧只能有一侧是字母或数字。 |
(?<!\w)(?=\w) |
要求当前位置为单词开头,只能是右侧是字母或数字。 |
(?<=\w)(?!\w) |
要求当前位置为单词结尾,只能是左侧是字母或数字。 |
关于反向预搜索中包含的表达式,Perl, Java, GRETA 以及 DEELX 的细节都不相同:
引擎 |
说明 |
举例 |
Perl |
只能使用固定长度的反向预搜索。 |
(?<=\t)print
|
Java |
允许使用不定长度的反向预搜索,但必须要有最大长度。 |
(?<=\{\s{0,100})print
|
GRETA |
允许使用没有长度限制,但否定格式存在一些问题。 |
(?<=\{\s*)print
|
DEELX 中的反向预搜索:
DEELX 采用 RIGHTTOLEFT 模式来匹配“反向预搜索”中的表达式。使反向预搜索与正向预搜索在逻辑上完全相同,而方向相反。因此,在 DEELX 中,反向预搜索与正向预搜索一样,没有长度限制。
比如,在 DEELX 引擎中:
文本 |
表达式 |
匹配结果 |
{print} |
(?<!\{\s*)print
|
匹配失败 |
(?<=\{\s*)print
|
匹配成功 |
移植简单
DEELX 全部使用模板库编写,因此没有任何 cpp 或者 lib 文件。全部代码位于一个头文件(deelx.h)中。使用时,不需要为 DEELX 创建 project,也不需要添加任何 cpp 或者静态库 lib 文件。运行时,也不依赖专门的动态库。
使用 DEELX 正则引擎时,只需要简单地添加一个 include 就可以了:
由于 deelx.h 已经直接包含到你的项目中,因此不会存在 Runtime Library 与主项目不同的问题,也不用担心会产生连接错误的问题。
兼容性
DEELX 采用纯 C++ 代码编写,没有使用任何 STL 类或者 MFC 类。DEELX 已测试能够在以下编译器及操作系统中编译:
编译器 |
版本 |
操作系统 |
备注 |
VC++ |
6.0, 7.1, 8.0 |
Windows |
|
GCC |
3.4 |
Cygwin |
|
GCC |
3.4 |
Linux |
|
Turbo C++ |
3.0 |
DOS |
|
C++ Builder |
6.0 |
Windows |
|
Borland C++ |
5.5 |
Windows |
|
GCC |
2.7 |
FreeBSD 2.2 |
|
GCC |
3.4 |
Solaris 10 Unix |
http://www.unix-center.net/ |
在其他平台以及其他编译器下,我们还未进行测试。
如果您在其他的编译器或者其他系统下编译成功或者编译失败了,可以通过 regexlab@gmail.com 告诉我们,我们将非常感谢。
命名分组
DEELX 支持 Python 及 .NET 风格的命名分组。命名分组的编号顺序按照 .NET 风格。DEELX 支持以下格式的命名分组:
表达式 |
风格 |
说明 |
(?P<the_name>xxxx)
|
Python |
定义一个命名 'the_name' 的命名分组 |
(?<the_name>xxxx) |
.NET |
(?'the_name'xxxx) |
匹配成功后,可通过分组的命名来获取分组捕获到的内容。
DEELX 允许多个命名分组的名字相同,这时它们捕获到的内容会放在同一个分组编号下。在逻辑上,它们是同一个分组,比如: (?<string>".*?")|(?<string>'.*?')
。如果两个命名相同分组之间有包含关系,那么被包含的那个分组将不进行捕获,比如: (?<number>(?<number>\d+)\.?)
。
此外,与命名分组相关的功能有:反向引用,递归表达式,条件表达式,以及替换操作。
功能 |
表达式 |
风格 |
说明 |
反向引用 |
\g<the_name> |
Python |
反向引用命名分组 'the_name' |
\k<the_name> |
.NET |
\k'the_name' |
递归表达式 |
(?R<the_name>) |
|
递归表达式引用命名分组 'the_name' |
(?R'the_name') |
条件表达式 |
(?(the_name)yes|no) |
.NET |
根据分组 'the_name' 是否有捕获 |
替换操作 |
${the_name} |
|
替换时,表示分组 'the_name' 的内容 |
条件表达式
条件表达式就是根据某个条件是否成立,来选择匹配 2 个可选表达式中的其中一个。可以用于条件表达式的条件有两种类型:
- 指定分组(group)是否进行了捕获。
- 文本中当前位置是否可以与指定表达式匹配。
条件表达式的举例及说明:
表达式 |
条件特点 |
条件说明 |
(?(1)yes|no) |
条件为数字 |
分组1如果有捕获,则进行 yes 部分匹配,否则 no 部分 |
(?(?=a)aa|bbb) |
条件为预搜索 |
如果当前位置右侧是 a,则进行匹配 aa,否则匹配 bbb |
(?(xxx)aa|bbb) |
不与分组命名吻合 |
如果不与任何分组命名吻合,则视为 (?=xxx)
相同 |
(?(name)yes|no) |
与分组命名吻合 |
如果与某分组命名吻合,则视为判断该分组是否进行捕获 |
补充说明:
- 如果表达式为 RIGHTTOLEFT 模式,那么 (?(xxx)aa|bbb)
与 (?(?<=xxx)aa|bbb)
相同。
- 如果条件表达式只有一个选择项,那么这个选项是在条件成立时进行匹配。
- 如果条件表达式中,使用“|”进行分隔的选项多于2个,则只有第一个“|”被视为条件表达式选项分隔符。比如: (?(?=xxx)yes|no1|no2),条件成立时,匹配
yes 部分,否则匹配 "no1|no2"。
递归表达式
“递归表达式”就是对另一部分子表达式本身的引用,而不是对其匹配结果的引用。举例说明:
表达式 |
等效的表达式1 |
等效的表达式2 |
可以匹配 |
(\w)(?1) |
(\w)(\w) |
|
ab |
(?1)(\w(?2))(\d) |
(?1)(\w(\d))(\d) |
(\w(\d))(\w(\d))(\d) |
a1b23 |
如果被引用的表达式又包含自身,则形成了递归引用。举例说明:
表达式 |
等效1 |
等效2 |
可以匹配 |
(\w(?1)?)
|
(\w(\w(?1)?)?) |
(\w+) |
ghjk5…… |
\(([^()]|(?R))*\) |
\(([^()]|\(([^()]|(?R))*\))*\) |
|
(a * (c + 2)) |
递归表达式的格式有:
格式 |
说明 |
(?R) |
对整个表达式的递归引用。 |
(?R1),(?R2) |
对指定分组的递归引用。 |
(?1),(?2) |
(?R<named>) |
对指定命名分组的递归引用。 |
(?R'named') |
防止死循环
能匹配零长度的子表达式,如果在被修饰匹配次数时被修饰为可以匹配任意次,则在很多正则引擎中,容易出现死循环。为此,DEELX 已通过技术手段,防止死循环的出现。
比如,容易出现死循环的表达式:
表达式 |
说明 |
(a*)*
|
因 a* 可以匹配零长度,整个表达式可能死循环。但在 DEELX 中不会出现死循环。 |
(a|b|c?)* |
其中一个选项可以匹配零长度,也容易出现死循环。但在 DEELX 中不会出现死循环。 |
|