Dea*_*ode 9 regex pcre regex-group conditional-regex
在PCRE2或支持前向反向引用的任何其他正则表达式引擎中,是否可以将在先前循环迭代中匹配的捕获组更改为非参与捕获组(也称为未设置捕获组或未捕获组),导致测试该组的条件与其"假"条款相匹配而不是它们的"真实"条款?
例如,采用以下PCRE正则表达式:
^(?:(z)?(?(1)aa|a)){2}
Run Code Online (Sandbox Code Playgroud)
当输入字符串时zaazaa,它根据需要匹配整个字符串.但是当zaaaa我吃饱时,我希望它能够匹配zaaa; 相反,它匹配zaaaa整个字符串.(这只是为了举例说明.当然这个例子可以处理,^(?:zaa|a){2}但不是重点.捕获组擦除的实际用法往往是循环,最常做的远远超过2次迭代.)
这样做的另一种方法,也不能按预期工作:
^(?:(?:z()|())(?:\1aa|\2a)){2}
Run Code Online (Sandbox Code Playgroud)
请注意,当循环"展开"时,这两个都可以正常工作,因为它们不再需要擦除已经进行的捕获:
^(?:(z)?(?(1)aa|a))(?:(z)?(?(2)aa|a))
^(?:(?:z()|())(?:\1aa|\2a))(?:(?:z()|())(?:\3aa|\4a))
Run Code Online (Sandbox Code Playgroud)
因此,不是能够使用最简单的条件形式,而是必须使用更复杂的条件,这只适用于此示例,因为"true"匹配z是非空的:
^(?:(z?)(?(?!.*$\1)aa|a)){2}
Run Code Online (Sandbox Code Playgroud)
或者只使用模拟条件:
^(?:(z?)(?:(?!.*$\1)aa|(?=.*$\1)a)){2}
Run Code Online (Sandbox Code Playgroud)
我已经走遍所有的文件我能找到,而且似乎没有甚至没有提及或这种行为明确描述(即捕获环路内进行通过循环迭代坚持,即使他们失败时,重新拍摄).
它与我的直觉预期不同.我会实现它的方法是,评估捕获组与0次重复将擦除/复位它(这样这可能发生在任何捕获基团与*,?或{0,N}量词),但是跳过它由于内的相同的并行其它替换在上一次迭代中获得捕获的组不会擦除它.因此,如果它们包含至少一个元音,那么这个正则表达式仍将匹配单词:
\b(?:a()|e()|i()|o()|u()|\w)++\1\2\3\4\5\b
Run Code Online (Sandbox Code Playgroud)
但是跳过一个捕获组,因为它位于一个未评估的替代组中,该组使用非零重复进行评估,嵌套在上一次迭代期间捕获组占用一个值的组中,将删除/取消设置它,所以这个正则表达式能够\1在循环的每次迭代中捕获或擦除组:
^(?:(?=a|(b)).(?(1)_))*$
Run Code Online (Sandbox Code Playgroud)
并匹配字符串,如aaab_ab_b_aaaab_ab_aab_b_b_aaa.但是,前向引用的方式实际上是在现有引擎中实现的,它匹配aaaaab_a_b_a_a_b_b_a_b_b_b_.
我想知道这个问题的答案,不仅因为它对构建正则表达式很有用,而且因为我编写了自己的正则表达式引擎,目前ECMAScript兼容一些可选的扩展(包括分子前瞻(?*),即非原子前瞻,据我所知,没有其他引擎有),我想继续添加其他引擎的功能,包括前向/嵌套后向引用.我不仅希望我的前向反向引用的实现与现有实现兼容,而且如果没有一种方法可以在其他引擎中擦除捕获组,我可能会在我的引擎中创建一种不冲突的方法与其他现有的正则表达式功能.
需要明确的是:只要有足够的研究和/或引用资料来支持,任何主流引擎都无法做到这一点的答案是可以接受的.的回答说,它是可能的将是国家要容易得多,因为它需要一个例子.
关于非参与捕获组是什么的一些信息:
http://blog.stevenlevithan.com/archives/npcg-javascript-这篇文章最初向我介绍了这个想法.
https://www.regular-expressions.info/backref2.html - 本页的第一部分给出了简要说明.
在ECMAScript/Javascript正则表达式中,对NPCG的反向引用始终匹配(进行零长度匹配).在几乎所有其他正则表达式的味道,他们无法匹配任何东西.
我发现此内容记录在PCRE的手册页中的“ PCRE2和PERL之间的差异”下:
Run Code Online (Sandbox Code Playgroud)12. There are some differences that are concerned with the settings of captured strings when part of a pattern is repeated. For example, matching "aba" against the pattern /^(a(b)?)+$/ in Perl leaves $2 unset, but in PCRE2 it is set to "b".
我正在努力思考一个实际问题,用替代解决方案无法更好地解决这个问题,但是为了保持简单,这里有:
假设您有一个简单的任务很适合使用正向引用解决;例如,检查输入字符串是否是回文。这通常不能通过递归来解决(由于子例程调用的原子性质),因此我们跳出以下内容:
/^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$/
Run Code Online (Sandbox Code Playgroud)
很简单。现在假设我们被要求验证输入中的每一行都是回文。让我们尝试通过将表达式放在重复的组中来解决此问题:
\A(?:^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$(?:\n|\z))+\z
Run Code Online (Sandbox Code Playgroud)
显然,这是行不通的,因为\ 2的值从第一行一直保留到下一行。这与您面临的问题类似,因此有多种解决方法:
1.将整个子表达式包含在(?!(?! )):
\A(?:(?!(?!^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$)).+(?:\n|\z))+\z
Run Code Online (Sandbox Code Playgroud)
非常简单,只需将它们推入其中,基本上就可以了。如果要保留任何特定的捕获值,则不是一个很好的解决方案。
2.分支重置组重置捕获组的值:
\A(?|^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$|\n()()|\z)+\z
Run Code Online (Sandbox Code Playgroud)
使用此技术,可以将捕获组的值从第一个(在这种情况下为\ 1)重置为某个值(在此处为\ 2)。如果您需要保留\ 1的值但擦除\ 2,则此技术将不起作用。
3.引入一个从特定位置捕获字符串其余部分的组,以帮助您以后识别您的位置:
\A(?:^(?:(.)(?=.*(\1(?(2)(?=\2\3\z)\2))([\s\S]*)))*+.?\2$(?:\n|\z))+\z
Run Code Online (Sandbox Code Playgroud)
所有其余的行集合都保存在\ 3中,从而使您能够可靠地检查是否已前进到下一行(当(?=\2\3\z)不再为真时)。
这是我最喜欢的技术之一,因为它可用于解决似乎不可能完成的任务,例如使用正向引用匹配 ol' 嵌套嵌套括号。使用它,您可以维护所需的任何其他捕获信息。唯一的缺点是它效率极低,特别是对于较长的对象。
4.这并不能真正回答问题,但可以解决问题:
\A(?![\s\S]*^(?!(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$))
Run Code Online (Sandbox Code Playgroud)
这是我正在谈论的替代解决方案。基本上,“重写模式” :)有时候是可能的,有时不是。
使用PCRE(以及我所知道的所有内容),不可能取消设置捕获组但使用子例程调用,因为它们的性质不记得前一次递归的值,您可以完成相同的任务:
(?(DEFINE)((z)?(?(2)aa|a)))^(?1){2}
Run Code Online (Sandbox Code Playgroud)
如果您要将行为实现到您自己的正则表达式中以取消设置捕获组,我强烈建议不要让它自动发生.只是提供一些标志.
这在 .NET 的正则表达式风格中部分可能实现。
首先要注意的是,.NET 记录给定捕获组的所有捕获,而不仅仅是最新的捕获。例如,^(?=(.)*)将第一行中的每个字符记录为组中的单独捕获。
为了实际删除捕获,.NET 正则表达式有一个称为平衡组的结构。该结构的完整格式是(?<name1-name2>subexpression).
name2必须是之前被捕获的。name1存在,则捕获 的结尾name2和子表达式匹配的开头之间的子字符串将被捕获到 中name1。name2然后删除最新捕获的内容。(这意味着可以在子表达式中反向引用旧值。)如果您知道您name2只捕获过一次,那么可以使用 ; 轻松删除它(?<-name2>)。如果您不知道是否已name2捕获,则可以使用(?>(?<-name2>)?)or 条件。如果此后您可能多次捕获,就会出现问题name2,这取决于您是否可以组织足够多的重复删除name2. ((?<-name2>)*不起作用,因为*相当于?零长度匹配。)