为什么Javascript不支持lookbehind断言?

l'L*_*L'l 23 javascript regex

最近我(通过一些尴尬)意识到正则表达式lookbehind assertions 不可能的Javascript.

这种断言缺席的(事实)理由是什么似乎很常见?

我意识到也许有其他方法可以实现相同的功能,虽然它是工作中禁止功能的基本语义,还是究竟是什么?

似乎一些从正则表达式模式生成Javascript代码的正则表达式测试工具似乎忽略了这个事实 - 这让我觉得有些奇怪.

nil*_*ils 29

今天

Lookbehind现在是ES 2018规范的官方部分.Axel Rauschmayer 在他的博客文章中给出了一个很好的介绍.

历史

看起来当时,Brendan Eich并不知道它的存在(因为Netscape是基于旧版本的Perl构建的):

这是1998年,我在97年做的Netscape 4工作是基于Perl 4(!),但是我们提议ECMA TC39 TG1(JS组 - 事情不同,包括大写)基于Perl 5的东西.我们没有得到一切,我们不得不理顺一些明显的怪癖.

我不记得lookbehind(在1998年7月出现在Perl 5.005中)被故意排除在外.Waldemar可能还记得更多,我在netscape.com里面把他的JS密钥递给了mozilla.org.

如果你是写游戏或迷你规格的游戏(甚至是ES5的风格),请告诉我.我将在下周与其他TC39的人聊聊这件事.

/是

在邮件列表上有许多不同的尝试包含它,但它似乎仍然是性能相当复杂的功能,因为EcmaScript正则表达式是基于回溯的,并且在使用捕获组时需要回溯.这可能导致诸如使用不当时的灾难性回溯等问题.

在某些时候,它被建议用于ES6/Es 2015,但它从未制定过草案,更不用说规范了.在讨论最后一篇文章中,似乎没有人承担实施它的任务.如果有人感觉被要求编写实现,他们可以注册ES讨论列表并提出建议.

2015年5月更新:

2015年5月,NozomuKatō提出了ES7后视实施方案.

2015年9月更新:

Regex Look-behind被添加为0阶段提案.

2017年5月更新:

该提案现在处于第3阶段.这意味着现在至少有两个浏览器需要实现它才能成为下一个EcmaScript标准的一部分.正如@martixy在评论中提到的,Chrome已经在JS实验标志后面实现了它.

  • MSDN链接与后面的断言有什么关系?后视如何导致灾难性的回溯? (2认同)
  • 来自V8项目的优秀人才已经实现了该功能.几天前,它在实验JavaScript标志后面的稳定Chrome v49中可用.(http://v8project.blogspot.bg/2016/02/regexp-lookbehind-assertions.html) (2认同)

nha*_*tdh 9

从结论来看,我认为在JavaScript中没有实现后视,因为没有人知道它应该如何表现,现有的实现表明添加对后视镜头的支持相当复杂.

JavaScript/ECMAScript与其他语言的不同之处在于规范包含正则表达式引擎的抽象实现,而大多数其他语言仅在描述每个正则表达式语法的行为时停顿不足,并且对不同标记如何与之交互的描述很少描述彼此.

展望?易于实施

预见的实施非常简单.您只需要以与前瞻模式相同的方式处理前瞻中的模式,并按照惯例执行从左到右的匹配,除了在前瞻成功之后1)当前位置是在进入前瞻之前恢复到,并且2)在前瞻中的选择点在匹配之后被丢弃.

对于可以包含在内部预测中的内容没有限制,因为它是对现有自然左右匹配设施的非常简单的扩展.

向后看?不那么容易

另一方面,后视的实施并不是那么简单.

想象一下如何实现以下后视构造:

(?<=fixed-string)
(?<=a|fixed|string)
(?<=t[abc]{1,3})
(?<=(abc){2,6})
(?<=^.*abc.*)
(?<=\G"[^"]+");
(?<=^(.....|.......)+)
\b(\w+)\b(?<!\b\1\b.*\1)
Run Code Online (Sandbox Code Playgroud)

除了(?<=fixed-string)任何后备实现必须支持的基本情况之外,(?<=a|fixed|string)还有一个非常理想的支持案例.

不同的正则表达式引擎对上面的正则表达式有不同程度的支持.

让我们看看它们是如何在.NET和Java中实现的.(这是我研究过的两种风格.)

.NET实现

在Microsoft .NET实现中,上面的所有正则表达式都是有效的,因为.NET通过使用从右到左模式实现后视,并在当前位置使用起始偏移量.后视构造本身不会产生任何选择点.

但是,如果在后视中使用捕获组,则会开始变得混乱,因为模式中的原子是从右到左解释的,如本文所示.这是这种方法的缺点:在编写一个后视时,你需要把思路从右到左思考.

Java实现

相比之下,Java正则表达式实现通过重用从左到右的匹配工具来实现后视.

它首先分析了后视内部的模式,了解模式的最小和最大长度.然后,通过尝试从左到右匹配内部模式来实现后视,从开始(current position - minimum length)(current position - maximum length).

有什么遗漏?是! 由于我们从左到右匹配,我们需要确保匹配在进入后视(current position)之前的位置结束.在Java中,这是通过在模式的末尾附加一个节点来实现的.

这种实现效率非常低,因为maximum - minimum + 1在我们甚至讨论由后视图中的模式创建的选择点之前,在后视本身中创建了选择点.

后视边界检查也是低效的,因为它被放置在模式的末尾,并且不能修剪明显无望的选择点(那些已经远远超过current position模式中间的选择点).

摘要

如您所见,添加对后视镜头的支持并不容易:

  • 从右到左的方法似乎相当有效.但是,它需要对其他现有构造的从右到左匹配行为的额外规范.
  • 重复使用从左到右匹配设施的方法很复杂,而且效率很低.它还需要在规范中引入模式分析,以免性能被抛到窗外.

(注意,当在内部预测中使用后视时我还没有涵盖这种行为,反之亦然.在为后置构造定义语义时也应该考虑这一点).

nils回答中引用的邮件中,Waldemar Horwat(编写ES3正则表达式规范)也提到了这些技术障碍:

目前还没有人提出明确定义的关于外观的提案.Lookbehinds难以转化为规范使用的语言,并且当正则表达式的部分评估顺序很重要时会变得非常模糊,如果涉及捕获括号则会发生这种情况.你从哪里开始寻找外观?最短的第一个,最长的第一个或反向字符串匹配?贪婪与否?回溯到捕获结果?