Pág. 2
O que é um Compilador?
“Um compilador é um programa que lê um programa
escrito em uma linguagem (linguagem fonte) e a
traduz em um programa equivalente em outra
linguagem (linguagem alvo).”
Aho, Sethi, Ullman.
Pág. 3
O que é um Compilador?
Nesse processo de tradução, há duas tarefas
básicas a serem executadas por um compilador,
conhecida como modelo análise-síntese:
– Análise, em que o texto de entrada (na linguagem fonte) é
examinado, verificado e compreendido
• Análise léxica, sintática e semântica
– Síntese, ou geração de código, em que o texto de saída
(na linguagem objeto) é gerado, de forma a corresponder
ao texto de entrada
Pág. 4
É possível representar completamente a sintaxe de
uma LP através de uma gramática sensível ao
contexto.
Mas como não existem algoritmos práticos para
tratar essas gramáticas, a preferência recai em usar
gramáticas livres de contexto.
Deixa-se para a análise semântica a verificação de
todos os aspectos da linguagens que não se
consegue exprimir de forma simples usando
gramáticas livres de contexto.
Pág. 5
A implementação de reconhecedores de linguagens
regulares (autômatos finitos) é mais simples e mais
eficiente do que a implementação de reconhecedores de
linguagens livres de contexto (autômatos de pilha).
Nesse caso, é possível usar expressões regulares para
descrever a estrutura de componentes básicos das LP, tais
como identificadores, palavras reservadas, literais numéricos,
operadores e delimitadores, etc.
Essa parte da tarefa de análise (análise léxica) é
implementada separadamente, pela simulação de autômatos
finitos.
Pág. 6
Compiladores – Separando em partes
Um dos modelos possíveis para a construção de compiladores faz a separação total entre o front-end, encarregado da fase de análise, e o back-end, encarregado da geração de código, de forma que◦ O front-end e back-end se comunicam
apenas através da representação intermediária
◦ O front-end depende exclusivamente da linguagem fonte;
◦ O back-end depende exclusivamente da linguagem objeto.
Verificador de Tipos
Parser
Desugarer
Sintaxe Core
CoreToSTG
Sintaxe STG
Gerador de IL
Código MSIL
(Texto)
ILDASM
Gerador GHC
Código Assembly
/ Código C
Assembler / Compilador C
Código Nativo
CorePrinter
ArquivoCore
PhxSTGCompiler
JIT
Código Nativo
Arquivo Haskell '
Otimizações
Haskell.
NE
T
GH
C N
ativo
Assembly MSIL
Front-end
Back-end
Pág. 7
Compiladores – Separando em partes
Um dos modelos possíveis para a construção de
compiladores faz a separação total entre o front-
end, encarregado da fase de análise, e o back-end,
encarregado da geração de código, de forma que
– O front-end e o back-end se comunicam apenas através
da representação intermediária
Pág. 8
Compiladores – Separando em partes
– O front-end depende exclusivamente da linguagem fonte;
• Consiste das fases que dependem primariamente da linguagem
fonte e são praticamente independentes da máquina alvo.
Normalmente, inclui a análise léxica, análise sintática, análise
semântica, a geração do código intermediário, além do
gerenciamento da tabela de símbolos e o tratador de erros. Às vezes
até o otimizador de código é colocado no front end.
– O back-end depende exclusivamente da linguagem destino.
• Consiste das fases que dependem da máquina alvo e geralmente
não dependem, em nada, do programa-fonte. As fases que contêm
estas propriedades são o otimizador de código e o gerador de
código, além do gerenciador da tabela de símbolos e o tratador de
erros.
Pág. 9
Compiladores – Separando em partes
Simplifica a implementação de novos
compiladores
◦ Front-end específico para cada
linguagem
◦ Back-end específico para a arquitetura
alvo
Pág. 10
Compiladores - Fases
Analisador
léxico
Analisador
sintático
Analisador
semântico
Gerador de
código
intermediário
Otimizador
de código
Gerador de
código
código fonte
código alvo
Tratador de
erros
Gerador tabela
de símbolos
Pág. 11
Fases
Conjunto de alterações feitas no código as quais
são responsáveis por uma atividade específica do
processo de compilação
– Análise Léxica (scanner)
– Análise Sintática (parser)
– Análise Semântica
– Otimização
– Geração de código
Pág. 12
Análise Léxica
Também chamada de scanner
Agrupa caracteres em símbolos (ou tokens)
Entrada: fluxo de caracteres
Saída: fluxo de símbolos
Símbolos são:
– Palavras reservadas, identificadores de variáveis e
procedimentos, operadores, pontuação,...
Uso de expressões regulares no reconhecimento
Lex/Flex são ferramentas que geram scanners.
Pág. 13
Análise Léxica
Dado os caracteres da instrução
montante := saldo + taxa_de_juros * 30;
São identificados os seguintes tokens:
Identificador montant
Símbolo de atribuição :=
Identificador saldo
Símbolo de adição +
Identificador taxa_de_juros
Símbolo de multiplicação *
Número 30
Pág. 14
Análise Sintática
Também chamada de parser
Agrupa símbolos em unidades sintáticas
– Ex.: os 3 símbolos A+B podem ser agrupados em uma estrutura
chamada de expressão.
Expressões depois podem ser agrupados para formar
comandos ou outras unidades.
Saída: representação da árvore de parse do programa
Gramática livre de contexto é usada para definir a estrutura
do programa reconhecida por um parser
Yacc/Bison são ferramentas para gerar parsers
Pág. 15
Análise Sintática
Comando
Expressão
Identificador
Expressão
Expressão
Expressão
Expressão
:=
+
*
Identificador
Identificador
montante
saldo
taxa_de_juros
Número
60
Árvore gerada para: montante := saldo + taxa_de_juros * 60
Pág. 16
Análise Semântica
Verifica se estruturas sintáticas, embora corretas
sintaticamente, têm significado admissível na linguagem.
Por exemplo, não é possível representar em uma gramática
livre de contexto uma regra como “todo identificador deve
ser declarado antes de ser usado“
Um importante componente é checagem de tipos.
Utiliza informações coletadas anteriormente e armazenadas
na tabela de símbolos
Considerando “A + B”, quais os possíveis problemas
semânticos?
Saída: árvore de parse anotada
Pág. 17
Análise Semântica
montante +
*
saldo
taxa_de_juros
*
60
montante +
*
saldo
taxa_de_juros
*
inttoreal
Conversão de inteiro para real inserida pela análise semântica
Pág. 18
Gerador de Código Intermediário
Usa as estruturas produzidas pelo analisador
sintático e verificadas pelo analisador semântico
para criar uma seqüência de instruções simples
(código intermediário)
Está entre a linguagem de alto nível e a linguagem
de baixo nível
Pág. 19
Gerador de Código Intermediário
Considere que temos um único registrador
acumulador.
Considere o comando de atribuição
x := a + b * c
pode ser traduzido em:
t1:=b*c
t2:=a+t1
x:=t2
Pode-se fazer um gerador de código relativamente
simples usando regras como:
Pág. 20
Gerador de Código Intermediário
Toda operação aritmética (binária) gera 3 instruções. Para b*c
1. Carga do primeiro operando no acumuladorload b
2. Executa a operação correspondente com o segundo operando, deixando o resultado no acumuladormult c
3. Armazena o resultado em uma nova variável temporáriastore t1
Um comando de atribuição gera sempre duas instruções. Para x:= t2
1. Carrega o valor da expressão no acumuladorload t2
2. Armazena o resultado na variávelstore x
Pág. 21
Gerador de Código Intermediário
Para o comando de atribuiçãox := a + b * c;
é gerado o código intermediário:1. Load b { t1 := b * c }2. Mult c3. Store t14. Load a { t2 := a + t1 }5. Add t16. Store t27. Load t28. Store x { x := t2 }
Pág. 22
Otimizador de Código
Independente da máquina
Melhora o código intermediário de modo que o
programa objeto seja menor (ocupe menos espaço
de memória) e/ou mais rápido (tenha tempo de
execução menor)
A saída do otimizador de código é um novo código
intermediário
Pág. 23
Otimizador de Código
1. Load b
2. Mult c
3. Add a
4. Store x
Pág. 24
Otimizador de Código
Pág. 25
Gerador de Código
Produz o código objeto final
Cada máquina ou cada plataforma possui um conjunto
diferente de instruções e de meios de acesso ao sistema
operacional. Em geral é necessário um gerador de código
para cada plataforma.
Toma decisões com relação à:
– Alocação de espaço para os dados do programa;
– Seleção da forma de acessá-los;
– Definição de quais registradores serão usados, etc.
Projetar um gerador de código que produza programas
objeto eficientes é uma das tarefas mais difíceis no projeto
de um compilador
Pág. 26
Gerador de Código
Várias considerações têm que ser feitas:
– Há vários tipos de instruções correspondendo a vários
tipos de dados e a vários modos de endereçamento;
– Há instruções de soma específicas, por exemplo para
incrementar/decrementar de 1;
– Algumas somas não foram especificadas explicitamente;
– Cálculo de endereço de posições em vetores;
– Incremento/decremento registrador de topo pilha;
– Local onde armazenar variáveis;
– Alocação de registradores.
Pág. 27
Gerenciamento da Tabela de Símbolos
Uma função essencial do compilador é registrar os
identificadores usados no programa fonte e coletar
as informações sobre os seus diversos atributos de
um identificador, tais como: memória alocada, tipo,
escopo).
Uma tabela de símbolos é uma estrutura de dados
contendo um registro para cada identificador, com
os campos contendo os atributos do identificador.
Pág. 28
Tratamento de Erros
Tipos de erros: léxicos, sintáticos, semânticos e
lógicos.
Os erros sintáticos são os mais freqüentes.
É ativado sempre que for detectado um erro no
programa fonte. Ele deve avisar o programador da
ocorrência do erro emitindo uma mensagem, e
ajustar-se novamente à informação sendo passada
de fase a fase de modo a poder completar o
processo de compilação (mesmo que não seja mais
possível gerar código objeto, a análise léxica e
sintática deve prosseguir até o fim).