Утверждения с обратным просмотром в регулярных выражениях
Регулярные выражения были введены в третьей редакции спецификации ECMA-262 и являются частью JavaScript с 1999 года. Функциональность и выразительность реализации регулярных выражений в JavaScript примерно совпадает с тем, что доступно в других языках программирования.
Одна из функций регулярных выражений в JavaScript, которая часто упускается из виду, но может быть весьма полезной, — это утверждения с прямым просмотром. Например, для нахождения последовательности цифр, за которой следует знак процента, мы можем использовать /\d+(?=%)/
. Сам знак процента не является частью результата совпадения. Его отрицание, /\d+(?!%)/
, будет соответствовать последовательности цифр, за которой не следует знак процента:
/\d+(?=%)/.exec('100% американских президентов были мужчинами'); // ['100']
/\d+(?!%)/.exec('это все 44 из них'); // ['44']
Противоположность утверждения с прямым просмотром — утверждения с обратным просмотром — отсутствовали в JavaScript, но доступны в других реализациях регулярных выражений, таких как платформа .NET. Вместо чтения вперед, механизм регулярных выражений читает назад для совпадения внутри утверждения. Последовательность цифр, следующая за знаком доллара, может быть найдена с помощью /(?<=\$)\d+/
, где знак доллара не является частью результата совпадения. Его отрицание, /(?<!\$)\d+/
, соответствует последовательности цифр, следующей за чем угодно, кроме знака доллара.
/(?<=\$)\d+/.exec('Бенджамин Франклин на купюре $100'); // ['100']
/(?<!\$)\d+/.exec('это стоит примерно €90'); // ['90']
В общем, существует два способа реализации утверждений с обратным просмотром. Например, в Perl от образца обратного просмотра требуется иметь фиксированную длину. Это означает, что квантователи, такие как *
или +
, запрещены. Таким образом, механизм регулярных выражений может просто отступить на фиксированную длину и сопоставить обратный просмотр точно так же, как он сопоставил бы прямой просмотр, с отступленной позиции.
Механизм регулярных выражений в платформе .NET использует другой подход. Вместо того чтобы знать, сколько символов совпадет образец обратного просмотра, он просто сопоставляет образец обратного просмотра задом наперед, читая символы в обычном направлении чтения. Это означает, что образец обратного просмотра может использовать полную синтаксис регулярных выражений и соответствовать шаблонам произвольной длины.
Очевидно, что второй вариант мощнее первого. Именно поэтому команда V8 и представители TC39, поддерживающие эту функцию, решили, что JavaScript должен принять более выразительную версию, хотя её реализация немного сложнее.
Поскольку утверждения с обратным просмотром сопоставляются задом наперед, есть некоторые тонкости, которые в противном случае могут вызвать удивление. Например, захватывающая группа с квантователем захватывает последнее совпадение. Обычно это самое правое совпадение. Но внутри утверждения с обратным просмотром мы сопоставляем справа налево, поэтому захватывается самое левое совпадение:
/h(?=(\w)+)/.exec('hodor'); // ['h', 'r']
/(?<=(\w)+)r/.exec('hodor'); // ['r', 'h']
Захватывающая группа может быть использована через обратную ссылку после её захвата. Обычно обратная ссылка должна находиться справа от захватывающей группы. В противном случае она будет соответствовать пустой строке, так как ничего ещё не было захвачено. Однако внутри утверждения с обратным просмотром направление совпадений меняется.
/(?<=(o)d\1)r/.exec('hodor'); // null
/(?<=\1d(o))r/.exec('hodor'); // ['r', 'o']
Утверждения с обратным просмотром находятся на очень ранней стадии процесса спецификации TC39. Однако, поскольку они являются очевидным расширением синтаксиса регулярных выражений, мы решили приоритетно их реализовать. Вы уже можете экспериментировать с утверждениями с обратным просмотром, используя V8 версии 4.9 или более поздней с ключом --harmony
, или включив экспериментальные функции JavaScript (используйте about:flags
) в Chrome начиная с версии 49.