Sei sulla pagina 1di 28

Compiladores

Introdução à Compilação
- Aula 01 -
Noiza Waltrick Trindade
[noiza@uems.br]
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.  2
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.  3
É 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.  4
 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.  5
Compiladores – Separando em partes

 Um dos modelos possíveis para a


construção
Front-end
de compiladores faz a Arquivo

'
Haskell

separação total entre o front-end,


encarregado da fase de análise, e o Parser

back-end, encarregado da geração de Verificador de Tipos

código, de forma que Desugarer

Otimizações Sintaxe Core


◦ O front-end e back-end se comunicam
CorePrinter

apenas através da representação CoreToSTG Arquivo


Core
Sintaxe STG
intermediária
◦ O front-end depende
Gerador GHC
Gerador de exclusivamente da
Haskell.NET

IL PhxSTGCompiler
GHC Nativo

Código Assembly

linguagem fonte;
/ Código C Código MSIL
(Texto)

◦Assembler
O back-end
/ dependeILDASM exclusivamente da Assembly MSIL
Compilador C
linguagem objeto.
Código Nativo JIT

Pág.  6 Back-end Código Nativo


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.  7
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.  8
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.  9
Compiladores - Fases

código fonte

Analisador
léxico

Analisador
sintático

Gerador tabela Analisador Tratador de


de símbolos semântico erros
Gerador de
código
intermediário

Otimizador
de código

Gerador de
código
Pág.  10 código alvo
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.  11
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.  12
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.  13
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.  14
Análise Sintática

Comando

:=

Identificador Expressão

montante +
Expressão Expressão

Identificador *
Expressão Expressão
saldo
Identificador Número

taxa_de_juros 60

Árvore gerada para: montante := saldo + taxa_de_juros * 60

Pág.  15
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.  16
Análise Semântica

* *

montante + montante +

saldo * saldo *

taxa_de_juros 60 taxa_de_juros inttoreal

Conversão de inteiro para real inserida pela análise semântica

Pág.  17
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.  18
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.  19
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 acumulador
load b
2. Executa a operação correspondente com o segundo operando,
deixando o resultado no acumulador
mult c
3. Armazena o resultado em uma nova variável temporária
store t1
 Um comando de atribuição gera sempre duas instruções.
Para x:= t2
1. Carrega o valor da expressão no acumulador
load t2
2. Armazena o resultado na variável
store x

Pág.  20
Gerador de Código Intermediário

Para o comando de atribuição


x := a + b * c;
é gerado o código intermediário:
1. Load b { t1 := b * c }
2. Mult c
3. Store t1
4. Load a { t2 := a + t1 }
5. Add t1
6. Store t2
7. Load t2
8. Store x { x := t2 }

Pág.  21
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.  22
Otimizador de Código

1. Load b
2. Mult c
3. Add a
4. Store x

Pág.  23
Otimizador de Código

Pág.  24
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.  25
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.  26
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.  27
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).
Pág.  28

Potrebbero piacerti anche