Будущие функции регулярных выражений

ES2015 представил множество новых функций в языке JavaScript, включая значительные улучшения синтаксиса регулярных выражений с помощью флагов Unicode ( /u ) и Sticky ( /y ). Но с тех пор развитие не остановилось. В тесном сотрудничестве с другими членами TC39 (орган по стандартизации ECMAScript) команда V8 предложила и совместно разработала несколько новых функций, которые сделают регулярные выражения еще более мощными.

Эти функции в настоящее время предлагаются для включения в спецификацию JavaScript. Несмотря на то, что предложения не были полностью приняты, они уже находятся на третьем этапе процесса TC39 . Мы реализовали эти функции с помощью флага (см. ниже), чтобы иметь возможность своевременно предоставлять обратную связь по проектированию и реализации соответствующим авторам предложений до того, как спецификация будет завершена.

Этот пост в блоге дает вам представление об этом захватывающем будущем. Если вы хотите следовать следующим примерам, включите экспериментальные функции JavaScript по адресу chrome://flags/#enable-javascript-harmony .

Именованные захваты

Регулярные выражения могут содержать так называемые захваты (или группы), которые могут захватывать часть совпавшего текста. До сих пор разработчики могли ссылаться на эти захваты только по их индексу, который определяется положением захвата внутри шаблона.

const pattern = /(\d{4})-(\d{2})-(\d{2})/u;
const result = pattern.exec('2017-07-10');
// result[0] === '2017-07-10'
// result[1] === '2017'
// result[2] === '07'
// result[3] === '10'

Но регулярные выражения уже общеизвестно сложны для чтения, записи и обслуживания, а числовые ссылки могут еще больше усложнить работу. Например, в более длинных шаблонах может быть сложно определить индекс конкретного захвата:

/(?:(.)(.(?<=[^(])(.)))/  // Index of the last capture?

И что еще хуже, изменения в шаблоне потенциально могут сместить индексы всех существующих захватов:

/(a)(b)(c)\3\2\1/     // A few simple numbered backreferences.
/(.)(a)(b)(c)\4\3\2/  // All need to be updated.

Именованные захваты — это новая функция, которая помогает смягчить эти проблемы, позволяя разработчикам назначать имена захватам. Синтаксис аналогичен Perl, Java, .Net и Ruby:

const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const result = pattern.exec('2017-07-10');
// result.groups.year === '2017'
// result.groups.month === '07'
// result.groups.day === '10'

На именованные захваты также можно ссылаться с помощью именованных обратных ссылок и через String.prototype.replace :

// Named backreferences.
/(?<LowerCaseX>x)y\k<LowerCaseX>/.test('xyx');  // true

// String replacement.
const pattern = /(?<fst>a)(?<snd>b)/;
'ab'.replace(pattern, '$<snd>$<fst>');                              // 'ba'
'ab'.replace(pattern, (m, p1, p2, o, s, {fst, snd}) => fst + snd);  // 'ba'

Полная информация об этой новой функции доступна в предложении по спецификации .

флаг dotAll

По умолчанию файл . атом в регулярных выражениях соответствует любому символу, кроме признака конца строки:

/foo.bar/u.test('foo\nbar');   // false

Предложение вводит режим dotAll, включаемый с помощью флага /s . В режиме dotAll . также соответствует терминаторам строки.

/foo.bar/su.test('foo\nbar');  // true

Полная информация об этой новой функции доступна в предложении по спецификации .

Экранирование свойств Юникода

Благодаря поддержке Unicode, представленной в ES2015, внезапно появилось гораздо больше символов, которые можно считать числами, например цифра, обведенная кружком: ①; или рассматриваемые словесные символы, например китайский иероглиф, обозначающий снег: 雪.

Ни один из них не может быть сопоставлен с \d или \w . Изменение значения этих сокращений нарушит существующие шаблоны регулярных выражений.

Вместо этого вводятся новые escape-последовательности свойств. Обратите внимание, что они доступны только для регулярных выражений с поддержкой Unicode, обозначенных флагом /u .

/\p{Number}/u.test('①');      // true
/\p{Alphabetic}/u.test('雪');  // true

Обратное можно сопоставить с \P .

/\P{Number}/u.test('①');      // false
/\P{Alphabetic}/u.test('雪');  // false

Консорциум Unicode определяет множество других свойств, например, для математических символов или символов японской хираганы:

/^\p{Math}+$/u.test('∛∞∉');                            // true
/^\p{Script_Extensions=Hiragana}+$/u.test('ひらがな');  // true

Полный список поддерживаемых классов свойств Unicode можно найти в текущем предложении спецификации . Дополнительные примеры можно найти в этой информативной статье .

Утверждения Lookbehind

Утверждения опережающего просмотра с самого начала были частью синтаксиса регулярных выражений JavaScript. Наконец-то вводятся их аналоги, ретроспективные утверждения. Некоторые из вас, возможно, помнят, что это уже довольно давно является частью V8. Мы даже используем утверждения просмотра назад для реализации флага Unicode, указанного в ES2015.

Название уже довольно хорошо описывает его значение. Он предлагает способ ограничить совпадение шаблона только в том случае, если ему предшествует шаблон в группе просмотра назад. Он бывает как совпадающих, так и несовпадающих вкусов:

/(?<=\$)\d+/.exec('$1 is worth about ¥123');  // ['1']
/(?<!\$)\d+/.exec('$1 is worth about ¥123');  // ['123']

Для получения более подробной информации ознакомьтесь с нашей предыдущей записью в блоге, посвященной утверждениям ретроспективного просмотра, а также примерами в соответствующих тестовых случаях V8 .

Благодарности

Этот пост в блоге был бы неполным без упоминания некоторых людей, которые усердно работали, чтобы это произошло: особенно чемпионов по языку Матиаса Байненса , Дэна Эренберга , Клода Паша , Брайана Терлсона , Томаса Вуда , Горкема Якина и гуру Irregexp Эрика Корри ; но и всех остальных, кто внес свой вклад в спецификацию языка и реализацию этих функций в V8.

Мы надеемся, что вы так же в восторге от этих новых функций регулярных выражений, как и мы!