Capítulo V Análise Sintática

Slides:



Advertisements
Apresentações semelhantes
ADT – Arvore Binária de Pesquisa
Advertisements

2008 LCG/UFRJ. All rights reserved. 1 Standard Template Library STL Claudio Esperança Paulo Roma Cavalcanti.
Construção de Compiladores
Algoritmos em Grafos Celso C. Ribeiro Caroline T. Rocha.
PROGRESSÃO ARITMÉTICA P.A.
Prof. Celso Gramática.
Prof. Celso Gramática Prof. Celso
Prof. Celso Gramática Prof. Celso
Prof. Celso Gramática.
Prof. Celso Gramática Prof. Celso
Método de diferenças finitas para problemas lineares
FORTRAN 90 Denise Yumi Takamura.
Introdução à Consulta 24/3/06. Categorias de comandos DDL : estruturação de objetos de BD Ex. create table, drop index, alter table... DML: manipulação.
Funções de Linha 24/04/06. Funções de Linha Gerar ou construir novo valor não existente na tabela inicial. Valor construído a partir de dados de uma ou.
VI: EQUILÍBRIO RADIATIVO
Prof. Dr. Helder Anibal Hermini
Prototipação de um Interpretador para IMP com ML Juliana Vizzotto Júlio Machado Fevereiro 2002.
Criptografia Quântica : Um Estudo
Análise e Solução de Problemas
Ilusão de Ótica O termo Ilusão de óptica aplica-se a todas ilusões que «enganam» o sistema visual humano fazendo-nos ver qualquer coisa que não está presente.
Profa. Graziela Santos de Araújo Algoritmos e Programação II, 2010
Geração de Código Otimizações independentes de máquina.
Geração de Código Cap. 8. Introdução Fase final para um compilador Entrada é uma representação intermediária do código fonte e a saída é um programa para.
1 Tradução Dirigida à Sintaxe Prof. Ricardo Santos.
Banco de Dados I Profa. Jiani Cardoso 2/2005
Planejando seu site Objetivos Abordagem Sílvia Dota.
Matemática e Educação Ambiental.
Capitulo 3 Técnicas de Diagnóstico CT-282 Tutores Inteligentes Diego Ajukas.
Adriana da Silva Jacinto CT-282 Prof. Dr.Parente Técnicas de diagnóstico 1º semestre 2005.
Aula II A tutela cautelar.. Onde encontrar? CPC – arts. 796 a 889.
CE-262 Ontologia e Web Semântica Prof. José M Parente de Oliveira
CES-11 LAB 03 Bitmap Quadtree
CES-41 COMPILADORES Capítulo IV Complementos de Análise Léxica.
Capítulo VII – Tipos Enumerativos e Estruturas 7.1 – Tipos enumerativos 7.2 – A necessidade de estruturas 7.3 – Manipulação dos campos de uma estrutura.
CES-10 INTRODUÇÃO À COMPUTAÇÃO Aulas Práticas – 2013 Capítulo III Comandos de Controle.
1.3 – Interpretadores – Compiladores versus Interpretadores
CES-41 COMPILADORES Capítulo VII Código Intermediário.
CES-41 COMPILADORES Aulas Práticas
Capítulo VI – Variáveis Indexadas
CES-10 INTRODUÇÃO À COMPUTAÇÃO Aulas Práticas – 2013 Capítulo XI Encadeamento de Estruturas por Ponteiros.
Capítulo IX – Ponteiros 9.1 – Introdução 9.2 – Relação entre ponteiros e variáveis indexadas 9.3 – Alocação dinâmica de memória 9.4 – Variáveis indexadas,
Capítulo III Diagramas de Transições
CE-262 Ontologia e Web Semântica Prof. José M Parente de Oliveira Sala 120 – Prédio da Computação Lógica de Descrições.
CES-41 COMPILADORES Capítulo I Introdução.
5.5.4 – Métodos para a construção da tabela de análise LR
A Árvore dos meus COLEGAS e por que não, AMIGOS..
Já definimos o coeficiente angular de uma curva y = f(x) no ponto onde x = x 0. Derivadas Chamamos esse limite, quando ele existia, de derivada de f em.
Teorema do Confronto Se não pudermos obter o limite diretamente, talvez possamos obtê-lo indiretamente com o teorema do confronto. O teorema se refere.
Interpolação Introdução Conceito de Interpolação
Resolução de Sistemas Não-Lineares- Parte 1
Sistemas Lineares Parte 2
Resolução de Sistemas Lineares- Parte 1
Desempenho A rápida taxa de melhoria na tecnologia de computadores veio em decorrência de dois fatores: avanços na tecnologia utilizada na construção.
7. INTEGRAÇÃO NUMÉRICA Parte 1
Prof. Giovanny Lucero Introdução Prof. Giovanny Lucero
Linguagens Formais e Tradutores Análise Sintática - 1
Linguagens Formais e Tradutores Linguagens Regulares (Revisão)
Tecnologia para Web JavaScript Enrique Pimentel Leite de Oliveira
Recursividade Estrutura de Dados.
Marco Antonio Montebello Júnior
Prof: Graciano Notação Científica.
PROTEÍNAS.
LINGUAGENS DE PROGRAMAÇÃO
Baseado no documento do Prof. Ronaldo Martins da Costa
Computação Gráfica Geometria de Transformações
Introdução Ciência da Computação estudo de algoritmos –ÊNFASE ao estudo de DADOS armazenamento manipulação refinamento (a partir de dados cru) estrutura.
8. Uma Função de duas Variáveis Aleatórias
Análise Sintática – Parte 1
Capítulo II Gramáticas e Linguagens

Transcrição da apresentação:

Capítulo V Análise Sintática CES-41 COMPILADORES Capítulo V Análise Sintática

Capítulo V – Análise Sintática 5.1 – Classificação dos analisadores sintáticos 5.2 – Tratamento de gramáticas 5.3 – Escolha de uma derivação 5.4 – Análise top-down 5.5 – Análise bottom-up 5.6 – Complementos de Yacc

5.1 – Classificação dos Analisadores Sintáticos Papel do analisador sintático (visto anteriormente): Verificar a estrutura sintática de um programa Detectar, sinalizar e tratar erros sintáticos Servir de esqueleto para o front-end

Classes de métodos de análise sintática: Analisadores universais Analisadores top-down Analisadores bottom-up Analisadores universais: Conseguem analisar qualquer GLC São muito ineficientes para uso em compiladores

Analisadores top-down e bottom-up: Varrem o programa da esquerda para a direita, um símbolo de cada vez Englobam os métodos mais comumente usados em compiladores Os métodos mais eficientes trabalham apenas com subclasses de gramáticas Várias dessas subclasses (gramáticas LL e LR, descritas mais adiante) conseguem descrever a maioria das construções sintáticas das linguagens de programação

Analisadores top-down: Caminham pela árvore sintática dos programas, da raiz para as folhas Trabalham com gramáticas LL (Left-Left) Englobam os métodos preditores, dentre os quais, os diagramas de transições As implementações manuais utilizam métodos top-down

Analisadores bottom-up: Caminham pela árvore sintática dos programas, das folhas para a raiz Trabalham com gramáticas LR (Left-Right) Englobam importantes métodos: Análise de precedência de operadores Análise LR São muito usados na geração automática de analisadores sintáticos

5.2 – Tratamento de Gramáticas Métodos de análise sintática se aplicam a determinadas classes de GLC’s Algumas gramáticas devem ser alteradas para que um método possa ser aplicado É a busca por uma GLC equivalente à original Duas GLC’s são equivalentes quando geram a mesma linguagem

5.2.1 – Eliminação de ambiguidades Alguns métodos de análise sintática exigem que a gramática seja não-ambígua Outros métodos mantém a ambiguidade das gramáticas e usam regras para eliminar árvores sintáticas indesejáveis

Exemplo: comando IF-ELSE da Linguagem C Produções típicas: Cmd  IF Expr Cmd | IF Expr Cmd ELSE Cmd | c1 | c2 A sentença: IF e1 IF e2 c1 ELSE c2 tem duas árvores sintáticas:

Esta regra pode ser incorporada à gramática Gramática equivalente: A regra usual para resolver a ambiguidade é fazer o ELSE corresponder ao segundo IF Esta regra pode ser incorporada à gramática Gramática equivalente: Cmd  CmdCasado | CmdNãoCasado CmdCasado  IF Expr CmdCasado ELSE CmdCasado | c1 | c2 CmdNãoCasado  IF Expr Cmd | IF Expr CmdCasado ELSE CmdNãoCasado

CmdCasado nunca produz IF sem ELSE Cmd  CmdCasado | CmdNãoCasado CmdCasado  IF Expr CmdCasado ELSE CmdCasado | c1 | c2 CmdNãoCasado  IF Expr Cmd | IF Expr CmdCasado ELSE CmdNãoCasado CmdCasado nunca produz IF sem ELSE Esta gramática impede o aparecimento de comandos não-casados entre Expr e ELSE

Essa nova gramática dificulta análise preditora: Cmd  CmdCasado | CmdNãoCasado CmdCasado  IF Expr CmdCasado ELSE CmdCasado | c1 | c2 CmdNãoCasado  IF Expr Cmd | IF Expr CmdCasado ELSE CmdNãoCasado Essa nova gramática dificulta análise preditora: Em Cmd, se aparecer um IF, qual das duas produções tomar? Cada tipo de ambiguidade deve ser eliminada mediante um tratamento particular

5.2.2 – Eliminação de recursividade à esquerda Uma GLC é recursiva à esquerda, se tiver um não-terminal A tal que haja uma derivação A + A, onde   (N  )* Os métodos de análise top-down não conseguem trabalhar com gramáticas recursivas à esquerda

Exemplo: sejam as produções Expr  Term  Expr + Term Num método top-down típico, há um procedimento para cada um dos não-terminais Expr e Term: Expr ( ) { if ( func (átomos analisados e/ou futuros) == TRUE ) { Expr ( ); if (atom != ‘+’) erro (“+ esperado”) else Term ( ); }

Expr ( ) { if ( func (átomos analisados e/ou futuros) == TRUE ) { Expr ( ); if (atom != ‘+’) erro (“+ esperado”) else Term ( ); } Caso func (átomos analisados e/ou futuros) seja TRUE, na chamada interna de Expr, os átomos serão os mesmos O valor de func se repete, resultando em infinitas chamadas recursivas

Para aplicar um método top-down, toda recursividade à esquerda deve ser eliminada da gramática Há recursividade imediata e não-imediata à esquerda Exemplos: Imediata: S  S b S  a Não-imediata: S  Aa  b , A  Sc  d

(,   (N  )* e não iniciam com A) a) Recursividade imediata à esquerda: É o caso de uma produção ser recursiva à esquerda Um caso bem simples é o das seguintes produções: A  A   (,   (N  )* e não iniciam com A) Estas produções podem ser substituídas pelas seguintes: A  R R  R  ε que geram as mesmas formas sentenciais que as originais, conforme o próximo slide

{A  A  } {A  R R  R  ε} Resolve muitos casos, mas não é geral

Transformação da gramática: Exemplo: seja a seguinte gramática, com duas produções contendo recursividade imediata à esquerda: Expr  Expr + Termo  Termo Termo  Termo * Fator  Fator Fator  ( Expr )  ID Transformação da gramática: Expr  Termo Eaux Eaux  + Termo Eaux  ε Termo  Fator Taux Taux  * Fator Taux  ε Fator  ( Expr ) | ID {A  A  } {A  R R  R  ε} Nas produções de Expr, substituindo: A por Expr  por + Termo  por Termo R por Eaux De modo análogo, na produção de Termo

Generalização: várias produções do mesmo não-terminal, com recursividade a esquerda Algoritmo 5.1: As seguintes produções: A  A 1  A 2  - - -  A m  1  2  - - -  n Podem ser substituídas por: A  1 A'  2 A'  - - -  n A' A'  1 A'  2 A'  - - -  m A'  ε Nenhum i pode ser ε

b) Recursividade não-imediata à esquerda: O algoritmo a seguir elimina toda recursividade à esquerda de uma gramática Condições da gramática: não deve ter ciclos nem produções vazias Gramática sem ciclos: sem derivações da forma A + A Gramática sem produções vazias: a única produção vazia permitida é S   (S: símbolo inicial) e S não aparece do lado direito de qualquer produção Existem algoritmos para eliminar ciclos e produções vazias de GLC’s

Algoritmo 5.2: eliminação de recursividade à esquerda Arranjar os não-terminais numa ordem A1, A2, ... , An Para (i = 1; i <= n; i++) { Para (j = 1; j <= i-1; j++) { Sendo Aj  1  2  - - - m as produções de Aj, no momento, Substituir cada produção da forma Ai  Aj  pelas produções Ai  1   2   - - - m  } Eliminar as recursividades imediatas a esquerda entre as produções de Ai;

Exemplo: Eliminar as recursividades à esquerda da gramática: S  S a  T b  c T  S d  U e  f U  S g  U h  i Renomeando os não-terminais: S = A1, T = A2, U = A3 A gramática torna-se em: A1  A1 a  A2 b  c A2  A1 d  A3 e  f A3  A1 g  A3 h  i

Eliminação das recursividades imediatas à esquerda de A1: Para i = 1: Eliminação das recursividades imediatas à esquerda de A1: A1  A2 b X  c X X  a X  ε Algoritmo 5.1 A  A1  1  2 A  1 A'  2 A' A'  1 A'  ε A1  A2 b X  c X X  a X  ε A2  A1 d  A3 e  f A3  A1 g  A3 h  i Novo estado das produções

Produções de A1 no momento: A1  A2 b X  c X Para i = 2, j = 1: Produções de A2: A2  A1 d  A3 e  f Produções de A1 no momento: A1  A2 b X  c X Substitui-se A1 d por A2 b X d  c X d : Produção da forma A2  A1 

Eliminação das recursividades imediatas à esquerda de A2: Para i = 2: Eliminação das recursividades imediatas à esquerda de A2: Algoritmo 5.1 A  A1  1  2  3 A  1 A'  2 A'  3 A' A'  1 A'  ε A2  c X d Y  A3 e Y  f Y Y  b X d Y  ε A1  A2 b X  c X X  a X  ε A2  c X d Y  A3 e Y  f Y Y  b X d Y  ε A3  A1 g  A3 h  i Novo estado das produções

Produções de A1 no momento: A1  A2 b X  c X Para i = 3, j = 1: Produções de A3: A3  A1 g  A3 h  i Produções de A1 no momento: A1  A2 b X  c X Substitui-se A1 g por A2 b X g  c X g : A3  A2 b X g  c X g  A3 h  i Produção da forma A3  A1 

c X d Y b X g  A3 e Y b X g  f Y b X g : Para i = 3, j = 2: Produções de A3: A3  A2 b X g  c X g  A3 h  i Produções de A2 no momento: A2  c X d Y  A3 e Y  f Y Substitui-se A2 b X g por c X d Y b X g  A3 e Y b X g  f Y b X g : Produção da forma A3  A2 

Eliminação das recursividades imediatas à esquerda de A3: Para i = 3: Eliminação das recursividades imediatas à esquerda de A3: A3  c X d Y b X g Z  f Y b X g Z  c X g Z  i Z Z  e Y b X g Z  h Z  ε A  A1  A2  1  2  3  4 A  1 A'  2 A'  3 A'  4 A' A'  1 A'  2 A'  ε Algoritmo 5.1

Novo estado das produções: A1  A2 b X  c X X  a X  ε A2  c X d Y  A3 e Y  f Y Y  b X d Y  ε A3  c X d Y b X g Z  f Y b X g Z  c X g Z  i Z Z  e Y b X g Z  h Z  ε

Restaurando os nomes originais dos não-terminais: A1  A2 b X  c X A2  c X d Y  A3 e Y  f Y A3  c X d Y b X g Z  f Y b X g Z  c X g Z  i Z X  a X  ε Y  b X d Y  ε Z  e Y b X g Z  h Z  ε Restaurando os nomes originais dos não-terminais: S  T b X  c X T  c X d Y  U e Y  f Y U  c X d Y b X g Z  f Y b X g Z  c X g Z  i Z S = A1, T = A2, U = A3 Não há mais recursividade a esquerda

5.2.3 – Fatoração à esquerda Sejam as seguintes produções do não-terminal A: A       onde , ,   (N  )* e o primeiro símbolo de  é diferente do primeiro símbolo de  Para um método preditor, não é claro qual produção usar para expandir A Pode-se atrasar a decisão, aplicando nesta gramática a seguinte transformação: A   A’ , A’    

A        A   A’ , A’     Isso ainda não garante a possibilidade de aplicação de um método preditor O primeiro símbolo de  e  pode ser um não- terminal, permanecendo a indecisão Fatoração é apenas um passo para adequar gramáticas a um método preditor

5.3 – Escolha de uma Derivação Mesmo numa GLC não-ambígua, a sequência de derivações diretas para produzir uma sentença pode variar Exemplo: seja a gramática S  ε | S ( S ) A sentença ( ) pode ser gerada pelas seguintes derivações: S  S(S)  (S)  () S  S(S)  S()  () Essas derivações diferem no trecho marcado

S(S) me (S) e S(S) md S() S  S(S)  (S)  () S  S(S)  S()  () Na primeira, escolheu-se o não-terminal S mais à esquerda para ser expandido Na segunda, foi escolhido o não-terminal S mais à direita Simbolicamente S(S) me (S) e S(S) md S() Ou seja, S(S) deriva diretamente mais à esquerda (S) S(S) deriva diretamente mais à direita S()

Derivação mais à esquerda ou mais à direita: quando todas as derivações diretas forem me ou md Simbolicamente: *me ou *md No exemplo anterior { S *me ( ) } = { S  S(S)  (S)  ( ) } { S *md ( ) } = { S  S(S)  S( )  ( ) }

Qualquer árvore sintática tem uma única derivação mais à esquerda e uma única derivação mais à direita Gramática ambígua – outra definição: produz mais de uma derivação mais à esquerda ou mais à direita para pelo menos uma de suas sentenças Os analisadores sintáticos mais conhecidos analisam derivações mais à esquerda ou mais à direita dos programas

Exemplo: Produção de derivações mais a esquerda da gramática: E  T | T opad E T  F | F opmult T F  id | cte | ( E ) Derivação mais à esquerda para a expressão: ( x + y ) * 10 / ( n – 2 )

( x + y ) * 10 / ( n – 2 ) E  T  F opmult T  ( E ) opmult T  ( T opad E ) opmult T  ( F opad E ) opmult T  ( id opad E ) opmult T  ( id opad T ) opmult T  ( id opad F ) opmult T  ( id opad id ) opmult T  ( id opad id ) opmult F opmult T  ( id opad id ) opmult cte opmult T  ( id opad id ) opmult cte opmult F  ( id opad id ) opmult cte opmult ( E )  ( id opad id ) opmult cte opmult ( T opad E )  ( id opad id ) opmult cte opmult ( F opad E )  ( id opad id ) opmult cte opmult ( id opad E )  ( id opad id ) opmult cte opmult ( id opad T )  ( id opad id ) opmult cte opmult ( id opad F )  ( id opad id ) opmult cte opmult ( id opad cte ) ( x + y ) * 10 / ( n – 2 ) Analisadores preditores examinam derivações mais a esquerda

Exemplo: Produção de derivações mais a direita da gramática: E  T | E opad T T  F | T opmult F F  id | cte | ( E ) Derivação mais à direita para a expressão: ( x + y ) * 10 / ( n – 2 )

E  T  T opmult F  T opmult ( E )  T opmult ( E opad T )  T opmult ( E opad F )  T opmult ( E opad cte )  T opmult ( T opad cte )  T opmult ( F opad cte )  T opmult ( id opad cte )  T opmult F opmult ( id opad cte )  T opmult cte opmult ( id opad cte )  F opmult cte opmult ( id opad cte )  ( E ) opmult cte opmult ( id opad cte )  ( E opad T ) opmult cte opmult ( id opad cte )  ( E opad F ) opmult cte opmult ( id opad cte )  ( E opad id ) opmult cte opmult ( id opad cte )  ( T opad id ) opmult cte opmult ( id opad cte )  ( F opad id ) opmult cte opmult ( id opad cte )  ( id opad id ) opmult cte opmult ( id opad cte ) ( x + y ) * 10 / ( n – 2 ) Analisadores bottom-up examinam derivações mais a direita reversas

( E ) opmult cte opmult ( id opad cte )  ( x + y ) * 10 / ( n – 2 ) ( id opad id ) opmult cte opmult ( id opad cte )  ( F opad id ) opmult cte opmult ( id opad cte )  ( T opad id ) opmult cte opmult ( id opad cte )  ( E opad id ) opmult cte opmult ( id opad cte )  ( E opad F ) opmult cte opmult ( id opad cte )  ( E opad T ) opmult cte opmult ( id opad cte )  ( E ) opmult cte opmult ( id opad cte )  F opmult cte opmult ( id opad cte )  T opmult cte opmult ( id opad cte )  T opmult F opmult ( id opad cte )  T opmult ( id opad cte )  T opmult ( F opad cte )  T opmult ( T opad cte )  T opmult ( E opad cte )  T opmult ( E opad F )  T opmult ( E opad T )  T opmult ( E )  T opmult F  T  E Em negrito, lados direitos a serem reduzidos

5.4 – Análise Top-Down Tentativa de construir uma árvore sintática para a sentença analisada, começando da raiz, indo em direção às folhas e criando os nós em pré-ordem Exemplo:

5.4 – Análise Top-Down Tentativa de construir uma árvore sintática para a sentença analisada, começando da raiz, indo em direção às folhas e criando os nós em pré-ordem Também: tentativa de achar uma derivação mais à esquerda para a sentença ou o programa analisado Análise preditora: os átomos são analisados linearmente da esquerda para a direita, sem a necessidade de observar átomos futuros e de caminhar de volta, da direita para a esquerda, sobre a sequência de átomos

5.4.1 – Análise backtracking Por vezes, a produção escolhida para produzir uma derivação direta mais à esquerda não leva à árvore sintática da sentença analisada Então, o analisador é obrigado a voltar para a esquerda na sequência de átomos e a eliminar uma subárvore da árvore em construção, substituindo-a por outra Exemplo: seja a gramática S  a A d  a B A  b  c B  ccd  ddc A seguir, análise da sentença: w = a c c d

S w = a c c d S  a A d  a B A  b  c B  ccd  ddc 1) Início: Árvore contendo somente o símbolo inicial Cursor na raiz da árvore Cursor no primeiro átomo da sentença S w = a c c d

Os cursores se casam: a = a S  a A d  a B A  b  c B  ccd  ddc 2) 1a produção para expandir S Liga-se S ao átomo da sentença apontado pelo cursor, pois é diante dele que ocorreu a expansão Cursor da árvore avança em pré-ordem S a A d w = a c c d Os cursores se casam: a = a

S a A d w = a c c d S  a A d  a B A  b  c B  ccd  ddc 3) Os dois cursores avançam S a A d w = a c c d

Os cursores não se casam: b ≠ c S  a A d  a B A  b  c B  ccd  ddc 4) 1a produção para expandir A Liga-se A ao átomo da sentença apontado pelo cursor, pois é diante dele que ocorreu a expansão Cursor da árvore avança em pré-ordem S a A d b w = a c c d Os cursores não se casam: b ≠ c

Os cursores se casam: c = c S  a A d  a B A  b  c B  ccd  ddc 5) Elimina-se a subárvore da expansão anterior 2a produção para expandir A Cursor da sentença retrocede para onde A foi expandido, ou seja, permanece onde está: Cursor da árvore avança em pré-ordem S a A d b c w = a c c d Os cursores se casam: c = c

Os cursores não se casam: d ≠ c S  a A d  a B A  b  c B  ccd  ddc 6) Os dois cursores avançam S a A d c w = a c c d Os cursores não se casam: d ≠ c

Os cursores se casam: a = a S  a A d  a B A  b  c B  ccd  ddc 7) Elimina-se a subárvore da expansão anterior Não existe outra produção para expandir A Elimina-se também a subárvore da expansão imediatamente anterior, ou seja, a de S Cursor da sentença retrocede até ao ponto em que S foi expandido 2a produção para expandir S O cursor da árvore avança S a A d c a B w = a c c d Os cursores se casam: a = a

S a B c c d w = a c c d S  a A d  a B A  b  c B  ccd  ddc 8) Os cursores avançam 1a produção para expandir B Cursor da árvore avança Casamento nos três últimos movimentos dos cursores: c c d = c c d A sentença é reconhecida w = a c c d

Só se chega à conclusão de que há erro na sentença, quando não houver mais opções para expandir o não-terminal da raiz Como detectar e tratar erros? No pior caso (erro na sentença), o tempo gasto na análise cresce exponencialmente com tamanho da sentença E se a gramática tivesse recursividade à esquerda? Exemplo: S  b  S b O uso de backtracking é raramente necessário para analisar construções de linguagens de programação

Análise sem backtracking: no momento de expansão escolhe a produção correta, dispensando tentativas desnecessárias Nem todas as GLC’s podem ser analisadas sem backtracking, mas, na prática, só ela é usada Existe análise sem backtracking em que o número de átomos necessários para a tomada de decisão é maior que 1 Ou seja, existem métodos sem backtracking também não preditores

5.4.2 – Gramáticas LL(k) e LL(1) Gramáticas LL(k): admitem analisadores sem back-tracking que: Analisam as sentenças da esquerda para a direita (o primeiro L - left) Produzem derivações mais à esquerda (o segundo L) Precisam analisar no máximo, os próximos k símbolos, para decidir que produção usar Gramáticas LL(1): gramáticas LL(k) em que k = 1

Métodos preditores só analisam gramáticas LL(1) Nesta disciplina serão abordados somente métodos preditores Livro que aborda com detalhes muitos outros métodos: Aho, A.V., Ullman, J.D. – The Theory of Parsing, Translation and Compiling – Prentice-Hall – 1972 É o primeiro Livro do Dragão – hoje são três

Espectro dos métodos top-down: Backtracking Sem Backtracking Não Preditores Preditores

5.4.3 – Análise preditora recursiva É uma análise preditora estruturada num conjunto de subprogramas recursivos O método dos diagramas de transições é preditor recursivo Outro método: também orienta-se por uma gramática fatorada à esquerda, mas dispensa os diagramas de transições

Exemplo: seja a gramática Expressão  Termo | Termo OPAD Expressão Termo  Fator | Fator OPMULT Termo Fator  ID | CTE | ( Expressão ) | OPNEG Fator Fatorando: Expressão  Termo Eaux Eaux  OPAD Expressão | ε Termo  Fator Taux Taux  OPMULT Termo | ε A seguir: Esquema de programa analisador sintático em C

Programa principal: void main () { printf ("Digite a expressao:\n\n"); gets (expressao); printf ("\n"); ptexpr = 0; carac = NovoCarac (); NovoAtomo (); Expressao (); if (atom.tipo != ENDOFFILE) Esperado (ENDOFFILE); }

Expressão  Termo Eaux Eaux  OPAD Expressão | ε void Expressao () { Termo (); Eaux (); } void Eaux () { if (atom.tipo == OPAD) { NovoAtomo (); Expressao ();

Termo  Fator Taux Taux  OPMULT Termo | ε void Termo () { Fator (); Taux (); } void Taux () { if (atom.tipo == OPMULT) { NovoAtomo (); Termo ();

Fator  ID | CTE | ( Expressão ) | OPNEG Fator void Fator () { switch (atom.tipo) { case CTE: NovoAtomo (); break; case ID: NovoAtomo (); break; case ABPAR: NovoAtomo (); Expressao (); if (atom.tipo != FPAR) Esperado (FPAR); NovoAtomo (); break; case OPNEG: NovoAtomo (); Fator (); break; default: NaoEsperado (atom.tipo); }

5.4.4 – Análise preditora não-recursiva Para melhor desempenho, é conveniente trocar uma solução recursiva por outra não-recursiva, usando pilha Chamadas de subprogramas são consumidoras de tempo Chamadas recursivas numa análise preditora podem ser substituídas por empilhamentos e por consultas a uma tabela de produções

Um analisador não-recursivo, diante de um átomo da sentença e de um não-terminal a ser expandido, no topo da pilha, consulta uma tabela de produções para saber como expandi-lo Os símbolos do lado direito da produção escolhida são então empilhados no lugar desse não-terminal A próxima figura ilustra o esquema de um analisador sintático preditor não-recursivo

Pilha: local para fazer expansões de não-terminais Tabela de produções: matriz bidimensional M para indicar a produção a ser usada para expandir um não-terminal, diante de um átomo na entrada

Se X = a = $: encerra com sucesso Funcionamento: seja X o elemento do topo da pilha e a o átomo em análise  Se X = a = $: encerra com sucesso Se X = a  $: desempilha X e avança na entrada Se X  a e ambos são terminais: erro Se X é um não terminal: consulta a entrada M[X, a] da tabela M M[X, a] X a Se M[X,a] for um erro: Acionar tratamento de erro Se, por exemplo, M[X,a] for a produção XYZW: Desempilhar X Empilhar W, Z, Y, com Y no topo M[X, a] pode ser uma produção de X ou um erro

A saída pode ser a produção usada Algoritmo 5.3: Análise preditora não recursiva Empilhar ($); Empilhar (S) /* S é o símbolo inicial */ ; w  sentença || $; p  &(w[0]); Repetir { X  topo da pilha; a  *p; Se (X = terminal ou X = $) { Se (X = a) {Desempilhar (X); Avançar (p);} Senão Erro ( X era esperado ); } Senão { /* X é não-terminal */ Se M[X, a] = {XY1Y2Y3 ... Yk} { Desempilhar (X); Empilhar (Yk ... Y3Y3Y1) /* Y1 no topo */ ; Imprimir a produção {XY1Y2Y3 ... Yk};} Senão Erro ( a não era esperado ); } } enquanto (X  $) /* enquanto pilha ainda não vazia */ A saída pode ser a produção usada

Exemplo: seja a seguinte gramática de expressões: E  T E’ E’  + T E’ | ε T  F T’ T’  * F T’ | ε F  ( E ) | id Seja a seguinte sentença em análise: id + id * id Supor a seguinte tabela de produções (sua construção é apresentada após o exemplo – as posições vazias são erros):

Programa analisador preditor Estado inicial: Ação: Expandir Sentença de entrada id + * $ T Programa analisador preditor Saída E’ E E  TE’ $ Pilha

Programa analisador preditor Ação: Expandir Sentença de entrada id + * $ F T T’ Programa analisador preditor Saída E  TE’ T  FT’ E’ E  TE’ $ Pilha

Programa analisador preditor Ação: Expandir Sentença de entrada id + * $ id F T’ Programa analisador preditor E  TE’ T  FT’ F  id Saída E  TE’ T  FT’ E’ $ Pilha

Programa analisador preditor Ação: Desempilhar Avançar Sentença de entrada id + * $ id T’ Programa analisador preditor E  TE’ T  FT’ F  id Saída E’ $ Pilha

Programa analisador preditor Ação: Expandir Sentença de entrada id + * $ E  TE’ T  FT’ F  id T’ ε T’ Programa analisador preditor E  TE’ T  FT’ F  id Saída E’ $ Pilha

Programa analisador preditor Ação: Expandir Sentença de entrada id + * $ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ + E  TE’ T  FT’ F  id T’ ε T Programa analisador preditor Saída E’ E’ $ Pilha

Programa analisador preditor Ação: Desempilhar Avançar Sentença de entrada id + * $ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ + T Programa analisador preditor Saída E’ $ Pilha

Programa analisador preditor Ação: Expandir Sentença de entrada id + * $ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ F E  TE’ T  FT’ F  id T’ ε E’  +T’E’ T’ T Programa analisador preditor Saída E’ $ Pilha

Programa analisador preditor Ação: Expandir Sentença de entrada id + * $ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ id F T’ Programa analisador preditor Saída E’ $ Pilha

Programa analisador preditor Ação: Desempilhar Avançar Sentença de entrada id + * $ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ id T’ Programa analisador preditor Saída E’ $ Pilha

Programa analisador preditor Ação: Expandir Sentença de entrada id + * $ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ T’  *FT’ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ * F T’ Programa analisador preditor Saída E’ $ Pilha

Programa analisador preditor Ação: Desempilhar Avançar Sentença de entrada id + * $ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ T’  *FT’ * F T’ Programa analisador preditor Saída E’ $ Pilha

Programa analisador preditor Ação: Expandir Sentença de entrada id + * $ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ T’  *FT’ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ T’  *FT’ id F T’ Programa analisador preditor Saída E’ $ Pilha

Programa analisador preditor Ação: Desempilhar Avançar Sentença de entrada id + * $ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ T’  *FT’ id T’ Programa analisador preditor Saída E’ $ Pilha

Programa analisador preditor Ação: Expandir Sentença de entrada id + * $ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ T’  *FT’ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ T’  *FT’ T’ Programa analisador preditor Saída E’ $ Pilha

Programa analisador preditor Ação: Expandir Sentença de entrada E  TE’ T  FT’ F  id T’ ε E’  +T’E’ T’  *FT’ E’ ε id + * $ E  TE’ T  FT’ F  id T’ ε E’  +T’E’ T’  *FT’ Programa analisador preditor Saída E’ $ Pilha

Programa analisador preditor Ação: Encerrar com sucesso Ação: Desempilhar Avançar Sentença de entrada E  TE’ T  FT’ F  id T’ ε E’  +T’E’ T’  *FT’ E’ ε id + * $ Resta saber como construir a tabela de produções !!! Programa analisador preditor Saída Derivação $ Pilha

Então não se pode aplicar um método preditor E  T E’ E’  + T E’ | ε T  F T’ T’  * F T’ | ε F  ( E ) | id Há gramáticas cujas tabelas apresentam duas ou mais produções para a mesma entrada Então não se pode aplicar um método preditor

Prepara um casamento entre pilha e entrada E  T E’ E’  + T E’ | ε T  F T’ T’  * F T’ | ε F  ( E ) | id Produção de X em que o primeiro símbolo do lado direito é um terminal a: pode ser colocada na entrada [X, a] Prepara um casamento entre pilha e entrada Exemplos: M[E’, +] = E’  +TE’ M[F, (] = F  (E) M[T’, *] = T’  *FT’ M[F, id] = F  id

É o caso das produções E  TE’ e T  FT’ E  T E’ E’  + T E’ | ε T  F T’ T’  * F T’ | ε F  ( E ) | id Produção de X em que o primeiro símbolo do lado direito é um não terminal Y: Para ser colocada numa entrada [X, a] é necessário ver se Y pode derivar uma forma sentencial iniciada por a É o caso das produções E  TE’ e T  FT’

E  T E’ E’  + T E’ | ε T  F T’ T’  * F T’ | ε F  ( E ) | id Produção vazia de X: Para ser colocada numa entrada [X, a] é necessário ver se a pode aparecer em alguma forma sentencial depois de X

5.4.5 – As funções Primeiro e Seguinte A construção da tabela de produções é auxiliada por duas funções: Primeiro e Seguinte Sendo   (N  )*, Primeiro () é o conjunto dos terminais que iniciam todas as sentenças derivadas de  Se  * ε, então ε  Primeiro () Observa-se que, se o primeiro símbolo de  for um terminal qualquer a, então Primeiro () = {a}

Exemplo: seja  = A a B C b c d D  + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _  _ _ _ _ _ _ _ _ _ _ _ _ _ Todas as possíveis e talvez infinitas cadeias só de terminais Como  possui terminais,  * ε portanto: ε  Primeiro () Eliminando-se os terminais repetidos, obtém-se Primeiro()

Utilidade da função Primeiro () em análise preditora: Sejam as produções A   |  Na expansão de A diante do terminal b, a decisão por  ou , depende de b pertencer a Primeiro() ou Primeiro() Caso b pertença aos dois conjuntos, a indecisão persiste Caso b não pertença a nenhum dos dois conjuntos: erro

Sendo A um não-terminal qualquer, Seguinte (A) é o conjunto dos terminais que podem aparecer imediatamente à direita de A, em alguma forma sentencial Mais precisamente, é o conjunto dos terminais b, tais que existe uma derivação da forma S * Ab (,  (N  )*) Se A for o símbolo mais a direita de alguma forma sentencial, então $  Seguinte (A)

Exemplo: sejam todas as possivelmente infinitas formas sentenciais de uma GLC _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _  _ _ _ _ _ _ _ _ _ _ _ _ _ Em algumas (possivelmente infinitas) o não-terminal A aparece

Exemplo: sejam todas as possivelmente infinitas formas sentenciais de uma GLC _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ A _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ A _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ A  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ A _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ A _ _ Colocando-se o finalizador ‘$’ em cada uma

Exemplo: sejam todas as possivelmente infinitas formas sentenciais de uma GLC _ _ _ _ _ _ _ _ _ _ _ _ _ _ $ _ _ _ _ _ A _ _ _ _ _ _ $ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ $ _ _ _ _ _ _ _ A _ _ _ _ $ _ _ _ _ _ _ _ _ _ _ _ _ _ _ A $  _ _ _ _ _ _ _ _ _ _ _ _ $ _ _ _ _ A _ _ _ _ _ _ _ _ $ _ _ _ _ _ _ _ _ _ _ _ _ _ $ _ _ _ _ _ _ _ _ _ A _ _ $ Colocando-se os símbolos que seguem A

Exemplo: sejam todas as possivelmente infinitas formas sentenciais de uma GLC _ _ _ _ _ _ _ _ _ _ _ _ _ _ $ _ _ _ _ _ A B _ _ _ _ _ $ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ $ _ _ _ _ _ _ _ A a _ _ _ $ _ _ _ _ _ _ _ _ _ _ _ _ _ _ A $  _ _ _ _ _ _ _ _ _ _ _ _ $ _ _ _ _ A b _ _ _ _ _ _ _ $ _ _ _ _ _ _ _ _ _ _ _ _ _ $ _ _ _ _ _ _ _ _ _ A D _ $ a, b, $  Seguinte (A)

Cálculo do Primeiro (): Seja  = X1 X2 X3 ... Xn, onde Xi (1  i  n) é um terminal ou um não terminal: O cálculo do Primeiro () exige o cálculo de Primeiro (Xi)

Algoritmo 5.4: Calcula Primeiro (X), onde X  (N  ) O cálculo é feito simultaneamente para todos os símbolos da gramática O algoritmo tem 2 etapas: Inicialização Processo rotativo convergente

Algoritmo 5.4: Inicialização Para todo símbolo X da gramática { Se (X é terminal) Primeiro (X)  {X}; Senão se (X   é uma produção) Primeiro (X)  {}; Senão Primeiro (X)  { }; }

Processo rotativo convergente Aplicar a seguinte regra a toda produção não-vazia da gramática de modo rotativo, até que nenhum terminal ou  seja acrescentado ao conjunto Primeiro de qualquer não-terminal: Para cada produção do tipo X  Y1Y2Y3 ... Yk (k  1) { Para (i  1; i  k; i++) Se (i = 1) || (j | (1  j  i-1),   Primeiro (Yj )) Acrescentar (Primeiro (Yi ) - {}) a Primeiro (X) Se (i | (1  i  k),   Primeiro (Yi )) Acrescentar  a Primeiro (X); }

Comentários: seja a produção X  Y1Y2Y3 ... Yk (k  1) Todo o terminal (excluído ) presente em Primeiro (Y1) certamente pertence a Primeiro (X) Se Y1 não deriva , então nada mais deve ser acrescentado a Primeiro (X) Mas se Y1 deriva , então deve-se acrescentar a Primeiro (X) todo terminal (excluído ) contido em Primeiro (Y2)

Comentários: seja a produção X  Y1Y2Y3 ... Yk (k  1) Se Y1, Y2 e Y3 derivam , então deve-se acrescentar a Primeiro (X) todo terminal contido em Primeiro (Y4) E assim por diante Se Y1, Y2 , Y3, ... , Yk derivam , então deve-se acrescentar  a Primeiro (X)

Exemplo: seja a gramática: E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Inicialização do Algoritmo 5.4: Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id}   Primeiro (E’) = Primeiro (T’) = {} Primeiro (E) = Primeiro (T) = Primeiro (F) = { }

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, 1a rotação: Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {} Primeiro (T’) = {} Primeiro (E) = { } Primeiro (T) = { } Primeiro (F) = { } E  T E’ : Primeiro (T) = { } nada acontece E’  + T E’ : Primeiro (+) = {+}, então Primeiro (E’) = {, +}

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, 1a rotação: Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {, +} Primeiro (T’) = {} Primeiro (E) = { } Primeiro (T) = { } Primeiro (F) = { } T  F T’ : Primeiro (F) = { } nada acontece T’  *F T’ : Primeiro (*) = {*}, então Primeiro (T’) = {, *}

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, 1a rotação: Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {, +} Primeiro (T’) = {, *} Primeiro (E) = { } Primeiro (T) = { } Primeiro (F) = { } F  ( E ) : Primeiro (‘(’) = { ( }, então Primeiro (F) = { ( }

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, 1a rotação: Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {, +} Primeiro (T’) = {, *} Primeiro (E) = { } Primeiro (T) = { } Primeiro (F) = { ( } F  id : Primeiro (id) = { id }, então Primeiro (F) = { (, id }

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, final da 1a rotação Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {, +} Primeiro (T’) = {, *} Primeiro (E) = { } Primeiro (T) = { } Primeiro (F) = { (, id }

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, 2a rotação: Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {, +} Primeiro (T’) = {, *} Primeiro (E) = { } Primeiro (T) = { } Primeiro (F) = { (, id } E  T E’ : Primeiro (T) = { } nada acontece E’  + T E’ : Primeiro (+) = {+}, T  F T’ : Primeiro (F) = { (, id }, então Primeiro (T) = { (, id }

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, 2a rotação: Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {, +} Primeiro (T’) = {, *} Primeiro (E) = { } Primeiro (T) = { (, id } Primeiro (F) = { (, id } T’  * F T’ : Primeiro (*) = {*}, nada acontece F  ( E ) : Primeiro (‘(’) = { ( }, F  id : Primeiro (id) = { id },

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, final da 2a rotação Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {, +} Primeiro (T’) = {, *} Primeiro (E) = { } Primeiro (T) = { (, id } Primeiro (F) = { (, id }

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, 3a rotação: Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {, +} Primeiro (T’) = {, *} Primeiro (E) = { } Primeiro (T) = { (, id } Primeiro (F) = { (, id } E  T E’ : Primeiro (T) = { (, id } Primeiro (E) = { (, id }

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, 3a rotação: Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {, +} Primeiro (T’) = {, *} Primeiro (E) = { (, id } Primeiro (T) = { (, id } Primeiro (F) = { (, id } Com as outras produções nada acontece

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, final da 3a rotação Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {, +} Primeiro (T’) = {, *} Primeiro (E) = { (, id } Primeiro (T) = { (, id } Primeiro (F) = { (, id }

T  F T’ T’  * F T’ |  F  ( E ) | id E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Processo rotativo do Algoritmo 5.4, 4a rotação: Primeiro (+) = {+} Primeiro (*) = {*} Primeiro (‘(’) = { ( } Primeiro (‘)’) = { ) } Primeiro (id) = {id} Primeiro (E’) = {, +} Primeiro (T’) = {, *} Primeiro (E) = { (, id } Primeiro (T) = { (, id } Primeiro (F) = { (, id } Nada acontece Ficam determinados os Primeiro’s de todos os símbolos

Algoritmo 5.5: Cálculo do Primeiro (), onde  = X1 X2 X3 ... Xn e Xi  (N  ), (1  i  n) Primeiro ()  { }; Para (i  1; i  n; i++) Se (i = 1) || (j | (1  j  i-1),   Primeiro (Xj )) Acrescentar (Primeiro (Xi ) - {}) a Primeiro () ; Se (i | (1  i  n),   Primeiro (Xi )) Acrescentar  a Primeiro (); Se ( = ) Primeiro ()  {};

Comentários: seja  = X1 X2 X3 ... Xn Todos os terminais de Primeiro (X1) devem pertencer a Primeiro () Caso (  Primeiro (X1)), todos os terminais de Primeiro (X2 ) devem pertencer a Primeiro () Caso (  Primeiro (X1) e   Primeiro (X2)), todos os terminais de Primeiro (X3) devem pertencer a Primeiro () E assim por diante

Algoritmo 5.6: Calcula Seguinte (A), onde A é um não-terminal O cálculo é feito simultaneamente para todos os não-terminais da gramática O algoritmo tem 3 etapas Inicialização trivial Inicialização não-trivial Processo rotativo convergente

Algoritmo 5.6: Inicialização trivial Seguinte (S)  {$}; /* S é o símbolo inicial */ /* $ é marca de fim da sentença */ Inicializar Seguinte de todos os outros não-terminais com o conjunto vazio

Comentários: S * $  Seguinte (S) _ _ _ _ _ _ _ _ _ _ _ _ _ _ $ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ $  _ _ _ _ _ _ _ _ _ _ _ _ $ S $ _ _ _ _ _ _ _ _ _ _ _ _ _ $ $  Seguinte (S)

Inicialização não-trivial Para toda produção do tipo A  w1 B1 w2 B2 w3 ... wn Bn wn+1, (n  1; wi  *, 1  i  n+1; Bi  N, 1  i  n) { Para (i  1; i  n; i++) { Expressão da produção: A  i Bi i, (i, i  (N  )*); Acrescentar (Primeiro (i ) - {}) a Seguinte (Bi) ; } Pelo menos um não-terminal do lado direito

Comentários: seja a produção A  i Bi i Sendo A um não-terminal útil, haverá pelo menos uma forma sentencial do tipo _ _ _ _ _ _ _ _ _ _ _ _ A _ _ _ _ $ E haverá outra do tipo _ _ _ _ _ _ _ _ _ _ _ _ i Bi i _ _ _ _ $ Então (Primeiro (i) – {})  Seguinte (Bi) i pode ser vazio ou derivar a cadeia vazia

3. Processo rotativo convergente Aplicar a seguinte regra a toda produção não-vazia da gramática de modo rotativo, até que nenhum terminal ou $ seja acrescentado ao conjunto Seguinte de qualquer não terminal: Seja A  w1 B1 w2 B2 w3 ... wn Bn wn+1, a produção em questão, onde (n  1; wi  *, 1  i  n+1; Bi  N, 1  i  n): Para (i  1; i  n; i++) { Expressão da produção: A  i Bi i, (i, i  (N  )*); Se ((i é vazia) ou (  Primeiro (i))) Acrescentar Seguinte (A ) a Seguinte (Bi) ; } Pelo menos um não-terminal do lado direito

Comentários: seja a produção A  i Bi i Se a, b, c, $  Seguinte (A), há formas sentenciais dos tipos _ _ _ _ _ _ A a _ _ _ $ _ _ _ _ A b _ _ _ $ _ _ _ _ _ A c _ _ _ $ _ _ _ _ _ _ A $ E também dos tipos _ _ _ _ _ _ i Bi i a _ _ _ $ _ _ _ _ i Bi i b _ _ _ $ _ _ _ _ _ i Bi i c _ _ _ $ _ _ _ _ _ _ i Bi i $ Se i é vazia: a, b, c, $  Seguinte (Bi) Se   Primeiro (i), ou seja, se i * : há formas sentenciais em que a, b, c, $ aparecem imediatamente depois de Bi Então, acrescentar a, b, c, $ ao Seguinte (Bi) Ou seja, acrescentar Seguinte (A) ao Seguinte (Bi)

Exemplo: seja a mesma gramática: E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Seja a tabela dos Primeiro’s de seus não-terminais calculada no exemplo anterior:

Inicialização trivial do Algoritmo 5.6: Seguinte (E) = {$} Seguinte (E’) = { } Seguinte (T) = { } Seguinte (T’) = { } Seguinte (F) = { }

Inicialização não-trivial do Algoritmo 5.6: Seguinte (E) = {$} Seguinte (E’) = { } Seguinte (T) = { } Seguinte (T’) = { } Seguinte (F) = { } Produção E  T E’: Seguinte (T) = Seguinte (T)  (Primeiro (E’) – {ε}) = { }  ({+, ε} – {ε}) = {+}

Inicialização não-trivial do Algoritmo 5.6: Seguinte (E) = {$} Seguinte (E’) = { } Seguinte (T) = {+} Seguinte (T’) = { } Seguinte (F) = { } Produção E  T E’: Seguinte (T) = Seguinte (T)  (Primeiro (E’) – {ε}) = { }  ({+, ε} – {ε}) = {+} Seguinte (E’) = Seguinte (E’)  (Primeiro (ε) – {ε}) = { }  ({ε} – {ε}) = { }

Inicialização não-trivial do Algoritmo 5.6: Seguinte (E) = {$} Seguinte (E’) = { } Seguinte (T) = {+} Seguinte (T’) = { } Seguinte (F) = { } Produção E’  +T E’: Seguinte (T) = Seguinte (T)  (Primeiro (E’) – {ε}) = {+}  ({+, ε} – {ε}) = {+} Seguinte (E’) = Seguinte (E’)  (Primeiro (ε) – {ε}) = { }  ({ε} – {ε}) = { }

Inicialização não-trivial do Algoritmo 5.6: Seguinte (E) = {$} Seguinte (E’) = { } Seguinte (T) = {+} Seguinte (T’) = { } Seguinte (F) = { } Produção T  FT’: Seguinte (F) = Seguinte (F)  (Primeiro (T’) – {ε}) = { }  ({*, ε} – {ε}) = {*}

Inicialização não-trivial do Algoritmo 5.6: Seguinte (E) = {$} Seguinte (E’) = { } Seguinte (T) = {+} Seguinte (T’) = { } Seguinte (F) = {*} Produção T  FT’: Seguinte (F) = Seguinte (F)  (Primeiro (T’) – {ε}) = { }  ({*, ε} – {ε}) = {*} Seguinte (T’) = Seguinte (T’)  (Primeiro (ε) – {ε}) = { }  ({ε} – {ε}) = { }

Inicialização não-trivial do Algoritmo 5.6: Seguinte (E) = {$} Seguinte (E’) = { } Seguinte (T) = {+} Seguinte (T’) = { } Seguinte (F) = {*} Produção T’  *FT’: Seguinte (F) = Seguinte (F)  (Primeiro (T’) – {ε}) = {*}  ({*, ε} – {ε}) = {*} Seguinte (T’) = Seguinte (T’)  (Primeiro (ε) – {ε}) = { }  ({ε} – {ε}) = { }

Inicialização não-trivial do Algoritmo 5.6: Seguinte (E) = {$} Seguinte (E’) = { } Seguinte (T) = {+} Seguinte (T’) = { } Seguinte (F) = {*} Produção F  (E): Seguinte (E) = Seguinte (E)  (Primeiro (‘)’) – {ε}) = {$}  ({ ) } – {ε}) = {$, )}

Inicialização não-trivial do Algoritmo 5.6: Seguinte (E) = {$, )} Seguinte (E’) = { } Seguinte (T) = {+} Seguinte (T’) = { } Seguinte (F) = {*}

Final da inicialização não-trivial do Algoritmo 5.6: Seguinte (E) = {$, )} Seguinte (E’) = { } Seguinte (T) = {+} Seguinte (T’) = { } Seguinte (F) = {*}

Processo rotativo do Algoritmo 5.6, 1a rotação: Seguinte (E) = {$, )} Seguinte (E’) = { } Seguinte (T) = {+} Seguinte (T’) = { } Seguinte (F) = {*} Produção E  T E’: (ε  Primeiro (E’)) Seguinte (T) = Seguinte (T)  Seguinte (E) = {+}  {$, )} = {+, $, )}

Processo rotativo do Algoritmo 5.6, 1a rotação: Seguinte (E) = {$, )} Seguinte (E’) = { } Seguinte (T) = {+, $, )} Seguinte (T’) = { } Seguinte (F) = {*} Produção E  T E’: Seguinte (T) = Seguinte (T)  Seguinte (E) = {+}  {$, )} = {+, $, )} Seguinte (E’) = Seguinte (E’)  Seguinte (E) = { }  {$, )} = {$, )}

Processo rotativo do Algoritmo 5.6, 1a rotação: Seguinte (E) = {$, )} Seguinte (E’) = {$, )} Seguinte (T) = {+, $, )} Seguinte (T’) = { } Seguinte (F) = {*} Produção T  F T’: (ε  Primeiro (T’)) Seguinte (F) = Seguinte (F)  Seguinte (T) = {*}  {+, $, )} = {*, +, $, )}

Processo rotativo do Algoritmo 5.6, 1a rotação: Seguinte (E) = {$, )} Seguinte (E’) = {$, )} Seguinte (T) = {+, $, )} Seguinte (T’) = { } Seguinte (F) = {*, +, $, )} Produção T  F T’: Seguinte (F) = Seguinte (F)  Seguinte (T) = {*}  {+, $, )} = {*, +, $, )} Seguinte (T’) = Seguinte (T’)  Seguinte (T) = { }  {+, $, )} = {+, $, )}

Processo rotativo do Algoritmo 5.6, 1a rotação: Seguinte (E) = {$, )} Seguinte (E’) = {$, )} Seguinte (T) = {+, $, )} Seguinte (T’) = {+, $, )} Seguinte (F) = {*, +, $, )} Produção T’  * F T’: (ε  Primeiro (T’)) Seguinte (F) = Seguinte (F)  Seguinte (T’) = {*, +, $, )}  {+, $, )} = {*, +, $, )} Seguinte (T’) = Seguinte (T’)  Seguinte (T’) = {+, $, )}  {+, $, )} = {+, $, )}

Processo rotativo do Algoritmo 5.6, 1a rotação: Seguinte (E) = {$, )} Seguinte (E’) = {$, )} Seguinte (T) = {+, $, )} Seguinte (T’) = {+, $, )} Seguinte (F) = {*, +, $, )} Produção F ( E ): (Não se aplica a regra, pois ε  Primeiro (‘)’))

Final da 1a rotação do processo rotativo do Algoritmo 5.6: Seguinte (E) = {$, )} Seguinte (E’) = {$, )} Seguinte (T) = {+, $, )} Seguinte (T’) = {+, $, )} Seguinte (F) = {*, +, $, )}

Processo rotativo do Algoritmo 5.6, 2a rotação: Seguinte (E) = {$, )} Seguinte (E’) = {$, )} Seguinte (T) = {+, $, )} Seguinte (T’) = {+, $, )} Seguinte (F) = {*, +, $, )} Nada é alterado Ficam determinados os Seguinte’s de todos os não-terminais

5.4.6 – Construção da tabela de produções Seja A um não-terminal e a um terminal

Sejam A       as produções de A Se a  Primeiro () Então: A pode expandir para , na presença de a na entrada  pode gerar sub-sentenças iniciadas por a, para casar com o a na entrada

Sejam A       as produções de A Se a  (Primeiro () e Primeiro () e Primeiro ()) E se ,  e  não derivarem ε Então: Erro com A na presença de a na entrada A só gera sub-sentenças não-vazias e não iniciadas por a Nunca aparecerá um a no topo da pilha para casar com o a na entrada

Sejam A       as produções de A Se a  (Primeiro () e Primeiro () e Primeiro ()), mas se pelo menos  derivar ε E se a  Seguinte (A) Então: A pode expandir para  na presença de a na entrada Existe a possibilidade de haver um a abaixo do A, na pilha, ou um não-terminal que gere algo iniciado por a

Algoritmo 5.7: Construção da tabela de produções Para cada produção A   da gramática { Para cada terminal a em Primeiro () Acrescentar A   a M[A, a]; Se (ε  Primeiro ()) { Para todo terminal b em Seguinte (A) Acrescentar A   a M[A, b]; Se ($  Seguinte (A)) Acrescentar A   a M[A, $]; } Colocar Erro em cada posição indefinida da tabela.

Exemplo: seja a mesma gramática: E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Tabelas dos Primeiro’s e Seguinte’s já calculadas:

E  T E’ E’  + T E’ |  T  F T’ T’  * F T’ |  F  ( E ) | id Tabela de produções:

Sejam A     as produções de A Se a  (Primeiro () e Primeiro ()) Então: indecisão A pode expandir para  ou , na presença de a na entrada A gramática não é LL(1), ou seja, não admite análise preditora

Sejam A     as produções de A Se a  (Primeiro () e Seguinte (A)) e se  derivar ε Indecisão: duas ou mais produções numa posição M[A, a] da tabela Então: indecisão A pode expandir para  ou , na presença de a na entrada A gramática não é LL(1), ou seja, não admite análise preditora

Exemplo: seja a gramática para if-else: S  i b S S’ | a S’  e S |  Tabelas: M[S’, e] tem duas produções Tipicamente decide-se por S’  e S O else corresponde ao último if

5.4.7 – Tratamento de erros Um erro é detectado quando: Um terminal no topo da pilha não casa com o próximo símbolo na entrada b era esperado O não-terminal no topo da pilha e o próximo símbolo de entrada determinam uma posição de erro na tabela de produções a não era esperado

O terminal no topo da pilha não casa com o próximo símbolo na entrada: Mensagem: b era esperado Ação típica: desempilhar o terminal da pilha Resolve o caso de esquecimento de b

Mensagem: a não era esperado É difícil escolher a melhor ação O não-terminal no topo da pilha e o próximo símbolo de entrada determinam erro na tabela de produções: Mensagem: a não era esperado É difícil escolher a melhor ação Uma das técnicas mais usadas é o uso de um conjunto de terminais de sincronismo Descarta-se terminais de entrada até que se encontre um desses terminais Seja sincr a designação desse terminal

Terminais de sincronismo: Terminais do Seguinte (A): desempilhar A - o que estiver abaixo do A na pilha pode ser: Um terminal também pertencente a Seguinte (A) que case com sincr Um não-terminal que expanda para uma cadeia iniciada por sincr

Terminais de sincronismo: Terminais do Primeiro (A): não desempilhar A: Haverá uma posição sem erro na tabela Há possibilidade de retomar a análise

Ainda para não-terminal A no topo da pilha: Se houver a produção A  ε, pode-se usá-la para expandir A, retardando o tratamento do erro

Pode-se incluir também, no conjunto de terminais de sincronismo, palavras reservadas que iniciam comandos Consultar a literatura para mais detalhes