Você está na página 1de 17

26-03-2012

Linguagens e Programao

4 Anlise Sintctica Descendente


Fontes: 1. Compiladores Princpios e prticas, Keneth C.Louden, Thomson, 2004. Cap. 4 Anlise sintctica descendente 2. Compiladores - Princpios , Tcnicas e Ferramentas, Alfred V. Aho, Monica S. Lam e Ravi Sethi, Pearson, 2 edio, 2007. Cap. 4 seco 4.5 Anlise Sintctica Descendente 3. Processadores de Linguagens da concepo implementao, Rui Gustavo Crespo. IST Press.1998. Cap. 4 Anlise sintctica descendente

Ana Madureira
Engenharia Informtica Ano Lectivo: 2011/2012

Anlise Top-Down
Neste tipo de anlise sintctica, a rvore de parse construda partindo da raiz, at se chegar sequncia de tokens do texto da entrada:
Gera-se sempre uma derivao mais esquerda Os ns da rvore de parse construda so visitados em pr-ordem (descida recursiva) ou prximo;

So usados essencialmente dois mtodos para implementar a anlise top-down:


O mtodo da descida recursiva, em que se associa uma rotina a cada no-terminal da gramtica que chamada quando se pretende reescrever esse smbolo; O mtodo da anlise preditiva no-recursiva, que necessita de um stack auxiliar e guiada por uma tabela de parse.

Estes tipos de anlise sintctica requerem geralmente uma classe de gramticas livres de contexto, denominadas LL(k) (na prtica LL(1))
O mtodo da descida recursiva, sendo geralmente escrito " mo", pode incorporar tcnicas ad-hoc e portanto relaxar um pouco as exigncias das gramticas LL(k).

26-03-2012

Anlise Top-Down - Exemplo


Considere-se a gramtica: Exp Exp Op Exp | ( Exp ) | num Op + | - | * | /

E a sequncia de tokens: (num - num) * num


Numa anlise top-down partimos do smbolo inicial (Exp) e tentamos chegar sequncia de tokens atravs de uma derivao geralmente mais esquerda:

Exp Exp Op Exp (Exp) Op Exp (Exp Op Exp) Op Exp (num Op Exp) Op Exp (num - Exp) Op Exp (num - num) Op Exp (num - num) * Exp (num - num) * num

Analisadores Sintcticos Descendentes Constri a derivao mais esquerda da sentena de entrada a partir do smbolo inicial da gramtica. Podem ser implementados:
com retrocesso (analisadores nodeterminsticos)
Fora bruta Maior conjunto de gramticas (ambguas) Maior tempo para anlise Dificuldade para recuperao de erros Dificuldade para anlise semntica e gerao de cdigo

sem retrocesso (analisadores determinsticos)


Classe limitada de gramticas (LL1)

26-03-2012

Analisadores Sintcticos Descendentes - Dificuldades


Quando mais do que uma regra, para o mesmo smbolo no-terminal, comeam pelo mesmo smbolo difcil escolher uma das regras, examinando apenas o prximo token Recursividade esquerda: (A + A) Pode levar a ciclos infinitos! Soluo: modificar gramtica de modo a eliminar recursividade esquerda. Produes que comeam com o mesmo smbolo: ( A X | X) dificuldade em decidir qual das produes aplicar Soluo: Atrasar a deciso Factorizao esquerda

Eliminao da recursividade esquerda


Uma gramtica recursiva esquerda se possuir um no terminal A, tal que exista uma derivao A + A, AV e , para alguma cadeia . Suponha uma gramtica com regras de produo na forma: A A onde AV e no comea por A A Esta gramtica recursiva esquerda. Para ser eliminada aplica-se o seguinte prcedimento:
Agrupam-se todas as produes A, na forma A A| onde no comea por A Substitui-se, de forma equivalente, o conjunto de regras, pelo seguinte conjunto: A A' A' A| Exemplo: Sejam as regras para expresso do exemplo anterior: EE+T EE-T ET Podem ser transformadas no conjunto: E T E' E' + T E' E' - T E' E'

26-03-2012

Eliminao da Factorizao esquerda


Quando mais do que uma regra, para o mesmo no-terminal, comeam pelo mesmo smbolo difcil escolher uma das regras, examinando apenas o prximo token Soluo: atrasar a escolha at haver distino Algoritmo de factorizao esquerda:
1. 2. Para cada no-terminal A da gramtica encontrar o prefixo mais longo , comum a duas ou mais das suas regras Se , substituir todas as produes de A 1 | 2 | | n | , onde representa regras que no comeam por por: A A' | A' 1 | 2 | | n Repetir o processo at = , para todas as regras, ou seja, at que todas as regras no mesmo no-terminal comecem com smbolos diferentes Exemplo: Seja a gramtica: S if E then S | if E then S else S Aplicando a factorizao esquerda obtemos: S if E then S X X else S|

3.

Eliminao da recursividade esquerda geral


Existem formas de recursividade esquerda no imediatas Existe recursividade esquerda quando A + A Exemplo: S Aa | b A Ac | Sd | S Aa Sda Estas formas no ocorrem geralmente nas gramticas das linguagens de programao

1. Ordenar os no-terminais numa ordem arbitrria: A1, A2, , Ai, , An 2. for i de 1 a n do for j de 1 a i-1 do substituir todas as produes da forma Ai Aj pelas produes Ai 1 | 2 | | k , onde Aj 1 | 2 | | k so todas as produes decorrentes de Aj eliminar, se for caso disso, a recursividade esquerda imediata de Ai

26-03-2012

Informao sobre gramticas


necessrio obter informao sobre smbolos no terminais em GIC, para decidir quais as produes a serem usadas durante o processo de anlise sintctica:
Se o smbolo no terminal A gera ou no a cadeia vazia Quais so os smbolos terminais iniciadores (Starters(A)) das frases geradas a partir de A:
Se A *, que terminais podem aparecer como primeiro smbolo de

Quais so os smblos terminais seguidores (Followers(A)): ou seja, se A *A, que terminais podem aparecer como primeiro smbolo de

Conjuntos Starters e Followers


Definies:
Para o no-terminal X define-se: nullable(X) - funo que retorna TRUE se X + e FALSE no caso contrrio Starters(X) - conjunto de terminais que iniciam as frases derivveis de X -

Starters(X) = { t | X * t }
Followers(X) - conjunto de terminais que podem aparecer a seguir a um X em qualquer forma sentencial - Followers(X) = { t | S * Xt }

Seguindo as definies de cima, so vlidas as afirmaes:


Para uma regra A X1 X2 Xn e para 1 i < j n Starters(A) Starters(X1) Followers(Xn) followers(A) Followers(Xi) Starters(Xi+1) se nullable(Xk) para 1 k n ento nullable(A) = TRUE se nullable(Xk) para qualquer 1 k i-1 ento Starters(A) Starters(Xi) Admite-se que Starters(t) = {t}, t terminal se nullable(Xk) para qualquer i+1 k n ento followers(Xi)followers(A) se nullable(Xk) para qualquer i+1 k j-1 ento Followers(Xi)Starters(Xj)

26-03-2012

Descida recursiva
Consiste na construo de um conjunto de procedimentos (normalmente recursivos), um para cada smbolo no terminal da gramtica em questo. A cada no-terminal est associada uma rotina, sendo a sua anlise efectuada pela chamada a essa rotina. Considere-se a seguinte gramtica para uma linguagem muito simples: S if E then S else S S begin S L S print E L end L;SL E num = num
A esta gramtica deve ser acrescentado um novo smbolo inicial P e a seguinte regra: PS$ ($ designa o fim do texto EOF) Ser necessrio adicionar uma rotina relativa a esta nova produo

Descida recursiva
enum token {EOF, IF, THEN, ELSE, BEGIN, END,PRINT, SEMI, NUM, EQ}; enum token getToken(void); enum token tok; void advance(void) { tok = getToken(); } void eat(enum token t) { if (tok == t) advance(); else error(); } void P(void) { S(); if (tok == EOF) accept(); else error(); } void S(void) { switch (tok) { case IF: eat(IF); E(); eat(THEN); S(); eat(ELSE); S(); break; case BEGIN: eat(BEGIN); S(); L(); break; case PRINT: eat(PRINT); E(); break; default: error(); }}

PS$ S if E then S else S S begin S L S print E L end L;SL E num = num

void L(void) { switch(tok) { case END: eat(END); break; case SEMI: eat(SEMI); S(); L(); break; default: error(); }} void E(void) { eat(NUM); eat(EQ); eat(NUM); } void main(void) { advance(); P(); }

26-03-2012

Descida recursiva
Desvantagens
no geral, ou seja, os procedimentos so especficos para cada gramtica tempo de anlise maior necessidade de uma linguagem que permita recursividade para sua implementao Dificuldade de validao

Vantagens
simplicidade de implementao facilidade para inserir as diferentes funes do processo de compilao nomeadamente a possibilidade de incluso de rotinas de anlise semntica se existem diferentes alternativas para reescrever o mesmo no terminal, eficincia no ser necessrio tentar diferentes rotinas para o reconhecimento de uma frase (backtraking)

Descida recursiva - dificuldades


Consideremos agora a seguinte gramtica para expresses aritmticas: SE$ TT*F F id EE+T TT/F F num EE-T TF F(E) ET Como escrever as rotinas relativas a E e T ? No sabemos como distinguir as vrias produes. Mesmo sabendo, as primeiras 2 produes de E e T produziriam chamadas recursivas infinitas. Se escrevermos a gramtica anterior na forma EBNF obtemos: SE$ E T { +|- T } T F { *|/ F } F num F id F ( E)

As expanses para E e T tornam-se evidentes: void E(void) { T(); while (tok==PLUS || tok==MINUS) { advance(); T(); } } void T(void) { F(); while (tok==MUL || tok==DIV) { advance(); F(); } }

No entanto existem mtodos para eliminar a recursividade esquerda de uma gramtica e para distinguir entre regras, quando um no-terminal possui vrias produes que comeam por no-terminais distintos, ou smbolos iguais. So eles: Recursividade esquerda: (A + A) Fazer uma eliminao da recursividade esquerda, transformando a gramtica Produes que comeam com o mesmo smbolo: ( A X | X) Fazer uma factorizao esquerda Produes que comeam por no-terminais distintos: (A B | C) Dispor do conjunto de terminais que pode comear qualquer derivao de B e C starters(B) e starters(C) Produes vazias: (A ) Dispor do conjunto de terminais que pode legalmente aparecer a seguir a A em qualquer forma sentencial - followers(A)

26-03-2012

Utilizao dos starters e dos followers para implementao de um analisador de descida recursiva (exemplo)
Verificou-se anteriormente que a gramtica para expresses no era adequada para a descida recursiva (era recursiva esquerda). Eliminando a recursividade esquerda daquela gramtica, obtm-se: Exemplo: Sejam as regras para expresso do exemplo anterior: EE+T EE-T ET Podem ser transformadas no conjunto: E T E' E' + T E' E' - T E' E'

Assim as rotinas de descida recursiva correspondentes a E' e T' seriam:


void Eprime(void) { switch (tok) { case PLUS: eat(PLUS); T(); Eprime(); break; case MINUS: eat(MINUS); T(); Eprime(); break; case RPAR: case EOF: break; default: error(); } } void Tprime(void) { switch (tok) { case MUL: eat(MUL); F(); Tprime(); break; case DIV: eat(DIV); F(); Tprime(); break; case RPAR: case PLUS: case MINUS: case EOF: break; default: error(); }

S E $ ET E' T F T' E' + T E' T' * F T' F id E' - T E' T' / F T' F num E' T' F ( E ) que j seria adequada para implementar uma descida recursiva.

Utilizao dos starters e dos followers para implementao de um analisador de descida recursiva (exemplo)
Verificou-se anteriormente que a gramtica para expresses no era adequada para a descida recursiva (era recursiva esquerda). Eliminando a recursividade esquerda daquela gramtica, obtm-se:

Assim as rotinas de descida recursiva correspondentes a E' e T' seriam:


void Eprime(void) { switch (tok) { case PLUS: eat(PLUS); T(); Eprime(); break; case MINUS: eat(MINUS); T(); Eprime(); break; case RPAR: case EOF: break; default: error(); } } void Tprime(void) { switch (tok) { case MUL: eat(MUL); F(); Tprime(); break; case DIV: eat(DIV); F(); Tprime(); break; case RPAR: case PLUS: case MINUS: case EOF: break; default: error(); }

S E $ ET E' E' + T E' E' - T E' E'

T F T' T' * F T' T' / F T' T'

F id F num F ( E )

que j seria adequada para implementar uma descida recursiva. No entanto se pretendermos validar as produes vazias de E' e T', necessitamos dos seus followers: followers(E') = { ), $ } followers(T') = { ), +, -, $ }

26-03-2012

Construo explcita da rvore de parse em descida recursiva


Para a construo da rvore de parse numa decida recursiva; basta que cada rotina retorne um apontador para a subrvore que representa. Admitindo que dispomos da funo makenode() que aloca um n de rvore, preenche a respectiva informao e retorna o apontador para esse n, as rotinas para E' e F da gramtica anterior ficariam:
tree Eprime(void) { tree t = makenode(EXP_P); if (tok == PLUS) { t->child[0] = makenode(PLUS); advance(); t->child[1] = T(); t->child[2] = Eprime(); } elseif (tok == MINUS) { t->child[0] = makenode(MINUS); advance(); t->child[1] = T(); t->child[2] = Eprime(); } else t->child[0] = makenode(EPSILON); return t; } tree F(void) { tree t = makenode(FACTOR); switch(tok) { case ID: t->child[0] = makenode(ID); advance(); break; case NUM: t->child[0] = makenode(NUM); advance(); break; case LPAR: t->child[0] = makenode(LPAR); advance(); t->child[1] = E(); eat(RPAR); t->child[2] = makenode(RPAR); break; default: error(); } return t; } Typedef struct node { enum kind info; struct node *child[MAX_CHILD]; } *tree;

Analisadores sintcticos preditivos (no recursivos)


Estes analisadores sintcticos utilizam uma tabela, que dirige as regras a aplicar, alm de uma stack auxiliar de smbolos gramaticais e a consulta de um ou mais dos tokens da entrada
LL(k): L - left to right parse L - leftmost derivation k - nmero de tokens de lookahead (na prtica k = 1)

26-03-2012

Tabela de Parse
Em cada entrada da tabela M existe uma nica produo viabilizando a anlise determinstica da sentena de entrada. Para isso necessrio que a gramtica:
No possua recursividade esquerda Estar factorizada ( determinstica) Para todo A | A * e, First(A) Follow(A) =

Tabela de parsing
As GLC que satisfazem estas condies so denominadas GLC LL(K)
podem ser analisadas deterministicamente da esquerda para a direita (Left-to-right) o analisador construir uma derivao mais esquerda (Leftmost derivation) sendo necessrio a cada passo o conhecimento de K smbolos de lookahead (smbolos de entrada que podem ser vistos para que uma aco seja determinada).

Somente GLC LL(K) podem ser analisadas pelos analisadores preditivos (os analisadores preditivos so tambm denominados analisadores LL(K)
na prtica usa-se K = 1, obtendo-se desta forma analisadores LL(1) para GLC LL(1).

10

26-03-2012

Analisadores sintcticos preditivos (no recursivos)


L L(1) analisadores preditivos
exactamente 1 smbolo da entrada examinado (lookahead) o analisador constri uma derivao esquerda Leftmost a cadeia de entrada examinada da esquerda para a direita Left-to-right Objectivo do mecanismo LL(1) seleco da regra a utilizar durante o processo de anlise descendente, baseada em: prximo smbolo no-terminal A a ser expandido primeiro smbolo a do resto da entrada a analisar

Funcionamento de um parser preditivo


Configurao inicial: Stack auxiliar: contm no fundo o smbolo $ (eof) e no topo o smbolo S Entrada: nada ainda consumido, o primeiro token est acessvel Tabela de parse uma matriz da forma M[N, T] onde N (linhas) o conjunto dos no-terminais e T (colunas) o conjunto dos terminais mais o smbolo $ Cada entrada da tabela contm uma regra gramatical ou a indicao de erro Aces do parser: Seja X o smbolo que est no topo do stack e seja z o prximo token disponvel; Se X = z = $ ento o texto da entrada aceite (conclui a anlise sintctica) Se X = z $ ento: fazer o pop() da stack (descartar X) avanar a entrada (descartar z) Se X = no-terminal consultar M[X, z] - se M[X, z] contiver uma regra:
fazer pop() (retirar X da stack) fazer push do lado direito da produo que estiver na tabela por ordem inversa (Se na tabela estiver X , fazer push(); push(); push(); ) indicar a aplicao dessa regra ( p. ex. printf(X ); )

Outras situaes: erro

11

26-03-2012

Algoritmo parser preditivo

push($); push(S); advance(); while (top() != $ && tok != $) do if ((a = top()) == terminal && tok == a) then pop(); advance(); else if ((X = top()) == non-terminal && M[X, tok] == X Y1 ... Yn) then pop(); for (k = n; k > 0; k--) push(Yk); output(" X Y1 ... Yn"); else error(); if (top() == $ && tok == $) then accept(); else error();

Exemplo
Considere-se a gramtica ET E' E' + TE' E' T F T' T' * FT' T' Anlise da entrada id + id * id $ e regras aplicadas para a aceitao Desta: F id F ( E )

Esta gramtica, d origem seguinte tabela de parse:

12

26-03-2012

Exemplo
Considere-se a seguinte gramtica para instrues:
S If-st | other If-st if ( Exp ) S El El else S | Exp 0 | 1

Contruir a tabela de parse para esta gramtica. Comeando por determinar nullable, starters e followers chegaramos aos seguintes resultados:

Repare-se na dupla entrada em M[El, else] que faz com que esta gramtica no seja LL(1). Deve-se conhecida ambiguidade da instruo if com else opcional. No entanto usando a habitual regra de elimio de ambiguidade (emparelhar cada else com o ltimo if) a produo El pode ser eliminada dessa entrada, tornando a tabela perfeitamente definida e correcta para aquela regra.

Anlise sintctica LL(1) Construo da tabela M[A, a]


Para todas as produes A da gramtica: Para cada regra i: A , temos M[A, a] = i, para cada a em Starters() Para cada regra i: A , se * , temos M[A, a] = i, para cada a em Followers(A) As entradas de M[A, a] que no recebem qualquer valor devem ser marcadas como entradas de erro ()
M[A, a] E T F E T ( 1 2 3 a 1 2 4 + 5 8 * 7 ) 6 8 $ 6 8

1. E TE 2. T FT 3. F (E) 4. F a 5. E +TE 6. E 7. T +FT 8. T

S(TE) = { (, a } S(FT) = { (, a } S((E)) = { ( } S(a) = { a } S(+TE) = { + } F(E) = { $, ) } S(*FT) = { * } F(T) = { $, +, ) }

M[ E, ( ] = M[ E, a ] = 1 M[ T, ( ] = M[ T, a ] = 2 M[ F, ( ] = 3 M[F, a ] = 4 M[ E, + ] = 5 M[ E, $ ] = M[ E, ) ] = 6 M[ T, * ] = 7 M[ T, $ ]=M[ T, + ]=M[ T, ) ] = 8

Condio para a gramtica ser LL(1): Para cada entrada M[A, a] s pode haver um valor; caso contrrio, diz-se que existe um conflito e a gramtica no LL(1)

13

26-03-2012

Transformao de uma gramtica no LL(1) numa gramtica LL(1) (1/3)


Em alguns casos conclui-se que uma gramtica no LL(1). As duas caractersticas mais bvias so a:
recursividade esquerda uma gramtica recursiva esquerda se permite uma derivao do tipo: A * A, A VNT, A A A (Para que A no seja intil necessrio pelo menos uma regra A sem recursividade esquerda) Esta gramtica gera cadeias da forma Mas estas cadeias podem ser geradas de forma alternativa (eliminando a recursividade esquerda): A A A A A (Apresenta recursividade direita, que no cria problemas) possibilidade de factorizao Se , substituir todas as produes de A 1 | 2 | | n | , onde representa regras que no comeam por por:
A A' | A' 1 | 2 | | n Exemplo: A aA | aB A aA A A|B

Transformao de uma gramtica no LL(1) numa gramtica LL(1) (2/3)


Possibilidade de factorizao Considere-se a situao em que duas regras comeam pelos mesmos smbolos, isto , regras como A e A , com S() . Neste caso, existe uma interseco entre S() e S(), e a gramtica no pode ser LL(1). Isto acontece porque no possvel decidir, olhando apenas para o primeiro smbolo derivado de , qual a regra correcta. A soluo envolve a factorizao: a cadeia inicial posta em evidncia. Temos: A A' A | Isto implica adiar a deciso entre e para quando o primeiro smbolo derivado de ou de estiver visvel.

14

26-03-2012

Transformao de uma gramtica no LL(1) numa gramtica LL(1) (3/3)


Estas duas tcnicas (eliminao de recursividade esquerda, factorizao) permitem transformar algumas gramticas em gramticas LL(1). No entanto, algumas gramticas independentes de contexto no tm gramticas equivalentes LL(1), e nesse caso a aplicao destas (ou de outras) tcnicas poder no ter sucesso. Exemplo: Transformao da gramtica seguinte numa gramtica LL(1) equivalente LL;S|S Frase: if e th s fi S if E th L el L fi L S if E th L fi if e th S fi if e th s fi | if E th L fi | s Ee Existe recursividade esquerda nas regras de L e possibilidade de factorizao nas regras de S. O resultado da transformao a gramtica L SL' Frase: if e th s fi L' ; S L' | L SL if E th LSL if e th LSL S if E th L S' | s if e th SLSL if e th s LSL if e th s SL S' el L fi | fi if e th s fi L if e th s fi Ee possvel verificar que uma gramtica LL(1).

Tratamento de erros
O tratamento de erros durante a anlise sintctica deve obedecer a certos princpios gerais: O erro deve ser declarado o mais cedo possvel, de modo a que possa ser localizado com preciso na sequncia de tokens da entrada Depois de detectado um erro o parser deve continuar, num ponto mais frente, de modo a detectar o maior nmero possvel de erros numa nica passagem O parser deve tentar evitar o problema da "cascata de erros", em que um nico erro gera uma srie de mensagens O parser deve evitar um ciclo infinito, reatando a anlise sem consumir nenhum token, e gerando sucessivamente as mesmas mensagens de erros A forma usual de tratamento de erros em parsers top-down tentar sincronizar a anlise a partir de um prximo token, descartando alguns se for necessrio, quando se detecta um erro sintctico Estes tokens de sincronizao, devero ser criteriosamente escolhidos para cada classe gramatical e so geralmente tokens dos conjuntos followersers( ) e starters( ) das classes gramaticais mais significativas da linguagem

15

26-03-2012

Tratamento de erros (parsers preditivos)


Nos parsers preditivos detecta-se um erro quando temos um no-terminal (A) no topo da stack e um token de entrada que no pertence a Starters(A) ou followers(A) se nullable(A) A situao de aparecer um terminal no topo da stack diferente da entrada nunca surge Nas situaes de erro, e para as recuperar podemos executar uma de 3 aces:
Retirar A da stack (pop)

Esta alternativa escolhida se o prximo token for $ ou se pertencer a followers(A)


Descartar sucessivamente tokens da entrada (scan), at se poder continuar

Escolhe-se esta alternativa nos casos no contemplados na alternativa anterior Vo-se descartando tokens da entrada at se encontrar um pertencente a Starters(A) followers(A)
Colocar (push) um no-terminal na stack

Se em virtude da primeira aco a stack ficar vazia, normalmente faz-se o push do smbolo inicial da gramtica Estas aces so codificadas na tabela de parse

Construo de um analisador Analisador baseado em tabelas e grafos sintcticos (1/3)

Cada regra representada por uma lista ligada em que os elementos so do tipo TABGRAFO As listas ligadas referentes a regras alternativas do mesmo no-terminal esto interligadas atravs do apontador ALT

16

26-03-2012

Construo de um analisador Analisador baseado em tabelas e grafos sintcticos (2/3)

S ab | aSc | dM | e M { fS }* Grafo sintctico:

2 3

b S M
4

8 10

5 7

d e

Quando se efectuam chamadas a outros smbolos no-terminais, necessrio usar uma stack para suporte da anlise: depois da chamada necessrio voltar ao ponto de onde foi efectuada a chamada. Exemplo: Frase aabc S aSc a ab c 3 Stack

Construo de um analisador Analisador baseado em tabelas e grafos sintcticos (3/3)

S M

d a b c d e f

S ab | aSc | dM | e M { fS }*

17

Você também pode gostar