CES-41
COMPILADORES
Capítulo I
Introdução
Capítulo I - Introdução
1.1 – Compiladores
1.2 – Estrutura de um compilador
1.3 – Interpretadores
1.4 – Automação da construção de compiladores
1.1 – Compiladores
1.1.1 – Definição
Genericamente falando, compilador é um software que
Lê um programa escrito numa linguagem: a linguagemfonte (é o programa-fonte)
Traduz em um programa equivalente, escrito noutra
linguagem : a linguagem-objeto (é o programa-objeto)
Reporta a presença de erros no programa-fonte
Esquematicamente:
Programa
fonte
Compilador
Mensagens
de erro
Programa
objeto
Possíveis linguagens-fontes:
Mais comuns: linguagens tradicionais de programação:
Fortran, Pascal, C, Modula-2, C++, Java, etc.
Linguagens especializadas: simulação, computação gráfica,
banco de dados, experimentos
Possíveis linguagens-objetos:
Mais comuns: linguagem assembly ou linguagem de
máquina de vários computadores
Outras linguagens de programação
Exemplos de linguagens de programação como
linguagens-objetos:
Transformação de programas sequenciais escritos em C ou
Fortran, em programas paralelos escritos em HPF (High
Performance Fortran)
for (i = 1; i <= n; i++)
C[i] = A[i] + B[i]
for (i = 1; i <= n; i++)
C[i] = A[i] + C[i-1]
do parallel i = 1, n
C[i] = A[i] + B[i]
do parallel i = 1, n
C[i] = A[i] + C[i-1]
Tradução de programas realizadores de certos
experimentos:
Escritos numa linguagem especializada
Traduzidos (compilados) para uma linguagem de
programação como C
Compilados depois por um compilador similar ao de C
Programas em Lex e Yacc são traduzidos para C
Lex e Yacc são linguagens especializadas para
construir componentes de um compilador
Há ainda o “compilador” Assembler:
Linguagem-fonte: Assembly
Linguagem-objeto: linguagem de máquina
Enfoque desta disciplina:
A linguagem-fonte é uma linguagem tradicional e a
linguagem-objeto é o Assembly de uma máquina
Para a implementação, a linguagem-fonte é Lex e Yacc e a
linguagem-objeto é C
1.1.2 – O contexto de um compilador
Além do compilador, outros programas são exigidos para
criar um programa executável nalguma máquina
Programa fonte
com diretivas de
pré-processamento
Pré-processador
Programa fonte puro
Compilador
Programa objeto em Assembly
Montador
Código de máquina com endereçamento deslocado
Editor de Ligações
Bibliotecas e outros arquivos
com endereçamento deslocado
Código de máquina executável com endereçamento deslocado
Carregador
Código de máquina
executável com
endereçamento correto
a) Pré-processador – realiza várias tarefas antes da compilação:
Inclusão de arquivos ao programa-fonte – Por exemplo, na
Linguagem C:
#include <math.h>: inclui protótipos de funções
matemáticas pertencentes à biblioteca da linguagem; essas
funções já estão em linguagem de máquina
#include “sistemas.c”: inclui arquivo pertencente ao
acervo do programador; contém código fonte
Processamento de macros – para abreviar construções longas
Exemplo, em C, com as macros:
#define EHPAR(x) (((x)%2)?0:1)
#define ERRO(msg) printf (“ERRO: % s/n”, msg)
pode-se escrever comandos dos tipos:
O
O pré-processador
substitui a primeira
if (EHPAR(a+b)) --------------;
if (valor max) ERRO(“valor muito grande”); parte do #define pela
segunda, realizando
inclusive passagem de
resultado do pré-processamento é:
argumentos
if ((((a+b)%2)?0:1)) ....... ;
if (valor > max) printf(“ERRO:%s\n”,“valor muito grande”);
Processamento de extensões de linguagens:
Algumas linguagens são acrescidas de certos artifícios para
propósitos específicos de certas aplicações
Exemplos: comandos para manipular banco de dados, para
computação gráfica, processamento paralelo, etc.
Muitas linguagens são, na realidade, extensões da
Linguagem C
Diretivas iniciadas pelos caracteres “##” são substituídas
pelo pré-processador por chamadas de funções, comandos do
sistema operacional, etc.
b) Montador (Assembler):
Transforma o código Assembly, produzido pelo compilador,
em código de máquina relocável
Exemplo: programa em C para o cálculo do fatorial de um
número digitado e seus correspondentes em Assembly e em
linguagem de máquina
Supõe-se uma CPU bem simples, com apenas um
registrador de propósitos gerais (AC - acumulador)
Para fazer
C=A+B
Sendo A, B e C
endereços de memória
Instrução
Significado
LD A
AC Mem(A)
ADD B
AC AC + Mem(B)
ST C
Mem(C) AC
C1:
C2:
#include <stdio.h>
void main ( )
{
int n, fat, i;
scanf (“%d”, &n);
fat = 1;
i = 2;
while (i <= n) {
fat = fat * i;
i = i + 1;
}
printf (“%d”, fat);
}
Primeiramente, reserva de espaço
para as constantes 1 e 2
CONST
CONST
1
2
#include <stdio.h>
void main ( )
{
int n, fat, i;
scanf (“%d”, &n);
fat = 1;
i = 2;
while (i <= n) {
fat = fat * i;
i = i + 1;
}
printf (“%d”, fat);
}
Em seguida, reserva de espaço para
as variáveis n, i, fat
C1:
C2:
n:
fat:
i:
CONST
CONST
CONST
CONST
CONST
1
2
0
0
0
#include <stdio.h>
void main ( )
{
int n, fat, i;
scanf (“%d”, &n);
fat = 1;
i = 2;
while (i <= n) {
fat = fat * i;
i = i + 1;
}
printf (“%d”, fat);
}
Agora a tradução dos comandos
C1:
C2:
n:
fat:
i:
CONST
CONST
CONST
CONST
CONST
1
2
0
0
0
Rótulo da 1ª instrução executável:
inic
#include <stdio.h>
void main ( )
{
int n, fat, i;
scanf (“%d”, &n);
fat = 1;
i = 2;
while (i <= n) {
fat = fat * i;
i = i + 1;
}
printf (“%d”, fat);
}
Na realidade, a tradução de scanf
é algo mais complexo:
É uma chamada de subprograma
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
1
2
0
0
0
n
Rótulo da 1ª instrução executável:
inic
#include <stdio.h>
void main ( )
{
int n, fat, i;
scanf (“%d”, &n);
fat = 1;
i = 2;
while (i <= n) {
fat = fat * i;
i = i + 1;
}
printf (“%d”, fat);
}
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
1
2
0
0
0
n
C1
fat
C2
i
Rótulo da 1ª instrução executável:
inic
#include <stdio.h>
void main ( )
{
int n, fat, i;
scanf (“%d”, &n);
fat = 1;
i = 2;
while (i <= n) {
fat = fat * i;
i = i + 1;
}
printf (“%d”, fat);
}
C1:
C2:
n:
fat:
i:
inic:
loop:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
SUB
JP
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
JUMP
loop
“escrever” é o rótulo da
instrução logo após JUMP
Rótulo da 1ª instrução executável:
inic
#include <stdio.h>
void main ( )
{
int n, fat, i;
scanf (“%d”, &n);
fat = 1;
i = 2;
while (i <= n) {
fat = fat * i;
i = i + 1;
}
printf (“%d”, fat);
}
C1:
C2:
n:
fat:
i:
inic:
loop:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
Rótulo da 1ª instrução executável:
inic
#include <stdio.h>
void main ( )
{
int n, fat, i;
scanf (“%d”, &n);
fat = 1;
i = 2;
while (i <= n) {
fat = fat * i;
i = i + 1;
}
printf (“%d”, fat);
}
Na realidade, a tradução de printf
é algo mais complexo:
É uma chamada de subprograma
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
Rótulo da 1ª instrução executável:
inic
#include <stdio.h>
void main ( )
{
int n, fat, i;
scanf (“%d”, &n);
fat = 1;
i = 2;
while (i <= n) {
fat = fat * i;
i = i + 1;
}
printf (“%d”, fat);
}
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
#include <stdio.h>
void main ( )
{
int n, fat, i;
scanf (“%d”, &n);
fat = 1;
i = 2;
while (i <= n) {
fat = fat * i;
i = i + 1;
}
printf (“%d”, fat);
}
Final da compilação
Agora vem a montagem
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
rótulo
endereço
endereço
0
1
2
3
4
5
6
7
8
9
10
11
12
O Assembler monta
uma tabela de
rótulos para ajudar a
preencher o
programa em
linguagem de
máquina
13
14
15
16
17
18
19
20
codop
ender
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
rótulo
endereço
endereço
0
1
2
3
4
5
6
7
8
9
Mnemônico
Cod Op
LD
1
ST
2
ADD
4
SUB
5
MULT
6
JUMP
11
JP
14
READ
15
WRITE
16
STOP
17
10
11
12
13
14
15
16
17
18
19
20
codop
ender
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
rótulo
endereço
endereço
C1
0
0
1
C2
1
1
2
n
2
2
0
3
0
fat
3
4
0
i
4
5
6
7
8
9
Mnemônico
Cod Op
LD
1
ST
2
ADD
4
SUB
5
MULT
6
JUMP
11
JP
14
READ
15
WRITE
16
STOP
17
10
11
12
13
14
15
16
17
18
19
20
codop
ender
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
rótulo
endereço
endereço
C1
0
0
1
C2
1
1
2
n
2
2
0
3
0
fat
3
4
0
i
4
5
inic
5
6
7
8
9
Mnemônico
Cod Op
LD
1
ST
2
ADD
4
SUB
5
MULT
6
JUMP
11
JP
14
READ
15
WRITE
16
STOP
17
10
11
12
13
14
15
16
17
18
19
20
codop
15
ender
2
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
rótulo
endereço
endereço
C1
0
0
1
C2
1
1
2
n
2
2
0
3
0
fat
3
4
0
i
4
5
15
2
inic
5
6
1
0
7
2
3
8
1
1
9
2
4
Mnemônico
Cod Op
LD
1
ST
2
ADD
4
SUB
5
MULT
6
JUMP
11
JP
14
READ
15
WRITE
16
STOP
17
10
11
12
13
14
15
16
17
18
19
20
codop
ender
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
rótulo
endereço
endereço
C1
0
0
1
C2
1
1
2
n
2
2
0
3
0
fat
3
4
0
i
4
5
15
2
inic
5
6
1
0
loop
10
7
2
3
8
1
1
9
2
4
10
5
2
Mnemônico
Cod Op
LD
1
ST
2
ADD
4
SUB
5
MULT
6
JUMP
11
JP
14
READ
15
WRITE
16
STOP
17
11
12
13
14
15
16
17
18
19
20
codop
ender
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
rótulo
endereço
endereço
C1
0
0
1
C2
1
1
2
n
2
2
0
3
0
fat
3
4
0
i
4
5
15
2
inic
5
6
1
0
loop
10
7
2
3
8
1
1
escrever
???
9
2
4
Mnemônico
Cod Op
10
5
2
LD
1
11
14
???
ST
2
ADD
4
SUB
5
MULT
6
JUMP
11
JP
14
READ
15
WRITE
16
STOP
17
12
13
14
15
16
17
18
19
20
codop
ender
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
rótulo
endereço
endereço
C1
0
0
1
C2
1
1
2
n
2
2
0
3
0
fat
3
4
0
i
4
5
15
2
inic
5
6
1
0
loop
10
7
2
3
8
1
1
escrever
???
9
2
4
Mnemônico
Cod Op
10
5
2
LD
1
11
14
???
ST
2
12
1
3
ADD
4
13
6
4
SUB
5
14
2
3
MULT
6
15
1
4
JUMP
11
16
4
0
JP
14
17
2
4
READ
15
18
11
10
WRITE
16
STOP
17
19
20
codop
ender
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
rótulo
endereço
endereço
C1
0
0
1
C2
1
1
2
n
2
2
0
3
0
fat
3
4
0
i
4
5
15
2
inic
5
6
1
0
loop
10
7
2
3
8
1
1
escrever
19
9
2
4
Mnemônico
Cod Op
10
5
2
LD
1
11
14
19
ST
2
12
1
3
ADD
4
13
6
4
SUB
5
14
2
3
MULT
6
15
1
4
JUMP
11
16
4
0
JP
14
17
2
4
READ
15
18
11
10
WRITE
16
19
16
3
STOP
17
20
codop
ender
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
rótulo
endereço
endereço
codop
ender
C1
0
0
1
C2
1
1
2
n
2
2
0
3
0
fat
3
4
0
i
4
5
15
2
inic
5
6
1
0
loop
10
7
2
3
8
1
1
escrever
19
9
2
4
Mnemônico
Cod Op
10
5
2
LD
1
11
14
19
ST
2
12
1
3
ADD
4
13
6
4
SUB
5
14
2
3
MULT
6
15
1
4
JUMP
11
16
4
0
JP
14
17
2
4
READ
15
18
11
10
WRITE
16
19
16
3
STOP
17
20
17
0
C1:
C2:
n:
fat:
i:
inic:
CONST
CONST
CONST
CONST
CONST
READ
LD
ST
LD
ST
loop: SUB
JP
LD
MULT
ST
LD
ADD
ST
JUMP
escrever:WRITE
STOP
END
1
2
0
0
0
n
C1
fat
C2
i
n
escrever
fat
i
fat
i
C1
i
loop
fat
inic
rótulo
endereço
endereço
C1
0
0
1
C2
1
1
2
n
2
2
0
3
0
fat
3
4
0
i
4
5
15
2
inic
5
6
1
0
loop
10
7
2
3
8
1
1
escrever
19
9
2
4
10
5
2
11
14
19
12
1
3
13
6
4
14
2
3
15
1
4
16
4
0
17
2
4
18
11
10
19
16
3
20
17
0
Endereço inicial
da execução: 5
Essa informação
deve acompanhar
o programa em
linguagem de
máquina
codop
ender
O programa em linguagem C é o
programa-fonte
O programa gerado pelo Assembler
é o programa-objeto
O programa-objeto foi montado a
partir do endereço zero da RAM
Esse programa é guardado num
arquivo (extensão obj)
Endereço inicial
da execução: 5
endereço
codop
ender
0
1
1
2
2
0
3
0
4
0
5
15
2
6
1
0
7
2
3
8
1
1
9
2
4
10
5
2
11
14
19
12
1
3
13
6
4
14
2
3
15
1
4
16
4
0
17
2
4
18
11
10
19
16
3
20
17
0
O local para execução é estabelecido
pelo sistema operacional do
computador
Esse local depende da
disponibilidade da RAM
E se o local não for o endereço
zero (por exemplo, endereço 3000)?
Endereço inicial
da execução: 5
endereço
codop
ender
0
1
1
2
2
0
3
0
4
0
5
15
2
6
1
0
7
2
3
8
1
1
9
2
4
10
5
2
11
14
19
12
1
3
13
6
4
14
2
3
15
1
4
16
4
0
17
2
4
18
11
10
19
16
3
20
17
0
Os locais para C1, C2, n, fat e i não
são mais 0, 1, 2, 3 e 4, mas sim 3000,
3001, 3002, 3003 e 3004
Os rótulos inic, loop e escrever
mudarão para os endereços 3005,
3010 e 3019
Então todos os endereços das
instruções estarão com um erro
(deslocamento de 3000 posições)
Isso tem de ser corrigido antes da
execução
Endereço inicial
da execução: 3005
endereço
codop
ender
3000
1
3001
2
3002
0
3003
0
3004
0
3005
15
2
3006
1
0
3007
2
3
3008
1
1
3009
2
4
3010
5
2
3011
14
19
3012
1
3
3013
6
4
3014
2
3
3015
1
4
3016
4
0
3017
2
4
3018
11
10
3019
16
3
3020
17
0
c) Editor de ligações
Antes de corrigir os endereços do programa-objeto, é
necessário juntar a ele todos os subprogramas auxiliares
pertencentes à biblioteca da linguagem
Exemplos: funções para entrada e saída (scanf, printf, etc.),
funções matemáticas (sqr, pow, sqrt, log, sin, cos, etc.)
Esse trabalho de juntar o programa-objeto com tais
subprogramas é feito por um software denominado editor de
ligações (linkage-editor)
O produto do editor de ligações é um arquivo denominado
programa-executável (extensão exe)
d) Carregador
A região de memória onde um programa será alocado para
execução só será conhecida quando ele for chamado para
isso
Então o endereçamento do arquivo executável precisa ser
corrigido, quando sua execução for solicitada
Esse trabalho é feito pelo carregador (loader), que produz a
versão final do programa, pronto para rodar
1.2 – Estrutura de um Compilador
1.2.1 – Componentes de um compilador
O trabalho de compilação é dividido em 2 fases:
Fase de análise e fase de síntese
Além disso, existem atividades que fazem parte das duas
fases
O processo
não precisa
ser
sequencial
while (i < n) i = i + j;
Analisador
léxico
while
Analisador
sintático
Analisador
semântico
Gerador de
código
intermediário
Otimizador de
código
intermediário
Gerador de
código objeto
Programa-fonte
(caracteres)
i
(
=
i
int
---
n
int
---
j
int
---
Tabela de
símbolos
R1: T1 = i < n
JF =
T1iR2
R1: T1
< n
T2
+ j
JF =
T1iR2
T2+ j
i = i
JUMP R1
R2: - - - - -
i
i
<
+
n
j
)
Sequência de
átomos
;
Árvore
sintática
while
<
i
=
n
i
Código
+
objeto
load i
i nj
R1: sub
JZ R2
Código
JP R2
load i
intermediário
add j
st i
Exemplo
J R1
R2: - - - - -
1.2.2 – A fase de análise
São realizados três tipos de análise:
Análise linear ou léxica
Análise hierárquica ou sintática
Análise semântica
a) Análise léxica
Os caracteres do texto são agrupados em átomos (tokens)
A validade dos átomos é verificada
Os átomos recebem uma classificação
Exemplo: frase da Língua Portuguesa:
ajbxswn o homem alto apanhou a laranja madura na
laranjeira tdhf
Exemplo: um comando while em Pascal:
while num 50 do num := num * 2
b) Análise sintática
Os átomos são agrupados em frases, em estrutura de árvore
(árvore sintática)
A validade da posição dos átomos é verificada
Exemplo: frase:
o homem alto apanhou a laranja madura na laranjeira
Exemplo: comando while de Pascal:
while num 50 do num := num * 2
c) Análise semântica
Verifica se a árvore sintática tem sentido
Coleta informações de tipos para a fase de síntese
Exemplo: frase sem sentido:
a laranja apanhou o homem
Erro: o verbo apanhar não admite sujeito do tipo vegetal
Exemplo: comando while de Pascal:
while n+3 do n*5 (com integer n)
Erro:
A expressão de
um comando
while deve ser
do tipo lógico
Erro:
O operador de
uma atribuição
deve ser “:=”
1.2.3 – A fase de síntese
Depois da análise, a fase de síntese constrói o programa
objeto
São realizadas três tarefas:
Geração do código intermediário
Otimização do código intermediário
Geração do código objeto
a) Geração do código intermediário:
A estrutura do programa-fonte costuma ser bem diferente
da estrutura do programa-objeto
A transformação da primeira estrutura para a segunda deve
ser aliviada passando por uma fase intermediária
Fazendo analogia com metamorfose na vida animal:
Ovo Lagarta Borboleta
Ovo Girino Sapo
Exemplo:
while n 50 do n := n * 2
Código de três endereços:
Quádruplas:
2 endereços p/operandos
1 endereço p/resultado
1º elemento: operador
2º e 3º elementos: operandos
4º elemento: resultado
O código intermediário deve ser fácil de:
Ser produzido
Ser transformado em código objeto (Assembly da
máquina)
b) Otimização do código intermediário:
Elimina operações desnecessárias e repetidas
Visa simplificar o código intermediário
Visa tornar o programa executável mais rápido
Visa economia de memória
Exemplo: sejam os comandos
x := a + b + c;
y := a + b + c + d;
Outros casos para otimização:
Detecção de atribuições e operações cujos valores não são
usados no programa
Detecção de código morto, ou seja, de código que não é
executado, qualquer que seja a entrada de dados
Otimização faz análise de fluxo de dados e de controle
c) Geração do código objeto:
Transformação do código intermediário otimizado no código
objeto (normalmente o Assembly da máquina alvo)
O código objeto pode também receber otimizações
Exemplo: seja o comando
while n < 50 do n := n * 2
1.2.4 – Atividades em ambas as fases
Manipulação da tabela de símbolos
Tratamento de erros
a) Tabela de símbolos
Guarda informações sobre todos os identificadores usados
em um programa
Informações sobre os identificadores:
Tipo do identificador:
Variável
Constante
Definição de tipo
Nome de subprograma
Rótulo
Escopo: em que trecho ele é válido
Se for nome de variável:
Tipo
Se for variável indexada
Se é ou não indexada
Número de dimensões
Se é ou não estrutura
Número de elementos
em cada dimensão
Se é ou não ponteiro
Endereço de memória
Espaço a ser alocado
Se for estrutura:
Nome e tipo de cada
campo
Se for ponteiro:
Tipo dos locais
apontados por ela
Exemplos em C:
int a;
a
var
nome tipo
int
não
tipovar eharray
float X[7][5][4];
X
var
nome tipo
real
sim
3
tipovar eharray ndim
0
7
1
5
dims
2
4
Se for nome de subprograma:
Tipo de subprograma (função ou procedimento)
Número de parâmetros
Lista de parâmetros por valor
Lista de parâmetros por referência
Tipo a ser retornado
Lista de variáveis locais
Exemplo em C:
int fff (int m, int n) {float a, b; - - - - - }
n
nome
var
tipo
m
nome
b
tipovar
nome
var
nome
fff
int
func
tipo
tipo
int
tipovar
int
tipofun
var
real
tipo
tipovar
a
var
real
tipo
tipovar
nome
2
nparam
List
param
List Var
Loc
A tabela de símbolos é manipulada pelos vários componentes
das fases de análise e síntese:
Análise léxica detecta e pode armazenar o identificador
Análise sintática pode armazenar seu tipo e outras
informações
Análise semântica faz consultas à tabela
Geração de código introduz e usa informações sobre espaço
alocado
b) Detecção e tratamento de erros
Cada componente do compilador pode encontrar erros
Encontrado um erro, um componente deve tratá-lo de
forma a permitir que outros erros sejam detectados
Um compilador que para, quando encontra o primeiro erro,
não pode ajudar muito
Existem erros léxicos, sintáticos e semânticos
Exemplos:
Erro léxico: Conjunto de caracteres que não corresponde
a nenhum átomo
Erro sintático: Sequência de átomos que viola construções
sintáticas
Erro semântico: Construções sintáticas corretas porém
sem sentido
Cada componente da estrutura do compilador será estudado
detalhadamente em capítulo específico
1.2.5 – Outra decomposição de um compilador
Compilador é uma interface entre:
linguagem-fonte e máquina-alvo
Frente ou front-end : parte do compilador dependente da
linguagem-fonte
Retaguarda ou back-end: parte do compilador dependente
da máquina-alvo
Frente (front-end) compreende:
Análises léxica, sintática e semântica
Criação e consultas à tabela de símbolos
Geração do código intermediário
Muita otimização do código intermediário
Tratamento de erros léxicos, sintáticos e semânticos
Retaguarda (back-end) compreende:
Alguma otimização do código intermediário
Geração de código objeto
Otimização do código objeto
Operações na tabela de símbolos
Tratamento de alguns erros
Essa decomposição facilita a criação de compiladores de:
Mesma linguagem-fonte para várias máquinas-alvos
Várias linguagens-fontes para mesma máquina-alvo
L
M1
M2
L1
Mi
L2
Lj
M
Uma única linguagem para código intermediário
No primeiro caso: sucesso absoluto
Pode-se projetar um código intermediário que se aproxime
bem da linguagem de máquina de uma variedade de
arquiteturas
L
M1
M2
L1
Mi
L2
Lj
M
No segundo caso: sucesso limitado
Diferenças sutis entre algumas linguagens dificultam a
obtenção de um código intermediário comum entre elas
L
M1
M2
L1
Mi
L2
Lj
M