快速入门
教程
工具和语言
示例
参考
书籍评论
正则表达式教程
简介
目录
特殊字符
不可打印字符
正则表达式引擎内部
字符类
字符类减法
字符类交集
简写字符类
单词边界
交替
可选项
重复
分组和捕获
反向引用
反向引用,第 2 部分
命名组
相对反向引用
分支重置组
自由间距和注释
Unicode
模式修饰符
原子分组
占有量词
前瞻和后顾
环视,第 2 部分
将文本排除在匹配之外
条件
平衡组
递归
子例程
无限递归
递归和量词
递归和捕获
递归和反向引用
递归和回溯
POSIX 方括号表达式
零长度匹配
继续匹配
本网站上的更多内容
简介
正则表达式快速入门
正则表达式教程
替换字符串教程
应用程序和语言
正则表达式示例
正则表达式参考
替换字符串参考
书籍评论
可打印 PDF
关于本网站
RSS 源和博客
RegexBuddy—Better than a regular expression tutorial!

指定递归级别的反向引用

本教程中的前面主题解释了正则表达式递归正则表达式子例程。在本主题中,“递归”一词指的是整个正则表达式的递归、捕获组的递归以及对捕获组的子例程调用。前面的主题还解释了这些功能在 Ruby 中处理捕获组的方式与在 Perl 和 PCRE 中不同。

Perl、PCRE 和 Boost 在退出递归时会恢复捕获组。这意味着 Perl、PCRE 和 Boost 中的反向引用与在同一递归级别捕获组匹配的文本相同。这使得可以执行诸如匹配回文之类的操作。

Ruby 在退出递归时不会恢复捕获组。 普通反向引用 匹配与最近匹配的捕获组相同的文本,该捕获组未回溯,无论该捕获组是否在与反向引用相同或不同的递归级别找到其匹配项。基本上,Ruby 中的普通反向引用不关注递归。

但是,虽然 Ruby 中的普通捕获组存储不会对递归进行任何特殊处理,但 Ruby 实际上会为所有递归级别的每个捕获组存储一个完整的匹配堆栈。此堆栈甚至包括正则表达式引擎已退出的递归级别。

Ruby 中的反向引用可以匹配与捕获组匹配的相同文本,该捕获组位于相对于反向引用求值所在的递归级别的任何递归级别。您可以使用 命名反向引用 的相同语法通过在名称后添加符号和数字来执行此操作。在大多数情况下,您将使用 +0 来指定您希望反向引用重用来自同一递归级别的捕获组的文本。您可以指定一个正数来引用更深层次递归中的捕获组。这将是正则表达式引擎已退出的递归。您可以指定一个负数来引用较浅级别的捕获组。这将是仍在进行的递归。

JGsoft V2 还支持使用与 Ruby 相同语法指定递归级别的反向引用。要使用 JGsoft V2 获得与 Ruby 相同的行为,您必须对子例程调用使用 Ruby 的 \g 语法。

Ruby 中的奇数长度回文

在 Ruby 中,您可以使用 \b(?'word'(?'letter'[a-z])\g'word'\k'letter+0'|[a-z])\b 匹配回文词,例如 adadradarracecarredivider。为了使此示例更简单,此正则表达式仅匹配长度为奇数个字母的回文词。

我们来看看这个正则表达式如何匹配 radar单词边界 \b 匹配字符串的开头。正则表达式引擎进入捕获组“word”。 [a-z] 匹配 r,然后将其存储在递归级别 0 的捕获组“letter”的堆栈中。现在,正则表达式引擎进入组“word”的第一个递归。 (?'letter'[a-z]) 匹配并捕获递归级别 1 的 a。正则表达式进入组“word”的第二个递归。 (?'letter'[a-z]) 捕获递归级别 2 的 d。在接下来的两个递归中,该组分别捕获了级别 3 和 4 的 ar。第五次递归失败,因为字符串中没有字符供 [a-z] 匹配。正则表达式引擎必须回溯。

现在,正则表达式引擎必须尝试组“word”中的第二个备选方案。正则表达式中的第二个 [a-z] 匹配字符串中的最后一个 r。现在,引擎从成功的递归中退出,向上返回一级到第三次递归。

在匹配 \g'word' 后,引擎到达 \k'letter+0'。反向引用失败,因为正则表达式引擎已经到达主题字符串的末尾。因此,它再次回溯。第二个备选方案现在匹配 a。正则表达式引擎退出第三次递归。

正则表达式引擎再次匹配 \g'word',需要再次尝试反向引用。反向引用指定 +0 或当前的递归级别,即 2。在此级别,捕获组匹配 d。反向引用失败,因为字符串中的下一个字符是 r。再次回溯,第二个备选方案匹配 d

现在,\k'letter+0' 匹配字符串中的第二个 a。这是因为正则表达式引擎已经返回到捕获组匹配第一个 a 的第一次递归。正则表达式引擎退出第一次递归。

正则表达式引擎现在已返回到所有递归之外。在此级别,捕获组存储r。反向引用现在可以匹配字符串中的最终r。由于引擎不再位于任何递归中,因此它将继续执行组之后的正则表达式的其余部分。\b匹配字符串末尾。正则表达式的末尾已达到,radar作为整体匹配返回。

对其他递归级别的反向引用

如果修改回文示例,则可以轻松理解对其他递归级别的反向引用。abcdefedcba也是由前一个正则表达式匹配的回文。考虑正则表达式\b(?'word'(?'letter'[a-z])\g'word'(?:\k'letter-1'|z)|[a-z])\b。反向引用现在希望匹配捕获组堆栈中深度小一级的内容。它与字母z交替使用,以便当反向引用无法匹配时可以匹配某些内容。

新正则表达式匹配abcdefdcbaz等内容。经过大量匹配和回溯后,第二个[a-z]匹配f。正则表达式引擎退出第五次递归成功。捕获组“letter”已将匹配项abcde存储在递归级别 0 到 4 中。该组的其他匹配已回溯,因此未保留。

现在,引擎评估反向引用 \k'letter-1'。当前级别为 4,反向引用指定 -1。因此,引擎尝试匹配 d,成功。引擎退出第四次递归。

反向引用继续匹配 cba,直到正则表达式引擎退出第一次递归。现在,在所有递归之外,正则表达式引擎再次到达 \k'letter-1'。当前级别为 0,反向引用指定 -1。由于递归级别 -1 从未发生,因此反向引用无法匹配。这不是错误,而仅仅是 对非参与捕获组的反向引用。但反向引用有替代方案。z 匹配 z\b 匹配字符串结尾。abcdefdcbaz 已成功匹配。

你可以随心所欲地进行。正则表达式 \b(?'word'(?'letter'[a-z])\g'word'(?:\k'letter-2'|z)|[a-z])\b 匹配 abcdefcbazz\b(?'word'(?'letter'[a-z])\g'word'(?:\k'letter-99'|z)|[a-z])\b 匹配 abcdefzzzzzz

反过来,\b(?'word'(?'letter'[a-z])\g'word'(?:\k'letter+1'|z)|[a-z])\b 匹配 abcdefzedcb。同样,在经历了一系列匹配和回溯之后,第二个 [a-z] 匹配 f,正则表达式引擎返回到递归级别 4,组“letter”在递归级别 0 到 4 的堆栈上具有 abcde

现在引擎评估反向引用 \k'letter+1'。当前级别为 4,反向引用指定 +1。捕获组在递归级别 5 处回溯。这意味着我们对一个非参与组进行了反向引用,它无法匹配。备选方案 z 确实匹配。引擎退出第四次递归。

在递归级别 3,反向引用指向递归级别 4。由于捕获组在递归级别 4 处成功匹配,因此它仍然在其堆栈中保留该匹配,即使正则表达式引擎已经退出该递归。因此 \k'letter+1' 匹配 e。递归级别 3 成功退出。

反向引用继续匹配 dc,直到正则表达式引擎退出第一次递归。现在,在所有递归之外,正则表达式引擎再次到达 \k'letter+1'。当前级别为 0,反向引用指定 +1。捕获组仍然保留其所有以前的成功递归级别。因此,反向引用仍然可以匹配该组在第一次递归期间捕获的 b。现在 \b 在字符串末尾匹配。abcdefzdcb 成功匹配。

您也可以在这方面进行尽可能深入的探索。正则表达式 \b(?'word'(?'letter'[a-z])\g'word'(?:\k'letter+2'|z)|[a-z])\b 匹配 abcdefzzedc\b(?'word'(?'letter'[a-z])\g'word'(?:\k'letter+99'|z)|[a-z])\b 匹配 abcdefzzzzzz