Como construir um compilador utilizando ferramentas Java

Propaganda
Como construir um compilador utilizando
ferramentas Java
Aula 6 – Análise Sintática
Prof. Márcio Delamaro
[email protected]
Como construir um compilador utilizando ferramentas Java – p. 1/2
Ascendente × descendente
Dada uma GLC, a análise sintática baseada nessa GLC
pode ser ascendente ou descendente.
Um analisador ascendente agrupa os símbolos da
entrada para formar os símbolos não terminais mais
distantes do símbolo inicial da GLC ou que estão em
níveis inferiores na estrutura da linguagem.
Na análise descendente, parte-se do símbolo inicial da
GLC e busca-se identificar na entrada as construções
que correspondem às produções da GLC.
Como construir um compilador utilizando ferramentas Java – p. 2/2
Descendente recursiva
Um analisador descendente recursivo utiliza essa última
estratégia.
Recursivo, pois a cada símbolo não terminal da GLC
corresponde um método (muitas vezes recursivo).
Cada método deve “ler” a entrada e reconhecer a
estrutrutura do não-terminal.
Como construir um compilador utilizando ferramentas Java – p. 3/2
Exemplo
hclassdecl i →
′′ class′′ ′′ ident′′
[ ′′ extends′′ ′′ ident′′ ]
hclassbodyi
Como construir um compilador utilizando ferramentas Java – p. 4/2
Exemplo
hclassdecl i →
′′ class′′ ′′ ident′′
[ ′′ extends′′ ′′ ident′′ ]
hclassbodyi
verificar se o próximo token da entrada é class.
Como construir um compilador utilizando ferramentas Java – p. 4/2
Exemplo
hclassdecl i →
′′ class′′ ′′ ident′′
[ ′′ extends′′ ′′ ident′′ ]
hclassbodyi
verificar se o próximo token da entrada é class.
verificar se o próximo token é ident.
Como construir um compilador utilizando ferramentas Java – p. 4/2
Exemplo
hclassdecl i →
′′ class′′ ′′ ident′′
[ ′′ extends′′ ′′ ident′′ ]
hclassbodyi
verificar se o próximo token da entrada é class.
verificar se o próximo token é ident.
verificar se o próximo token é extends. Se não for, deve
continuar a análise, pois essa parte da produção é
opcional. Se for, deve verificar se o token seguinte é um
ident. Se for, deve continuar com a análise.
Como construir um compilador utilizando ferramentas Java – p. 4/2
Exemplo
hclassdecl i →
′′ class′′ ′′ ident′′
[ ′′ extends′′ ′′ ident′′ ]
hclassbodyi
verificar se o próximo token da entrada é class.
verificar se o próximo token é ident.
verificar se o próximo token é extends. Se não for, deve
continuar a análise, pois essa parte da produção é
opcional. Se for, deve verificar se o token seguinte é um
ident. Se for, deve continuar com a análise.
verificar se o resto da entrada corresponde ao
não-terminal classboby. Isso não é feito diretamente
pelo método, mas, sim, invocando o método
correspondente àquele não-terminal.
Como construir um compilador utilizando ferramentas Java – p. 4/2
Exemplo
void classdecl()
{
if ( curToken.type == CLASS) // token corrente é "class"
analex();
// chama AL. curToken recebe novo token
else
SintaticError();
// ocorreu erro sintático
if ( curToken.type == IDENT) // token corrente é identificador
analex();
else
SintaticError();
if ( curToken.type == EXTENDS) // token corrente é "extends"
{
analex();
if ( curToken.type == IDENT) // token é identificador
analex();
else
SintaticError();
}
classbody();
}
Como construir um compilador utilizando ferramentas Java – p. 5/2
Gramáticas LL(1)
Cuidado na escolha da grámática.
Como construir um compilador utilizando ferramentas Java – p. 6/2
Gramáticas LL(1)
Cuidado na escolha da grámática.
O primeiro L significa left to right e indica que a leitura
da entrada é feita da esquerda para a direita.
Como construir um compilador utilizando ferramentas Java – p. 6/2
Gramáticas LL(1)
Cuidado na escolha da grámática.
O primeiro L significa left to right e indica que a leitura
da entrada é feita da esquerda para a direita.
O segundo L significa left linear e indica que sempre o
símbolo não terminal que estiver mais à esquerda será
reconhecido primeiro.
Como construir um compilador utilizando ferramentas Java – p. 6/2
Gramáticas LL(1)
Cuidado na escolha da grámática.
O primeiro L significa left to right e indica que a leitura
da entrada é feita da esquerda para a direita.
O segundo L significa left linear e indica que sempre o
símbolo não terminal que estiver mais à esquerda será
reconhecido primeiro.
E o número 1 indica que o número de símbolos de
lookahead necessários é 1.
Como construir um compilador utilizando ferramentas Java – p. 6/2
Gramática LL(1)?
hclassdecl i →
′′ class′′ ′′ ident′′
[ ′′ extends′′ ′′ ident′′ ]
hclassbodyi
Como construir um compilador utilizando ferramentas Java – p. 7/2
Gramática LL(1)?
hclassdecl i →
′′ class′′ ′′ ident′′
[ ′′ extends′′ ′′ ident′′ ]
hclassbodyi
hclassdecl i →
′′ class′′ ′′ ident′′
hclassbodyi |
′′ class′′ ′′ ident′′ ′′ extends′′ ′′ ident′′
hclassbodyi
Como construir um compilador utilizando ferramentas Java – p. 7/2
Tabela preditiva
Indica qual “caminho” seguir
S → aSAb | bAa
A → bAb | c
Terminais
Não-terminal
S
A
a
b
c
S → aSAb S → bAa
A → bAb A → c
As posições vazias ou mais do que uma produção.
Como construir um compilador utilizando ferramentas Java – p. 8/2
Recursão à esquerda
Um caso típico de problemas ao construir a tabela
preditiva é relativo à a existência de recursão à
esquerda.
∗
B ⇒ Bα
A partir de B podemos gerar uma forma sentencial em
que o próprio B aparece como primeiro símbolo.
Ao tentar reconhecer B, o AS descendente recursivo
invoca o método correspondente ao não-terminal.
Olhando para o token da entrada, esse método pode
escolher um caminho que leve ao aparecimento de
outro B, sem que nenhum token tenha sido consumido –
o que caracteriza a recursão à esquerda.
Como construir um compilador utilizando ferramentas Java – p. 9/2
Construção da tabela preditiva
À primeira vista, pode parecer fácil calcular a tabela
preditiva de uma GLC. Basta olhar quais são os tokens que
iniciam as produções do não-terminal B e colocar na linha
de B, na coluna desses tokens, as produções que eles
iniciam. Porém, existem diversas situações que podem
complicar essa tarefa.
Como construir um compilador utilizando ferramentas Java – p. 10/2
Dificuldade I
Se temos B → Aα | Cβ , então é preciso saber quais são os
tokens que iniciam as produções de A e C para decidir qual
produção utilizar. E essa situação pode se propagar, uma
vez que A e C podem também iniciar com não-terminais.
hprogi →
′′ class′′ ′′ ident′′
[ hclassbodyi ]
hclassbodyi → hfuncbodyi ′′ .′′ | hvardecl i ′′ ;′′
Como construir um compilador utilizando ferramentas Java – p. 11/2
Dificuldade II
Se temos B → A1 A2 ...An aα, precisamos saber se A1 ,... An
podem gerar a cadeia vazia. Se isso acontecer, então o
token a também deve ser utilizado para decidir qual
produção de B utilizar.
hprogi →
′′ class′′ ′′ ident′′
[ hclassbodyi ]
hclassbodyi → hfuncbodyi hvardecl i ′′ ;′′
hfuncbodyi →
[ halgumacoisai ]
hvardecl i →
[ houtracoisai ]
Como construir um compilador utilizando ferramentas Java – p. 12/2
Dificuldade III
∗
Se temos A → aBb; B → α e α ⇒ λ, então b deve ser
considerado como um token que deve ser utilizado para
decidir pela produção B → α ao tentar reconhecer B.
hprogi →
′′ class′′ ′′ ident′′
hclassbodyi ′′ ;′′
hclassbodyi → hfuncbodyi |hvardecl i ′′ .′′
hfuncbodyi →
[ halgumacoisai ]
hvardecl i →
[ houtracoisai ]
Como construir um compilador utilizando ferramentas Java – p. 13/2
Algoritmo para construir a TP
Adicionar $, que aparece no fim da cadeia
Computar FIRST: Seja α uma forma sentencial da
gramática. Então FIRST(α) é definido como o conjunto
∗
de todos os símbolos terminais a tal que α ⇒ aβ , ou
seja, o conjunto de terminais que iniciam alguma cadeia
derivada a partir de α.
Computar FOLLOW: Seja B um não-terminal da
gramática e S o seu símbolo inicial. O conjunto
FOLLOW(B) é formado pelos terminais a tal que
∗
S ⇒ αBaβ , ou seja, existe uma forma sentencial
derivável a partir do símbolo inicial em que a aparece
imediatamente à direita de B.
Como construir um compilador utilizando ferramentas Java – p. 14/2
FIRST
para o terminal a, FIRST(a) = {a};
para o não-terminal B, tal que B → A1 A2 ...An , onde Ai
são símbolos terminais ou não terminais, fazemos:
inicialmente FIRST(B) = {}
repetimos FIRST(B) = FIRST(B) ∪ FIRST(Ai ), para
i = 1, 2, ... até que encontremos algum i tal que
Ai não deriva λ;
para um string α = A1 A2 ...An , onde Ai são símbolos
terminais ou não terminais, fazemos:
repetimos FIRST(α) = FIRST(α) ∪ FIRST(Ai ), para
i = 1, 2, ... até que encontremos algum i tal que
Ai não deriva λ.
Como construir um compilador utilizando ferramentas Java – p. 15/2
Exemplo
expression →
term expression′
expression′ → −term [expression′ ] |
+term [expression′ ] | λ
term →
f actor term′
term′ →
∗f actor [term′ ] | /f actor [term′ ] | λ
f actor →
ident | constant | (expression)
Como construir um compilador utilizando ferramentas Java – p. 16/2
FIRST: exemplo
FIRST($) = {$}
Como construir um compilador utilizando ferramentas Java – p. 17/2
FIRST: exemplo
FIRST($) = {$}
FIRST(+) = {+}
Como construir um compilador utilizando ferramentas Java – p. 17/2
FIRST: exemplo
FIRST($) = {$}
FIRST(+) = {+}
FIRST(-) = {-}
Como construir um compilador utilizando ferramentas Java – p. 17/2
FIRST: exemplo
FIRST($) = {$}
FIRST(+) = {+}
FIRST(-) = {-}
FIRST(*) = {*}
Como construir um compilador utilizando ferramentas Java – p. 17/2
FIRST: exemplo
FIRST($) = {$}
FIRST(+) = {+}
FIRST(-) = {-}
FIRST(*) = {*}
FIRST(/) = {/ }
Como construir um compilador utilizando ferramentas Java – p. 17/2
FIRST: exemplo
FIRST($) = {$}
FIRST(+) = {+}
FIRST(-) = {-}
FIRST(*) = {*}
FIRST(/) = {/ }
FIRST(ident) = {ident}
Como construir um compilador utilizando ferramentas Java – p. 17/2
FIRST: exemplo
FIRST($) = {$}
FIRST(+) = {+}
FIRST(-) = {-}
FIRST(*) = {*}
FIRST(/) = {/ }
FIRST(ident) = {ident}
FIRST(constant) = {constant}
Como construir um compilador utilizando ferramentas Java – p. 17/2
FIRST: exemplo
FIRST($) = {$}
FIRST(+) = {+}
FIRST(-) = {-}
FIRST(*) = {*}
FIRST(/) = {/ }
FIRST(ident) = {ident}
FIRST(constant) = {constant}
FIRST(( ) = {( }
Como construir um compilador utilizando ferramentas Java – p. 17/2
FIRST: exemplo
FIRST($) = {$}
FIRST(+) = {+}
FIRST(-) = {-}
FIRST(*) = {*}
FIRST(/) = {/ }
FIRST(ident) = {ident}
FIRST(constant) = {constant}
FIRST(( ) = {( }
FIRST(factor) = {ident, constant, ( }
Como construir um compilador utilizando ferramentas Java – p. 17/2
FIRST: exemplo
FIRST(term’) = {*, / }
Como construir um compilador utilizando ferramentas Java – p. 18/2
FIRST: exemplo
FIRST(term’) = {*, / }
FIRST(term) = {ident, constant, ( }
Como construir um compilador utilizando ferramentas Java – p. 18/2
FIRST: exemplo
FIRST(term’) = {*, / }
FIRST(term) = {ident, constant, ( }
FIRST(expression’) = {+, -}
Como construir um compilador utilizando ferramentas Java – p. 18/2
FIRST: exemplo
FIRST(term’) = {*, / }
FIRST(term) = {ident, constant, ( }
FIRST(expression’) = {+, -}
FIRST(expression) = {ident, constant, ( }
Como construir um compilador utilizando ferramentas Java – p. 18/2
FOLLOW
adicionar o indicador de fim de cadeia $ ao conjunto
FOLLOW(S), onde S é o símbolo inicial da GLC;
dada a produção B → αAβ , fazemos:
FOLLOW(A) = FOLLOW(A) ∪ FIRST(β)
∗
se β ⇒ λ, então fazemos
FOLLOW(A) = FOLLOW(A) ∪ FOLLOW(B).
Como construir um compilador utilizando ferramentas Java – p. 19/2
FOLLOW: exemplo
FOLLOW(expression) = {$, )}
FOLLOW(expression′ ) = {$, )}
FOLLOW(term) = {+, -, $, )}
FOLLOW(term′ ) = {+, -, $, )}
FOLLOW(f actor) = {*, /, +, -, $, )}
Como construir um compilador utilizando ferramentas Java – p. 20/2
Finalizando a tabela
dada a produção B → α, para todo terminal
a ∈ FIRST(α), devemos colocar essa produção na
posição (B, a) da tabela;
∗
dada a produção B → α, onde α ⇒ λ, para todo terminal
a ∈ FOLLOW(B), devemos colocar essa produção na
posição (B, a) da tabela.
Como construir um compilador utilizando ferramentas Java – p. 21/2
Resultado
Terminais
Não terminais ident constant +, - *, / ( ) $
expression
1
1
1
expression’
2
3 3
term
4
4
4
term’
6
5
6 6
factor
7
8
9
Como construir um compilador utilizando ferramentas Java – p. 22/2
Resultado
(1)expression → term expression′
(2)expression′ → +term expression′
(3)expression′ → λ
(4)term → f actor term′
(5)term′ → ∗f actor term′
(6)term′ → λ
(7)f actor → ident
(8)f actor → constant
(9)f actor → (expression)
Como construir um compilador utilizando ferramentas Java – p. 23/2
Como usar a TP
A tabela preditiva pode ser usada, além da verificação
da gramática, para construir o AS. Para implementar o
método de um não-terminal, podemos olhar a tabela e
determinar para cada terminal válido quais são as
seqüências de ações a tomar, como, por exemplo,
consumir um terminal ou invocar os métodos de outros
não-terminais.
Como construir um compilador utilizando ferramentas Java – p. 24/2
Como usar a TP
A tabela preditiva pode ser usada, além da verificação
da gramática, para construir o AS. Para implementar o
método de um não-terminal, podemos olhar a tabela e
determinar para cada terminal válido quais são as
seqüências de ações a tomar, como, por exemplo,
consumir um terminal ou invocar os métodos de outros
não-terminais.
Porém, se a linguagem é complexa, o cálculo da tabela
preditiva e a construção do AS de forma manual são
tarefas árduas. Ainda mais se estivermos trabalhando
com uma gramática na BNF, pois, como vimos, o cálculo
dos conjuntos FIRST e FOLLOW consideram apenas
produções normais, sem os operadores da BNF.
Como construir um compilador utilizando ferramentas Java – p. 24/2
Como usar a TP
Nesse ponto entram os programas como o JavaCC, que,
baseado na definição da linguagem em BNF, gera todos
os métodos correspondentes aos não-terminais e
verifica possíveis problemas, avisando ao
implementador os pontos da gramática que os
originaram.
Como construir um compilador utilizando ferramentas Java – p. 25/2
Exercícios
Dê exemplo de uma gramática que gera uma tabela
preditiva com mais de uma entrada em alguma posição.
Dê exemplo de uma gramática com recursão à
esquerda. Elimine a recursão à esquerda.
Dê exemplos, retirados da gramática de X ++ que
mostrem as três dificuldades na construção da tabela
preditiva.
Compute o FIRST e FOLLOW de alguns símbolos de
X ++ .
Como construir um compilador utilizando ferramentas Java – p. 26/2
Download