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}; P: S AB A aA | a B bB | b As relações cabeça existentes são: Cabeça (S) = {A} Cabeça (A) = {a} Cabeça (B) = {b}
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}
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 xb) onde a Î (Vn U Vt); b Î (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
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
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}
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 aAg e x Î Firstk (g )} onde A Î Vn; x Î Vt*; a,g Î (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 aAab para algum a e b. Regras para se determinar o conjunto Follow1 (B), para todo B e Vn: 1 - Se existe uma produção A ® aBb, b¹ l, então tudo o que estiver em First1 (b), exceto l, está em Follow1 (B); 2 - Se existe uma produção A ® aB, ou uma produção A ® aBb e l e First1 (b), (isto é, b deriva l), então tudo o que estiver em Follow1 (A) está em Follow1 (B);
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) = {+, ) }
Análise Sintática - Introdução Analisador Sintático Árvore Gramatical Lista de tokens Analisador Sintático token Árvore Gramatical Obter próximo token
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.
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.
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.
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
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
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;
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.