A apresentação está carregando. Por favor, espere

A apresentação está carregando. Por favor, espere

Linguagens Formais e Tradutores Análise Sintática - 1

Apresentações semelhantes


Apresentação em tema: "Linguagens Formais e Tradutores Análise Sintática - 1"— Transcrição da apresentação:

1 Linguagens Formais e Tradutores Análise Sintática - 1
Prof. André Luis Meneses Silva

2 Análise Sintática (parsing)
Verificar se um programa é sintaticamente válido Reconhecer a estrutura sintática do programa Produzir uma representação abstrata dos programas (sintaxe abstrata) Produzir mensagens de erros. Se recuperar. Programa fonte analisador léxico Tokens parser Árvore Abstrata Analisador Semântico

3 Especificação da Sintaxe
Definições regulares não têm o poder de expressão suficiente Gramáticas livres de contexto = def. regulares + recursão Uma GLC G é definida por um conjunto de produções (ou regras) simbolo → simbolo simbolo simbolo Cada símbolo pode ser terminal ou não terminal O símbolo à esquerda de → é um não terminal Existe um símbolo destacado: símbolo inicial

4 Especificação da Sintaxe
Exemplo E → E+E ou E → E+E E → E*E | E*E E → num | num E → (E) | (E) Derivação E  E+E  E + E + E  num + E + E  num + E + num  num + num + num

5 Árvore de derivação E + E E E num E + yield num num

6 Derivação mais à esquerda
E  E+E  E + E + E  num + E + E  num + num + E  num + num + num Gramática é ambígua sse existe mais de uma árvore de derivação para uma mesma sentença (yield) sse existe mais de uma derivação mais à esquerda

7 E + E E E num E + num num E E + E E + E num num num

8 Gramáticas ambíguas Problemáticas para compilação
Qual é a estrutura gramatical escolhida? Como lidar Adicionar regras extra-gramaticais precedência de operadores, associatividade, else opcional, ...; ou Usar GLC não ambígua equivalente com a original Associatividade à esquerda (direita) consegue-se com gramática recursiva à esquerda (direita) Precedência: com gramáticas por camadas

9 Gramáticas ambíguas Precedência e associatividade podem ser codificadas dentro da GLC E → E+T | E-T | T T → T*F | T/F | F F → id | num | (E) Regras recursivas à esq.  associatividade à esquerda Camada mais alta  precedência mais baixa Associatividade à esquerda (direita) consegue-se com gramática recursiva à esquerda (direita) Precedência: com gramáticas por camadas

10 Gramática por camadas - exemplo
E → E + T | E - T | T T → T * F | T / F | F F → -F | +F | V V → V . id | V [ E ] | A A → id | num | (E) Em a + b.x, o “.” tem mais precedência Observem que permitimos, por exemplo: (2+3).x --- Isto é fixado na checagem de tipos

11 else opcional S  if E then S else S | if E then S | outro
Qual é a árvore de if E1 then if E2 then S1 else S2 ? S' : comando sempre com else associado S" : comando sempre com else não associado A idéia: entre um then e um else sempre vai um comando associado (veja as prod. com then e else)

12 else opcional S  if E then S else S | if E then S | outro
Qual é a árvore de if E1 then if E2 then S1 else S2 ? LPs adotam: else sempre casa com o if mais próximo. Podemos codificar isto numa GLC, assim: S  S' | S" S' if E then S' else S' | outro S" if E then S | if E then S' else S" S' : comando sempre com else associado S" : comando sempre com else não associado A idéia: entre um then e um else sempre vai um comando associado (veja as prod. com then e else)

13 Métodos de Análise Métodos universais são ineficientes (CYK e outros)
Top Down (recursivos-descendentes) Constroem a árvore de deriv. em pré-ordem Caso particular: parser preditivos (LL(n)) Decidem que produção usar olhando n tokens de lookahead LL(1) são adequados para implementação manual Servem para a maioria das linguagens Exigem transformações na gramática original Bottom Up (pós-ordem) Cobrem uma classe mais ampla de gramáticas Menos transformações Usualmente construídos com ferramentas Menos transformaçaõ: gramática mais abstrata => mais próxima da sintaxe abstrata => mais modular e simples a geração das árvores abstratas LL 1ro L: varredura da entrada de esquerda para direita 2doL: derivação mais à esquerda

14 Parser Recursivo Descendente
Pode envolver retrocesso (Backtracking) L → c A d A → ab | a O parser de cad exige retrocesso Ineficiente para ser usado na prática Em geral, LPs não precisam de retrocesso Construir a árvore

15 Análise preditivo Não precisam de retrocesso
Porém, só servem para um tipo de gramáticas -- LL(n) não permitem recursão à esquerda LL(1): Se queremos expandir A e temos a na entrada Só deve ser possível usar uma única produção a é chamado de lookahead Exemplo: Sintaxes com palavras reservadas como S  if E then S else S | while E do S | begin L end Em gramáticas LL(n) usa-se n símbolos de lookahead

16 Parser preditivo Um procedimento para cada não terminal
Um switch com um caso para cada produção S  if E then S else S | begin S L | print E L  end | ; S L E  num = num void S( ) { switch(tok) { case IF: proxToken( ); E( ); reconhecer(THEN); S( ); reconhecer(else); S( ); break; case BEGIN: proxToken( ); S( ); L( ); break; case PRINT: proxToken( ); E( ); break; default: erro( ); } }

17 void L( ) { switch(tok) {
case END: proxToken( ); break; case SEMICOLON: proxToken( ); S( ); L( ); break; default: erro( ); } } void E( ) { switch(tok) { case NUM: proxToken( ); reconhecer(EQ); reconhecer(NUM); break; default : erro( ); } } void proxToken( ) { tok = scanner.proximoToken( ).codigo; } void reconhecer(int t) { if (t!=tok) erro( ); else tok = scanner.proximoToken( ).codigo; }

18 Gramáticas não LL V  V [ E ] | id
O procedimento V( ) chama recursivamente V( ) sem consumir nenhum token. Entra em loop. E  num + E | num * E | num | (E) Com num inicialmente scaneado, que produção escolho? Porém, ela é LL(2). E  T + E | T T  F * T | F F  num | (E) Não é LL(2). (num)+(num) “( num” não determina a produção a escolher Na primeira, como dois lookahead poderia determinar a regra (porém não é LL(2) pois é rec. à esq.). Na segunda: que par de lookaheads determinam a escolha de E->num? ... “num $” e “num )”

19 Primeiro e Seguinte Primeiro()={a |  * a}  { |  * }
Seguinte(A)={a | S * Aa} Permitem ver se a gramática é LL(1) Permitem construir a tabela sintática preditiva c/entrada contém a produção a ser aplicada para um par lookahead – terminal Exemplo Z  d Y   X  Y W  b | X Y W Z | c | a Primeiro(Z)=? Primeiro(Z)={d,a,c,b} Aa é chamada de forma sentencial

20 Algoritmo para Primeiro
Inicializar Primeiro(a)={a}, para todo terminal a Se X  , adicionar  a Primeiro(X) Repetir até não poder adicionar mais Se X  Y1Y2...Yk,   Primeiro(Y1), ... ,   Primeiro(Yi-1) (i.e., Y1Y2..Yi-1  ), e a  Primeiro(Yi) então adicionar a a Primeiro(X) Primeiro(X1X2...Xn) = Primeiro(X1), se   Primeiro(X1); Primeiro(X1X2...Xn) = Primeiro(X1)...Primeiro(Xi+1) - {} se   Primeiro(Xk), para k =1...i , e   Primeiro(Xi+1); e Primeiro(X1X2...Xn) = Primeiro(X1)  ... Primeiro(Xn) se   Primeiro(Xk), para k = 1...n 1ro. Algoritmo: ITERATIVO e calcula primeiro para todos os símbolos (terminais e n/ao terminais) 2do. Calcula primeiro para cadeias (alfa)

21 a b c d X Y Z W a Z  d Y   X  Y W  b | X Y W Z | c | a b c d
Primeiro a b c d X Y Z W a b c d , a, c , c a, b, c, d Construir a tabela no quadro, seguindo o algoritmo

22 Seguinte - Algoritmo Seguinte(A)={a | S * Aa}
Colocar $ em Seguinte(S) Repetir até não poder adicionar mais Se existir AB adicionar Primeiro()-{ε} a Seguinte(B) Se existir AB ou AB e   Primeiro() adicionar Seguinte(A) a Seguinte(B)

23 S  Z $ c, b Z  d Y   X  Y W  b | X Y W Z | c | a $ a, b, c, d
Seguinte X Y Z W c, b $ a, b, c, d Construir tabela no quadro, seguindo algoritmo

24 Tabela sintática preditiva
A é uma entrada para (A,a) na tabela sse: Se A e a  Primeiro() Se A,   Primeiro() e a  Seguinte(A) Z  d Y   X  Y W  b | X Y W Z | c | a a b c d X Y W Z X  a X  Y Y  Y  c W  b Z  X Y W Z Z  d

25 G é LL(1) sse a sua tabela sintática preditiva é determinística
LL(n): Com n lookaheads as entradas para as colunas serão cadeias de tamanho n Gramáticas não LL: Nenhuma G ambígua é LL Nenhuma G com recursão à esquerda é LL V  V [ E ] | id id  Primeiro(V[E]) Não é possível escolher a regra.

26 Eliminando recursão à Esquerda
E → E+T | E-T | T Podemos escrever sem recursão à esquerda, assim E → T E' E'→ + T E' |  Em geral, se temos A → A1 | A2 |...| An | 1 | 2 |...| m Escrevemos A → 1A' | 2A' |...| mA' A' → 1A' | 2A' |...| nA' |  Dificuldades futuras Árvore de derivação associativa à direita Geração de árvore abstrata exige tratamento especial Recursão à esquerda direta. Ver teorema de Greibach para eliminar recursão à esquerda indireta.

27 Tabela preditiva da gramática de expressões anterior S → E $ E → T E'
Adicionar produção S->E

28 Tirando em evidência (fatoração) à esquerda
podemos escrever A → A' |... A' →  |  Por exemplo D → int Id ; | int Id ( A ) ; | outro pode ser re-escrito como D → int Id D' | outro D' → ; | ( A ) ; Pequena dificuldade: A estrutura gramatical foi mudada Inicialmente não sabemos que regra escolher pois o  aparece em duas regras. Solução: retardar a escolha para ser feita após o parser de  Mostrar exemplo de gra

29 Exemplos de fatoração E  id(E) | id[e] | id | num E  id E' | num
A introdução de produções vazias pode introduzir conflitos. E  id E' | num E'  (E) | [e] | 

30 Xelse S e X S  if E then S else S | if E then S | outro
Pode ser escrito como S  if E then S X | outro X  else S |  Porém, neste caso, a fatoração não é suficiente pois a nova gramática ainda é ambígua Em (X,else), a tabela vai ter duas entradas: Xelse S e X Solução ad-hoc: (adotada pela javacc, por ex.) Escolher sempre a primeira regra (neste caso, o else associa com o if mais próximo, como esperado). Não há prescrição geral. Não se aconselha usar soluções ad-hoc, salvo no caso bem conhecido do else opcional

31 Recuperação de erros em Parsers preditivos
Quando acontecem? A( ) foi chamado com o lookahead a e a entrada (A,a) na tabela preditiva é vazia; ou Ao reconhecer(a) o lookahead é diferente de a Desespero trabalha bem Pular todos os tokens até achar um de sincronização Heurísticas para o conjunto de sincronização Ajustes por tentativa e erro Outros métodos Substituir o token. Exemplo: lookahead = "," e Reconhecer(";") falha Inserindo tokens (inseguro, pode entrar em loop) Inserir ";” quando detectar sua falta Compilador que não se recupera não é de muita utilidade na prática – Objetivo não é só reconhecer validez do prog. mas também dar mensagens de erro adequadas e se recuperar ... Conjunto de sincronização: pular até achar um de sincronização

32 Modalidade de despero -- Heurísticas
A( ) foi chamado com lookahead a Ponto de partida: sincronização = Seguinte(A) e A( ) é resumido Acrescentar como sincronização as palavras chaves de construções mais altas ou da mesma altura (se houver seqüências). Por ex. Se faltar “;” e começar depois um while, o while poderia ser ignorado sem esta heurística Expressões estão dentro de comandos Usar produção vazia (ou que gera vazio) como default Ao reconhecer token Ignorar token (apagar) – sincroniza com todos (Em C, palavras reservadas não estão no próximo(cmd) pois “;” é finalizador (e “}” num comando composto (bloco))

33 Resumo Análise Sintática Gramáticas
Ambigüidade Classificação dos métodos de análise Análise top-down Análise preditivo – Gramáticas LL Tabela sintática preditiva Fatoração e eliminação da recursão á esquerda Recuperação de erros

34 Bibliografia Seções 4.1-4.6 do livro do Dragão
Seções 3.1 e 3.2 do livro do Tigre


Carregar ppt "Linguagens Formais e Tradutores Análise Sintática - 1"

Apresentações semelhantes


Anúncios Google