Anrs Hu

你所知的一切…

Archive for November, 2009

环视(二)

with one comment

肯定逆序环视和否定逆序环视

逆序环视与顺序环视效果作用相同但“看”的方向相反,它要求引擎在目标文本中回溯并检查回溯后得到的字符是否能和逆序环视中的表达式相匹配。使用逆序环视 (?<!a)b 可以匹配字符 b,但 b 不能跟在字符 a 的后面出现,因此它不能匹配目标文本 cab 中 b 字符,但可以匹配 bed 和 debt 中的 b 字符;而肯定逆序环视 (?<=a)b 则可以匹配 cab 中的字符 b,却不能匹配 bed 和 debt 中的字符 b。

肯定逆序环视结构 (?<=text) 以一对圆括号标识,开括号后面依次是问号、小于号、等号,接着再是表达式字符;否定逆序环视像这样 (?<!text),区别仅是以叹号代替了等号。

引擎细节

尝试以 (?<=a)b 匹配目标文本 thingamabob。引擎以逆序环视中的第 1 个表达式字符开始尝试匹配。本例中逆序环视要求引擎在目标文本中回溯一个字符并看回溯后得到的是不是字符 a。一开始引擎无法回溯,因为字符 t 是目标文本中的第 1 个字符,因此逆序环视失败(注意这里如果是否定逆序环视就能匹配成功),接着引擎又从下一个字符 h 再次开始新一轮匹配,再次尝试回溯一个字符并判断能不能找到字符 a,可惜看到的是字符 t 因此肯定逆序环视再次匹配失败。

逆序环视一直匹配失败直到前进到目标文本中的字符 m 为止,当前进到 m 时,引擎回溯一个字符后是字符 a 于是肯定逆序环视匹配成功。接着引擎丢弃获得的字符 a 并返回到目标文本中的 m,表达式也前进到 b,结果 b 和 m 匹配失败。于是引擎继续在目标文上前进到字符 a,回溯后获得 b,逆序环视匹配失败。

目标文本继续前进到字符 b,回溯后获得字符 a,肯定逆序环视再次匹配成功,放弃获得的 a 返回字符 b 后,第二个表达式字符依然匹配成功,终于整个正则表达式匹配成功,而匹配结果就是唯一的一个字符 b。

逆序环视要点

首先,好消息是可以在正则表达式的任意位置使用逆序环视,比如想要匹配一个不以字符 s 结尾的单词就可以使用 \b\w+(?<!s)\b 来实现。这跟 \b\w+[^s]\b 是完全不同的,考虑目标文本 John’s,前者匹配结果是 John,而后者的匹配结果则是 John(包括了单引号,原因就留给各位自己参悟了),同样的后者也无法匹配 a 或者 l 这样的单字符。如果想不使用逆序环视而实现同样的需求,那表达式应该写成这样 \b\w*[^s\W]\b,用星号代替加号,并在字符组中添加 \W 元字符。个人认为逆序环视的写法更易于理解,不使用逆序环视的表达式虽然也能正确匹配,但包括了双重否定(\W 也受 ^ 的作用),双重否定让程序员思路混乱,但并不会影响到正则引擎的运转。

糟糕的是多数正则流派都不允许在逆序环视中随意地构造表达式,因为正则引擎需要在检查逆序环视中的表达式之前确定“需要回溯多少个字符”,而其中一些表达式则可能造成引擎回溯失败。

因此很多流派包括 Perl 和 Python 在内,支持在逆序环视中构造“匹配结果定长”的表达式,即可以在逆序环视中使用任意表达式字符,但匹配结果的长度一定要一开始就能够被确定,也就是说逆序环视中可以使用字面值、字符组而不能使用变长量词和可选项,同时也可以使用所有子项长度完全相同的多选分支,在这方面,PCRE 并不和 Perl 完全兼容,虽然 Perl 要求多选分支中的子项长度完全相同,但 PCRE 则并无这个限制,PCRE 允许各子项长度不同但各子项的匹配结果依然要求是定长的。

唯一允许在逆序环视中无限制构造表达式的正则流派只有 JGsoft 正则引擎和 .NET,而 JavaScript, Ruby 和 Tcl 甚至根本就不支持逆序环视结构。

Written by admin

November 14th, 2009 at 3:48 am

Posted in Regular Expression

Tagged with