• Nenhum resultado encontrado

EXPRESSÕES REGULARES NA VIDA VIRTUAL

19VISÃO GERAL DO CAPÍTULO

EXPRESSÕES REGULARES NA VIDA VIRTUAL

Expressões regulares são usadas em muitas aplicações para especificar padrões em strings de caracteres. Parte do trabalho inicial na tradução de REs para código foi feita para oferecer um modo flexível de especificar strings no comando “find” (ou “localizar”) de um editor de textos. A partir desta gênese inicial, a notação passou para muitas aplicações diferentes.

Unix e outros sistemas operacionais utilizam o asterisco como um curinga para corresponder substrings a nomes de arquivo. Aqui, * é uma abreviação para a RE O*, especificando zero ou mais caracteres retirados do alfabeto inteiro de caracteres válidos. (Como poucos teclados têm a tecla O, a abreviação foi adotada) Muitos sistemas usam ? como curinga que corresponde a um único caractere.

A família de ferramentas grep, e seus aparentados em sistemas não Unix, implementa a correspondência de padrões de expressão regular. (Na verdade, grep é um acrônimo para global regular-expression pattern match and print — padrão de expressão regular global). As expressões regulares encontraram uso generalizado porque são facilmente escritas e entendidas, e uma das técnicas escolhidas quando um programa precisa reconhecer um vocabulário fixo. Funcionam bem para linguagens que se encaixam em suas regras limitadas. E facilmente traduzidas para uma forma executável, e o reconhecedor resultante é rápido.

2²Nt←{n} ∪ {mm←←n←dN};m←an← ←bad, badbadsi=0input← input stream←←

Fechamento finito

Para qualquer inteiro i, a RE Ri designa de uma a i ocorrências de R.

Fechamento positivo

A RE R+ indica uma ou mais ocorrências de R, normalmente escrita como

= Ri

i 0 .

Se a linguagem limitar o tamanho máximo de um identificador, podemos usar o fechamento finito apropriado. Assim, identificadores limitados a seis caracteres poderiam ser especificados como ([A…Z] | [a…z]) ([A…Z] | [a…z] | [0…9])5. Se

tivéssemos que escrever a expansão completa do fechamento finito, a RE seria muito maior.

2. Um inteiro sem sinal pode ser descrito como zero ou um dígito diferente de

zero seguido por zero ou mais dígitos. A RE 0 | [1…9] [0…9]* é mais concisa. Na prática, muitas implementações admitem uma classe maior de strings como inteiros, aceitando a linguagem [0…9]+.

3. Números reais sem sinal são mais complexos que os inteiros. Uma RE possível

poderia ser (0 | [1…9] [0…9]*) (ε |. [0…9]*). A primeira parte é simplesmente a RE para um inteiro. O restante gera, ou a string vazia, ou um ponto decimal seguido por zero ou mais dígitos.

As linguagens de programação normalmente estendem os números reais para a notação científica, como em (0 | [1…9] [0…9]*) (ε |. [0…9]*) E (ε | + | −) (0 | [1…9] [0…9]*).

Esta RE descreve um número real, seguido por um E, seguido por um inteiro para especificar um expoente.

4. As strings de caracteres entre aspas possuem suas própria complexidade. Na

maioria das linguagens, qualquer caractere pode aparecer dentro de uma string. Embora possamos escrever uma RE para strings usando apenas os operadores básicos, este é o nosso primeiro exemplo no qual um operador de complemento simplifica a RE. Usando o complemento, uma string de caracteres em C ou Java pode ser descrita como “ ( ̂”)*”.

C e C++ não permitem que uma string se estenda por várias linhas no código-fonte — ou seja, se o scanner alcançar o final de uma linha enquanto estiver dentro de uma string, ele termina a string e emite uma mensagem de erro. Se representarmos uma nova linha pela sequência de escape \n, no estilo C, então a RE “( ̂(” | \n) )*” reconhecerá uma string corretamente formada e levará a uma transição de erro em uma string que inclui uma nova linha.

5. Comentários aparecem em diversas firmas. C++ e Java oferecem ao programador

duas formas de escrevê-los. O delimitador // indica um comentário que vai até o final da linha de entrada atual. A RE para este estilo de comentário é simples: //(^\n)* \n, onde \n representa o caractere newline.

Comentários em múltiplas linhas em C, C++ e Java começam com o delimitador /* e terminam com */. Se pudéssemos não permitir * em um comentário, a RE seria simples: /* (^*)* */. Com *, a RE é mais complexa: /* ( ^* | *+^/ )*

*/. Um FA para implementar esta RE é o seguinte:

Operador de complemento

A notação ^c especifica o conjunto {O – c}, o com- plemento de c em relação a O. O complemento tem precedência maior do que *, | ou +.

Sequência de escape

Dois ou mais caracteres que o scanner traduz para outro caractere. Sequências de escape são usadas para caracteres que não possuem um glifo, como newline (nova linha) ou tab (tabulação), e para aqueles que ocorrem na sintaxe, como uma aspa de início ou fim.

A correspondência entre a RE e este FA não é tão óbvia como nos exemplos anteriores deste capítulo. A Seção 2.4 apresenta construções que automatizam a construção de um FA a partir de uma RE. A complexidade de ambos para comentários em múltiplas linhas surge do uso de delimitadores com mais de um caractere. A transição de s2 para s3 codifica o fato de que o reconhecedor viu um

*, de modo que pode lidar com o aparecimento de uma / ou sua falta de maneira correta. Ao contrário, Pascal usa delimitadores de comentário de único caractere: { e }, de modo que um comentário em Pascal é apenas { ^}* }.

Tentar ser específico com uma RE também pode levar a expressões complexas. Consi- dere, por exemplo, que o especificador de registradores em uma linguagem assembly típica consiste na letra r seguida imediatamente por um inteiro pequeno. Em ILOC, que admite um conjunto ilimitado de nomes de registrador, a RE poderia ser r[0.. .9]+,

com o seguinte FA:

Este reconhecedor aceita r29 e rejeita s29. Ele também aceita r99999, embora atualmente nenhum computador disponível tenha 100.000 registradores.

Em um computador real, porém, o conjunto de nomes de registrador é bastante limitado — digamos, para 32, 64, 128 ou 256 registradores. Um modo do scanner verificar a validade de um nome de registrador é convertendo os dígitos para um número e tes- tando se ele se encontra ou não no intervalo de números válidos para o registrador. A alternativa é adotar uma especificação de RE mais precisa, como:

Esta RE especifica uma linguagem muito menor, limitada aos números de registrador de 0 a 31 com um 0 inicial opcional nos nomes de registrador com único dígito. Ela aceita r0, r00, r01 e r31, mas rejeita r001, r32 e r99999. O FA correspondente se parece com este:

Esta RE é conceitualmente mais simples, porém muito maior do que a versão anterior. O FA resultante ainda exige uma transição por símbolo de entrada. Assim, se pudermos controlar o crescimento no número de estados, poderíamos preferir esta versão da RE, pois é clara e óbvia. Porém, quando nosso processador, de repente, tem 256 ou 384 registradores, a enumeração também pode ser tediosa.