Próximos recursos de expressão regular

Jakob Gruber
Yang Guo

O ES2015 introduziu muitos novos recursos na linguagem JavaScript, incluindo melhorias significativas na sintaxe da expressão regular com sinalizações Unicode (/u) e fixas (/y). Mas o desenvolvimento não parou desde então. Em estreita colaboração com outros membros do TC39 (o corpo de padrões ECMAScript), a equipe do V8 propôs e colaborou em vários recursos novos para tornar as expressões regulares ainda mais poderosas.

No momento, esses recursos estão sendo propostos para inclusão na especificação do JavaScript. Mesmo que as propostas não tenham sido totalmente aceitas, elas já estão na fase 3 do processo de TC39. Implementamos esses recursos com uma sinalização (veja abaixo) para fornecer feedback oportuno de projeto e implementação aos respectivos autores da proposta antes que a especificação seja finalizada.

Esta postagem do blog mostra uma prévia desse futuro incrível. Para acompanhar os próximos exemplos, ative os recursos experimentais de JavaScript em chrome://flags/#enable-javascript-harmony.

Capturas nomeadas

As expressões regulares podem conter as chamadas capturas (ou grupos), que podem capturar uma parte do texto correspondente. Até agora, os desenvolvedores só podiam consultar essas capturas pelo índice, que é determinado pela posição da captura no padrão.

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'

Mas as expressões regulares já são notoriamente difíceis de ler, escrever e manter, e referências numéricas podem adicionar mais complicações. Por exemplo, em padrões mais longos, pode ser complicado determinar o índice de uma captura específica:

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

E, pior ainda, mudanças em um padrão podem mudar os índices de todas as capturas existentes:

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

As capturas nomeadas são um recurso futuro que ajuda a reduzir esses problemas, permitindo que os desenvolvedores atribuam nomes às capturas. A sintaxe é semelhante a Perl, Java, .Net e 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'

As capturas nomeadas também podem ser referenciadas por referências anteriores nomeadas e por 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'

Todos os detalhes desse novo recurso estão disponíveis na proposta de especificações.

flag dotAll

Por padrão, o átomo . em expressões regulares corresponde a qualquer caractere, exceto para terminadores de linha:

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

Uma proposta introduz o modo pontoAll, ativado por meio da sinalização /s. No modo pontoAll, . também corresponde aos terminadores de linha.

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

Todos os detalhes desse novo recurso estão disponíveis na proposta de especificações.

Escapes de propriedade Unicode

Com o reconhecimento de Unicode apresentado no ES2015, de repente muitos outros caracteres podem ser considerados números, por exemplo, o dígito um círculo: 1; ou considerados caracteres de palavras, por exemplo, o caractere chinês para neve: 雪.

Nenhuma dessas opções pode corresponder a \d ou \w. Alterar o significado dessas abreviações quebraria os padrões de expressão regular existentes.

Em vez disso, novas sequências de escape de propriedade estão sendo introduzidas. Elas estão disponíveis apenas para expressões regulares compatíveis com Unicode indicadas pela sinalização /u.

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

O inverso pode ser correspondido por \P.

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

O consórcio Unicode define muitas outras propriedades, por exemplo, para símbolos matemáticos ou caracteres iragana japoneses:

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

A lista completa de classes de propriedade Unicode compatíveis pode ser encontrada na proposta de especificação atual. Para mais exemplos, confira este artigo informativo.

Declarações do Lookbehind

As declarações antecipadas fazem parte da sintaxe de expressão regular do JavaScript desde o início. As declarações Lookbehind delas finalmente estão sendo introduzidas. Alguns de vocês podem se lembrar que isso já faz parte do V8 há algum tempo. Também usamos declarações lookbehind em segundo plano para implementar a sinalização Unicode especificada no ES2015.

O nome já descreve seu significado muito bem. Ele oferece uma maneira de restringir um padrão para que corresponda apenas se for precedido pelo padrão no grupo lookbehind. Ela é oferecida em variações correspondentes e não correspondentes:

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

Para saber mais, confira nossa postagem anterior do blog (link em inglês) dedicada a declarações de análise e exemplos de casos de teste do V8 relacionados (links em inglês).

Agradecimentos

Esta postagem do blog não estaria completa sem mencionar algumas das pessoas que trabalharam duro para fazer isso acontecer: especialmente os campeões de linguagem Mathias Bynens, Dan Ehrenberg, Claude Pache, Brian Terlson, Thomas Wood, Gorkem Yakin e Irregexp guru Erik Corry guru que também implementaram a linguagem e a especificação V8.

Esperamos que você esteja tão animado quanto nós com os novos recursos de expressão regular.