Falando sobre Expressões Regulares: Feliz Natal... digo... Feliz Páscoa!
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:
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:
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:
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”:
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:
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.
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 | ||
---|---|---|
Metacaractere | Nome | Função |
. | ponto | captura qualquer caractere |
[ ] | colchetes ou lista | captura qualquer um dos caracteres listados |
\ | barra invertida | torna literal o metacaractere à sua direita |
Modificadores que determinam quantidade: Quantificadores | ||
Metacaractere | Nome | Função |
? | interrogação | torna o elemento à sua esquerda opcional |
* | asterisco | torna o elemento à sua esquerda opcional e permite múltiplas ocorrências |
+ | mais | elemento à sua esquerda deve aparecer uma ou mais vezes |
{n,m} | chaves | elemento à sua esquerda deve aparecer no mínimo n e no máximo m vezes |
Operadores que batem com uma posição: Âncoras | ||
Metacaractere | Nome | Função |
^ | circunflexo | bate com a posição incial do texto |
$ | cifrão | bate com a posição final do texto |
Outros (grupo dos que ficaram sem grupo) | ||
Metacaractere | Nome | Função |
( ) | parênteses | delimita escopo para outros operadores |
| | pipe | divide a expressão ou grupo em partes e bate com qualquer uma destas partes |
\1, \2, ... , \9 | retrovisor | bate com o trecho capturado no grupo 1, 2, ... , 9 |
Valeu pessoas! Feliz Páscoa! Desejo que ganhem muitos presentes de Natal!
Falou…