Análise Sintática de Programas

21
Análise Sintática de Programas 1 INTRODUÇÃO Cada linguagem de programação possui as regras que descrevem a estrutura sintática dos programas bem formados. Em Pascal por exemplo, um programa é constituído por blocos, um bloco por comandos, um comando por expressões , uma expressão por tokens e assim por diante. A sintaxe das construções de uma linguagem de programação pode ser descrita pelas gramáticas livres de contexto ou pela notação BNF (Forma de Bakcus – Naur). As gramáticas oferecem vantagens significativas tanto para os projetistas de linguagens quanto para os escritores de compiladores. Uma gramática oferece, para uma linguagem de programação, uma especificação sintática precisa e fácil de entender. Para certas classes de gramáticas, podemos construir automaticamente um analisador sintático que determine se um programa -fonte está sintaticamente bem-formado. Como benefício adicional, o processo de construção do analisador pode revelar ambigüidades sintáticas bem como outras construções difíceis de se analisar gramaticalmente, as quais poderiam, de outra forma, seguir indetectadas na fase de projeto inicial de uma linguagem e de seu compilador. Uma gramática propriamente projetada implica uma estrutura de linguagem de programação útil à tradução correta de programas-fonte em códigos objeto e também à detecção de erros. Existem ferramentas disponíveis para a conversão de descrições de traduções, baseadas em gramáticas, em programas operativos. As linguagens evoluíram ao longo de um certo período de tempo, adquirindo novas construções e realizando tarefas adicionais. Essas novas construções podem ser

Transcript of Análise Sintática de Programas

Anlise Sinttica de Programas

1 INTRODUO

Cada linguagem de programao possui as regras que descrevem a estrutura sinttica dos programas bem formados. Em Pascal por exemplo, um programa constitudo por blocos, um bloco por comandos, um comando por expresses , uma expresso por tokens e assim por diante. A sintaxe das construes de uma linguagem de programao pode ser descrita pelas gramticas livres de contexto ou pela notao BNF (Forma de Bakcus Naur). As gramticas oferecem vantagens significativas tanto para os projetistas de linguagens quanto para os escritores de compiladores.

Uma gramtica oferece, para uma linguagem de programao, uma especificao sinttica precisa e fcil de entender.

Para certas classes de gramticas, podemos construir automaticamente um analisador sinttico que determine se um programa-fonte est sintaticamente bem-formado. Como benefcio adicional, o processo de construo do analisador pode revelar ambigidades sintticas bem como outras construes difceis de se analisar gramaticalmente, as quais poderiam, de outra forma, seguir indetectadas na fase de projeto inicial de uma linguagem e de seu compilador.

Uma gramtica propriamente projetada implica uma estrutura de linguagem de programao til traduo correta de programas-fonte em cdigos objeto e tambm deteco de erros. Existem ferramentas disponveis para a converso de descries de tradues, baseadas em gramticas, em programas operativos.

As linguagens evoluram ao longo de um certo perodo de tempo, adquirindo novas construes e realizando tarefas adicionais. Essas novas construes podem ser mais facilmente includas quando existe uma implementao baseada numa descrio gramatical da linguagem.

2 O PAPEL DO ANALISADOR SINTTICO

Existem trs tipos gerais de analisadores sintticos. Os mtodos universais de anlise sinttica, tais como o algoritmo de Cocke-younger-Kasami e o de Earley, podem tratar qualquer gramtica. Esses mtodos, entretanto, saio muito ineficientes parta se usar num compilador de produo. Os mtodos mais comumente usados nos compiladores so classificados como top-down ou bottom-up. Como indicado por seus nomes, os analisadores sintticos top-down, constroem rvores do topo (raiz) para o fundo (folhas), enquanto que os bottom-up comeam pelas folhas e trabalham rvore acima at a raiz. Em ambos os casos, a entrada varrida da esquerda para a direita, um smbolo de cada vez.

Os mtodos de anlise sinttica mais eficientes, tanto top-down quanto bottom-up, trabalham somente em determinadas subclasses de gramticas, mas vrias dessas subclasses, como as das gramticas LL e LR, so suficientemente expressivas para descrever a maioria das construes sintticas das linguagens de programao. Os analisadores implementados manualmente trabalham freqentemente com gramticas LL; por exemplo. Assumimos que a sada de um analisador sinttico seja alguma representao da rvore gramatical para o fluxo de tokens produzido pelo analisador lxico. Na prtica, existe um certo nmero de tarefas que poderiam ser conduzidas durante a anlise sinttica, tais como coletar informaes sobre os vrios tokens na tabela de smbolos, realizar a verificao de tipos e outras formas de anlise semntica, assim como gerar o cdigo intermedirio.

3 TRATAMENTO DE ERROS DE SINTAXE

Se um compilador tivesse que processar somente programas corretos, seu projeto e sua implementao seriam grandemente simplificados. Mas os programadores freqentemente escrevem programas incorretos, e um bom compilador deveria assistir o programador na identificao e localizao de erros. gritando que, apesar dos erros serem lugar-comum, poucas linguagens sejam projetadas tendo-se o tratamento de erros em mente. Nossa civilizao seria radicalmente diferente se as linguagens faladas tivessem as mesmas exigncias de correo sinttica que as das linguagens de computadores. A maioria das especificaes das linguagens de programao no descreve como um compilador deveria responder aos erros; tal tarefa deixada para o projetista desde o incio poderia ser tanto simplificar a estrutura de um compilador quanto melhorar sua resposta aos erros.

Sabemos que os programas podem conter erros em muitos nveis diferentes. Por exemplo, os erros podem ser:

lxicos, tais como errar a grafia de um identificador, palavra-chave ou operador

sintticos, tais como uma expresso aritmtica com parnteses no balanceados

semnticos, tais como um operador aplicado a um operando incompatvel

lgicos, tais como uma chamada infinitamente recursiva

Freqentemente, boa parte da deteco e recuperao de erros num compilador gira em torno da fase de anlise sinttica. Isto porque os erros ou so sintticos por natureza ou so expostos quando o fluxo de tokens proveniente do analisador lxico desobedece s regras gramaticais que definem a linguagem de programao. Outra razo est na preciso dos modernos mtodos de anlise sinttica; podem detectar muito eficientemente a presena de erros sintticos num programa. Detectar precisamente a presena de erros semnticos ou lgicos em tempo de compilao muito mais difcil.

Um tratador de erros num analisador sinttico possui metas simples de serem estabelecidas:

- Deve relatar a presena de erros clara e acuradamente.

- Deve se recuperar de cada erro suficientemente rpido a fim de ser capaz de detectar erros subseqentes.

- No deve retardar significativamente o processamento de programas corretos.

A realizao efetiva dessas metas apresenta desafios difceis.

Felizmente, os erros comuns so simples e freqentemente basta um mecanismo de tratamento de erros relativamente direto. Em alguns casos, entretanto, um erro pode ter ocorrido muito antes de sua presena ter sido detectada e sua natureza precisa pode ser muito difcil de ser deduzida. Em casos difceis, o tratador de erros pode ter que advinhar o que o programador tinha em mente quando o programa foi escrito.

Vrios mtodos de anlise sinttica, tais como os mtodos LL e LR, detectam os erros to cedo quanto possvel. Mais precisamente, possuem a propriedade do prefixo vivel, significando que detectam que um erro ocorreu to logo tenham examinado um prefixo da entrada que no seja o de qualquer cadeia da linguagem.

Como deveria um tratador de erros reportar a presena de um erro? No mnimo, deveria informar o local no programa fonte onde o mesmo foi detectado, uma vez que existe uma boa chance de o erro efetivo ter ocorrido uns poucos tokens antes. Uma estratgia comum empregada por muitos compiladores a de imprimir a linha ilegal com um apontador para a posio na qual o erro foi detectado. Se existir um razovel prognstico de que o erro realmente foi, uma compreensvel mensagem de diagnstico informativa tambm includa; por exemplo, ponto-e-vrgula ausente nesta posio.

Uma vez que o erro tenha sido detectado, como deveria o analisador sinttico se recuperar? Existe um nmero de estratgias gerais, mas nenhum mtodo claramente se impe sobre os demais. Na maioria dos casos, no adequado para o analisador sinttico encerrar logo aps detectar o primeiro erro, porque o processamento da entrada restante ainda pode revelar outros. Usualmente, existe alguma forma de recuperao de erros na qual o analisador tenta restaurar a si mesmo para um estado onde o processamento da entrada possa continuar com uma razovel esperana de que o resto correto da entrada ser analisado e tratado adequadamente pelo compilador.

Um trabalho inadequado de recuperao pode introduzir uma avalancha de erros esprios, que no foram cometidos pelo programador, mas introduzidos pelas modificaes no estado do analisador sinttico durante a recuperao de erros. Numa forma similar, uma recuperao de erros sintticos pode introduzir erros semnticos esprios que sero detectados posteriormente pelas fases de anlise semtntica e de gerao de cdigo. Por exemplo, ao se recuperar de um erro, o analisador pode pular a declarao de alguma varivel, digamos zap. Quando zap for posteriormente encontrada nas expresses, no haver nada sintaticamente errado, mas como no h uma entrada na tabela de smbolos para zap, a mensagem zap no definido ser gerada.

Uma estratgia cautelosa para o compilador a de inibir as mensagens de erro que provenham de erros descobertos muito proximamente no fluxo de entrada. Em alguns casos, pode haver erros demais para o compilador continuar um processamento sensvel (por exemplo, como deveria um compilador Pascal responder ao receber um programa Fortran como entrada?). Parece que uma estratgia de recuperao de erros tem que ser um compromisso cuidadosamente considerado levando em conta os tipos de erros que so mais propensos a ocorrer e razoveis de processar.

Alguns compiladores tentam reparar os erros, num processo em que tentam adivinhar o que o programador queria escrever. O compilador PL/C (Conway e Wilcox [1973]) um exemplo desse tipo. Exceto, possivelmente, num ambiente de pequenos programas escritos por estudantes principiantes, a reparao extensiva de erros no propensa a pagar o seu custo. De fato, com a nfase crescente na computao interativa e bons ambientes de programao, a tendncia parece estar na direo de mecanismos simples de recuperao de erros.

4 ANLISE SINTTICA TOP-DOWN

A anlise sinttica top-down pode ser vista como uma tentativa de ser encontrar uma derivao mais esquerda para uma cadeia de entrada. Equivalentemente, pode ser vista como uma tentativa de se construir uma rvore gramatical, para a cadeia de entrada, a partir da raiz, criando os ns da rvore gramatical em pr ordem. Consideramos agora uma forma geral de anlise sinttica top-down, chamada de descendncia recursiva, que pode envolver retrocesso, ou seja, a realizao de esquadrinhamentos repetidos da entrada. Por outro lado, os analisadores sintticos com retrocesso no so vistos muito freqentemente. Uma razo est em que o retrocesso raramente necessitado para analisar sintaticamente construes de linguagens de programao. Em situaes tais como a anlise sinttica de linguagens naturais, o retrocesso ainda ineficiente e mtodos tabulares, tais como o algoritmo de programao dinmica ou mtodo de Earley [1970] so preferidos.

O retrocesso exigido no prximo exemplo, e iremos sugerir uma forma de controlar a entrada quando o mesmo ocorrer.

Exemplo: Consideremos a gramtica

S cAd

A ab | a

E a cadeia de entrada w=cad. Para construir uma rvore gramatical para esta cadeia, de cima para baixo, criamos inicialmente uma rvore constituindo de um nico n rotulado S. O apontador da entrada aponta para c, o primeiro smbolo de w. Em seguida, usamos a primeira produo para S a fim de expandir a rvore.

A folha mais esquerda, rotulada c, reconhece o primeiro smbolo de w e, por conseguinte, avanamos o apontador da entrada para a, o segundo smbolo de w, e consideramos a prxima filha, rotulada A. Em seguida, expandimos A usando a sua primeira alternativa, obtendo a rvore da figura (b). Temos agora um reconhecimento para o segundo smbolo da entrada e, conseqentemente, avanamos para o apontador da entrada para d, o terceiro smbolo da entrada, e comparamos d com a prxima folha, rotulada b. Como b no igual a d, reportamos uma falha e retornamos a A a fim de verificar se existe uma outra alternativa que no tenhamos tentado ainda, mas que poderia produzir um reconhecimento.

Ao irmos de volta para A, precisamos restabelecer o apontador da entrada para a posio 2, aquela que o mesmo detinha quando passamos pela primeira vez por A, o que significa que o procedimento para A precisa armazenar o apontador da entrada numa varivel local. Tentamos agora a segunda alternativa de A afim de obter a rvore na figura (c). A folha a reconhece o segundo smbolo de w e a folha d o terceiro. Uma vez que produzimos uma rvore gramatical para w, paramos e anunciamos o trmino com sucesso da anlise sinttica.

Uma gramtica recursiva esquerda pode levar um analisador sinttico de descendncia recursiva, mesmo com retrocesso, a um lao infinito. Isto , quando tentamos expandir A, podemos eventualmente nos encontrar de novo tentando expandir A sem ter consumido nenhum smbolo da entrada.

5 ANALISADORES SINTTICOS PREDITIVOS

Em muitos casos, escrevendo-se cuidadosamente uma gramtica, eliminando-se a recurso a esquerda e fatorando-se esquerda a gramtica resultante, podemos obter uma nova gramtica processvel por um analisador sinttico de descendncia recursiva que no necessite de retrocesso, isto , um analisador preditivo. Para construir um analisador sinttico preditivo, precisamos conhecer, dado o smbolo corrente de entrada a e o no terminal A a ser expandido, qual das alternativas de produo A 1 | 2 |... | n a nica que deriva uma cadeia comeando por a. Ou seja, a alternativa adequada precisa ser detectvel examinando-se apenas para o primeiro smbolo da cadeia que a mesma deriva. As construes de controle de fluxo na maioria das linguagens de programao, com suas palavras-chave distintivas, so usualmente detectveis dessa forma. Por exemplo, se tivermos as produes:

cmd if expr then cmd else cmd | while expr do cmd | begin lista_de_comandos end

ento as palavras-chave if, while e begin nos informam qual alternativa a nica que possivelmente teria sucesso, se quisssemos encontrar um comando.

5.1 Diagramas de Transies para Analisadores Sintticos Preditivos

As vrias diferenas entre os diagramas de transies para um analisador lxico e um analisador sinttico preditivo so imediatamente aparentes. No caso de um analisador sinttico, existe um diagrama para cada no terminal. Os rtulos dos lados so tokens e no terminais. Uma transio em um token (terminal) significa que devemos realiz-la se aquele token for o prximo smbolo da entrada. Uma transio num no-terminal A chamada de procedimento para A.

Para construir um diagrama de transies de um analisador sinttico preditivo a partir de uma gramtica, eliminamos primeiro da gramtica a recursividade esquerda e, em seguida, a fatoramos esquerda. Para cada no-terminal A, ento fazemos o seguinte:

1. Criamos um estado inicial e um final (de retorno).

2. 2. Para cada produo A X1,X2...Xn, criamos um percurso a partir do estado inicial at o estado final, com os lados rotulados X1,X2,...,Xn.

O analisador preditivo ao trabalhar sobre os diagramas de transies se comporta como segue. Comea no estado inicial para o smbolo de partida. Se aps algumas aes estiver no estado s, o qual possui um lado rotulado pelo terminal a apontado para o estado t, e se o prximo smbolo de entrada for a, move o cursor de entrada uma posio direita e vai para o estado t. Se, de outra feita, o lado for rotulado pelo no terminal A, vai para o estado de partida A, sem movimentar o cursor de entrada. Se em algum instante for atingido o estado final de A, vai imediatamente para o estado t, tendo como efeito, lido A a partir da entrada, durante o tempo em que se movia do estado s para t. Finalmente, se existir um lado de s para t rotulado , vai, a partir do estado s, imediatamente para o estado t, sem avanar na entrada.

Um programa de anlise sinttica preditiva baseado num diagrama de transies tenta reconhecer smbolos terminais na entrada e faz uma chamada de procedimento potencialmente recursiva sempre que precisar seguir um lado rotulado por um no terminal. Uma implementao no recursiva pode ser obtida empilhando-se o estado s quando existir uma transio em um no terminal para fora de s e removendo-se o topo da pilha quando o estado final para o no terminal for atingido.

A abordagem acima funcionar se o diagrama de transies dado for determinstico, isto , no existir mais de uma transio de um mesmo para outros mesma entrada. Se a ambigidade ocorrer, deveremos estar capacitados a resolv-la de uma forma ad-hoc. Se o no determinismo no puder ser eliminado, no poderemos construir um analisador sinttico preditivo, mas poderemos construir um analisador de descendncia recursiva com retrocesso, de forma a tentar sistematicamente todas as possibilidades, se esta fosse a melhor estratgia de anlise que pudssemos encontrar.

5.2 Anlise Sinttica Preditiva No-Recursiva

possvel construir um analisador preditivo no-recursivo mantendo explicitamente uma pilha, ao invs de implicitamente atravs de chamadas recursivas. O problema-chave durante a anlise preditiva determinar que produo deve ser aplicada a um dado no terminal.

Um analisador sinttico preditivo dirigido por uma tabela possui um buffer de entrada, uma pilha, uma tabela sinttica e um fluxo de sada. O buffer de entrada possui a cadeia a ser analisada, seguida por um $ direita para indicar o fim da cadeia de entrada. A pilha contm uma seqncia de smbolos gramaticais, com $ indicando o fundo da pilha. Inicialmente, a pilha contm o smbolo de partida da gramtica acima de $. Uma tabela sinttica um array bidimensional M[A,a], onde A um no terminal e a um terminal ou outro smbolo $.

O analisador sinttico controlado por um programa que se comporta como segue. O programa considera X o smbolo ao topo da pilha e a o smbolo corrente de entrada.

Esses dois smbolos determinam a ao do analisador. Existem trs possibilidades:

1. Se X=A=$, o analisador pra e anuncia o trmino com sucesso da anlise sinttica.

2. Se X=a$, o analisador sinttico remove X da pilha e avana o apontador da entrada para o prximo smbolo.

3. Se X um no terminal, o programa consulta a entrada M[X,a] da tabela sinttica M. Essa entrada ser uma produo X da gramtica ou uma entrada de erro. Se, por exemplo, M[X,a]={X UVW}, o analisador substitui X no topo da pilha por WVU (com U ao topo). Como sada, iremos assumir que o analisador sinttico simplesmente imprima a produo usada; de fato, qualquer outro cdigo poderia ser executado aqui. Se M[X,a]=erro, o analisador chama uma rotina de recuperao de erros.

O comportamento do analisador sinttico pode ser descrito em termos de suas configuraes, que do o contedo da pilha e a entrada restante.

5.2.1 Primeiro e Seguinte

A construo de um analisador sinttico preditivo auxiliada por duas funes associadas gramtica G. Essas funes, Primeiro e Seguinte, nos permitem preencher as entradas de uma tabela sinttica preditiva para G, sempre que possvel. Os conjuntos de tokens produzidos pela funo seguinte podem tambm ser usados como tokens de sincronizao durante a recuperao de erros na modalidade do desespero.

Se for qualquer cadeia de smbolos gramaticais, seja primeiro() o conjunto de terminais que comeam as cadeias derivadas a partir de . Definamos seguinte(A), para o no terminal A, como sendo um conjunto de terminais a que podem figurar imediatamente direita de A em alguma forma sentencial, isto , o conjunto de terminais a tais que exista uma derivao para algum e . Se A puder ser o smbolo mais direita em alguma forma sentencial, ento $ est em SEGUINTE(A).

5.3 Recuperao de Erros na anlise Preditiva

A pilha de um analisador preditivo no recursivo torna explcitos os terminais e no terminais que o mesmo espera reconhecer com o restante da entrada. Iremos conseqentemente nos referir aos smbolos na pilha do analisador da discusso que se segue. Um erro detectado durante a anlise preditiva quando o terminal ao topo da pilha no reconhece o prximo smbolo de entrada ou quando o no terminal A est ao topo da pilha, a o prximo smbolo de entrada e a entrada da tabela sinttica M[A,a] est vazia.

A recuperao de erros na modalidade do desespero est baseada na idia de se pular smbolos na entrada at que surja um token pertencente a um conjunto pr selecionado de tokens de sincronizao. Sua efetividade depende da escolha do conjunto de sincronizao. Os conjuntos deveriam ser escolhidos de tal forma que o analisador se recuperasse rapidamente dos erros que tendessem a ocorrer na prtica. Algumas tcnicas heursticas so:

Como ponto de partida, podemos colocar todos os smbolos de SEGUINTE(A) no conjunto de tokens de sincronizao para o no terminal A. Se pularmos tokens at que um elemento de SEGUINTE(A) seja visto e removermos A da pilha, provvel que a anlise sinttica possa continuar.

No suficiente usar SEGUINTE(A) como o conjunto de sincronizao para A. Por exemplo, se os pontos-e-vrgulas terminarem os enunciados, como em C, ento as palavras-chave que iniciam os enunciados no devem aparecer no conjunto SEGUINTE do no-terminal que gera expresses. Um ponto-e-vrgula ausente aps uma atribuio pode conseqentemente resultar na omisso da palavra chave que inicia o prximo enunciado. Freqentemente, existe uma estrutura hierrquica nas construes da linguagem; por exemplo, as expresses aparecem dentro de enunciados, que figuram dentro de blocos e assim por diante. Podemos adicionar ao conjunto de sincronizao de uma construo mais baixa os smbolos que comeam as construes mais altas. Por exemplo, poderamos adicionar palavras-chave que iniciam comandos aos conjuntos de sincronizao para os no-terminais que geram expresses.

Se adicionarmos os smbolos em PRIMEIRO(A) ao conjunto de sincronizao para o no terminal A, pode ser possvel retornar a anlise a partir de A, se um smbolo em PRIMEIRO(A) figurar na entrada.

Se um no terminal puder gerar a cadeia vazia, ento a produo que deriva pode ser usada como default. Agindo-se assim, pode-se postergar a deteco de algum erro, mas no se pode fazer com que um erro seja perdido. Esse enfoque reduz o nmero de no terminais que tenham de ser considerados durante a recuperao de erros.

Se um terminal ao topo da pilha no puder ser reconhecido, uma idia simples a de remov-lo, emitir uma mensagem informando da remoo e prosseguir a anlise sinttica. Com efeito, este enfoque faz com que o conjunto de sincronizao de um token consista em todos os demais tokens.

6 ANLISE SINTTICA BOTTOM-UP

A anlise sinttica bottom-up conhecida como anlise de empilhar e reduzir. A anlise gramatical de empilhar e reduzir tenta construir uma rvore gramatical para uma cadeia de entrada comeando pelas folhas (o fundo) e trabalhando rvore acima em direo raiz (o topo). Podemos pensar neste processo como o de reduzir uma cadeia w ao smbolo de partida de uma gramtica. A cada passo de reduo, uma subcadeia particular, que reconhea o lado direito de uma produo, substituda pelo smbolo esquerda daquela produo e, se a subcadeia tiver sido escolhida corretamente a cada passo, uma derivao mais direita ter sido rastreada na ordem inversa.

Exemplo:

Considerando a gramtica

SaABe

AAbc | b

B d

A sentena abbcde pode ser reduzida a S pelos seguintes passos:

Aabbcde

aAbcde

aAde

aABe

S

Podemos esquadrinhar abbcde procurando por uma subcadeia que reconhea o lado direito de alguma produo. As subcadeias b e d se qualificam. Vamos escolher o b mais esquerda e substitu-lo por A, o lado esquerdo da produo Ab; obtemos dessa forma a cadeia aAbcde. Agora as subcadeias Abc, b e d reconhecem o lado direito de alguma produo. Apesar de b ser a subcadeia mais esquerda que reconhea o lado direito de alguma produo, escolhemos substituir a subcadeia Abc por A, o lado esquerdo da produo AAbc. Obtemos agora aAde. Com a substituio de d por B, o lado esquerdo da produo Bd, obtemos aABe. Podemos agora substituir toda esta cadeia por S. Conseqentemente, atravs de uma seqncia de quatro redues, estamos capacitados a reduzir abbcde a S. Essas redues, de fato, rastreiam a seguinte derivao mais direita, na ordem reversa:

S aABe aAde aAbcde abbcde

7 HANDLES

Informalmente, um handle uma subcadeia que reconhece o lado direito de uma produo e cuja reduo ao no terminal do lado esquerdo da produo representa um passo ao longo do percurso de uma derivao mais direita. Em muitos casos, a subcadeia mais esquerda que reconhece o lado direito de uma produo A no um handle, porque uma reduo pela produo A produz uma cadeia que no pode ser reduzida ao smbolo de partida.

7.1 A Poda do Handle

Uma derivao mais esquerda na ordem inversa pode ser obtida podando-se os handles. Ou seja, comeamos pela primeira cadeia de terminais w que desejamos decompor. Se w for uma sentena da gramtica em questo, ento w=yn, onde yn a ensima forma sentencial direita de alguma derivao mais direita ainda desconhecida.

Para reconstruir esta derivao na ordem inversa, localizamos o handle n em yn e substitumos n pelo lado direito de alguma produo An n de modo a obtermos a ensima menos uma forma sentencial direita yn-1.

Repetimos, em seguida, esse processo. Isto , localizamos o handle n-1 em yn-1 e o reduzimos de forma a obter a forma sentencial direita yn-2. Continuando esse processo, produzimos uma forma sentencial direita consistindo somente no smbolo de partida S e ento paramos e anunciamos o trmino com sucesso da anlise sinttica. O reverso da seqncia de produes usadas nas redues uma derivao mais direita para a cadeia de entrada.

8 IMPLEMENTAO DE PILHA DA ANLISE SINTTICA DE EMPILHAR E REDUZIR

Existem dois problemas que precisam ser resolvidos se estivermos dispostos a analisar sintaticamente atravs da poda de handles. O primeiro o de localizar a subcadeia a ser reduzida numa forma sentencial direita e o segundo o de determinar que produo escolher no caso de existir mais de uma produo com aquela subcadeia no lado direito.

Uma forma conveniente de implementar um analisador sinttico de empilhar e reduzir usar uma pilha para guardar os smbolos gramaticais e um buffer de entrada para a cadeia w a ser decomposta. Usamos $ para marcar o fundo da pilha e tambm o final direita da entrada. Inicialmente, a pilha est vazia e a cadeia w est entrada como segue

Pilha Entrada

$ w$

O analisador sinttico opera empilhando zero ou mais smbolos (na pilha) at que um handle surja no topo da pilha. Reduz, ento, para o lado esquerdo da produo apropriada. Repete este ciclo at que tenha detectado um erro ou que a pilha contenha o smbolo de partida e a entrada esteja vazia:

Pilha Entrada

$S $

Aps entrar nesta configurao, pra e anuncia o trmino com sucesso da anlise sinttica.

8.1 Prefixos Viveis

Os prefixos de uma forma sentencial direita que podem figurar na pilha deu m analisador sinttico de empilhar e reduzir so chamados de prefixos viveis. Uma definio equivalente de um prefixo vivel a de ser um prefixo de uma forma sentencial direita, o qual no se estende para alm do limite direita do handle mais direita, daquela forma sentencial. Por esta definio sempre possvel adicionar smbolos terminais ao final de um prefixo vivel de modo a obter uma forma sentencial direita. Por conseguinte, no h aparentemente erro na medida em que a poro da entrada enxergada at um dado ponto possa ser reduzida a um prefixo vivel.

9 ANLISE SINTTICA DE PRECEDNCIA DE OPERADORES

A mais ampla classe de gramticas, para a qual os analisadores sintticos de empilhar e reduzir podem ser construdos com sucesso so as gramticas LR. Entretanto, para uma pequena, porm importante classe de gramticas, podemos facilmente construir manualmente eficientes analisadores sintticos de empilhar e reduzir. Essas gramticas possuem a propriedade (dentre outras exigncias essenciais) de que nenhum lado direito de produo seja , ou tenha dois no terminais adjacentes. Uma gramtica com a ltima propriedade chamada de uma gramtica de operadores.

Exemplo:

A seguinte gramtica para expresses

E EAE | (E) | -E |id

A + | - | * | / |

No uma gramtica de operadores porque o lado direito EAE possui dois (de fato trs) no terminais consecutivos. Entretanto, se substituirmos A por cada uma de suas alternativas, obtemos a seguinte gramtica de operadores:

E E + E | E E | E * E| E / E | E E | (E) | -E | id

Descrevemos agora uma tcnica de anlise sinttica fcil de implementar chamada de anlise sinttica de precedncia de operadores. Historicamente, esta tcnica foi primeiramente descrita como uma manipulao sobre tokens sem qualquer referncia a uma gramtica subjacente. De fato, uma vez que tenhamos terminado de construir um analisador sinttico de precedncia de operadores a partir de uma gramtica, podemos ignorar esta ltima usando os no terminais na pilha apenas como marcadores de lugar para os atributos associados aos no terminais.

Como uma tcnica geral de anlise sinttica, a de precedncia de operadores possui uma srie de desvantagens. Por exemplo, difcil tratar os tokens como o sinal de menos, que possui duas diferentes precedncias (dependendo de ser unrio ou binrio). Sobretudo, uma vez que o relacionamento entre a gramtica para a linguagem analisada e o analisador sinttico de precedncia de operadores tnue, no se pode estar sempre certo de que o analisador aceite exatamente a linguagem desejada. Finalmente, somente uma pequena classe de gramticas pode ser decomposta usando as tcnicas de precedncia de operadores.

Apesar de tudo, em virtude de sua simplicidade, numerosos compiladores usando as tcnicas de anlise sinttica de precedncia de operadores tm sido construdos com sucesso. Freqentemente, esses analisadores so de descendncia recursiva. Analisadores sintticos de precedncia de operadores tem sido construdos at mesmo para linguagens inteiras.

10 ANALISADORES SINTTICOS LR

Uma tcnica eficiente de anlise sinttica bottom-up, que pode ser usada para decompor uma ampla classe de gramticas livres de contexto chamada anlise sinttica LR(k); oL significa varredura da entrada da esquerda para a direita (left-to-right), o R, construir uma derivao mais direita ao contrrio (right)most derivation) e o k, o nmero de smbolos de entrada de lookahead que so usados ao se tomar decises na anlise sinttica. Quando (k) for omitido, k assumido ser 1. A tcnica de anlise sinttica LR atrativa por uma srie de razes.

Analisadores sintticos LR podem ser elaborados para reconhecer virtualmente todas as construes de linguagens de programao para as quais as gramticas livres de contexto podem ser escritas.

O mtodo de decomposio LR o mais geral dentre os mtodos sem retrocesso de empilhar e reduzir conhecidos e ainda pode ser implementado to eficientemente quanto os demais mtodos de empilhar e reduzir, .

A classe de gramticas que podem ser decompostas usando-se os mtodos LR um superconjunto prprio da classe de gramticas que podem ser decompostas usando-se analisadores sintticos preditivos.

Um analisador sinttico LR pode detectar um erro sinttico to cedo quanto possvel numa varredura da entrada da esquerda para a direita.

A principal desvantagem deste mtodo est em ser muito trabalhoso construir um analisador sinttico LR manualmente para uma gramtica tpica de linguagem de programao. Necessita-se em geral de uma ferramenta especializada um gerador de analisadores LR. Felizmente, muitos de tais geradores esto disponveis. Com um tal gerador, podemos escrever uma gramtica livre de contexto e us-lo para produzir automaticamente um analisador sinttico para a mesma. Se a gramtica contiver ambigidades ou outras construes que sejam difceis de decompor, numa varredura da entrada da esquerda para a direita, o gerador de analisadores pode localiz-las e informar ao projetista do compilador de suas ocorrncias.

10.1 O Algoritmo de Anlise Sinttica LR

Consiste em uma entrada, uma sada, uma pilha, um programa diretor e uma tabela sinttica que possui duas partes (ao e desvio). O programa diretor o mesmo para todos os trs tipos de analisadores LR; somente a tabela sinttica muda de um analisador para o outro. O programa de anlise sinttica l os caracteres provenientes de um buffer de entrada, um de cada vez. Usa uma pilha para armazenar as cadeias sob a forma s0X1s1X2s2...Xmsm onde sm est ao topo. Cada Xi um smbolo gramatical e cada si, um smbolo chamado de estado. Cada estado sumariza a informao contida na pilha abaixo do mesmo e a combinao do smbolo do estado no topo da pilha e o smbolo corrente de entrada usada para indexar a tabela sinttica e determinar a deciso de empilhar ou reduzir durante a anlise. Numa implementao, os smbolos gramaticais no precisam figurar na pilha; entretanto, iremos sempre inclu-los em nossas discusses, a fim de auxiliar a explicao do comportamento de uma analisador sinttico LR.

A tabela sinttica consiste em duas partes, uma uno de aes sintticas, ao, e uma funo de desvio, desvio. O programa diretor do analisador LR se comporta como se segue. Determina sm, o estado correntemente no topo da pilha, e ai, o smbolo corrente de entrada. Consulta, ento ao[sm,ai], a entrada da tabela de aes sintticas para o estada sm e a entrada ai, que pode ter um dos seguintes valores:

1. empilhar s, onde s um estado,

2. reduzir atravs da produo gramatical A ,

3. aceitar, e

4. erro.

A funo desvio toma um estado e um smbolo gramatical como argumentos e produz um estado como sada. Veremos que a funo desvio de uma tabela sinttica, construda a partir de uma gramtica G, usando os mtodos SLR, LR cannico ou LALR, a funo de transio de um autmato finito determinstico que reconhece os prefixos viveis de G. Relembremos que os prefixos viveis de G so aqueles das formas sentenciais direita que podem aparecer na pilha de um analisador sinttico de empilhar e reduzir, porque os mesmos no se estendem para depois do handle mais direita. O estado inicial deste AFD o estado inicialmente colocado no topo da pilha do analisador sinttico LR.

Uma configurao de um analisador sinttico LR um par cujo primeiro componente o contedo da pilha e cujo segundo componente a entrada ainda no consumida:

(s0X1S1x2S2...Xmsm,aiai+1...an$)

Esta configurao representa a forma sentencial direita

X1X2...Xmaiai+1...an

Essencialmente da mesma maneira que um analisador de empilhar e reduzir faria: somente a presena dos estados na pilha inovadora.

O prprio movimento do analisador determinado pela leitura de ai, o smbolo corrente da entrada e de sm, o estado no topo d pilha, e pela consulta entrada da tabela de aes, ao[sm,a i]. As configuraes resultantes aps cada um dos quatro tipos de movimentos so como se segue:

1. Se ao [sm,a i]=empilhar s, o analisador executa um movimento e empilhar, entrando na configurao

(s0X1s1X 2s2...Xmsm,ais,ai+1...an$)

Aqui, o analisador sinttico empilhou tanto o smbolo corrente de entrada como o prximo estado s, que dado por ao[sm,a i]; ai+1 se torna o smbolo corrente da entrada.

2. Se ao[sm,a i]=reduzir A , o analisador executa um movimento de reduo, entrando na configurao

(s0X1s1X 2s2...Xm-rsm-r,As,ai ai+1...an$)

onde s=desvio[sm-r,A] e r o comprimento de , o lado direito da produo. Aqui o analisador sinttico remove primeiro 2r smbolos para fora da pilha (r smbolos de estados e r smbolos gramaticais), expondo o estado sm-r. Em seguida, empilha tanto A, o lado esquerdo da produo, quanto s, a entrada para desvio[sm-r,A]. Para os analisadores sintticos LR que iremos construir, Xm-r+1... Xm, a seqncia de smbolos gramaticais removidos da pilha ir sempre reconhecer , o lado direito da produo redutora.

A sada de um analisador sinttico LR gerada aps um movimento de reduo, atravs da execuo de uma ao semntica associada produo redutora. Para o momento, assumiremos que a sada consista somente na impresso da produo redutora.

3. Se ao[sm,a i]=aceitar, a anlise sinttica est completa.

4. Se ao[sm,a i]=erro, o analisador sinttico descobriu um erro e chama um procedimento de recuperao de erros.

Autoria: Elisson Oliveira Lima