Lookaround

O lookahead (olhar para frente) e o lookbehind (olhar para trás) são chamados de lookarounds (olhar em volta). Os lookarounds verificam se uma determinada subexpressão casa, retornando true ou false.

A engine não se move depois que um lookaround tem sua condição satisfeita. Isso torna os lookarounds muito poderosos. Você pode checar quantas coisas você quiser e continuar na mesma posição. Isso é ótimo.

Tanto o lookahead como o lookbehind tem duas versões: uma positiva e uma negativa. Vamos ver a sintaxe de cada uma dessas versões:

Lookahead
Também é chamado de positive lookahead.Sua sintaxe é (?=subexpressão). Casa quando o que estiver à frente dele casar com a subexpressão informada.
Lookbehind
Também é chamado de positive lookbehind. Sua sintaxe é (?<=subexpressão). Casa quando o que estiver atrás dele casar com a subexpressão informada.
Negative lookahead
Sua sintaxe é (?!subexpressão). Casa quando o que estiver à frente dele não casar com a subexpressão informada.
Negative lookbehind
Sua sintaxe é (?<!subexpressão). Casa quando o que estiver atrás dele não casar com a subexpressão informada.

Você pode usar uma regrinha pra entender a sintaxe dos lookarounds. Você sempre vai colocar ? depois de abrir parênteses. Se o lookaround for positivo, você vai colocar = antes da subexpressão. Se for negativo, você vai colocar ! antes da subexpressão. Se for lookbehind, você vai colocar também um < após o ?.

Exemplos:

  • (?=vi)vi: casa com os vi da string Avião do vizinho. A engine anda até chegar ao primeiro vi. Tanto é que se você tentar casar com o A do Avião depois do lookahead, você não vai conseguir. Isso aconteceu porque ela precisa andar pela string até que a condição dela seja atendida e ela não volta no início da string depois disso. Quando eu disse que lookaround não anda, eu quis dizer que ele não anda depois que ele encontra os caracteres que casam com a subexpressão dele. Se tivéssemos um grupo ou apenas os caracteres vi, a engine andaria mais e ficaria um caractere à frente do vi. E é esse tipo de "caminhada" que o lookaround não faz. Se ele fizesse, a expressão não teria casado com nenhum vi.
  • (?<=vi)vi: nesse exemplo, a expressão não casa com nada. Como eu uso um lookbehind, que olha para trás, ele só tem sua condição atendida quando ele já passou do vi, porque ele só vai conseguir enxergar vi atrás dele depois que já tiver passado dele. No caso da string Avião do vizinho, isso ocorre no ã do Avião e no z do vizinho. Como nenhum lookaround volta, eu não consigo casar com vi.
  • (?<=vi).: na string Avião do vizinho, esse exemplo casa com o ã do Avião e o z do vizinho.
  • (?![A-zÀ-ÿ])\d: esse exemplo tem um negative lookahead. Ele verifica se o que está na frente dele não é uma letra. Depois, ele casa com um dígito. Na prática, isso faz com que ele só case com dígitos. Por isso, na string Casa 27, casa com os dígitos do número 27.
  • (?<![A-zÀ-ÿ])\d: esse exemplo é bem parecido com o anterior. A diferença é que ele é um lookbehind, mas também é negativo como o anterior. Na string Sala AB34, ele só casa com o dígito 4. Isso acontece porque no dígito 3 há a letra B antes dele.

Um detalhe importante é que lookarounds são atômicos, o que faz com que uma vez que um lookaround seja avaliado como true ou false, a engine não faça o backtracking. Na prática, isso só faz diferença quando você captura alguma coisa dentro de um lookaround. Aí, se a expressão falhar, não dá para fazer backtracking pra tentar mudar o que casou no que foi capturado, como pedir algum caractere de um quantificador guloso ou tentar outro branch de uma alternação. Esse comportamento também ocorre em grupos atômicos.