Escolar Documentos
Profissional Documentos
Cultura Documentos
Aula 05 - Compiladores
Aula 05 - Compiladores
AULA 05
printf("%d\n",tokenval);
} else {
4
CONSTRUÇÃO DE UM
ANALISADOR LÉXICO SIMPLES
• Observando o código podemos notar que:
» A função lexan retorna os tokens como
inteiros.
» Caso o token sejam um número (NUM) a
variável global tokenval contem seu valor.
» A constante NUM é definida como 256 pois
tal valor não corresponde a qualquer outro
caractere válido (0 - 255 no ASCII).
» A variável global clinha mantêm uma
contagem do número de linha que está sendo
correntemente processada.
Análise Léxica
CONSTRUÇÃO DE UM
ANALISADOR LÉXICO SIMPLES
• Para compilar os arquivos
separadamente:
» gcc -c lexan.c
» gcc -c lexantest.c
» gcc lexan.o lexantest.o -o lexantest
• Para compilar todos os arquivos
simultaneamente:
» gcc lexan.c lexantest.c -o lexantest
Análise Léxica
BUFERIZAÇÃO DE ENTRADA
• Como a leitura da entrada pode
consumir quantidade significativa de
tempo da compilação, é interessante
otimizar tal operação.
• Duas técnicas simples podem ser
particularmente úteis:
» Uso de pares de buffers e
» Uso de sentinelas.
Análise Léxica
PARES DE BUFFERS
• A leitura de caracteres um a um é
ineficiente em termos de tempo.
• Como também existe a necessidade da
leitura de vários caracteres à frente do
lexema para sua identificação, o retorno
dos caracteres não usados significa
desperdício.
• Propõe-se o uso de um buffer de
tamanho 2N dividido em duas metades (N
é número de caracteres em um bloco de
disco).
Análise Léxica
PARES DE BUFFERS
• Princípios de funcionamento:
» Cada metade do buffer pode ser lida através de uma única operação de leitura.
Um eof é inserido caso a metade não seja preenchida.
» Mantêm-se dois apontadores: um para o início do lexema e outro para leitura
adiante.
» Se um apontador avança além do limite do buffer, a outra metade é lida e o
apontador transferido para tal metade.
• Manipulando-se tais apontadores reduz-se ao máximo as operações de leitura e o
desperdício de tempo devido a “devolução” de caracteres.
: ; : \n : \r : \t : E : : = : : M : * : C : * : * : 2 : ; : eof
Apontador Apontador
de Início do de Leitura
Lexema Adiante
Análise Léxica
SENTINELAS
• O esquema dos pares de buffers apresenta o inconveniente de um
duplo teste quando do avanço do ponteiro de leitura adiante.
• Adicionando um caractere especial ao final de cada metade do buffer
(cujo tamanho agora é 2N+2) reduz-se o teste duplo para outro simples.
• Tal caractere é denominado sentinela, devendo ser distinto dos
caracteres possíveis do alfabeto de entrada.
1a. Metade do Buffer 2a. Metade do Buffer
Apontador Apontador
de Início do de Leitura
Lexema Adiante
0 S0 S1 1
0
0 S0 S1 1
0
Análise Léxica
AUTÔMATOS DE ESTADOS FINITOS
• Um diagrama de Conway é uma forma conveniente de representarmos
uma máquina abstrata denominada “Autômatos de Estados Finitos” ou
abreviadamente AF.
• Dizemos que um AF aceita ou reconhece uma certa cadeia c contendo n
símbolos (n>=0) quando é conduzida a um estado final por meio das
transições provocadas por todos os caracteres contidos na cadeia c.
início 1
0 S0 S1 1
0
início
a
b S0 S1 b
Análise Léxica
AUTÔMATOS DE ESTADOS FINITOS
• TIPOS
» Existem os AF Não-Determinísticos (AFN) e os AF Determinísticos
(AFD):
Num AFN um mesmo símbolo pode rotular duas diferentes transições
para fora de um mesmo estado assim como pode rotular uma
transição.
Num AFD só pode existir uma transição rotulada com um dado símbolo
para fora de um mesmo estado e não se aceitam transições rotuladas por
.
• IMPLEMENTAÇÃO DE AF’s
» Um AF Determinístico pode ser facilmente implementado utilizando-se
comandos de seleção tipo switch (C ou Java).
» Também podem ser usados vetores mapeando a aplicação de
transição.
» Um AFD genérico pode ser construído de forma que sua
especificação seja obtida de um arquivo de definição.
Análise Léxica
Implementação
EXEMPLO de AFs
// Fonte Java
public class BasicAF { case 1:
public static void main(String a[]) { switch(a[i].charAt(p)) {
int p=0; case '0':
int state=0; state=0; break;
case '1':
while (p<a[i].length()) { state=1; break;
switch(state) { }
case 0: break;
switch(a[i].charAt(p)) { }
case '0': p++;
state=0; break; }
case '1': if (state==1)
state=1; break; System.out.println(”Aceita");
} else
System.out.println("Nao
break; aceita");
}}
Análise Léxica
ANÁLISE SINTÁTICA
• Conceito
» Estuda os métodos usados para decidir se uma cadeia pertence
ou não a uma linguagem definida por uma gramática.
» O objetivo é, se a cadeia pertencer á linguagem, obter a estrutura
sintática correspondente a mesma, em forma de árvore de
derivação.
» Corresponde a segunda fase da compilação e o Analisador
Sintático também é conhecido como Parser.
» A ligação entre o Analisador Sintático e o Léxico se ocorre da
seguinte forma: o A.L. após identificar os tokens existentes na
cadeia, os repassa ao A.S para a criação da árvore de derivação.
» A árvore de derivação pode ser construída explicitamente, ou
apenas realizar as ações conceitualmente como se baseadas na
árvore
» É desejável que os Analisadores Sintáticos possam “tentar
corrigir” erros, de modo a efetuar a análise até o final do código
fonte, retornando de uma só vez a maior quantidade possível de
erros. Análise Sintática
ANÁLISE SINTÁTICA
• Estratégias para Análise Sintática
» São dois os métodos mais usados para
Análise Sintática:
Método Descendente (Top-down)
• Começam pela raiz (regra de partida da gramática)
• Em cada passo, caminha-se em direção as folhas,
substituindo-se símbolos não-terminais por símbolos
não terminais ou terminais.
Método Ascendente (Bottom-up)
• Começam pelas folhas das árvores (tokens do código
fonte)
• Em cada passo, caminha-se em direção à raiz,
substituindo-se símbolos terminais por não-terminais
Análise Sintática
ATIVIDADES
• http://www.linhadecodigo.com.br/artigo/1643/compilad
ores-revisao-dos-principios-tecnicas-e-
ferramentas.aspx
Análise Sintática