Transcript sintaticaI

Relações em uma Gramática
Relação Cabeça (Head)
É uma das mais simples de identificar e seu nome deve-se ao fato de
que um de seus elementos é a cabeça do lado direito de uma regra.
Cabeça: Vn  (Vt U Vn)
onde  = eh formado por
Cabeça (A): {  | A   é uma regra de produção de G}
onde A  Vn;   (Vn U Vt);   (Vn U Vt)* onde * = eh regra de producao
Exemplo: Seja a gramática G = (Vn, Vt, P, S) onde Vn = {S,A,B} e
Vt = {a,b};
As relações cabeça existentes são:
P: S  AB
Cabeça (S) = {A}
A  aA | a
Cabeça (A) = {a}
B  bB | b
Cabeça (B) = {b}
Construção de Compiladores 1
Relações em uma Gramática
Relação Último (Last)
Último: Vn  (Vn U Vt)
Último (A) = { | A    é uma regra de produção de G}
onde A  Vn
  (Vn U Vt)
 (Vn U Vt)*
Intuitivamente, a relação último relaciona um dado não terminal,
existente do lado esquerdo de uma certa regra, com o último
elemento que aparece do lado direito desta regra.
No exemplo anterior: Último(S) = {B}; Último(A) = {A, a};
Último(B) = {B, b}
Construção de Compiladores 1
Relações em uma Gramática
Relação First()
A relação Firstk é uma parente próxima da relação Cabeça. É definida
para k e N como sendo:
Firstk (a) = {x  Vt* | (|x| < k e a deriva x) ou (|x| = k e a deriva x)
onde a  (Vn U Vt);   (Vn U Vt)*; x  Vt*
A relação Firstk(a) de uma gramática consiste de todas as cadeias de
terminais de tamanho k, que são geradas a partir de a, numa
seqüência de 0 ou mais produções.
Se a deriva l, então l também está no conjunto Firstk(a). No
exemplo anterior:
First1 (S) = {a}, First1(A) = {a} e First1 (B) = b
Construção de Compiladores 1
Relações em uma Gramática
Regras para determinar o conjunto First1 (x), para todo símbolo e
(Vn U Vt):
1 - Se x é terminal, First1(x) = {x}, pois x deriva x;
2 - Se x é não terminal e x  aa é uma produção, então acrescente a a
First1(x). Se x  l é uma produção, acrescente l a First1(x);
3 - Se x  Y1Y2Y3...Yk é uma produção, então para todo i tal que todos
Yi...Yi-1 são não terminais e First1(Yj) contém l, para j = 1,2,...,i-1 (isto
é, Y1Y2...Yi-1 l) acrescente todo símbolo diferente de l de First1(Yi) em
First1(x). Se l First1(Yj) para todo j = 1,2,...,k, então acrescente l a
First1 (x);
Para calcular First1(x1x2..xn) como segue: adicione a First1(x1x2..xn)
todos símbolos diferentes de l de First1(x1). Se l First1(x1) acrescente também
todos símbolos diferentes de lde First1(x2); se l First1(x2) então acrescente
também os símbolos diferentes de l de First1(x3) e assim por diante
Construção de Compiladores 1
Relações em uma Gramática
Exemplo:
G:
E  TE'
E'  +TE' | l
T  FT'
T'  *FT' | l
T  (E) | id
onde Vn = {E,T,F,E',T'}
Vt = {(,),id,+,*}
S=E
Temos: First (E) = First (TE') = First (FT'E') = {(,id}
First (E') = {+, l}
First (T') = {*, l}
Construção de Compiladores 1
Relações em uma Gramática
Relação Seguidork (Follow)
Seja G = (Vn, Vt, P, S) definimos a relação Follow como:
Followk (A) = {x e Vt* | (S deriva aA e x  Firstk ( )}
onde A  Vn; x  Vt*; a, (Vn U Vt)*
Assim, o conjunto Follow1 (A) é o conjunto dos terminais a que podem
aparecer imediatamente à direita de A em alguma forma sentencial ("
estágio da geração de uma sentença constitui uma forma sentencial), tal
que S deriva aAa para algum a e .
Regras para se determinar o conjunto Follow1 (B), para todo B e Vn:
1 - Se existe uma produção A a,l, então tudo o que estiver em First1 (),
exceto l, está em Follow1 (B);
2 - Se existe uma produção A a, ou uma produção A ae leFirst1 (),
(isto é, deriva l), então tudo o que estiver em Follow1 (A) está em Follow1 (B);
Construção de Compiladores 1
Relações em uma Gramática
Exemplo:
Considere a gramática do exemplo do First:
Então: Follow1 (E) = { ) }
Follow1 (T') = First1 (E') = {+} U Follow1 (E') = {+} U Follow1 (E) =
{+,)}
Follow1 (F) = First1 (T') = {*} U Follow1 (T') = {*} U Follow1 (T) =
{*,+,)}
Follow1 (E') = Follow1 (E) = { ) }
Follow1 (T') = Follow1 (T) = {+, ) }
Construção de Compiladores 1
Análise Sintática - Introdução
Lista de
tokens
token
Obter próximo
token
Analisador
Sintático
Árvore
Analisador
Sintático
Árvore
Construção de Compiladores 1
Gramatical
Gramatical
Análise Sintática - Introdução
 Funções do Analisador Sintático:
- comprovar que a sequência de tokens cumpre as regras
gramaticais;
- gerar a árvore gramatical.
Vantagens de utilizar gramáticas:
- especificações sintáticas precisas de linguagens;
- podemos usar um gerador automático de parser;
- o processo de construção pode levar a identificar
ambiguidades;
- facilidade de ampliar/modificar a linguagem.
Construção de Compiladores 1
Papel dos Analisadores Sintáticos
 Identificar erros de Sintaxe:
A * / B;
 Tornar clara a estrutura hierárquica da evolução da
sentença:
A/B*C
(A/B) * C em Fortran
A / (B*C) em APL
 Recuperação de erros de sintaxe;
 Não retardar, de forma significativa, o processamento
de programas corretos.
Construção de Compiladores 1
Observações
60% dos programas compilados corretos sintaticamente
e semanticamente;
80% dos enunciados com erros apresentam apenas um
erro;
13% dos enunciados com erros apresentam apenas dois
erros;
90% dos erros envolvem um único token.
Construção de Compiladores 1
Analisadores Sintáticos - Tipos
 Métodos de Cocke-Younger-Kasami e Early:
 universais: servem para qualquer gramática (bem eficientes)
 Métodos Descendentes (Top Down): constroem a árvore
sintática de cima para baixo
 Analisadores Descendentes Recursivos
 Analisadores LL(k)
 Métodos Ascendentes (Bottom-up):constroem a árvore
sintática de baixo para cima
 Analisadores SR
 Analisadores LR
 Analisadores LALR
Construção de Compiladores 1
Analisadores Sintáticos - Tipos
Tanto para a análise ascendente, quanto para a descendente:
- a entrada é examinada da esquerda para a direita, um
símbolo por vez;
- trabalham com subclasses de gramáticas.
 Em geral, as gramáticas são LL e LR
- na prática, utilizam-se LL(1) e LR(1)
 Muitos compiladores são dirigidos pela sintaxe (parsen driver),
onde o analisador sintático chama o analisador léxico
 Há ferramentas para geração automática de analisadores
sintáticos, como o Yacc e o Bison
Construção de Compiladores 1
Recuperação de Erros
 Desespero: identificado um erro, o analisador sintático descarta
símbolos de entrada, até que seja encontrado um token pertencente
ao subconjunto de tokens de sincronização;
- tokens de sincronização: delimitadores etc.
 Recuperação de Frases: ao descobrir um erro, o analisador
sintático pode realizar uma correção local na entrada restante
(substituir por alguma cadeia que permita a análise prosseguir)
- exemplo: substituir uma vírgula inadequada por um ponto e
vírgula, remover um “:” excendente;
Construção de Compiladores 1
Recuperação de Erros
 Produções de Erro: aumenta-se a gramática, de forma a
acomodar os erros mais comuns. Quando uma produção de erro é
identificada pelo analisador, diagnósticos apropriados são
apresentados;
 Correção Global: usa algoritmos de escolha da sequência
mínima de mudanças necessárias para se obter a correção global,
com custo adequado.
- exemplo: dada uma cadeia x, o parser procura árvores gramaticais
que permitam transformar x em y (cadeia correta) com um mínimo
de modificações.
Construção de Compiladores 1