CES-41 Teoria Cap 5-a
Download
Report
Transcript CES-41 Teoria Cap 5-a
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; alguns deles por vezes precisam
voltar da direita para a esquerda
–
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 topdown
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
árvores sintáticas:
tem duas
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
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ãocasados entre Expr e ELSE
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 ( );
}
else Term ( );
}
Expr ( ) {
if ( func (átomos analisados e/ou futuros) == TRUE ) {
Expr ( );
if (atom != ‘+’) erro (“+ esperado”)
else Term ( );
}
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
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
Exemplo: seja a seguinte gramática, com duas produções
contendo recursividade imediata à esquerda:
{A A }
Expr Expr + Termo Termo
Termo Termo * Fator Fator
Fator ( Expr ) ID
{A R
R R ε}
Transformação da gramática:
Nas produções de Expr,
substituindo:
Expr Termo Eaux
Eaux + Termo Eaux ε
Termo Fator Taux
Taux * Fator Taux ε
Fator ( Expr ) | ID
A
R
por
por
por
por
Expr
+ Termo
Termo
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:
SSaTb c
TSdUe f
USgUh i
Renomeando os não-terminais:
S = A1, T = A2, U = A3
A gramática torna-se em:
A1 A1 a A 2 b c
A2 A1 d A 3 e f
A3 A1 g A 3 h i
Para i = 1:
Eliminação das recursividades imediatas à esquerda de A1:
Algoritmo 5.1
A A1 1 2
A1 A2 b X c X
X aX ε
A 1 A' 2 A'
A' 1 A' ε
A1 A2 b X c X
X aX ε
A2 A1 d A3 e f
Novo estado
das produções A A g A h i
3
1
3
Para i = 2, j = 1:
Produções de A2:
A2 A1 d A 3 e f
Produção da forma
A2 A1
Produções de A1 no momento: A1 A2 b X c X
Substitui-se A1 d por A2 b X d c X d :
Para i = 2:
Eliminação das recursividades imediatas à esquerda de A2:
Algoritmo 5.1
A A1 1 2 3
A2 c X d Y A3 e Y f Y
YbXdYε
Novo estado
das produções
A 1 A' 2 A' 3 A'
A' 1 A' ε
A1 A2 b X c X
X aX ε
A2 c X d Y A3 e Y f Y
YbXdYε
A3 A1 g A3 h i
Para i = 3, j = 1:
Produções de A3:
A3 A1 g A 3 h i
Produção da forma
A3 A1
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
Para i = 3, j = 2:
Produções de A3:
A3 A2 b X g c X g A3 h i
Produção da forma
A3 A2
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 :
Para i = 3:
Eliminação das recursividades imediatas à esquerda de A3:
Algoritmo 5.1
A A1 A2 1 2
3 4
A 1 A' 2 A'
3 A' 4 A'
A' 1 A' 2 A' ε
A3 c X d Y b X g Z f Y b X g Z c X g Z i Z
ZeYbXgZhZε
Novo estado das produções:
A1 A2 b X c X
X aX ε
A2 c X d Y A 3 e Y f Y
YbXdYε
A3 c X d Y b X g Z f Y b X g Z c X g Z i Z
ZeYbXgZhZε
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
XaX ε
YbXdYε
ZeYbXgZhZε
Restaurando os nomes originais dos não-terminais:
S = A1, T = A2, U = A3
STbX cX
TcXdYUeY fY
UcXdYbXgZfYbXgZcXgZiZ
XaX ε
Não há mais recursividade a
YbXdYε
esquerda
ZeYbXgZhZε
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ãoterminal, 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(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
No exemplo anterior
ou
*md
{ 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 )
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
Analisadores preditores
( id opad id ) opmult cte opmult T
examinam derivações
( id opad id ) opmult cte opmult F
mais a esquerda
( 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 )
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 )
Analisadores bottom-up
T opmult cte opmult ( id opad cte ) examinam derivações
F opmult cte opmult ( id opad cte ) mais a direita reversas
( 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 )
(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 )
Em negrito,
F opmult cte opmult ( id opad cte )
lados direitos a
T opmult cte opmult ( id opad cte )
serem reduzidos
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
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 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
SaAdaB
Abc
B ccd ddc
A seguir, análise da
sentença:
w=accd
SaAdaB
Abc
S
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
w = a c c d
SaAdaB
Abc
2) 1a produção para expandir S
S
a
A
B ccd ddc
d
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
w = a c c d
Os cursores se casam:
a=a
SaAdaB
Abc
3) Os dois cursores avançam
S
a
A
B ccd ddc
d
w = a c c d
SaAdaB
Abc
4) 1a produção para expandir A
S
a
A
B ccd ddc
d
b
w = a c c d
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
Os cursores não se casam:
b≠c
SaAdaB
Abc
B ccd ddc
5) Elimina-se a subárvore da
expansão anterior
S
2a produção para expandir A
a
A
d
c
b
w = a c c d
Cursor da sentença retrocede
para onde A foi expandido, ou
seja, permanece onde está:
Cursor da árvore avança em préordem
Os cursores se casam:
c=c
SaAdaB
Abc
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
SaAdaB
Abc
B ccd ddc
7) Elimina-se a subárvore da
expansão anterior
a
S
Não existe outra produção para
expandir A
A
Elimina-se também a subárvore da
expansão imediatamente anterior,
ou seja, a de S
dB
c
Cursor da sentença retrocede até o
ponto em que S foi expandido
2a produção para expandir S
w = a c c d
O cursor da árvore avança
Os cursores se casam:
a=a
SaAdaB
Abc
S
B ccd ddc
8) Os cursores avançam
1a produção para expandir B
a
B
c c d
w = a c c d
Cursor da árvore avança
Casamento nos três
últimos movimentos dos
cursores:
ccd =ccd
A sentença é reconhecida
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 backtracking 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:
Top-down
Backtracking
Não Preditores
Sem Backtracking
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 | ε
Fator ID | CTE | ( Expressão )
A seguir:
Esquema de programa
analisador sintático
em C
| OPNEG Fator
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);
NovoAtomo (); break;
}
}
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
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
a
SeM[X,
Se,
M[X,a]
por a]
exemplo,
pode
for um
sererro:
M[X,a]
uma produção
for a produção
de X
Acionar
de
ou um tratamento
erro
XYZW:
erro
Desempilhar X
Empilhar W, Z, Y, com
Y no topo
X
M[X, a]
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 ); }
A saída pode ser a
Senão {
/* X é não-terminal */
produção usada
Se M[X, a] = {XY1Y2Y3 ... Yk} {
Desempilhar (X);
Empilhar (Yk ... Y3Y3Y1) /* Y1 no topo */ ;
Imprimir a produção {XY1Y2Y3 ... Yk};}
Senão Erro ( a não era esperado ); }
} enquanto (X $)
/* enquanto pilha ainda não vazia */
Exemplo: seja a seguinte gramática de expressões:
E T E’
T F T’
E’ + T E’ | ε
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):
Ação:
Expandir
Estado inicial:
Sentença de entrada
id
T
E’
E
$
Pilha
+
id
*
id
Programa
analisador
preditor
$
Saída
E TE’
Ação:
Expandir
Sentença de entrada
id
+
id
*
id
$
F
T’
E’
$
Pilha
Programa
analisador
preditor
Saída
E TE’
T
E
FT’
TE’
Ação:
Expandir
Sentença de entrada
id
+
id
*
id
$
id
F
T’
E’
$
Pilha
Programa
analisador
preditor
Saída
E TE’
E FT’
TE’
T
T id
FT’
F
Sentença de entrada
id
+
id
*
id
Ação:
Desempilhar
Avançar
$
id
T’
E’
$
Pilha
Programa
analisador
preditor
Saída
E TE’
T FT’
F id
Ação:
Expandir
Sentença de entrada
id
T’
E’
$
Pilha
+
id
*
id
Programa
analisador
preditor
$
Saída
E TE’
T
E
FT’
TE’
F
T
id
FT’
T’
ε
F id
Ação:
Expandir
Sentença de entrada
id
+
T
E’
$
Pilha
+
id
*
id
Programa
analisador
preditor
$
Saída
E TE’
E
TE’
T FT’
T id
FT’
F
F id
T’
ε
T’
E’ ε+T’E’
Sentença de entrada
id
+
T
E’
$
Pilha
+
id
*
id
Programa
analisador
preditor
Ação:
Desempilhar
Avançar
$
Saída
E TE’
T FT’
F id
T’ ε
E’ +T’E’
Ação:
Expandir
Sentença de entrada
id
+
id
*
id
$
F
T’
T
E’
$
Pilha
Programa
analisador
preditor
Saída
E TE’
E FT’
TE’
T
T id
FT’
F
F
id
T’
ε
T’
E’ ε+T’E’
E’
FT’
+T’E’
T
Ação:
Expandir
Sentença de entrada
id
id
F
T’
E’
$
Pilha
+
id
*
id
Programa
analisador
preditor
$
Saída
E TE’
E
TE’
T FT’
T
FT’
F id
F
id
T’
ε
T’
E’
ε+T’E’
E’
FT’
+T’E’
T
T
FT’
F id
Sentença de entrada
id
id
T’
E’
$
Pilha
+
id
*
id
Programa
analisador
preditor
Ação:
Desempilhar
Avançar
$
Saída
E TE’
T FT’
F id
T’ ε
E’ +T’E’
T FT’
F id
Ação:
Expandir
Sentença de entrada
id
+
id
*
id
$
*
F
T’
E’
$
Pilha
Programa
analisador
preditor
Saída
E TE’
E
TE’
T FT’
T id
FT’
F
F id
T’
ε
T’
E’ ε+T’E’
E’
FT’
+T’E’
T
T
FT’
F id
F
T’
id
*FT’
Sentença de entrada
id
+
id
*
id
$
*
F
T’
E’
$
Pilha
Programa
analisador
preditor
Ação:
Desempilhar
Avançar
Saída
E TE’
T FT’
F id
T’ ε
E’ +T’E’
T FT’
F id
T’ *FT’
Ação:
Expandir
Sentença de entrada
id
id
F
T’
E’
$
Pilha
+
id
*
id
Programa
analisador
preditor
$
Saída
E TE’
T
E
FT’
TE’
F
T
id
FT’
T’
ε
F id
E’
ε+T’E’
T’
T
E’
FT’
+T’E’
F
T
id
FT’
T’
id
*FT’
F
F
T’
id
*FT’
Sentença de entrada
id
id
T’
E’
$
Pilha
+
id
*
id
Programa
analisador
preditor
$
Saída
Ação:
Desempilhar
Avançar
E TE’
T FT’
F id
T’ ε
E’ +T’E’
T FT’
F id
T’ *FT’
F id
Ação:
Expandir
Sentença de entrada
id
T’
E’
$
Pilha
+
id
*
id
Programa
analisador
preditor
$
Saída
E TE’
E
TE’
T FT’
T id
FT’
F
F id
T’
ε
T’
E’ ε+T’E’
E’
FT’
+T’E’
T
T
FT’
F id
F
T’
id
*FT’
T’
id
*FT’
F
F id
T’
ε
Ação:
Expandir
Sentença de entrada
id
E’
$
Pilha
+
id
*
id
Programa
analisador
preditor
$
Saída
E TE’
E FT’
TE’
T
T id
FT’
F
F
id
T’
ε
T’
E’ ε+T’E’
E’
FT’
+T’E’
T
T id
FT’
F
F
T’
id
*FT’
T’
id
*FT’
F
F
id
T’
ε
T’
E’ ε
Sentença de entrada
id
Resta saber como
construir a tabela
de produções !!!
$
Pilha
+
id
*
id
Programa
analisador
preditor
$
Saída
Ação:
Encerrar
Desempilhar
com
Avançar
sucesso
E TE’
T FT’
F id
T’ ε
E’ +T’E’
T FT’
F id
T’ *FT’
F id
T’ ε
E’ ε
Derivação
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
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]
–
E’ + T E’ | ε
Prepara um casamento entre pilha e entrada
Exemplos: M[E’, +] = E’ +TE’
M[T’, *] = T’ *FT’
M[F, (] = F (E)
M[F, id] = F id
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:
–
E’ + T E’ | ε
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
Eliminando-se os
terminais repetidos,
obtém-se Primeiro()
Como possui
terminais, * ε
portanto:
ε 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
S *
______________
____________
_______________
______________
____________
_______________
____________
_____________
____________
_____________
____________
_____________
Em algumas
(possivelmente infinitas)
o não-terminal A aparece
Exemplo: sejam todas as possivelmente infinitas formas
sentenciais de uma GLC
S *
______________
_____A______
_______________
______________
_______A____
______________A
____________
____A________
____________
_____________
_________A__
_____________
Colocando-se o
finalizador ‘$’ em
cada uma
Exemplo: sejam todas as possivelmente infinitas formas
sentenciais de uma GLC
S *
______________$
_____A______$
_______________$
______________$
_______A____$
______________A$
____________$
____A________$
____________$
_____________$
_________A__$
_____________$
Colocando-se os
símbolos que
seguem A
Exemplo: sejam todas as possivelmente infinitas formas
sentenciais de uma GLC
S *
______________$
_____AB_____$
_______________$
______________$
_______Aa___$
______________A$
____________$
____Ab_______$
____________$
_____________$
_________AD_$
_____________$
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:
1. Inicialização
2. Processo rotativo convergente
Algoritmo 5.4:
1.
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) { };
}
2. 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’
T F T’
E’ + T E’ |
T’ * F T’ |
F ( E ) | id
Inicialização do Algoritmo 5.4:
Primeiro (+) = {+}
Primeiro (‘(’) = { ( }
Primeiro (id) = {id}
Primeiro (*) = {*}
Primeiro (‘)’) = { ) }
Primeiro (E’) = Primeiro (T’) = {}
Primeiro (E) = Primeiro (T) = Primeiro (F) = { }
E T E’
T F T’
E’ + T E’ |
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’) = {, +}
E T E’
T F T’
E’ + T E’ |
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’) = {, *}
E T E’
T F T’
E’ + T E’ |
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) = { ( }
E T E’
T F T’
E’ + T E’ |
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 }
E T E’
T F T’
E’ + T E’ |
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 }
E T E’
T F T’
E’ + T E’ |
T’ * F T’ |
F ( E ) | id
Processo rotativo do Algoritmo 5.4, 2a rotação:
Primeiro (+) = {+}
E T E’ : Primeiro (T) = { }
Primeiro (*) = {*}
nada acontece
E’ + T E’ : Primeiro (+) = {+},
Primeiro (‘(’) = { ( }
nada acontece
Primeiro (‘)’) = { ) }
T F T’ : Primeiro (F) = { (, id }, então
Primeiro (id) = {id}
Primeiro (T) = { (, id }
Primeiro (E’) = {, +}
Primeiro (T’) = {, *}
Primeiro (E) = { }
Primeiro (T) = { }
Primeiro (F) = { (, id }
E T E’
T F T’
E’ + T E’ |
T’ * F T’ |
F ( E ) | id
Processo rotativo do Algoritmo 5.4, 2a rotação:
Primeiro (+) = {+}
T’ * F T’ : Primeiro (*) = {*},
Primeiro (*) = {*}
nada acontece
F ( E ) : Primeiro (‘(’) = { ( },
Primeiro (‘(’) = { ( }
nada acontece
Primeiro (‘)’) = { ) }
F id : Primeiro (id) = { id },
Primeiro (id) = {id}
nada acontece
Primeiro (E’) = {, +}
Primeiro (T’) = {, *}
Primeiro (E) = { }
Primeiro (T) = { (, id }
Primeiro (F) = { (, id }
E T E’
T F T’
E’ + T E’ |
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 }
E T E’
T F T’
E’ + T E’ |
T’ * F T’ |
F ( E ) | id
Processo rotativo do Algoritmo 5.4, 3a rotação:
Primeiro (+) = {+}
E T E’ : Primeiro (T) = { (, id }
Primeiro (*) = {*}
Primeiro (E) = { (, id }
Primeiro (‘(’) = { ( }
Primeiro (‘)’) = { ) }
Primeiro (id) = {id}
Primeiro (E’) = {, +}
Primeiro (T’) = {, *}
Primeiro (E) = { }
Primeiro (T) = { (, id }
Primeiro (F) = { (, id }
E T E’
T F T’
E’ + T E’ |
T’ * F T’ |
F ( E ) | id
Processo rotativo do Algoritmo 5.4, 3a rotação:
Primeiro (+) = {+}
Com as outras produções
Primeiro (*) = {*}
nada acontece
Primeiro (‘(’) = { ( }
Primeiro (‘)’) = { ) }
Primeiro (id) = {id}
Primeiro (E’) = {, +}
Primeiro (T’) = {, *}
Primeiro (E) = { (, id }
Primeiro (T) = { (, id }
Primeiro (F) = { (, id }
E T E’
T F T’
E’ + T E’ |
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 }
E T E’
T F T’
E’ + T E’ |
T’ * F T’ |
F ( E ) | id
Processo rotativo do Algoritmo 5.4, 4a rotação:
Primeiro (+) = {+}
Nada acontece
Primeiro (*) = {*}
Ficam determinados os Primeiro’s de
Primeiro (‘(’) = { ( }
todos os símbolos
Primeiro (‘)’) = { ) }
Primeiro (id) = {id}
Primeiro (E’) = {, +}
Primeiro (T’) = {, *}
Primeiro (E) = { (, id }
Primeiro (T) = { (, id }
Primeiro (F) = { (, id }
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ãoterminal
O cálculo é feito simultaneamente para todos os nãoterminais da gramática
O algoritmo tem 3 etapas
1. Inicialização trivial
2. Inicialização não-trivial
3. Processo rotativo convergente
Algoritmo 5.6:
1.
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 *
______________$
___________$
_______________$
______________$
___________$
______________$
____________$
S$
____________$
_____________$
___________$
_____________$
$ Seguinte (S)
2. Inicialização não-trivial
Pelo menos um
não-terminal
do lado direito
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 )*);
Se (i não é vazia) Acrescentar
(Primeiro (i ) - {}) a Seguinte (Bi) ;
}
}
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____$
i pode ser vazia ou
derivar a cadeia vazia
E haverá outra do tipo
Se for vazia,
Primeiro(i) = {}
_ _ _ _ _ _ _ _ _ _ _ _ i Bi i _ _ _ _ $
Então (Primeiro (i) – {}) Seguinte (Bi)
3. Processo rotativo convergente
Pelo menos um nãoterminal do lado direito
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) ;
}
Comentários: seja a produção A i Bi i
Se a, b, c, $ Seguinte (A), há formas sentenciais dos tipos
______Aa___$
____Ab___$
_____Ac___$
______A$
E também dos tipos
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
_ _ _ _ _ _ i Bi i a _ _ _ $ Então, acrescentar a, b, c, $ ao
_ _ _ _ i Bi i b _ _ _ $
Seguinte (Bi)
_ _ _ _ _ i Bi i c _ _ _ $
Ou seja, acrescentar Seguinte (A) ao
_ _ _ _ _ _ i Bi i $
Seguinte (Bi)
Exemplo: seja a mesma gramática:
E T E’
T F T’
E’ + T E’ |
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’) – {ε}) =
{ } ({+, ε} – {ε}) = {+}
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 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’) – {ε}) =
{ } ({*, ε} – {ε}) = {*}
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 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 subsentenç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ãoterminal 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’
T F T’
E’ + T E’ |
T’ * F T’ |
F ( E ) | id
Tabelas dos Primeiro’s e Seguinte’s já calculadas:
E T E’
T F T’
E’ + T E’ |
T’ * F T’ |
Tabela de produções:
F ( E ) | id
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
Tabelas:
S’ e S |
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
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