是否可以删除已经匹配的捕获组,使其不参与?
Is it possible to erase a capture group that has already matched, making it non-participating?
在 PCRE2 或任何其他支持正向反向引用的正则表达式引擎中,是否可以将在循环的前一次迭代中匹配的捕获组更改为 非参与捕获组 (也称为 未设置捕获组 或 非捕获组 ),导致测试该组与其 "false" 匹配的条件子句而不是他们的 "true" 子句?
例如,采用以下 PCRE 正则表达式:
^(?:(z)?(?(1)aa|a)){2}
当输入字符串 zaazaa
时,它会根据需要匹配整个字符串。但是当喂 zaaaa
时,我希望它匹配 zaaa
;相反,它匹配 zaaaa
整个字符串。 (这只是为了说明。当然,这个例子可以由 ^(?:zaa|a){2}
处理,但这不是重点。捕获组擦除的实际使用往往是在循环中进行,而循环通常执行的迭代次数远远超过 2 次。)
执行此操作的另一种方法,也无法按预期工作:
^(?:(?:z()|())(?:aa|a)){2}
请注意,当循环为 "unrolled" 时,这两种方法都可以正常工作,因为它们不再需要擦除已经进行的捕获:
^(?:(z)?(?(1)aa|a))(?:(z)?(?(2)aa|a))
^(?:(?:z()|())(?:aa|a))(?:(?:z()|())(?:aa|a))
所以不能使用最简单的条件形式,必须使用更复杂的形式,它只在这个例子中有效,因为 z
的 "true" 匹配是非空的:
^(?:(z?)(?(?!.*$)aa|a)){2}
或者只是使用模拟条件:
^(?:(z?)(?:(?!.*$)aa|(?=.*$)a)){2}
我已经搜索了我能找到的所有文档,似乎甚至没有提及或明确描述这种行为(在循环中进行的捕获即使在循环迭代失败时也会持续存在) -捕获)。
这与我的直觉预期不同。我将实现它的方式是评估具有 0 次重复的捕获组将 erase/unset 它(因此这可能发生在具有 *
、?
或 [=25= 的任何捕获组中] 量词),但由于在上一次迭代期间获得捕获的同一组中处于并行替代中而跳过它不会删除它。因此,如果 contain at least one of every vowel:
这个正则表达式仍然匹配单词
\b(?:a()|e()|i()|o()|u()|\w)++\b
但是跳过捕获组是因为它位于一个组的未评估替代项中,该替代项是用非零重复评估的,该替代项嵌套在捕获组在前一次迭代中取值的组中 would erase/unset 它,所以这个正则表达式将能够在循环的每次迭代中捕获或删除组 </code>:</p>
<pre><code>^(?:(?=a|(b)).(?(1)_))*$
并且会匹配 aaab_ab_b_aaaab_ab_aab_b_b_aaa
等字符串。然而,forward references的方式实际上是在现有引擎中实现的,它匹配aaaaab_a_b_a_a_b_b_a_b_b_b_
.
我想知道这个问题的答案,不仅因为它对构建正则表达式很有用,而且因为我有 written my own regex engine,目前 ECMAScript 兼容一些可选扩展(包括分子先行 (?*)
,即非原子前瞻,据我所知,其他引擎没有),我想继续添加其他引擎的功能,包括 forward/nested 反向引用。我不仅希望我的前向反向引用实现与现有实现兼容,而且如果 没有 一种在其他引擎中擦除捕获组的方法,我可能会创建一种方法在我的引擎中执行此操作不会与其他现有的正则表达式功能冲突。
需要说明的是:只要得到充分研究 and/or 引用来源的支持,任何主流引擎都无法接受的回答是可以接受的。一个回答说 是 可能会更容易说明,因为它只需要一个例子。
关于什么是非参与捕获组的一些信息:
http://blog.stevenlevithan.com/archives/npcg-javascript - 这是最初向我介绍这个想法的文章。
https://www.regular-expressions.info/backref2.html - 此页面的第一部分给出了简要说明。
在 ECMAScript/Javascript 正则表达式中,对 NPCG 的反向引用始终匹配(进行零长度匹配)。在几乎所有其他正则表达式风格中,它们无法匹配任何内容。
我在 PCRE 的手册页中找到了此文档,位于 "DIFFERENCES BETWEEN PCRE2 AND 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
unset, but in PCRE2 it is set to "b".
我正在努力想一个无法用替代解决方案更好地解决的实际问题,但为了保持简单,这里是:
假设您有一个非常适合使用前向引用来解决的简单任务;例如,检查输入字符串是否为回文。这通常不能用递归来解决(由于子例程调用的原子性质),因此我们提出以下内容:
/^(?:(.)(?=.*((?(2)))))*+.?$/
很简单。现在假设我们被要求验证 输入中的每一行 都是回文。让我们尝试通过将表达式放在重复组中来解决这个问题:
\A(?:^(?:(.)(?=.*((?(2)))))*+.?$(?:\n|\z))+\z
显然这不起作用,因为 \2 的值从第一行持续到下一行。这与您面临的问题类似,因此这里有多种解决方法:
1.将整个子表达式括在 (?!(?! ))
:
中
\A(?:(?!(?!^(?:(.)(?=.*((?(2)))))*+.?$)).+(?:\n|\z))+\z
非常简单,只需将它们推到那里,您就可以开始了。如果您希望保留任何特定的捕获值,这不是一个很好的解决方案。
2。分支重置组重置捕获组的值:
\A(?|^(?:(.)(?=.*((?(2)))))*+.?$|\n()()|\z)+\z
使用此技术,您可以将捕获组的值从第一个(在本例中为 \1)重置为某个特定的值(此处为 \2)。如果您需要保留\1的值但擦除\2,则此技术将不起作用。
3。引入一个从某个位置捕获字符串剩余部分的组,以帮助您稍后识别您所在的位置:
\A(?:^(?:(.)(?=.*((?(2)(?=\z)))([\s\S]*)))*+.?$(?:\n|\z))+\z
所有行集合的其余部分都保存在 \3 中,使您能够可靠地检查是否已进入下一行(当 (?=\z)
不再为真时)。
这是我最喜欢的技巧之一,因为它可以用来解决看似不可能完成的任务,例如 ol'matching nested brackets using forward references。有了它,您可以维护您需要的任何其他捕获信息。唯一的缺点是它的效率低得可怕,尤其是对于长主题。
4.这并没有真正回答问题,但它解决了问题:
\A(?![\s\S]*^(?!(?:(.)(?=.*((?(2)))))*+.?$))
这是我所说的替代解决方案。基本上,"re-write the pattern" :) 有时可能,有时则不然。
使用 PCRE(据我所知)无法取消设置捕获组,但使用子例程调用是因为它们的性质不记得先前递归的值,您可以完成相同的任务:
(?(DEFINE)((z)?(?(2)aa|a)))^(?1){2}
如果您打算在您自己的正则表达式风格中实施一种行为以取消设置捕获组,我强烈建议不要让它自动发生。只需提供一些标志。
这在 .NET 风格的正则表达式中部分可行。
首先要注意的是,.NET 会记录给定捕获组的所有捕获,而不仅仅是最新的捕获。例如,^(?=(.)*)
将第一行中的每个字符记录为组中的单独捕获。
要真正删除捕获,.NET 正则表达式有一个称为 balancing groups 的结构。此构造的完整格式为 (?<name1-name2>subexpression)
.
- 首先,
name2
之前必须被捕获。
- 子表达式必须匹配。
- 如果存在
name1
,则name2
捕获结束和子表达式匹配开始之间的子串被捕获到name1
.
name2
的最新捕获随后被删除。 (这意味着可以在子表达式中反向引用旧值。)
- 匹配前进到子表达式的末尾。
如果您知道 name2
恰好捕获了一次,那么可以使用 (?<-name2>)
轻松删除它;如果您不知道是否捕获了 name2
,则可以使用 (?>(?<-name2>)?)
或条件。如果从那时起您可能多次捕获 name2
,就会出现问题,这取决于您是否可以组织足够多的重复删除 name2
。 ((?<-name2>)*
不起作用,因为 *
等同于 ?
对于零长度匹配。)
还有另一种方法可以在 .NET 中“删除”捕获组。与 (?<-name>)
方法不同,这会清空组而不是删除它 - 因此它不会不匹配,而是匹配一个空字符串。
在 .NET 中,可以多次捕获具有相同名称的组,即使该名称是一个数字。这允许将使用平衡组的 PCRE 表达式移植到 .NET。考虑这个 PCRE 模式:
(?|(pattern)|())
假设两组都在</code>以上,那么使用这个技术,在.NET中它会变成:</p>
<pre><code>(?:(pattern)|(?<1>))
我今天使用这种技术制作了一个 38 字节的 .NET 正则表达式 matches strings whose length is a fourth power:
^((?=(?>^((?<3>|x))|())*$)){2}
以上是以下 35 字节 PCRE 正则表达式的端口,它使用平衡组:
^((?=(?|^((|x))|())*+$)){2}
(在这个例子中,捕获组实际上并没有被清空。但是这个技术可以用来做平衡组可以做的任何事情,包括清空一个组。)
在 PCRE2 或任何其他支持正向反向引用的正则表达式引擎中,是否可以将在循环的前一次迭代中匹配的捕获组更改为 非参与捕获组 (也称为 未设置捕获组 或 非捕获组 ),导致测试该组与其 "false" 匹配的条件子句而不是他们的 "true" 子句?
例如,采用以下 PCRE 正则表达式:
^(?:(z)?(?(1)aa|a)){2}
当输入字符串 zaazaa
时,它会根据需要匹配整个字符串。但是当喂 zaaaa
时,我希望它匹配 zaaa
;相反,它匹配 zaaaa
整个字符串。 (这只是为了说明。当然,这个例子可以由 ^(?:zaa|a){2}
处理,但这不是重点。捕获组擦除的实际使用往往是在循环中进行,而循环通常执行的迭代次数远远超过 2 次。)
执行此操作的另一种方法,也无法按预期工作:
^(?:(?:z()|())(?:aa|a)){2}
请注意,当循环为 "unrolled" 时,这两种方法都可以正常工作,因为它们不再需要擦除已经进行的捕获:
^(?:(z)?(?(1)aa|a))(?:(z)?(?(2)aa|a))
^(?:(?:z()|())(?:aa|a))(?:(?:z()|())(?:aa|a))
所以不能使用最简单的条件形式,必须使用更复杂的形式,它只在这个例子中有效,因为 z
的 "true" 匹配是非空的:
^(?:(z?)(?(?!.*$)aa|a)){2}
或者只是使用模拟条件:
^(?:(z?)(?:(?!.*$)aa|(?=.*$)a)){2}
我已经搜索了我能找到的所有文档,似乎甚至没有提及或明确描述这种行为(在循环中进行的捕获即使在循环迭代失败时也会持续存在) -捕获)。
这与我的直觉预期不同。我将实现它的方式是评估具有 0 次重复的捕获组将 erase/unset 它(因此这可能发生在具有 *
、?
或 [=25= 的任何捕获组中] 量词),但由于在上一次迭代期间获得捕获的同一组中处于并行替代中而跳过它不会删除它。因此,如果 contain at least one of every vowel:
\b(?:a()|e()|i()|o()|u()|\w)++\b
但是跳过捕获组是因为它位于一个组的未评估替代项中,该替代项是用非零重复评估的,该替代项嵌套在捕获组在前一次迭代中取值的组中 would erase/unset 它,所以这个正则表达式将能够在循环的每次迭代中捕获或删除组 </code>:</p>
<pre><code>^(?:(?=a|(b)).(?(1)_))*$
并且会匹配 aaab_ab_b_aaaab_ab_aab_b_b_aaa
等字符串。然而,forward references的方式实际上是在现有引擎中实现的,它匹配aaaaab_a_b_a_a_b_b_a_b_b_b_
.
我想知道这个问题的答案,不仅因为它对构建正则表达式很有用,而且因为我有 written my own regex engine,目前 ECMAScript 兼容一些可选扩展(包括分子先行 (?*)
,即非原子前瞻,据我所知,其他引擎没有),我想继续添加其他引擎的功能,包括 forward/nested 反向引用。我不仅希望我的前向反向引用实现与现有实现兼容,而且如果 没有 一种在其他引擎中擦除捕获组的方法,我可能会创建一种方法在我的引擎中执行此操作不会与其他现有的正则表达式功能冲突。
需要说明的是:只要得到充分研究 and/or 引用来源的支持,任何主流引擎都无法接受的回答是可以接受的。一个回答说 是 可能会更容易说明,因为它只需要一个例子。
关于什么是非参与捕获组的一些信息:
http://blog.stevenlevithan.com/archives/npcg-javascript - 这是最初向我介绍这个想法的文章。
https://www.regular-expressions.info/backref2.html - 此页面的第一部分给出了简要说明。
在 ECMAScript/Javascript 正则表达式中,对 NPCG 的反向引用始终匹配(进行零长度匹配)。在几乎所有其他正则表达式风格中,它们无法匹配任何内容。
我在 PCRE 的手册页中找到了此文档,位于 "DIFFERENCES BETWEEN PCRE2 AND 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 unset, but in PCRE2 it is set to "b".
我正在努力想一个无法用替代解决方案更好地解决的实际问题,但为了保持简单,这里是:
假设您有一个非常适合使用前向引用来解决的简单任务;例如,检查输入字符串是否为回文。这通常不能用递归来解决(由于子例程调用的原子性质),因此我们提出以下内容:
/^(?:(.)(?=.*((?(2)))))*+.?$/
很简单。现在假设我们被要求验证 输入中的每一行 都是回文。让我们尝试通过将表达式放在重复组中来解决这个问题:
\A(?:^(?:(.)(?=.*((?(2)))))*+.?$(?:\n|\z))+\z
显然这不起作用,因为 \2 的值从第一行持续到下一行。这与您面临的问题类似,因此这里有多种解决方法:
1.将整个子表达式括在 (?!(?! ))
:
\A(?:(?!(?!^(?:(.)(?=.*((?(2)))))*+.?$)).+(?:\n|\z))+\z
非常简单,只需将它们推到那里,您就可以开始了。如果您希望保留任何特定的捕获值,这不是一个很好的解决方案。
2。分支重置组重置捕获组的值:
\A(?|^(?:(.)(?=.*((?(2)))))*+.?$|\n()()|\z)+\z
使用此技术,您可以将捕获组的值从第一个(在本例中为 \1)重置为某个特定的值(此处为 \2)。如果您需要保留\1的值但擦除\2,则此技术将不起作用。
3。引入一个从某个位置捕获字符串剩余部分的组,以帮助您稍后识别您所在的位置:
\A(?:^(?:(.)(?=.*((?(2)(?=\z)))([\s\S]*)))*+.?$(?:\n|\z))+\z
所有行集合的其余部分都保存在 \3 中,使您能够可靠地检查是否已进入下一行(当 (?=\z)
不再为真时)。
这是我最喜欢的技巧之一,因为它可以用来解决看似不可能完成的任务,例如 ol'matching nested brackets using forward references。有了它,您可以维护您需要的任何其他捕获信息。唯一的缺点是它的效率低得可怕,尤其是对于长主题。
4.这并没有真正回答问题,但它解决了问题:
\A(?![\s\S]*^(?!(?:(.)(?=.*((?(2)))))*+.?$))
这是我所说的替代解决方案。基本上,"re-write the pattern" :) 有时可能,有时则不然。
使用 PCRE(据我所知)无法取消设置捕获组,但使用子例程调用是因为它们的性质不记得先前递归的值,您可以完成相同的任务:
(?(DEFINE)((z)?(?(2)aa|a)))^(?1){2}
如果您打算在您自己的正则表达式风格中实施一种行为以取消设置捕获组,我强烈建议不要让它自动发生。只需提供一些标志。
这在 .NET 风格的正则表达式中部分可行。
首先要注意的是,.NET 会记录给定捕获组的所有捕获,而不仅仅是最新的捕获。例如,^(?=(.)*)
将第一行中的每个字符记录为组中的单独捕获。
要真正删除捕获,.NET 正则表达式有一个称为 balancing groups 的结构。此构造的完整格式为 (?<name1-name2>subexpression)
.
- 首先,
name2
之前必须被捕获。 - 子表达式必须匹配。
- 如果存在
name1
,则name2
捕获结束和子表达式匹配开始之间的子串被捕获到name1
. name2
的最新捕获随后被删除。 (这意味着可以在子表达式中反向引用旧值。)- 匹配前进到子表达式的末尾。
如果您知道 name2
恰好捕获了一次,那么可以使用 (?<-name2>)
轻松删除它;如果您不知道是否捕获了 name2
,则可以使用 (?>(?<-name2>)?)
或条件。如果从那时起您可能多次捕获 name2
,就会出现问题,这取决于您是否可以组织足够多的重复删除 name2
。 ((?<-name2>)*
不起作用,因为 *
等同于 ?
对于零长度匹配。)
还有另一种方法可以在 .NET 中“删除”捕获组。与 (?<-name>)
方法不同,这会清空组而不是删除它 - 因此它不会不匹配,而是匹配一个空字符串。
在 .NET 中,可以多次捕获具有相同名称的组,即使该名称是一个数字。这允许将使用平衡组的 PCRE 表达式移植到 .NET。考虑这个 PCRE 模式:
(?|(pattern)|())
假设两组都在</code>以上,那么使用这个技术,在.NET中它会变成:</p>
<pre><code>(?:(pattern)|(?<1>))
我今天使用这种技术制作了一个 38 字节的 .NET 正则表达式 matches strings whose length is a fourth power:
^((?=(?>^((?<3>|x))|())*$)){2}
以上是以下 35 字节 PCRE 正则表达式的端口,它使用平衡组:
^((?=(?|^((|x))|())*+$)){2}
(在这个例子中,捕获组实际上并没有被清空。但是这个技术可以用来做平衡组可以做的任何事情,包括清空一个组。)