Compiladores
Profº. Johni Douglas Marangon
cpl.johnidouglas.com.br
• A escrita de compiladores abrange conceitos de linguagens de
programação, arquitetura de máquina, teoria de linguagens e
engenharia de software.
• O objetivo dessa aula é apresentar uma introdução ao processo de
compilação dando uma visão de alto nível da estrutura de um
compilador.
Linguagens de programação
• A linguagem é o meio de comunicação entre pessoas e é chamada de
língua ou idioma.
• Em programação a linguagem é o meio de comunicação entre o
pensamento humano e as ações de um computador.
• Todo o software é escrito em uma linguagem e deve ser traduzido
para um formado em que o computador possa ser executado.
• Os programas executados no computador são sequencias de zeros e
uns e representam valores como inteiros, strings e instruções.
• Exemplo de execução de uma soma em linguagem de máquina.
• Uma linguagem de programação é de alto nível se sua representação
esta próxima do domínio da aplicação e do problema a ser resolvido.
• As linguagem de baixo nível é uma linguagem própria do computador
e possuem uma gramatica composta por zeros e uns.
• O processo de tradução de uma linguagem de alto nível para uma
linguagem de abaixo nível é feita normalmente por software
conhecidos como compiladores ou interpretadores.
• As linguagem são classificadas em 5 gerações.
•
•
•
•
•
Primeira geração: linguagem de máquina – baixo nível.
Segunda geração: Linguagem simbólica ou assembly – baixo nível.
Terceira geração: Linguagem orientadas a usuários – alto nível.
Quarta geração: Linguagem orientada a aplicação – alto nível.
Quinta geração: Linguagem de conhecimento – alto nível.
• As primeiras aplicações eram escrita em linguagem de máquina logo
depois surgiram as linguagem simbólicas ou de montagem que
diminuíram o problema e substituíram as instruções binarias pro
mnemônicos.
• O montador converte o código assembly para linguagem de maquina.
• As linguagem de terceira geração são voltadas a solução de
problemas específicos – aplicações comerciais e cientificas – e se
caracterizam por serem por serem declarativas, imperativas,
procedimentais e logicas.
• Seus programas descreverem como o problema deve ser resolvido.
• A terceira geração de linguagem foi projetada para serem utilizada
por engenheiro de software ou simplesmente programadores.
• As linguagem de quarta geração surgiram para serem utilizar pro
usuários finais sendo de fácil programação permitindo que os
próprios usuário resolvam os seus problemas. Excel , Acesse, SQL.
• As linguagem de quinta geração são voltadas para inteligência
artificial. Como exemplo temos o PROLOG.
Tradutores
• São sistemas que aceitam como entrada um programa escrito em
uma linguagem e produzem como resultado um programa
equivalente em outra forma. São classificados em:
• Montadores: ou assemblers, mapeiam instruções simbólicas para instruções
de máquina.
• Marco-assemblers: funcionam como os montadores mas tem a possibilidade
de criar “macros”.
• Compiladores: Mapeiam programas escritos em linguagem de alto nível para
linguagem simbolica ou de máquina.
• Pre-compiladores: pré-proessadores ou filtros são programas que convertem
uma linguagem de alto nível estendida da original para outra linguagem de
alto nível.
• Interpretadores: Como entrada possuem uma linguagem intermediaria ou a
própria linguagem fonte e um programa compilador produz o efeito de
execução, interpretadores não geram código objeto.
O tempo de compilação é o intervalo de tempo de conversão de um programa fonte para um
programa objeto. Já o programa objeto é executado no intervalo de tempo chamado tempo de
execução.
Compiladores cruzados ou cross-compilers são compiladores que geram código objeto para outras
máquinas que não são hospedeiras
Compiladores x Interpretadores
• Um programa gerado por um compilador tende a ser mais rápidos
que um programa executado por um interpretador, porem um
interpretadores oferecem melhora condições de diagnósticos de
erros.
Estrutura de um compilador
• O processo de compilação é muito complexo, porem existe uma
estrutura básica dividia em fases.
• As fases são representadas por duas tarefas a analise e a síntese.
• A tarefe de analise é conhecida como front-end e a tarefa de síntese é
conhecida como back-end.
• Front-end tem a função de garantira a estrutura gramatical e
semântica do programa fonte.
• Back-end tem o objetivo de construir o programa objeto.
• No processo de compilação pode ocorre a combinação de diferentes
linguagem fontes para diferentes maquinas alvo.
• A compilação inicia com a fase de analise léxica, que varre o programa
fonte e transfora o texto em conjunto de tokens.
• Logo após tem a analise sintática que garante a validade de todas as
regras semânticas do fluxo de tokens e cria a arvores sintática.
• A terceira fase é analise semântica que garante as validade das regras
semânticas.
• Na próxima fase temos a geração do código intermediário que cria
uma abstração do código fonte.
• Na sequencia temos a otimização do código intermediário
• E por fim a geração do código objeto.
• Os interpretadores são uma forma de tradução no qual as duas
ultimas fases de um compilador são substituídas por um programa
que executa o código intermediário.
• Dividimos os interpretadores em:
• Interpretadores puros: AS instruções são executadas uma a uma. Ex:
comandos shell.
• Integradores mistos: traduzem todo o script em código intermediário.
Analise Léxica
• Primaria faze do processo de compilação.
• Também conhecida como leitura ou scanning.
• Tem como objetivo identificar as unidade léxicas(lexemas).
• Le caractere a caractere do programa fonte.
• Os comentário e espaços em branco são removidos.
• O resultado é um conjunto de tokens.
• Nesse momento é iniciado a construção da tabelas de símbolos.
Analise Léxica – exemplo
123 x1 ; Y2 true begin
Lexema
Categoria
123
Constante inteira
X1
Nome de variável ou procedimento
;
Símbolo especial (ponto e vírgula)
Y2
Nome de variável ou procedimento
true
Constante booleana
begin
Palavra reservada
<nome-token, valor-atributo>
position = initial + rate * 60
Lexema
position
=
initial
+
rate
*
60
Símbolo
Id
Significado
Identificador
Entrada
1
Id
Identificador
2
Id
Identificador
3
Número
Inteiro
4
Token
<ID, 1>
<=>
<ID, 2>
<+>
<ID, 3>
<*>
<60>
<ID, 1> <=> <ID, 2> <+> <ID, 3> <*> <60>
Analise Sintática
• A analise sintática é o coração do compilador e tem com objetivo
validar os símbolos do programa.
• O objetivo é validar os símbolos que pertencem ao programa.
• Caso sejam invalido devem emitir um erro sintático informando a
posição exata do erro.
• A estrutura gramatica da linguagem é gerada.
• Os tokens são varridos com o objetivo de criar a árvore sintática.
if (a – 10 > b * 2) a = b;
• No caso do comando acima o analisador deve reconhecer como um
comando valido ou invalido.
position = initial + rate * 60
Mesmo com uma boa técnica de detecção de erros o analisador sintático deve recuperá-los e
continuar o processo de compilação identificando o maior número de erros possíveis.
Sintático tem o sentido de verificação de forma, estrutura sem referência a significado.
Analise Semântica
• O objetivo dessa etapa é garantir a semântica do programa fonte.
• Ele utiliza a arvore sintática e as informações da tabela de símbolos.
• Um exemplo de erro de semântica é operações incompatíveis com
tipos de dados.
• Índice de um vetor deve ser um inteiro.
• Caso necessário nessa fazer deve ser feito a conversão de tipos.
position = initial + rate * 60
• Suponha que a variável position for declarada como inteiro, nesse
caso deverá deve ser identificado um erro de tipo e o processo de
compilação deve ser interrompido.
• Caso position, initial, rate sejam declarados como
ponto flutuante o inteiro 60 deve ser convertido para ponto flutuante.
• Tipos de erros semânticos:
•
•
•
•
•
Tipos de operandos incompatíveis com operadores.
Identificadores não declarados (variáveis e procedimento).
Chamada de funções ou métodos com número incorreto de operadores.
Comando fora de contextos (um comando continue fora de um loop).
Operações de conversão.
Geração de código intermediário
• Nesse momento é gerado o código intermediário que vai ser utilizado
nas próximas fases da compilação.
• Essa faze podem não existir e ser feito diretamente a geração de
código objeto.
• O código intermediário não tem detalhes do código objeto final.
• A representação intermediária deve ser de fácil interpretação pois
será utilizada para a geração do código da máquina alvo.
T1 = inttofloar(60);
T2 = id3 * t1
T3 = id2 + t2
id1 = t3
• Exemplo de código intermediário gerado. Esse exemplo gera código
em uma forma conhecida como código de três endereços.
Uma das vantagens de gerar um código intermediário é a possibilidade de obter um
código final mais eficiente.
Otimização de Código
• Nessa faze o código é otimizado para possuir uma represarão que ira
consumir menos espaço em memória e ira ter velocidade de execução.
• Essa etapa não depende da arquitetura da maquina alvo.
T1 = id3 * 60.0
id1 = id2 * t1
• O numero de otimizações ira depender de como o compilador foi
implementado.
• E uma etapa que consome muitos recursos.
• Alguns compiladores tem uma fase chamada de otimização de código
dependente de maquina.
Geração de código objeto
• E a etapa final onde a representação intermediária e mapeada para
uma linguagem objeto.
• Se for linhagem de máquina gera o código binário para a arquitetura
especifica, fazendo a seleção de registradores alocando espaço na
memoria.
• É uma etapa muito importante pois deve produzir um código objeto
eficiente com uma cuidadosa seleção de registradores.
position = initial + rate * 60
LDF R2,
MULF R2,
LDF R1,
ADDF R1,
STF id1
id3
R2 #60.0
id2
R1, R2
R1
• O programa objeto reflete as instruções de baixo nível da plataforma
que esta sendo utilizada.
• Após essa faze concluir um programa chamado linkeditor reúne
outros programas objetos e recursos adicionais e monta o arquivo
executável.
Linkeditor ou Linker é um programa que reúne módulos compilados e arquivos para criar o
programa executável.
Registradores são áreas no processador que possuem uma grande velocidade e são utilizados
para armazenar valores. Existem vários tipos de registradores com finalidade especificas.
Tabela de símbolos
• Durante toda a faze de compilação algumas informações são
armazenadas em uma tabela chamada de tabela de símbolos. As
informações coletadas nessa tabela dependem do tipo de tradutor
desenvolvido e do programa objeto a ser gerado.
• Exemplo de informação armazenadas na tabela de símbolos
• Variáveis: classe(var), tipo, endereço no texto, posição e tamanho.
• Parâmetros formais: classe(par), tipo mecanismo de passagem.
• Procedimentos/sub-rotinas: classe(proc), número de parâmetros.
• A tabela de símbolos é uma estrutura de dados utilizada em todo o
processo de compilação e tem como objetivo extrair e inserir
informação de forma rápida.
Construção de compiladores
• Os avanços nas linguagem de programação impõem vários desafios
aos projetistas de compiladores. Eles devem criar algoritmos para
traduzir esse recursos para linguagem de maquina.
• Outro desafio e a evolução da arquitetura de computadores.
• A tradução para uma linguagem objeto deve tirar o máximo de
proveito sobre o hardware e o software.
• A compilação tem um papel muito importante no desempeno de
sistemas computacionais.
• A ideia da disciplina não é ensinar todas as técnicas e algoritmos
envolvidos no processo e compilação, pois é algo muito complexo e
infinito.
Java é interprestado ou compialdo?
• O Java é um exemplo incomum de um sistema que combina
compilação e interpretação.
• O javac compila o código fonte para byte codes.
• O java interprea os byte codes na JVM.
• Ainda tem o Just-in-Time Compiler que compila para linguagem de
máquina os byte codes mais utilizados.
Exercícios
Download

Slides - Compiladores