Está en la página 1de 51
Capitulo 2 Varredura 2.1 O processo de varredura 2.5 Implementagao de um sistema de varre- 2.2 Expressées regulares dura TINY 2.3 Autématos finitos 2.6 Uso de Lex para gerar automatica- 24 Das expresses regulares para os mente um sisiema de varredura aut6matos finitos deterministicos A varredura, ou andlise léxica, 6 a fase de um compilador que Ié 0 programa-fonte como um arquivo de caracteres e 0 separa em marcas, Essas marcas s4o como palavras em uma lin- ‘gvagem natural: cada marca é uma seqdéncia de caracteres que representa uma unidade de informagao do programa-fonte. Exempios tipicos sao palavras-chave, como if e while, que so ccadeias fixas de caracteres; identificadores, que so cadeias de caracteres definidas pelo usuario, ‘usualmente compostas por letras e niimeros comegando por uma letra; simbolos especiais, como ‘0s simbolos arimétcos + e *; e algurs simbols com mais de um caraclere, como >= e <>. Em cada caso, uma marca representa um determinado padrao de caracteres reconhecido pela varredura a partir do inicio & medida que esses caracteres so fornecidos. Como a tareta efetuiada pelo sistema de varredura é um caso especial de casamento de padres, precisamos estudar métodos de especificacao e de reconhecmento de padrdes que ‘se apiquem ao processo de varredura. Esses métodes sao basicamente os de expressoes rogulares e de autématos finitos. Entretanto, um sistema de varredura 6 também a parte do compilador que manipula a entrada do cédigo-fonte, e como essa entrada frequen- temente consome muito tempo, a varredura precisa ser tao eficiente cuanto possivel. Assim, precisamos também ser muito cuidadosos quanto aos detalhes préticos da estrutura do sistema de varedura, Dividimos 0 estudo de sistemas de varredura da seguinte forma: primeiro, damos uma visdo geral da operagdo de um sistema de varredura e os respectivos conceitos © estruturas. Depois, estudamos expressées regulares, uma notagéo padréo para representar padrées om cadelas de caracteres que formam a estrutura léxica de uma linguagem de programagao. Em seguida, estudamos méquinas de estados finitos. ou autimatos finitos, que representam algoritmos para reconhecimento de padres em cadeias de caracteres dadas por expressées regulares. Estudamos também o processo de construgdo de autématos fintos a partir de ‘expressdes regulares. Passamos, em seguida, para métodos praticos, os quais escrevem programas que imple- mentam os processos de reconhecimento representados por autémaios finitos, ¢ estudamos ura implementagdo completa de um sistema de varredura para a linguagem TINY. Finalmente, estudamos como 0 proceso de producéo de um programa para varredura pode ser auto- matizado pelo uso de um gerador de sistemas de varredura, e repetimos a implementagao de tum sistema de varredura para TINY utiizando Lex, que é um gerador de sistemas de varredura padrao disponivel em Unix bem como em outros sistemas. 2.1 0 PROCESSO DE VARREDURA Cabe ao sistema de varredura ler caracteres do cOdigo-fonte e organizé-los em unidades logi- cas para as outras partes do compilador (como o analisador sintatico). As unidades logicas xgeradas pela varredura so denominadas marcas, ¢ organizar caracteres em marcas é simi- lar a organizar caracteres em palavras para uma sentenga em inglés e decidir que palavras usar. A varredura se assemelha, portanto, & atividade de soletrar. ‘As marcas sio entidades légicas, usualmente definidas como um tipo enumerado. Por exemplo, marcas podem ser definidas em C como!: typedef enum {IF THEN, ELSE, PLUS, MINUS, NUM, ID, ... ‘TokenType; Hi diversas categorias de marcas, Entre elas temos as palavras reservadas, como IF € THEN, que representam as cadeias de caracteres “if” (se) € “then” (entao). Uma segunda categoria é a de simbolos especiais, como os simbolos aritméticos PLUS e MINUS, que tepresentam os caracteres “+” e "~”. Finalmente, h4 as marcas para representar cadeias multiplas de caracteres. Alguns exemplos s4o NUM e ID, que representam nuimeros e identificadores. ‘As marcas, como entidades ldgicas, precisam set claramente diferenciadas das cadeias de caracteres as quais representam. Por exemplo, a marca de palavra reservada TF precisa ser diferenciada da cadeia com dois caracteres “it” que a representa, Para tomar clara essa ferenciacao, a cadeia de caracteres representada por uma marca é por vezes denominada como seu valor ou lexema. Algumas mareas tém apenas um lexema:as palavras reservadas tém essa propriedade. Uma marca, entretanto, tem o potencial de representar infinitos lexemas. Identificadores, por exemplo, sio todos representados pela marca TD, mas podem ter muitos valores que representam seus nomes individuais. Esses nomes no podem ser ignorados, pois um compilador precisa acompanhé-los na tabela de simbolos. Assim, um sistema de varredura precisa também construir os valores de pelo menos algumas das marcas. Qualquer valor associado a uma marca recebe o nome de um atributo da marca, e valor é um exemplo de um atributo. As marcas podem ter também outros atributos. Por exemplo, uma marca NUM pode ter como atributo um valor como “32767”, que é composto [por cinco caracteres numéricos, mas ela também tera como atributo um valor numérico que 6efetivamente o valor 32767. No caso de uma marca de simbolo especial como PLUS, temos nao apenas o caractere “+”, mas também a operacao aritmética + associada a ela. O proprio 1. Em uma linguagem sem tipos eniumerados, terfamos de defini as marcas diretamente como valores numéricos simbélicos. Assim, em C antigo encontramos, ds vezes,o guint: factine ar 256 define THEY 257 define ELSE 258 (Ess0s niimeros comesam em 256 para evitar confusio com valores numéricos da tabela ASCIL) simbolo da marca pode ser visto como outro atributo, ¢ a marca pode ser vista como a colegio de todos os seus atributos. Um sistema de varredura deve computar pelo menos tantos atributos de uma marca quanto necessario para possibilitar a continuidade do processamento. Por exemplo, © valor em caracteres de uma marca NUM deve ser computado, mas seu valor numérico io precisa ser computado de imediato, pois ele pode ser obtido de seu valor em caracteres. Se o valor numérico for, no entanto, computado, os caracteres podem ser descartados. Por ‘vezes, 0 proprio sistema de varredura pode efetuar as operacOes necessarias para gravar um atributo no local apropriado, ou pede simplesmente passar o atributo para uma fase posterior cdo compilador. Por exemplo, um sistema de varredura poderia usar 0 valor em caracteres de um identificador como entrada em sua tabela de simbolos, ou passé-lo adiante. Como 0 sistema de varredura precisaré possivelmente computar diversos atributes para cada marca, é freqientemente iitil coletar todos 0s atributos em um tinico tipo de dados estruturado, que denominaremos registro de marca. Esse registro poderia ser decla- redo em C como typedef struct { Tokentype tokenval, char * stringval; int numval; } TokenRecord; ou pessivelmente como uma unio typedef struct { TokenType tokenval; {char * stringval; int numval; Jateribute; } TokenRecord; (0 qual assume que 0 atributo de valor em caracteres & necessério apenas para os identi- ficadores e o atributo de valor numérico apenas para mimeros). Um arranjo mais comum 6 sistema de varredura retornar apenas o valor da marca e colocar os outros atributos em varidveis que possam ser acessadas por outras partes do compilador. Embora a tarefa do sistema de varredura seja converter todo © programa-fonte em ‘uma seqtiéncia de marcas, o sistema de varredura raramente fard isso tudo de uma vez. Em vez disso, a varredura ocorreré sob 0 controle de um analisador sintético, retomando a marca seguinte demandada por meio de uma funcio com declarac3o similar & seguinte declaracao em C TokenType getToken(void) + A fungdo getToken declarada dessa maneira, quando ativada, retornard a proxima marca fomecida, bem como computaré atributos adicionais, como o valor em caracteres da marca. A cadeia de caracteres de entrada geralmente nao € colocada como parametro dessa funcdo, mas sim armazenada em um repositdrio ou fornecida pelos recursos de entrada do sistema, Como exemplo de operacio de get Token, considerea seguinte linha de cédigo fonteC, utilizada como exemplo no Capitulo 1: alindex] = 4 +2 Suponha que essa linha de e6digo seja armazenada em um repositério de entrada conforme apresentado a seguir, com 0 préximo caractere de entrada indicado pela seta: al t[s]#] 4[e] =|) =-LELEL[E A ativagio de getToken precisaré agora saltar os quatro brancos seguintes, re~ conhecer a cadeia de caracteres “a” composta unicamente pelo caractere a como a proxima marca, e retornar 0 valor de marca ID como proxima marca, deixando o repositério de entrada assim: Dessa forma, uma chamada subseqiiente de get Token iniciaré o reconhecimento a partir do caractere de colchete a esquerda, Passamos agora ao estudo de métodos para definir e reconhecer padrdes em cadeias de caracteres. 2.2. EXPRESSOES REGULARES Expresses regulares representam padrdes de cadeias de caracteres. Uma expressio regular r€ completamente definida pelo conjunto de cadeias de caracteres com as quais ela casa. Esse conjunto é denominado linguagem gerada pela expressio regular e é denotado como L(7). Aqui a palavra linguagemt ¢ utilizada apenas para indicar uma “‘cadeia de caracteres” € (pelo menos nesse estégio) nao tem relacdo especifica com uma linguagem de programagao. Essa linguagem depende, primeiro, do conjunto de caracteres disponivel. Em geral, esse sera o conjunto de caracteres ASCII ou algum subconjunto dele. Por vezes, 0 conjunto sera mais geral que o conjunto de caracteres ASCII, e nesse caso 0s elementos do conjunto sio identificados como simbolos. Esse conjunto de simbolos legais 6 denominado alfabeto, ¢ usualmente denotado pelo simbolo grego (sigma). Uma expresso regular r também conterd caracteres do alfabeto, mas estes tém um. significado distinto: em uma expressio regular, todos os simbolos indicam padries. Neste capitulo, diferenciaremos o uso de um caractere e de um padrao escrevendo os padrdes em negrito. Assim, a é 0 caractere a usado como padrio. Finalmente, uma expressio regular r pode conter caracteres com significados especiais. Esses caracteres So denominados metacaracteres ou meta-simbolos. De maneira geral, eles nao sao caracteres legais do aliabeto, caso contrério nés no poderfamos diferenciar seu uso como metacaracteres de seu uso como membros do alfabeto. Freqientemente, entretanto, nao 6 possivel exigir essa exclusio, e uma conven¢io precisa ser utilizada para diferenciar ‘os dois possiveis usos de um metacaractore. Em muitas situagoes, isso é feito por um earac- tere de escape que “desliga” o significado especial de um metacaractere. Caracteres de escape comuns sao 0 de barra invertida e as aspas. Observe que os caracteres de escape S50 também metacaracteres, caso sejam também caracteres legais do alfabeto. 2.2.1. Definigdo de expresses regulares Pedemos agora descrever osignificado de expressdes regulares pelo estabelecimento de quais linguagens do geradas e por quais padres. Fazemos isso em varios estigios. Primeiro, descrevemos o conjunto de expresses regulares basicas, que € composto por simbolos indi- viduais. Em seguida, descrevemos operagoes que geram novas expresses regulares a partir das existentes. Isso ¢ similar 4 forma como expressées aritméticas sio construidas: as expressdes aritméticas bésicas sd 0s ntimeros, como 43 ¢ 2.5. As operacdes aritméticas, como adicéo e multiplicagio, podem ser utilizadas para formar novas expressoes a partir das existentes, como em 43 *2.5.¢ 43" 2.5414 © grupo de expresses regulares descrito aqui é minimal, no sentido de conter somente as operagdes e meta-simbolos essenciais. Mais adiante, consideraremos extensdes desse conjunto minimal Expressoes regulares bisicas Sao simplesmente os caracteres em separado do alfabeto, que casam com eles mesmos. Dado um caractere « do alfabeto E, indicamos que a expresso regular a casa com o caractere « escrevendo L(a) = {a}. Ha dois simbolos adicionais necessirios em situagdes especiais. Precisamos ser capazes de indicar um casamento da cadeia vazia, ou seja, a cadeia sem caracteres. Utilizamos o simbolo ¢ (epsilon) para deno- tar a cadeia vazia, e definimos 0 meta-simbolo e (epsilon negrito) como L(e) = (el. Precisamos também, por vezes, escrever um simbolo que nao casa com nenhuma cadeia de caracteres, ou seja, um simbolo cuja linguagem € 0 conjunto vazio, denotado como (). Utilizamos o simbolo para isso, e escrevemos L(®)=|). Observe a diferenca entre {) e [e}: © conjunto [} no contém cadeias de caracteres, enquanto 0 conjunto [e} contém uma tinica cadeia composta por zero caractere. Operacdes de Expressies Regulares Ha trés operagdes basicas em expresses regulares: (1) escolha entre altemativas, que ¢ indicada pelo metacaractere | (barra vertical); (2) concate~ ago, que ¢ indicada pela justaposicdo (sem metacaracteres);e (3) repeticio ou “fecho”, que € indicada pelo metacaractere *. Discutimos cada uma delas, apresentando a construgao de conjuntos correspondente para as linguagens de cadeias que casam. Escolka Entre Alternativas Se re 3 380 expresses regulares, entio r|s é uma expressio regular que casa com qualquer cadeia que case com r ou com s. Em termos de linguagens, a linguagem de r|s € a uniae das linguagens de re s, ou seja, L(r|s) = L(r) U L{s)-Como um exemplo simples, considere a expressio regular a|b: ela casa com qualquer um dos carac- teres 1 ou b, ou seja, L(a |) = L(a) U L(b) = [a} U [b} = {a,b}. Como um segundo exemplo, a expresso regular ae casa com 0 caractere isolado a ou com a cadeia vazia (sem nenhum caractere). Em outras palavras, L(a |e) = (2, €). Aescolha pode ser estendida para mais de uma alternativa, para que, por exemplo, L(a|b| c|4) = (a, », cd). Por vezes, também escrevemos longas seqtiéncias de escolhas com reticéncias, como em a| | . .. | 2, que casa com qualquer letra em caixa baixa de a az. Concatenacto A concatenacio de duas expressdes regulares res € denotada como rs, e casa com qualquer cadeia de caracteres que seja a concatenacao de duas cadeias, desde que a primeira case com re a segunda case com s. Por exemplo, a expressio regular ab casa is com a cadeia de caracteres ab, enquanto a expressio regular (a|b) casa com as cadeias ac e be. (O uso de parénteses como metacaracteres nessa expressio regular sera explicado em breve.) Podemos descrever o efeito da concatenacdo em termos das linguagens geradas pela definicio da concatenagao de dois conjuntos de cadeias de caracteres. Dados dois conjuntos de cadeias de caracteres 5, e S,, 0 conjunto concatenado de cadeias de caracteres $; S> 6 0 Conjunto de cadeias de S, seguido de todas as cadeias de $,. Por exemplo, se S, = jaa, b] e S2= (a, bb], entio ; S,= aaa, aabb, ba, bbb|. A operacdo de concatenacio para expressies regulares pode ser definida como: L(rs) = L(F)L(s). Assim (utilizando nosso exemplo ante- rior), L((a|b) c) = L(a| B)L(c) = fa, ble] = fac, be]. A concatenacio também pode ser estendida para mais do que duas expresses regu- lates: L(ryr.F,) = L(rL(r)..U(r,) = 0 conjunto de cadeias de caracteres formadas pela concatenaggo de todas as cadeias de L(r))owy L(,)- Repeticdo A operagao de repetigao de uma expresso regular, as vezes também denomi- nada fecho (de Kleene), é denotada como r*, onder é uma expressio regular. A expresso regular r* casa com qualquer concatenagao finita de cadeias de caracteres, desde que cada cadeia case com r. Por exemplo, a* casa com as cadeias ¢ a, aa, aaa... (Ela casa com € porque 6 a concatenagio de zero cadeias que casam com a.) Nés podemos definir a operagao de repetigao em termos das linguagens geradas pela definicao de uma operacao simular * para conjuntos de cadeias de caracteres. Dado um conjunto $ de cadeias, St = [e} USUSS USSU... Iso € uma unio infinita de conjuntos, em que cada elemento ¢ uma concatenacao finita de cadeias de S. Por vezes, 0 conjunto S* é denotado como: Us onde S = 5...5 6 a concatenagao de § n vezes. (5° =e.) Podemos agora definir a operacao de repeticao para expressoes regulates assim: Lee Lint Como exemplo, considere a expressio regular (a |bb)*. (Novamente, a razio para os parénteses como metacaracteres sera explicada a seguir.) Essa expressdo regular casa com qualquer uma das seguintes cadeias: e, a, bd, aa, abb, ba, bbbb, aaa, azbb, e assim por diante. Em termos de linguagens, L( (a | bb) *) = L(a | bb)* = {a, bb}* = {e, a, bb, az, ab, bba, bbb, aaa, aabb, abba, abbbo, baa, Precedéncia de Operacées e Uso de Parénteses As descrigdes acima desconsideraram a questo de precedéncia entre escolha, concatenagao e repetigio. Por exemplo, dada a expressao regular a|b*, ela deveria ser interpretada como (a|b)* ou como al (b*)? (Existe uma diferenca significativa, pois L((ab)*) = le, a, b, aa, ab, ba, bb, .), enquanto L(a| (b*)) = le, a,b, bb, bbb, ..),) A convengao padrao é que a repeticao deveria ter pre- cedéncia maior, assim a segunda interpretacio é a correta, Entre os trés operadores, # recebe ‘ maior precedéncia, seguido da concatenagio, de tal forma que | recebe a menor precedéncia, Assim, por exemplo, a|be* é interpretada como a| (b(c*)), € ab|ctd é interpretada como (ab) | ((c*) 4). Quando desejarmos indicar uma precedéncia diferente, devemos utilizar parénteses. E por isso que tivemos de escrever (a|b)¢ para indicar que a escolha deveria ter pre- cedéncia sobre a concatenacdo, caso contrario a |be seria interpretada como casendo com a ‘ou com be. Similarmente, sem 03 parénteses, (a|bb) * seria interpretada como a|bb*, que casa com a, b, bb, bbh,... Esse uso dos parénteses é totalmente andlogo ao seu uso em aritmética, onde (3 + 4) *5 = 35, mas 3 + 4* 5 = 23, pois * tem precedéncia superior a +. Nomes para Expressdes Regulares Freqiientemente, ¢ titil simplificar a notaglo com nomes para expressdes regulares longas, para que no tenhamos de escrever a expresso cada vez que ela for utilizada. Por exemplo, se quisermos desenvolver uma expresso regular para uma seqiiéncia com um ou mais digitos, podemos escrever (o2/2|...|9) (0]2|2|...|/9)* ouentio aigito aigito* onde @igito = 0/1/2|.../9 6 ums definigio regular do nome digito. O uso de uma definicao regular é de grande conveniéncia, mas ele introduz a compli- cacao de tornar o préprio nome um meta-simbolo, assim é preciso encontrar um meio de distinguir o nome da concatenagio de seus caracteres. Em nosso caso, fizemos a distingso com 0 uso de itélicos no nome. Observe que um nome nio pode ser usado em sua propria definigao (ou seja, recursivamentte) ~ precisamos remover nomes de sua substituigdo suices- siva para expresses regulares que eles representam, ‘Antes de considerar uma série de exemplos para elaborar nossa definigio de expresses regulares, juntamos todas as partes da definicéo de uma expressio regular. Definigio Umua expressao regular ¢ uma das seguintes: 1. Uma expressio regular basica, composta por um tinico caractere a, onde a pertencea um alfabeto de caracteres legais; 0 metacaractere & ou. o metacaractere 4 No primeiro caso, L(a) = {a}; no segundo caso, L(€) = {e}; no terceiro caso, L(@) = {}- 2. Uma expressio da forma r/s, onde re s sio expresses regulares, Nesse caso, L(r| 8) = = Lr) U Lis). 3. Uma expressio da forma rs, onde re s sio expressdes regulares. Nesse caso, L(rs) = L(MLAs). 4. Uma expressdo da forma rs, onde r é uma expresso regular. Nesse caso, L(r#) = L(1)*. 5. Uma expressio da forma (1), onde r ¢ uma expressdo regular. Nesse caso, L((9) = L(1). Assim, 08 parénteses no modificam a linguagem. Eles sio ufilizados apenas para ‘ajustar a precedéncia dos operadores. Observamos que, nessa defini¢ao, a precedéncia dos operadores em (2), (3) e (4) esté na ordem inversa 4 de sua apresentacdo; ou seja, | tem precedéncia mais baixa que con- catenagio € concatenagio tem precedéncia mais baixa que *. Observamos também que essa definigao da um significado de metacaractere aos seis simbolos 6, € |, *, () No restante desta segao, consideramos uma série de exemplos designados para tra- balhar as definicoes dadas anteriormente. Eles so um tanto artificiais, pois ndo aparecem. uusualmente como descrigies de marcas em uma linguagem de programacao. Na Secio 2.2.3, consideramos algumas expresses regulares comuns que aparecem freqiientemente como ‘marcas em linguagens de programagao, Nos exemplos a seguir, hd uma descricio em portugués das cadeias a serem casadas, e atarefa é traduzira descrigdo em uma expressio regular. Essa situagdo, na qual um manual de linguagem contém descrigdes de marcas, é a mais comum para quem escreve um compilador. Ocasionalmente, pode ser necessétio inverter a dimeg3o, ou seja, partir da ex- pressdo regular em direcdo a descricdo em portugués, assim também incluimos alguns exercicios desse tipo. Fxemplo 21 Considere o alfabeto simples composto por smente tis caracteres alfabéticos: 3 = |a, b,c}. Con- sidere 0 conjunto de todas as cadeias sobre esse alfabeto que contém exatamente um b, Esse conjunto é gerado pela expressio regular (ale) *b(aley* Observe que, embora b aparea no centro da expressio regular, a letra b no precisa estar no centro da cadeia que casa com a expresso. A repeticao de « ouc antes e depois de b pode ocorrer em um diferente ntimero de vezes. Assim, todas essas cadeias casam com a expresso regular acima: b, abe, abaca, baaaac, ccben,ccccccb. Exemplo 2.2 Com 0 mesmo alfabeto do exemplo anterior, considere 0 conjunto de todas as cadeias de caracteres que coniém no maximo um b. Uma expresso regular para esse conjunto pode ser obtida usando a solugéo do exemplo anterior como uma alternativa (que casa com as cadeias com exatamente um b) e a expressio regular (ac) * como uma segunda alterna- tiva (que nao casa com bs), Assim, temos a solugao: (ale)*| (ale) *b(ale)* Uma solucao alternativa seria permitir b ou a cadeia vazia entre as duas repetigbes deaoue: (ale) * (ble) (aley* Fsse exemplo apresenta um ponto importante sobre expressdes regulares: a mesma linguagem pode ser gerada por muitas expresses regulares diferentes. Usualmente, tenta- ‘mos encontrar uma expressao regular tao simples quanto possivel para descrever um conjunto de cadeias, mas nés nunca tentaremos provar que encontramos a cadeia “mais simples” - por exemplo, a mais curta. Ha dois motivos pat Primeiro, raramente ocorre em situagdes praticas, onde usualmente ha uma solugao “mais simples” padrao. Segundo, quando estudarmos os métodos para reconhecer expresses regulars, 0s algoritmos serdo capazes de simplificar o processo de reconhecimento, sem a necessidade de antes simpli- ficar a expressao regular. Exemplo 23 Considere 0 conjunto de cadeias de caracteres $ sobre o alfabeto E = {a, b] composto por um tinico b rodeado pelo mesmo niimero de as: = (b, aba, aabaa, aaabaaa,..) = {a"ba*|n #0) Esse conjunto nao pode ser descrito por uma expresso regular. © motivo é que a Xinica operacdo de repeticdo que temos é a operacio de fecho *, a qual permite qualquer nuimero de repetigies. Assim, se escrevermos a expressio a*ba (gue € 0 mais pero que podemos chegar de uma expressao regular para 5), no hé garantia que o mimero de as antes e depois de b seré o mesmo. Expressamos isso dizendo que “expresses regulares no podem contar”. Uma prova matemitica desse fato, entretanto, solicitaria o uso de um famoso teorema sobre expressdes regulares denominado lema do bombeamento, estudado em teoria de automatos, mas que nao sera mais mencionado aqui. Claramente, nem todos os conjuntos de cadeias de caracteres que podemos des- crever em termos simples podem ser gerados por expressées regulares. Um conjunto de cadeias que ¢ a linguagem para uma expressao regular ¢, portanto, diferenciado de outros conjunios e recebe 0 nome de conjunto regular. Ocasionalmente, conjuntos nao regulares aparecerao como cadeias de caracteres em linguagens de programagio e precisardo ser reconhecidos em uma varredura. Trataremos deles quando surgirem, € retornaremos a essa questo novamente de forma breve na secdo sobre consideracdes priticas em temas de varredura. Exemplo 2.4 Considere as cadeias de caracteres sobre o alfabeto E = (a,b, c| que nao contém dois bs con- secuitivos. Assim, entre quaisquer dois bs deve ocorrer pelo menos um 2 ou c. Construimos uma expresséo regular para esse conjunto em varios estagios. Primeiro, podemos forgar um ou €a ocorrer antes de cada b: ((ale))* Podemos combinar isso com a expresso (a |e) *, que casa com cadeias as quais nao tém nenhum b, ¢ escrever ((aje)*| (b(ale))** ou, enido, notando que (r* |s#)* casa com as mesmas cadeias que (r|s)*, (ale) | (b(ale)))* {alc|pa|be) * (Aviso! Essa ainda nao é a resposta correta.) A linguagem gerada por essa expresso regular tem a propriedade que buscamos, ou seja, nao ocorrem dois bs consecutivos (mas ainda nao esta totalmente correta). Ocasionalmente, deveriamos provar essas afirmativas, entio esbocamos uma prova de que todas as cadeias em L( (a|¢|ba|be) *) nao contém bs consecutivos. Isso é provado por indugao nas cadeias de comprimento 0, 1 ou 2: essas cadeias sao precisamente as ca- dias ¢, a,c, aa, ac, ca, cc, ba, be. Agora, assuma que isso é verdade para todas as cadeias na linguagem de comprimento i < n, e sejas uma cadeia de comprimento n > 2. Entao, s contém mais de uma das cadeias diferentes de e listadas acima, e portanto s = 5,5, onde 5; € 5) também est3o na linguagem e nao s4o e. Assim, por indugio, tanto s; como s nao tm bs consecutivos. Portanto, a tinica forma de s ter dois bs consecutivos seria se 5; terminasse com um b es iniciasse com um b. Mas isso € impossfvel, pois nenhuma cadeia na linguagem termina com b- Este tiltimo fato usado no esboco da prova ~ 0 fato de que nenhuma cadeia gerada pela expressao regular acima pode terminar com um b - também mostra porque nossa solugao ainda nao ests totalmente correta: ela nio gera as cadeias b, ab e cb, que nao contém bs consecutivos. Nés corrigimos isso adicionando um b final opcional, da seguinte maneira: ( ¢|ba|be) * (b|e) Observe que a imagem em espelho dessa expresso regular também gera a lin- guagem dada (b|e) (ale|ab|cb)* ‘Também poderiamos gerar a mesma linguagem assim: (notb|b notb)* (b/e) onde notb = alc. Esse é um exemplo de uso de um nome para uma subexpressio. Essa solugio, na verdade, € preferivel quando 0 alfabeto € grande, pois a definigao de notb pode ser ajustada para incluir todos as caracteres exceto b, sem complicar a ex- pressao original. Exemplo 25 Neste exemplo recebemos a expresso regular e precisamos determinar uma descrigao concisa em portugués da linguagem gerada. Considere o alfabeto E = (a, b, cl e a expresso regular ((b |e) #a(b|c) ta) * (ble) * Isso gera a linguagem de todas as cadeias contendo um nimero par de a3. Para veri- ficar isso, considere a expresso dentro da repeticao externa a esquerda: (|e)saiblerea Isso gera cadeias terminando com a que contém exatamente dois as (qualquer mimero de bs e cs pode aparecer antes ou entre os dois as). A repeticdo dessas cadeias resulta em todas as cadeias terminando com a cujo ntimero de as é um miiltiplo de 2 (ou sea, par). O acréscimo da repeticao (b| ¢) * no final (como no exemplo anterior) forneceu o resultado desejado. Observamos que essa expressio regular poderia também ser escrita como (nota* @ nota* a)* notat 22.2 Extenstes de expressies regulares Nés demos uma definigéo de expressées regulares que utiliza um conjunto minimal de operadores comuns a todas as aplicagdes, e poderiamos nos limitar ao uso apenas das trés operacdes basicas (junto com parénteses) em todos os nossos exemplos. Entretanto, ja ‘vimos nos exemplos anteriores que escrever expressdes regulares utilizando somente esses operadores é, por vezes, muito complicado, criando expressdes regulares que poderiam ser menos complicadas se um conjunto mais expressivo de operagGes estivesse disponivel. Por exemplo, seria titil ter uma notacdo para um casamento de qualquer caractere (por enquanto, precisamos listar todos os caracteres do alfabeto). Adicionalmente, seria util ter uma expresso regular para um conjunto de caracleres e uma expressio regular para todos (05 caracteres exceto um. Nos paragrafos a seguir, descreveremos algumas extensdes para as expressdes regu- lares padrdo jf discutidas, com novos meta-simbolos correspondentes, para cobrir essas € outras situacdes comuns similares. Na maioria desses casos, nao existe uma terminologia comum, assim usaremos uma notacdo similar usada pelo gerador de sistemas de varre- dura Lex, descrito mais adiante neste capitulo. Muitas das situagdes descritas, a seguir, aparecerao novamente em nossa descrigao de Lex. Entretanto, nem todas as aplicagdes que uusam expresses regulares incluirdo essas operagdes, e mesmo quando as incluirem, uma notagio diferente pode ser usada. ‘Vamos agora a nossa lista de novas operagies. ‘UMA OU MAIS REPETICOES Dada uma expressdo regular r, a repeticao de r é descrita usando a operacdo padrio de fecho, denotada como r*. Isso permite que r seja repetida zero ou mais Vezes. Uma situagio tipica é a necessidade de uma ou mais repeticies em vez de zero, © que garante que pelo menos uma cadeia que case com r apareya, e impede a cadeia vazia € Umexemplo ¢ um numero natural, que deve ser uma sequencia de digitos, com pelo menos um digito. Por exempio, se quisermos casar com mimeros binirios, podemos escrever (0/1) *, mas isso também admite a cadeia vazia, que nao é um nimero. Poderiamos, evidentemente, escrever (0)1) (oj a)* mas essa situacio ocorre tdo freqtientemente que uma noiacao relativamente padrao foi desenvolvida usando + em vez de *: r+ indica uma ou mais repeticoes de r-Assim, nossa expressio regular anterior para nimeros bindries pode ser escrita (oja)+ QUALQUER CARACTERE Uma situagdo comum € a necessidade de casamento com qualquer caractere do alfa- beto. Sem uma operacio especial, isso requer que todos os caracteres do alfabeto sejam listados como altemativas. Um metacaractere tipico utilizado para expressar 0 casa- mento com qualquer caractere ¢ 0 ponto “.”, que nao requer a apresentacdo explicita de todo o alfabeto. Com esse metacaractere, podemos escrever uma expressto regular para todas. as cadeias que contém pelo menos um b da seguinte forma: tb. LUM INTERVALO DE CARACTERES Freqiientemente, precisamos escrever um intervalo de caracteres, como todas as letras em caixa baixa ou todos os digitos. Até aqui nds fizemos isso com a notagio |b]... | para as letras em caixa baixa ou 0/1]. .|9 para os digitos. Uma alter- nativa € ter uma notacio especial para essa situacdo, e uma notagdo comum ¢ usar colchetes e um hifen, como em {a-2] para as letras em caixa baixa e [0-9] para os digitos. Isso pode ser usado também para alternativas individuais, assim a |b|¢ pode ser escrita como [abe]. Intervalos muiltiplos também podem ser incluidos, assim [a-zA-2] representa todas as letras em caixa baixa e em caixa alta. Essa notacao geral rrecebe o nome de classes de caracteres. Observe que essa notacao pode depender da onienagdo do conjunto de caracteres. Por exemplo, [A~Z] assume que os caracteres B, Ce assim por diante vém entre os caracteres A e Z (o que é verdade para 0 conjunto de caracteres ASCII). Entretanto, a expresso [A-z] rifo casa com os mesmos caracteres que a expresso [A-Za-2], mesmo para o conjunto de caracteres ASCII. ‘QUALQUER CARACTERE FORA DE UM DADO CONJUNTO Conforme visto, ¢ frequientemente util poder excluir um unico caractere de um con- junto. Isso pode ser feito pela designagio de um metacaractere para indicar 0 “no”, ou complemento, de um conjunto de alternativas. Por exemplo, um caractere pa- drdo para representar 0 “ndo” em logica é 0 til ~, e poderfamos escrever uma expressio regular para um caractere no alfabeto que no seja # como ~a € um caree~ tere que nio seja anem b nem c como ~(alble) Uma alternativa para essa notacio é utilizada em Lex, onde o caractere de cir- cunflexo * € usado em conjungao com as classes de caracteres anteriormente descritas para formar os complementos. Por exemplo, qualquer caractere que nao seja a ¢é escrito como [*a],, e qualquer caractere que nao seja « nem b nem c escrito como (abel SUBEXPRESSOES OPCIONAIS Uma iiltima ocorréncia comum sio cedeias de caracteres com partes opcionais. Por exemplo, um mimero pode ou nao ter um sinal, como + ou -. Podemos usar alterna- tivas para expressar isso, como nas definigSes regulares natural = [0-9)+ signedNatural = natural | + natural | - natural Isso pode rapidamente se tomar incbmodo, entretanto, e assim introduzimos o caractere de ponte de interrogacdo r? para indicar que as cadeias que casam com r si0 opcionais (ot que zer ou uma eSpias de r esto presentes). Assim, 0 exemplo dos rniimeros com sinal fica natural = [0-9]+ signedNatural = (+|-)? natural 2.23 ExpressOes regulares para marcas de linguagem de programagao. ‘As marcas de linguagem de programagao tendem a se enquadrar em diversas categorias limitadas que sa0 relativamente padronizadas para as diferentes linguagens de progra- macao. Uma categoria ¢ a de palavras reservadas, 4s vezes denominadas palavras-chave, que so cadeias fixas de caracteres alfabéticos com significado especial na linguagem. Por exemplo, temos 4£, while ¢ do em linguagens como Pascal, C e Ada. Outra categoria sto 8 simbolos especiais, como operadores aritméticos, de atribuicéo e de igualdade. Podemos. ter um caractere tinico, como =, ou caracteres miiltiplos, como := ou ++. Uma terceira categoria sdo 05 identificadores, usualmente definidos como sequéncias de letras e digitos iniciando por uma letra. Uma categoria final sao 0s literais ou constantes, que podem ser constantes numéricas como 42 ¢ 3.14159, literais de cadeias de caracteres como “hello, world” e caracteres como “a” ¢ “b”. Descrevemos aqui expressoes regulares tipicas para algumas dessas categorias € discutimos algumas outras questdes relativas ao reconhe- cimento de marcas. Mais detalhes sobre questdes priticas de reconhecimento aparecem no decorrer do capitulo. ‘Niimeros Os nimeros podem ser apenas seqiiéncias de digitos (mimeros naturais), mimeros decimais ou ntimeros com um expoente (indicado por um e/ou E). Por exemplo, 271E-2 representa o ntimero 0,0271. Podemos escrever definicdes regulares para esses ntimeros assim: nat = [0-91+ signedNat = (+|-)? nat number = signedNat(*." nat)?(E signedNat)? Escrevemos 0 ponto decimal entre aspas para enfatizar que ele poderia casar direta~ mente, sem a necessidade de ser interpretado como um metacaractere. Palavras reservadas ¢ identificadores Palavras reservadas Sto as mais simples de escrever como expresses regulares: elas s3o representadas por seqiiéncias fixas de caracteres. Se quiser- ‘mos juntar todas as palavras reservadas em uma tinica defini¢do, podemos escrever algo como rvadas = if | while | do | ... Identificadores, no entanto, sdo cadeias de caracteres que nao sao fixas. Usualmente um identificador deve iniciar com uma letra e conter somente letras e digitos. Podemos expressar isso em termos de definigdes regulares: letra = [a-:A-2] digito = [0-9] identificador = letra(letra|digito)* Comentérios Comentsrios so normalmente ignorados durante a varredura? Nao obstante, um sistema de varredura precisa reconhecer 0s comentérios e descarté-los. Assim, preci- saremos escrever expressies regulares para comentarios, mesmo se 0 sistema de varredura nao tiver uma marca constante explicita (que poderia ser denominada pseudomarea). Os comentarios podem ter muitas formas diferentes. Geralmente, eles tém formato livre e si0 cercados por delimitadores como {comentarios em Pascal} J* comentérios em C */ ou iniciam com um ou mais caracteres especificados ¢ vao até o final da linha, como ; Comentarios em Scheme — comentérics em Ada Nao é dificil escrever uma expressao regular para comentérios com delimitadores de caractere tinico, como.em Pascal, ott para comentirios que vio de um (ou mais) caractere(s) até o final da linha. Por exemplo, o caso dos comentarios em Pascal pode ser escrito como (-)*} onde escrevemos ~} para indicar “nao }” € assumimos que o caractere } nio tem signifi- cado como metacaractere. (Uma expresso diferente precisa ser escrita para Lex, e isso sera discutido mais adiante neste capitulo.) De maneira similar, um comentario em Ada pode casar com a expresso regular ++ (-newline)* na qual assumimos que new1 ine casa com o fim de uma linha (0 que pode ser escrito como |\n em muitos sistemas), que o caractere ”-” nao tem significado como metacaractere e que brancos no final da linha nio sio incluidos no comentario. (Veremos como escrever isso em Lex na Secio 26.) E muito mais dificil eserever uma expreseto regular para 0 caso de delimitadores com comprimento de mais de um caractere, como em C. Para verificar isso, considere o conjunto de cadeias de caracteres ba... (nenhuma ocorréncia de a)... ab (utilizamos ba..ab em vez dos delimitadores em C /*...°/ porque 0 asterisco, e as vezes a barra, é um metacaractere que requer cuidados especiais). Nao podemos simplesmente eserever ba(~(ab)) *ab porque o “nao” é usualmente restrito a caracteres isolados, em vez de cadeias de caracteres. Podemos tentar escrever uma definigao para ~ (ab) usando ~a, ~b e ~ (ab), mas isso nao é trivial. Uma solugio & be (at~(a|b)b*) ta* mas ela € dificil de ler (e de provar que esta correta). Assim, uma expressio regular para comentarios em C € tao complicada que ela quase nunca € escrita na pratica. De fato, esse 2. As vezes, eles podem conter diretivas do compilador. caso ¢ usualmente tratado por métodos ad hoc nos sistemas de varredura, os quais serdo vistos mais adiante neste capitulo. Finalmente, outra complicacao no reconhecimento de comentarios é que, em algumas linguagens de programacao, os comentérios podem ser aninhados. Por exemplo, em Modula-2 podemos ter comentarios da forma (* esse é (* em Modula-2 +) um comentério *) Os delimitadores de comentérios precisam vir aos pares. Assim, a expressio a seguir ‘nao € um comentirio legal em Modula: (* isso é (* ilegal em Modula-2 *) O aninhamento de comentérios exige que a varredura conte o ntimero de delimita- dores. Mas nés jé observamos no Exemplo 2.3 (Seco 2.2.1) que expressdes regulares nao podem expressar operagdes de contagem. Na pritica, utilizamos um esquema simples de contagem como solucao ad hac para esse problema (ver exercicios). Ambigitidade, espacosem branco em verificagio ifrente Freqiientemente, na descrigio das marcas de uma linguagem de programagao utilizando expressdes regulares, algumas cadeias de caracteres podem casar com diversas expressbes regulares. Por exemplo, cadeias como 4 ewhile poderiam ser identificadores ou palavras-chave. Similarmente, acadeia <> poderia ser interpretada como a representagao de duas marcas (menor” e “maior”) ou de luma tinica marca (“diferente”). Uma definigio de linguagem de programagio deve deter- minar que interpretacdo deve ser observada, eas expresses regulares nao podem fazer isso. Alternativamente, uma definigao de linguagem deve fornecer regras de eliminagio de ambigitidade, que indicarao o significado para cada caso. Hi duas regras sipicas para os exemplos ja vistos. A primeira diz que quando uma cadeia de caracteres pode ser um identificador ou uma palavra-chave, a interpretagio de palavra-chave 6, em geral, preferida. Isso resulta do uso do termo palavra reservada, o que significa simplesmente uma palavra-chave que no pode também ser um identificador. ‘A segunda diz que quando uma cadeia de caracteres pode ser uma tinica marca ou uma seqiiéncia de marcas, a interpretagao de marca tinica € normalmente a preferida, Essa preferincia ¢ freqdentemente identificada como o prineipio da subcadeia mais longa: a cadeia mais longa de caracteres que poderia constituir uma tinica marca deve representar a proxima marca? Um assunto relacionado com o uso do principio da subcadeia mais longa sio os delimi- tadores de mareas, ou caracteres 03 quais indicam que uma cadeia mais longa no ponto onde eles ocorrem no pode representar uma marca. Caracteres que sio determinantemente parte de outras marcas so delimitadores. Por exemplo, na cadeia xtemp=y temp, sinal de igualdade delimita o identificedlorxtemp, pois = no pode aparecer como parte de um iclen- lificador. Espacos em branco, mudancas de linha e tabulagées so geralmente admitidos também como delimitadores de marcas: while x... 6, dessa forma, interpretada como duas marcas que representam a palavra reservada while e 0 identificador x, pois um espago em branco separa as duas cadeias. E froqtientemente vitil nessa situagio definir uma 3. As vezes, ele échamado de principio do “bolo maximal”. pseudomarca de espago em branco, similar & pseudomarca de comentario, que simplesmente serve para a varredura diferenciar internamente entre marcas. Os préprios comentarios servem como delimitadores, assim, por exemplo, o fragmento de cédigo em C do/**/it representa as duas palavras reservadas do e 4£ em vez do identificador doi £. Uma definigéo tipica da pseudomarca de espaco em branco em uma linguagem de programagio é espaco em branco = (mudanca de linha tario)+ ago] tabulacdo/comen- na qual 05 identificadores a direita representam as cadeias ou caracteres apropriados. Observe que, se ndo for delimitador de marcas, o espaco em branco é normalmente igno- rado. As linguagens que especificam esse comportamento sio chamadas de formato livre. Entre as alternativas para 0 formato livre, temos 0 formato fixo de algumas linguagens, como Fortran, e diversos usos de tabulacao, como a regra do deslocamento (ver a seca0 de Notas e Bibliografia). Uma varredura para linguagem de formato livre precisa descartar 0s espacos em branco apés verificar possiveis efeitos de delimitacdo de marcas. (Os delimitadores encerram cadeias de marcas, mas nao fazem parte das marcas. Assim, um sistema de varredura precisa lidar com o problema de verificagao a frente: a0 encontrar um delimitador, é preciso nao remové-lo até o final da entrada, devolvendo-o 2 cadeia de entrada (“retorno”) ou verificando & frente antes de remové-lo. Na maioria dos casos, basta fazer a verificago para um tinico caractere (“verificacdo & frente de um caractere”). Por exemplo, na cadeia xtemp=ytemp, 0 final do identificador xtemp ¢ deter- minado quando 0 = é encontrado, e o = precisa permanecer na entrada, pois representa a préxima marca a ser reconhecida. Observe também que a verificagio a frente pode nao ser necessfria para reconhecer uma marca. Por exemplo, o sinal de igualdade pode ser a tinica ‘marca que inicia com =, e assim ele pode ser reconhecido de imediato, sem a necessidade de consultar 0 préximo caractere. Por vezes, uma linguagem pode requerer mais do que a verificacdo a frente de um tinico caractere, e a varredura precisa estar preparada para retroceder tantos caracteres quanto necessirio. Nesse caso, 0 armazenamento dos carecteres de entrada e dos pontos para retrocesso se tormam questdes importantes para o projeto do sistema de varredura. (Algumas dessas quesides serdo tratadas mais adiante neste capitulo.) Fortran é um bom exemplo de linguagem que viola muitos dos principios discutidos até aqui. Essa é uma linguagem de formato fixo em que o espaco em branco é removido por ‘um pré-processador antes de iniciar a tradugdo. Assim, a linha em Fortran IF (X 2. 80. 0) THEN apareceria para 0 compilador assim IP (X2.2Q.0) THEN portanto, 0 espago em branco nao serve como delimitador. Em Fortran também nao ha palavras reservadas, de forma que todas as palavras-chave podem ser também identi- ficadores, ea posicio da cadeia de caracteres em cada linha de entrada 6 importante para determinar a marca a ser reconhecida. Por exemplo, a seguinte linha de cbdigo esta per- feitamente correta em Fortran: IF (IF .BQ.0) THENTHEN=1.0 Os primeiros IF e THEN sio palavras-chave, enquanto os TF € THEN seguintes s30 identificadores que representam varidveis. O resultado disso ¢ que um sistema de varredura para Fortran precisa ser capaz de retroceder para posicOes arbitrarias em uma linha de c6digo. lere, como caso concreto, o seguinte exemplo canhecido: pos9r=1,10 Isso inicia um laco da linha subseqiiente a linha de mimero 99, com efeito igual ao cédigoPascal for i := 1 to 10. Do outrolado, a mudanga de virgula para ponto Do99T=1.10 altera o significado do cédigo completamente: 0 valor 1.1 éatribuido a varidvel cujo nome € po98z. Assim, a varredura ndo pode concluir que o DO inicial ¢ uma palavra-chave enquanto no atingir a virgula (ou ponto), e precisa retroceder até o inicio da linha e recomegar. 2.3 AUTOMATOS FINITOS Autématos finitos, ou mdquinas de estados finitos, sao uma forma matemitica de descrever tipos particulares de algoritmos (ou “maquinas”). Em particular, autdmatos finitos podem ser ulilizados para descrever 0 proceso de reconhecimento de padrées em cadeias de entrada, e assim podem ser utilizados para construir sistemas de varredura, Hé também, evidentemente, uma forte relacao entre autématos finitos e expressdes regulares, e veremos na préxima segiio como consiruir um autdmato finito a partir de uma expressio regular, Antes de iniciarmos 0 estudo de autématos finitos propriamente dito, vamos considerar um exemplo elucidativo. © padrdo para identificadores conforme definido comumente em linguagens de programagao é dado pela seguinte definicdo regular (assumimos que Letra ¢ digito jé foram definidos): identificador = letra(letra/digito)* Isso representa uma cadeia de caracteres que comesa com uma letra e continua com qualquer seqiiéncia de letras e/ou digitos. © processo de reconhecimento de uma cadeia como essa pode ser descrito pelo diagrama da Figura 2.1. tecra sera —>(1)}° Jdsasto Figura 2.1. Um autimato fino para identicedotos Nesse diagrama, os circulos de nimeros 1 e 2 representam estados, que so locais no proceso de reconhecimento que registram © quanto do padrio ja foi visto. As setas representam transicdes, que registram tuma alteracio de um estado para outro com base rno casamento dos caracteres os quais as rotulam. No exemplo, 0 estado 1 6 o estado inicial, ou seja, 0 estado do inicio do processo de reconhecimento, Por convengio, 0 estado inicial & indicado por uma seta sem rétulo que chega até ele vinda “do nada”. O estado 2 repre- senta 0 ponto em que uma tinica letra casou (indicado pela transicao do estado 1 para o estado 2 com 0 rotulo Zetra), Uma vez no estado 2, qualquer ntimero de letras ¢/ou digitos pode ser visto, e seus casamentos retomam para 0 estado 2. Os estados que repre- sentam 0 final do processo de reconhecimento, e nos quais podemos nos declarar bem-suce- didlos, 30 denominados estados de aceitagio, e indicados pelo desenho de uma linha dupla em torno do estado no diagrama. H4 mais de um deles. No exemplo, o estado 2 6 um estado de aceitagio, 0 que indica que depois de uma letra ter sido vista, qualquer seqiiéncia subseqiente de letras digitos (incluindo a seqiiéncia vazia) representa um identificador legal ‘© processo de reconhecimento de uma cadeia de caracteres como um identificador pode agora ser indicado pela listagem da seqtiéncia de estados e transig6es no diagrama que S80 utilizados no processo de reconhecimento. Por exemplo, 0 processo de reconhecer xetemp como um identificador pode ser indicado assim: 1m 2th 22> 2 Be 2 Pe 2

También podría gustarte