是否有可能抹杀已经匹配的捕获组,使之成为非参与?

问题描述 投票:9回答:3

在PCRE2或任何其它正则表达式引擎支持正向反向引用,是有可能改变,在一个循环的前一迭代匹配到非参与捕获基团的捕获组(也被称为非设定捕获组或非捕获组),导致该测试组,以配合他们的“假”的条款,而不是他们的“真实”的条款条件语句?

例如,采取以下PCRE正则表达式:

^(?:(z)?(?(1)aa|a)){2}

当供给的串zaazaa,它整个字符串相匹配,如需要的话。但是喂zaaaa的时候,我想它匹配zaaa;相反,它匹配zaaaa,整个字符串。 (这只是为了说明。当然,这个例子可以通过^(?:zaa|a){2}处理但那是题外话。捕获组擦除的实际使用会倾向于在循环最常做的远不止2次迭代)。

这样做,是一种替代方法也没有如期望的工作:

^(?:(?:z()|())(?:\1aa|\2a)){2}

请注意,这两个工作的需要时循环是“展开”,因为他们不再需要擦除已经取得了拍摄的:

^(?:(z)?(?(1)aa|a))(?:(z)?(?(2)aa|a))
^(?:(?:z()|())(?:\1aa|\2a))(?:(?:z()|())(?:\3aa|\4a))

这样反而能够使用的条件最简单的形式,更复杂的一个必须被使用,这只能在这个例子中,因为z的“真正的”匹配非空:

^(?:(z?)(?(?!.*$\1)aa|a)){2}

或者只是使用仿真条件:

^(?:(z?)(?:(?!.*$\1)aa|(?=.*$\1)a)){2}

我已经走遍所有的文件我能找到,而且似乎没有甚至没有提及或这种行为明确描述(即捕获环路内进行通过循环迭代坚持,即使他们失败时,重新拍摄)。

这比我预期的直觉不同。我会实现它的方式是,评估捕获组0的重复会删除/取消设置它(所以这可能发生在同一个*?,或{0,N}量词任何捕获组),但跳过它由于并行其它替换同一组中的其中一个先前迭代期间获得了捕获不会抹掉它。因此,这个正则表达式仍然会匹配的话当且仅当他们contain at least one of every vowel

\b(?:a()|e()|i()|o()|u()|\w)++\1\2\3\4\5\b

但跳过捕获组,由于它是一个与该嵌套在其中捕获组发生在一个值的先前迭代期间将擦除/复位它的组内的非零重复评估的一组的一个未计算的替代方案中,所以此正则表达式将能够捕获两种或循环的每次迭代中删除组\1

^(?:(?=a|(b)).(?(1)_))*$

并且将匹配字符串,例如aaab_ab_b_aaaab_ab_aab_b_b_aaa。然而,前向引用在现有的发动机实际执行的方式,它匹配aaaaab_a_b_a_a_b_b_a_b_b_b_

我想知道这个问题的答案不仅是因为这将是构建正则表达式是有用的,但因为我有written my own regex engine,一些可选的扩展(包括分子先行(?*),即非原子超前,目前的ECMAScript兼容其远我所知,没有其他的发动机有),我想继续从其他引擎,包括向前/嵌套反向引用添加新的特性。我不仅希望我实现着反向引用的是与现有实现兼容,但如果没有删除在其他引擎捕获组的方式,我可能会创造我的引擎做的一种方式,并不冲突与其他现有的正则表达式的功能。

需要明确的是:一个答案,指出这是不可能在任何主流引擎是可以接受的,只要它是通过充分的研究和/或来源援引备份。的回答说,它有可能是国家要容易得多,因为它需要一个例子。

对未参加捕获组是什么的一些信息: http://blog.stevenlevithan.com/archives/npcg-javascript - 这是最初给我介绍了这个想法的文章。 https://www.regular-expressions.info/backref2.html - 此页上的第一部分给出了一个简要的解释。 ECMAScript中/正则表达式的Javascript,反向引用到NPCGs总是匹配(使一个零长度匹配)。在几乎所有其他的正则表达式的味道,他们未能匹配任何。

regex pcre regex-group conditional-regex
3个回答
4
投票

这部分是可能的正则表达式的.NET的味道。

首先要注意的一点是,.NET记录了所有捕获的对于给定的捕获组,而不仅仅是最新的。例如,^(?=(.)*)记录在第一行作为组在一个单独的捕获的每个字符。

要真正删除捕获,.NET正则表达式有一个被称为balancing groups的结构。这种结构的完整格式是(?<name1-name2>subexpression)

  • 首先,name2必须以前被抓获。
  • 然后,子表达式必须匹配。
  • 如果name1存在,name2的捕获和子表达式匹配的开始的端部之间的子串被捕获到name1
  • 然后name2最新捕获被删除。 (这意味着旧的值可以在子表达式进行反向引用。)
  • 比赛进行到子表达式的结尾。

如果你知道你有name2准确捕捉一次,然后就可以很容易地使用(?<-name2>)被删除;如果你不知道你是否已经name2捕捉你,那么你可以使用(?>(?<-name2>)?)或条件。问题就出现了,如果你可能有name2捕获不止一次因为那就要看你是否能组织name2删除的重复不够。 ((?<-name2>)*不起作用,因为*相当于?为零长度匹配。)


5
投票

我发现这个记录在PCRE的手册页,“BETWEEN PCRE2和Perl差异”下:

   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$/

很容易的。现在假设我们被要求验证在输入的每一行是一个回文。让我们通过放置在重复组的表达来解决这个问题:

\A(?:^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$(?:\n|\z))+\z

显然不行,因为\ 2的值从第一行到下依然存在。这类似于你所面临的问题,所以这里有一些方法来克服它:

1.括在(?!(?! ))整个子表达式:

\A(?:(?!(?!^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$)).+(?:\n|\z))+\z

很简单,只需推“时间在那里,你基本上是好去。如果你不希望任何特定的被捕获值持续一个很好的解决方案。

2.科复位组到复位组捕获的值:

\A(?|^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$|\n()()|\z)+\z

利用这种技术,则可以重置捕获基团的第一个(\ 1在这种情况下)达到一定的一个(\ 2这里)的值。如果你需要保持\ 1的值,但是擦\ 2,这种技术将无法正常工作。

3.把捕获的字符串的其余部分从某个位置的一组,以帮助您以后识别您所在的位置:

\A(?:^(?:(.)(?=.*(\1(?(2)(?=\2\3\z)\2))([\s\S]*)))*+.?\2$(?:\n|\z))+\z 

线的集合的整个其余部分保存在\ 3,让您能够可靠地检查您是否已经进展到下一行(当(?=\2\3\z)不再是真实的)。

这是我最喜欢的技术之一,因为它可以用来解决似乎是不可能的,比如醇” matching nested brackets using forward references任务。有了它,你可以保持您需要的任何其他捕获信息。唯一的缺点是它的效率极其低下,尤其是长期主题。

4.这并没有真正回答这个问题,但它解决了这个问题:

\A(?![\s\S]*^(?!(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$))

这是我在谈论的替代解决方案。基本上,“重新写入模式” :)有时是可能的,有时则不然。


5
投票

随着PCRE(以及所有我所知),这是不可能取消设置捕获组,但使用调用子程序,因为其性质不从以前的递归记住值,你可以完成相同的任务:

(?(DEFINE)((z)?(?(2)aa|a)))^(?1){2}

live demo here

如果你要实现一个行为到自己的正则表达式的味道来取消捕获组,我强烈建议不要让它自动发生。只需提供一些标志。

© www.soinside.com 2019 - 2024. All rights reserved.