Perl vs Javascript正则表达式

Tim*_*gus 6 javascript regex perl pcre

为什么以下正则表达式(通过捕获组)在Javascript中捕获字符串'abc',但在PCRE中不捕获(尽管它仍然匹配)?

(.*)*

Luc*_*ski 8

这就是PCRE中捕获组为空的原因:

JS和PCRE之间的行为差​​异是由于指定了正则表达式引擎的方式.PCRE的行为与Perl一致:

PCRE:

$ pcretest
PCRE version 8.39 2016-06-14

  re> /(.*)*/
data> abc
 0: abc
 1:
Run Code Online (Sandbox Code Playgroud)

Perl的:

$ perl -e '"abc" =~ /(.*)*/; print "<$&> <$1>\n";'
<abc> <>
Run Code Online (Sandbox Code Playgroud)

让我们将它与具有相同行为但支持多次捕获的.NET进行比较:

.NET正则表达式

当捕获组第二次匹配时,.NET会将捕获的值添加到捕获堆栈.Perl和PCRE将简单地覆盖它.


至于JavaScript:

这是ECMA- 262§21.2.2.5.1运行时语义:RepeatMatcher抽象操作:

抽象操作RepeatMatcher采用八个参数,Matcher m,整数min,整数(或∞)max,布尔值greedy,状态x,Continuation c,整数parenIndex和整数parenCount,并执行以下步骤:

  1. 如果max为零,则返回c(x).
  2. 创建一个内部Continuation闭包d,它接受一个State参数y并在评估时执行以下步骤:
    • 一个.如果min为零且y's endIndex等于x's endIndex,则返回failure.
    • 湾 如果min为零,min2则为零; 否则令min2min?1.
    • C.如果max是∞,则为max2∞; 否则令max2max?1.
    • d.调用RepeatMatcher(m, min2, max2, greedy, y, c, parenIndex, parenCount)并返回其结果.
  3. 让我们cap成为x捕获列表的新副本.
  4. 对于每一个整数k,其满足parenIndex < kk ? parenIndex+parenCount设置cap[k]undefined.
  5. 我们ex的endIndex的.
  6. 让我们xr成为国家(e, cap).
  7. 如果min不为零,则返回m(xr, d).
  8. 如果greedyfalse,那么
    • 一个.打电话c(x)让它z成为结果.
    • 湾 如果z不是failure,请返回z.
    • C.调用m(xr, d)并返回其结果.
  9. 打电话m(xr, d)让它z成为结果.
  10. 如果z不是failure,请返回z.
  11. 调用c(x)并返回其结果.

这基本上是对量词进行评估时应该发生什么的定义.RepeatMatcher是处理内部操作匹配的操作m.

你还需要了解一个国家是什么(§21.2.2.1,强调我的):

状态是有序对(endIndex,captures),其中endIndex是整数,捕获是NcapturingParens值列表.状态用于表示正则表达式匹配算法中的部分匹配状态.endIndex是一加到目前为止由图案匹配的最后一个输入字符的索引,而captures保持捕获括号的结果.第nth个元素captures是一个List,表示由第一n组捕获括号获得的值,如果n尚未到达第二组捕获括号,则为undefined .由于回溯,许多国家可能在匹配过程中随时使用.

对于您的示例,RepeatMatcher参数是:

  • m:Matcher负责处理子模式(.*)
  • min:0(Kleene星形量词的最小匹配数)
  • max:∞(Kleene星形量词的最大匹配数)
  • greedy:true(使用的Kleene星形量词是贪婪的)
  • x:( (0, [undefined])见上面的州定义)
  • c:延续,此时它将是:在折叠父规则后,一直将其State参数作为成功返回的ContinuationMatchResult
  • parenIndex:0(根据§21.2.2.5,这是在此生产的左侧发生的整个正则表达式中的左捕获括号数量)
  • parenCount:1(相同的spec段落,这是此生产的Atom扩展中左侧捕获括号的数量 - 我不会在此处粘贴完整的规范,但这基本上意味着m定义了一个捕获组)

规范中的正则表达式匹配算法是根据连续传递方式定义的.基本上,这意味着c操作意味着接下来会发生什么.

让我们展开这个算法.

第一次迭代

在第一次传球时,x1状态是(0, [undefined]).

  1. max 不是零
  2. 我们此时创建了延续闭包d1,它将在第二遍中使用,所以我们稍后会再回到这一点.
  3. 制作捕获列表的副本cap1
  4. 重置在捕获cap1undefined,这是一个无操作在拳头通
  5. e1 = 0
  6. xr1 =(e1,cap1)
  7. min 为零,跳过此步骤
  8. greedy 是的,跳过这一步
  9. z1 = m(xr,d1) - 这将评估子模式(.*)

现在,让我们退一步一点- m将匹配(.*)abc,然后调用我们的d1封,所以让我们解开那一个.

d1与状态评价y1 = (3, ["abc"]):

  • min是0,但y1endIndex3和x1endIndex是0,所以不回failure
  • min2= min= 0,因为min= 0
  • max2= max=∞自max=∞
  • 调用RepeatMatcher(m, min2, max2, greedy, y, c, parenIndex, parenCount)并返回其结果.即:RepeatMatcher(m,0,∞,false,y 1,c,0,1)

第二次迭代

所以,现在我们要进行第二次迭代,其中x2 = y1 = (3, ["abc"]).

  1. max 不是零
  2. 我们创建延续关闭d2在这一点上
  3. 制作捕获列表的副本cap2["abc"]
  4. 复位捕捉在cap2undefined,我们得到cap2 =[undefined]
  5. e2 = 3
  6. xr2 =(e2,cap2)
  7. min 为零,跳过此步骤
  8. greedy 是的,跳过这一步
  9. z2 = m(xr2,d2) -此评估子模式(.*)

    这个时间m将与之后的空字符串匹配abc,并用那个调用我们的d2个闭包.让我们来评估d2的作用.它的参数是y2 =(3, [""])

    min仍然是0,但是y2endIndex是3和x2endIndex是也3(记住,这时间xy从先前的迭代的状态)中,封闭简单地返回failure.

  10. z2failure,跳过这一步
  11. return c(x2),c((3, ["abc"]))在此迭代中.

c只需返回一个有效的MatchResult,就像我们在模式的末尾一样.这意味着d1返回此结果,并且第一次迭代返回从步骤10传递它.

基本上,正如您所看到的,导致JS行为与PCRE不同的规范行如下:

一个.如果min为零且y's endIndex等于x's endIndex,则返回failure.

结合使用时:

  1. 调用c(x)并返回其结果.

如果迭代失败,则返回先前捕获的值.