Análise de expressões não-manuais em avatares tradutores de ...
1 Linguagens Formais e Tradutores Análise Sintática - 1 Prof. André Luis Meneses Silva...
Transcript of 1 Linguagens Formais e Tradutores Análise Sintática - 1 Prof. André Luis Meneses Silva...
1
Linguagens Formais e Tradutores
Análise Sintática - 1
Prof. André Luis Meneses [email protected]
www.dsi.ufs.br
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.
analisadorléxico
parser
Tokens
Programa fonte
AnalisadorSemântico
Árvore Abstrata
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 ExemploE → E+E ouE → E+EE → E*E | E*EE → 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 E+
num num
num
yield
6
Derivação mais à esquerdaE 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)sseexiste mais de uma derivação mais à esquerda
7
E
E E+
E E+
num num
E
E E+
E E+
num 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
9
Gramáticas ambíguas Precedência e associatividade podem ser
codificadas dentro da GLC
E → E+T | E-T | TT → T*F | T/F | FF → id | num | (E)
Regras recursivas à esq. associatividade à
esquerda Camada mais alta precedência mais baixa
10
Gramática por camadas - exemplo
E → E + T | E - T | TT → T * F | T / F | FF → -F | +F | VV → V . id | V [ E ] | AA → id | num | (E)
11
else opcionalS if E then S else S | if E then S | outro
Qual é a árvore deif E1 then if E2 then S1 else S2 ?
12
else opcionalS if E then S else S | if E then S | outro
Qual é a árvore deif 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' | outroS" if E then S | if E then S' else S"
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
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
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
S if E then S else S | begin S L | print E
L end | ; S LE 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( ); } }
Um procedimento para cada não terminal Um switch com um caso para cada produção
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 LLV 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 | FF num | (E)
Não é LL(2). (num)+(num)
“( num” não determina a produção a escolher
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
ExemploZ d Y X Y W b | X Y W Z | c | a
Primeiro(Z)=?
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 ), ea 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); ePrimeiro(X1X2...Xn) = Primeiro(X1) ... Primeiro(Xn) se Primeiro(Xk), para k = 1...n
21
Z d Y X Y W b | X Y W Z | c | a
abcdXYZW
a
b
c
d
, a, c
, c
a, b, c, d
b
Primeiro
22
Seguinte - Algoritmo Seguinte(A)={a | S * Aa} Colocar $ em Seguinte(S)
Repetir até não poder adicionar mais Se existir AB
adicionar Primeiro()-{ε} a Seguinte(B)
Se existir AB ouAB e Primeiro()
adicionar Seguinte(A) a Seguinte(B)
23
Z d Y X Y W b | X Y W Z | c | a
XYZW
c, b
c, b
$
a, b, c, d
Seguinte
S Z $
24
Tabela sintática preditiva
Z d Y X Y W b | X Y W Z | c | a
X a X Y X Y
Y Y cY
W b
Z X Y W Z Z X Y W Z Z X Y W ZZ d
Z X Y W Z
X
Y
WZ
a b c d
A é uma entrada para (A,a) na tabela sse:– Se A e a Primeiro() – Se A, Primeiro() e a Seguinte(A)
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 ] | idid Primeiro(V[E])
26
Eliminando recursão à Esquerda• E → E+T | E-T | T
Podemos escrever sem recursão à esquerda, assimE → T E'E'→ + T E' |
Em geral, se temosA → A1 | A2 |...| An
| 1 | 2 |...| m
EscrevemosA → 1A' | 2A' |...| mA'A' → 1A' | 2A' |...| nA' |
Dificuldades futuras Árvore de derivação associativa à direita Geração de árvore abstrata exige tratamento especial
27
Tabela preditiva da gramática de expressões anterior
S → E $E → T E'E'→ + T E' |
28
Tirando em evidência (fatoração) à esquerda
• A → | |...podemos escrever
A → A' |... A' → |
Por exemploD → int Id ; | int Id ( A ) ; | outro
pode ser re-escrito comoD → int Id D' | outroD' → ; | ( A ) ;
Pequena dificuldade: A estrutura gramatical foi mudada
29
Exemplos de fatoração
E id E' | num
E' (E) | [e] |
E id(E) |id[e] |id |num
30
• S if E then S else S | if E then S | outro
Pode ser escrito comoS if E then S X
| outroX 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:
Xelse 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.
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
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
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