回溯设计是对错误设计的一种妥协

在正则表达式设计中,必须考虑匹配的回溯:若是匹配失败,就要从可能的上一个分支从新进行匹配。正则表达式

回溯设计,极大的下降了匹配的效率,让一些简单的匹配耗费大量的资源。缓存

pattern = / a.*?abc /

这就是典型的一个须要回溯支持的正则表达式,在每次匹配字符的时候,都要试着匹配 a. 若是匹配成功,则设置一个锚点,继续匹配,若是在匹配 abc 过程当中失败,那么匹配过程(自动机)将重置到最近设置的锚点,前进一位后,继续相同的过程。数据结构

这种匹配效率很低。函数

那么如何设计正则表达式,消除这种回溯呢?工具

须要一个不匹配字符串单元:性能

pattern = / a (!abc)* /

彷佛,正则表达式没有这种设计,是的,绝大多数正则表达式没有这种匹配单元。只有字符级别的否认:设计

pattern = / a [^abc]* /

正则表达式设计的引领者,Perl 语言的先驱们,在 Perl6 的设计中提出了 Longest Matching 概念。也就是最长匹配:code

pattern = / abc | abcd /

这个匹配中,一般会匹配先命中的规则,但 Perl6 会尝试全部的匹配,只返回那个匹配字符长度最长的部分。 这听起来很不错,但这须要牺牲正则表达式的性能。若是提早对规则进行排序(假设都是字符串),那么就不须要这种方式了。排序

回溯设计,是由于许多规则有重复,因此才不得不设计回溯引擎来让匹配能够中途中止,从新来过。资源

funcName = / \a+ /
	variable = / \a+ /

在许多语言中,函数的名称和变量的名称,用的是同样的规则,但实际上,他们是不一样的东西:函数名称一般要调用参数,而变量则直接返回值。 pattern = / return funcname / pattern = / variable(callargs) /

这两种状况都是错误的语法,但却没法在匹配中识别,但在 PHP 中:

funcname = / [_\a]+ /
	variable = / \$[_\a]+ /

PHP 语言的解析器,在识别函数名称和变量上,更简单,固然匹配速度更快。

规则冲突设计的越多,在语法树的处理上就越复杂,在 Java 中:

Name.Name

多是一个 class 的名称,一个类型的名称,也多是方法调用,甚至是结构的 field 调用,这须要看的人有至关的知识才能分辨。 但在 Perl 中,则不须要这些东西:

Class name: Name::Name
	Object call: Name->Name
	get field: Name->{Name}

这是语言设计上对回溯的影响,在实际的工做中,咱们须要设计一些正则来匹配规则,若是不考虑类似规则的回溯,效率会很低。

如何设计规则,尽可能避免回溯呢?

越早分辨越好

彻底不回溯的规则匹配,是设计了彻底不一样的首字符:

group = / ( ... ) /
	chars = / [ ... ] /
	expr  = / { ... } /
	string = / " ... " /
	char = / ' ... ' /

这些规则的第一个字符不一样,因此在匹配中,不会出现回溯问题,也不用担忧顺序问题。在有回溯的设计中,不一样的顺序可能会出现不一样的结果。

pattern = / keyword keywordname /

这个匹配永远不会匹配到完整的 keywordname.

过多的回溯设计,会让解析器结果须要更大的缓存,在流数据处理上,效率影响很大。

正则表达式虽然很好用,但在处理复杂的数据结构上,依然有不少自然的缺陷,这时候,就要考虑使用另外的匹配工具:语法匹配。

下次在讲语法匹配。

相关文章
相关标签/搜索