E aí pessoas!

Primeiro, eu gostaria de desejar a todos uma Feliz Páscoa!

Aproveitando o clima e o assunto, estou aqui pra mostrar 2 recursos muito interessantes das expressões regulares. Vamos lá!

No natal passado, eu mandei presentes pros chegados (mentira, eu não presenteei ninguém). Junto dos presentes, enviei um cartão com o texto:

Feliz Natal! Espero que goste do presente e desejo que ganhe muitos presentes de Natal!

Agora na páscoa fiz o mesmo (fiz nada): mandei ovos de páscoa com um cartão, porém com a mensagem ligeiramente modificada:

Feliz Páscoa! Espero que goste do ovo e desejo que ganhe muitos ovos de Páscoa!

Legal! Agora que os ovos já foram distribuídos, eu estou sem mais o que fazer. Então, eu resolvi que quero passar o tempo criando uma única expressão regular que capture apenas estas 2 frases. Bora escrever essa expressão?

Vamos começar pelo caso mais simples, capturar a mensagem de Natal:

imagem ilustrando matching com mensagem de natal

Beleza, um matching direto com grau de novidade zero!

Agora vamos pensar em qual é a lógica que devemos usar pra incluir a segunda mensagem no match.

O que mudou na mensagem foi: “Natal” virou “Páscoa” e “presente” virou “ovo”. Como devemos aceitar os dois casos, temos que dizer pra expressão que naquele pedaço do texto, ela deve dar match com “Natal” ou com “Páscoa”, qualquer um deles. A mesma lógica vale para “presente” e “ovo”.

Precisamos de um operador parecido com os operadores lógicos que usamos nas linguagens de programação: um operador OU. Nas expressões regulares, temos o metacaractere | (pipe) que faz essa função.

Sabendo disso, podemos definir as novas regras usando pipe:

  • ou “Natal” ou “Páscoa” => Natal|Páscoa;
  • ou “presente” ou “ovo” => presente|ovo.

Aplicando na regex:

imagem ilustrando matching errado com mensagem de natal e pascoa

Ih! Deu ruim!

Pra entender o que aconteceu aqui, precisamos entender melhor como funciona o pipe dentro da expressão. A função do pipe é dividir a regex em partes, de modo que o match de qualquer uma das partes é aceito.

Acho que a explicação ainda está meio nebulosa. Pra ficar mais claro, vamos analisar a nossa própria regex e estudar o comportamento do pipe:

imagem ilustrando análise do matching errado com mensagem de natal e pascoa

A imagem mostra que os pipes dividiram a regex em 5 expressões menores e destaca os trechos que deram match com cada uma destas expressões.

Isto aconteceu porque o escopo padrão do pipe é atuar sobre a regex inteira. Mas podemos definir escopos menores usando os parênteses pra criar grupos. O pipe dentro de um grupo divide apenas o grupo, sem interferir no restante da expressão.

Então, o que temos que fazer pra resolver nosso problema é: agrupar as palavras “Natal” e “Páscoa”, assim como “presente” e “ovo”:

imagem ilustrando matching com mensagem de natal e pascoa

Agora sim! Com o escopo bem definido, o pipe fez o OU nos trechos esperados.

Mas ainda tem um probleminha aí. Deixe-me adicionar alguns casos para teste:

imagem ilustrando matching com mensagem inconsistente de natal e pascoa

As duas últimas frases estão meio estranhas. Não faz muito sentido uma mensagem de Feliz Natal desejando que a pessoa receba muitos ovos de Páscoa, nem uma mensagem de Páscoa desejando presentes de Natal.

Pra resolver esta questão, usaremos um recurso muito interessante das expressões regulares: os retrovisores (o termo original em inglês é backreference; em português eu gosto muito do termo retrovisor, que vi pela primeira vez no excelente livro sobre regex do Aurélio Jargas).

Pra quebrar um pouco a rotina, vou mostrar primeiro a solução com os retrovisores em ação e depois explico como eles funcionam.

imagem ilustrando matching com mensagem de natal e pascoa usando retrovisores

Tudo certo! Muito Bom!

Agora vamos tentar entender melhor o que são esses tais de retrovisores:

Os retrovisores são aquelas sequências com barra invertida e um número: \1 e \2. Eles são o meio que usamos pra dizer pra regex que ela deve dar match com o trecho que já foi capturado anteriormente no grupo indicado.

Na prática, se o primeiro grupo capturar o texto “Natal”, \1 só dará match com “Natal”; se o match do primeiro grupo for “Páscoa”, \1 só dará match com “Páscoa”. A mesma lógica se aplica ao segundo grupo e o retrovisor \2.

É importante saber que os retrovisores clássicos podem fazer referência à no máximo 9 grupos.

Mas por que 9 grupos?

Porque a sintaxe é barra invertida mais um único dígito, o que nos limita aos retrovisores: \1, \2, \3, \4, \5, \6, \7, \8 e \9. Qualquer coisa que vier depois, será tratado como outro elemento da expressão. Por exemplo, a expressão \10 significa o retrovisor \1 seguido do 0 literal.

A título de curiosidade, posso dizer que existem alguns modos de acessar mais de 9 retrovisores em algumas ferramentas e APIs. Mas estes recursos específicos não estão no escopo deste post.

É isso aí pessoas! Este post encerra a série “Falando sobre Expressões Regulares”.

Pra finalizar, segue a atualização da tabelinha clássica:

Itens que batem com um caractere
MetacaractereNomeFunção
.pontocaptura qualquer caractere
[ ]colchetes ou listacaptura qualquer um dos caracteres listados
\barra invertidatorna literal o metacaractere à sua direita
Modificadores que determinam quantidade: Quantificadores
MetacaractereNomeFunção
?interrogaçãotorna o elemento à sua esquerda opcional
*asteriscotorna o elemento à sua esquerda opcional e permite múltiplas ocorrências
+maiselemento à sua esquerda deve aparecer uma ou mais vezes
{n,m}chaveselemento à sua esquerda deve aparecer no mínimo n e no máximo m vezes
Operadores que batem com uma posição: Âncoras
MetacaractereNomeFunção
^circunflexobate com a posição incial do texto
$cifrãobate com a posição final do texto
Outros (grupo dos que ficaram sem grupo)
MetacaractereNomeFunção
( )parêntesesdelimita escopo para outros operadores
|pipedivide a expressão ou grupo em partes e bate com qualquer uma destas partes
\1, \2, ... , \9retrovisorbate com o trecho capturado no grupo 1, 2, ... , 9

Valeu pessoas! Feliz Páscoa! Desejo que ganhem muitos presentes de Natal!

Falou…


O que foi que ele disse? (anterior)