Alguns anos atrás eu comecei a falar que há três regras para se usar expressões regulares
- não use regex
- reconsidere se você precisa usar regex
- conheça sua engine
Pode parecer engraçado eu usar duas regras para desencorajar o uso de regex sendo que eu estudo isso e estou inclusive escrevendo uma série de artigos dedicados a ensinar como usá-la, mas eu vou explicar.
A primeira e a segunda regra são na verdade a mesma e estão duplicadas para efeito de ênfase.
Quando começamos a aprender regex (e até quando já sabemos há um tempo), ficamos tentados a usar para solucionar qualquer problema. Daí fazemos coisas como essa para validar uma data.
/^(?:([0-1])|(2)|(3))(?(1)[0-9])(?(2)(?:([0-8])|(9)))(?(3)[0-1])/
(?:(01|03|05|07|09|11)|(04|06|08|10|12)|(02))(?(3)(?(8)(?!)))/d*
(?:(04|08|12|16|20|24|28|32|36|40|44|48|52|56|60|64|68|72|76|80|84|
88|92|96)|(dd))(?:(00)|(04|08|12|16|20|24|28|32|36|40|44|48|52|56|
60|64|68|72|76|80|84|88|92|96)|d{2})(?(2)(?(5)(?(8)(?(11)(?(9)|
(?!))|(?(12)|(?!))))))/
(podem testá-la, a propósito 😉 https://regex101.com/r/aqEsf1/1)
Essa atrocidade de regex valida não somente o formato da data (que precisa ser dd/MM/yyyy
), mas também valida se a data é válida. Dia 31 de setembro não é validada. Dia 29 de fevereiro de 2021 não é validada. Eu tenho um certo orgulho dessa regex, francamente (sim, eu que fiz).
Porque não a usamos então? Eu apresento dois motivos.
O primeiro é mais óbvio: essa regex é incompreensível e um pesadelo de se mexer. Se houver algum bug nela, qualquer tentativa de mudá-la será muito custosa.
O segundo não é tão óbvio, mas deve ser simples de entender: essa regex é muito custosa para a engine.
Para explicar, precisamos falar sobre como a engine funciona internamente. Apertem os cintos e vamos lá!
Funcionamento interno das engines de regex
Embora cada engine tenha uma particularidade, podemos dividí-las em duas categorias: engines text-directed e regex-directed.
Não falaremos de engines text-directed aqui, pois seu funcionamento é trivial porque não existe backtracking e as engines mais usadas são as regex-directed. Vamos a elas então.
As engines regex-directed, chamadas a partir de agora somente de engines, funcionam basicamente da seguinte forma:
- Enquanto houver tokens na regex, pegue o próximo token
- Tente dar match com o próximo caracter da string
- Se houver match, avance para o próximo token da regex e o próximo caracter da string
- Se não, volte até uma posição anterior da regex para testar um outro caminho por ela (esse é o backtracking)
Vamos de exemplo. Vamos aplicar a regex /<.*?>/
no texto Teste
.
- Enquanto houver tokens na regex, pegue o próximo token (
<
) - Dá match com o próximo caracter da string (
<
)
1 Houve match, então avança para o próximo token da regex (.*?
) e próximo caracter da string (s
) - O token
.*?
é o quantificador lazy que vai dar match no mínimo possível para que a regex seja válida. A primeira tentativa é de um match sem tamanho (match em ''). - Dá match, então avança para o próximo token da regex (
>
) e o próximo caracter da string (s
). - Não dá match, então retorna a regex pro token anterior (
.*?
) - Dá match, pois
.
dá match em qualquer caracter (exceto quebra de linha), então avança para o próximo token da regex (>
) e o próximo caracter da string (p
). - Não dá match, então retorna regex (
.*?
). -
.*?
deu match ems
antes, mas agora precisa dar match emsp
. Tudo bem, pois.
dá match em qualquer caracter (exceto quebra de linha) e*
dá match em mais de um caracter. - Dá match, então avança regex (
>
) e string (a
). - Não dá match. Isso vai se repetir até a regex encontrar na string o caracter
>
. Nesse caso, ela vai dar match e retornar ok.
Segue um esquema tirado da aplicação RegexBuddy para explicar melhor.
Beginning match attempt at character 0
<
< ok
< backtrack
Match found in 11 steps
(espaços adicionados por clareza)
Dessa maneira, é possível notar que há as engines vão e voltam na string até encontrar uma solução para a regex. Vamos pegar o diagrama do RegexBuddy para uma data e aquela regex.
Beggining match attempt at character 0
ok
1
1
1
12
12
12 ok
12 ok
12 ok
12 ok
12/
12/0
12/0 backtrack
12/0
12/0 backtrack
12/0
12/0 backtrack
12/0
12/0 backtrack
12/0
12/0 backtrack
12/ backtrack
12/ backtrack
12/0
12/0 backtrack
12/0
12/0 backtrack
12/0
12/0 backtrack
12/ backtrack
12/ backtrack
12/ backtrack
12/0
12/02
12/02
12/02
12/02 ok
12/02 ok
12/02/
12/02/ backtrack
12/02/ backtrack
12/02/ backtrack
12/02/ backtrack
12/20/2
12/02/20
12/02/20
12/02/20
12/02/200
12/02/200 backtrack
12/02/20 backtrack
12/02/200
12/02/200 backtrack
12/02/200
12/02/200 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/20 backtrack
12/02/2003
12/02/2003
12/03/2003 ok
12/03/2003 ok
Match found in 81 steps
Um tanto mais complexo que a solução preferida, que em Java seria algo como:
Date date = new Date('12/02/2003');
Pois a implementação do Java já testa se a data é válida ao instanciar o objeto.
É importante lembrar que nenhuma engine de regex tem informações semânticas sobre o texto. Enquanto o Java tem como saber que 12
na data é dia e 2003
é ano, a engine de regex só entende texto.
A mesma coisa para email. Em vez de usar uma regex monstruosa como a seguinte:
(?:(?:rn)?[ t])*(?:(?:(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[
t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+
(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:
(?:rn)?[ t])*))*|(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z
|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)
?[ t])*)*<(?:(?:rn)?[ t])*(?:@(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:
rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[
t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)
?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t]
)*))*(?:,@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[
t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*
)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t]
)+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*)
*:(?:(?:rn)?[ t])*)?(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+
|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:r
n)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:
rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t
]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31
]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](
?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?
:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?
:rn)?[ t])*))*>(?:(?:rn)?[ t])*)|(?:[^()<>@,;:\".[] 00- 31]+(?:(?
:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?
[ t]))*"(?:(?:rn)?[ t])*)*:(?:(?:rn)?[ t])*(?:(?:(?:[^()<>@,;:\".[]
00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|
\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>
@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"
(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t]
)*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\
".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?
:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[
]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*|(?:[^()<>@,;:\".[] 00-
31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(
?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)*<(?:(?:rn)?[ t])*(?:@(?:[^()<>@,;
:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([
^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\"
.[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[
]r\]|\.)*](?:(?:rn)?[ t])*))*(?:,@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".
[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]
r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[]
00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]
|\.)*](?:(?:rn)?[ t])*))*)*:(?:(?:rn)?[ t])*)?(?:[^()<>@,;:\".[]
00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\
.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,
;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?
:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*
(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".
[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[
^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]
]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*>(?:(?:rn)?[ t])*)(?:,s*(
?:(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\
".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(
?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[
["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t
])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t
])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?
:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|
Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*|(?:
[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[
]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)*<(?:(?:rn)
?[ t])*(?:@(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["
()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)
?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>
@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*(?:,@(?:(?:rn)?[
t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,
;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t]
)*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\
".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*)*:(?:(?:rn)?[ t])*)?
(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".
[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:
rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z|(?=[[
"()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])
*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])
+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:
.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00- 31]+(?:(?:(?:rn)?[ t])+|Z
|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*>(?:(
?:rn)?[ t])*))*)?;s*)
uma solução mais simples é mandar um email para o endereço. Se estiver válido, o email chegará. Para auxiliar o usuário a preencher o formulário, basta verificar com essa regex
.*@.*..*
ou algo parecido.
A terceira regra será tratada no próximo artigo. Abraços.