Você está na página 1de 20

Apostila de Compiladores - UFPR

by Bart Simpson
2011

Sumario
1 Livros 2

2 Introducao 2

3 Conceitos gerais, estrutura do compilador 2


3.1 Ferramentas que manipulam programas fontes . . . . . . . . . . . . . . . . . . . 3

4 Analise 4

5 Analise lexica 5
5.1 Lex e Flex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

6 Estruturas basicas 5
6.1 Deteccao de erros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
6.2 Fases do compilador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
6.3 Em tempo de compilacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
6.4 Em tempo de execucao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

7 Diagramas de execucao 7
7.1 Referencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

8 MEPA 7
8.1 Introducao a MEPA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
8.2 Instrucoes basicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
8.2.1 exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
8.3 Comandos de decisao e repeticao . . . . . . . . . . . . . . . . . . . . . . . . . . 9
8.3.1 IF e THEN c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
8.3.2 WHILE e do c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
8.3.3 IF e THEN c1 ELSE c2 . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

9 Procedimentos, Registro de ativacao 9

10 Tabela de smbolos 9

11 Passagem de parametros por valor e por referencia, Funcoes 10

12 Rotulos e desvios incondicionais 10

13 Analise sintatica 10
13.1 Yacc e Bison . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

1
14 Analise sintatica descendente 11

15 Gramaticas, Linguagens, Ambiguidade 11

16 Analise sintatica descendente recursiva com retrocesso 11

17 Analise sintatica descendente preditiva LL(1) e LL(k) 11

18 Fatoracao, Eliminacao de recursao a esquerda 11

19 Construcao de tabelas LL(1) 11

20 Implementacoes preditiva e recursiva 11

21 Diagramas sintaticos 11

22 Geradores de analisadores lexicos e sintaticos 11

23 Integracao Lex/Flex-Yacc/Bison 11

24 Analise sintatica ascendente 11

25 Construcao de tabelas SLR 11

26 Ambiguidade 11

27 Construcao de tabelas LR 11

28 Nocoes sobre construcao de tabelas LALR 11

29 Analise semantica 11

30 Tabela de smbolos 11

31 Verificacao de tipos, Acoes semanticas 12

32 Geracao de codigo 12
32.1 Codigo intermediario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
32.2 Codigo objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

33 Traducao dirigida pela sintaxe 18

Lista de Tabelas

Lista de Figuras
1 Analise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2 Analise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3 Fases do compilador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
4 Instrucoes MEPA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5 Codigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
6 Arvore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2
7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
8 Arvore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
9 Sequencia de codigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

3
1 Livros
livro do dragao(AHO...); Compiladores, principios, tecnicas e ferramentas (2007);

Implementacao de linguagem de programacao (Tomasz Kowaltowski) (1983);

Compiladores: Princpios e praticas (Kenneth Louden) (1997).

2 Introducao
Um COMPILADOR e um programa que le um programa escrito numa linguagem - a lingua-
gem fonte - e o traduz num programa equivalente numa outra linguagem - a linguagem objeto,
relatando ao usuario a presenca de erros no programa fonte.

3 Conceitos gerais, estrutura do compilador


Existem duas partes na compilacao: A ANALISE e a SINTESE.

ANALISE: Divide o programa fonte nas partes constituintes e cria uma representacao
intermediaria;

SINTESE: Constroi o programa objeto desejado, a partir da representacao intermediaria.

Durante a analise, as operacoes implicadas pelo programa fonte sao determinadas e regis-
tradas numa estrutura hierarquica, chamada de arvore (ex.: Arvore Sintatica).

Figura 1: Analise

4
3.1 Ferramentas que manipulam programas fontes
Editores de estruturas: Recebe como entrada um conjunto de comandos para construir
um programa fonte. Estabelece uma estrutura hierarquica apropiada.

Pretty printers: Analisa um programa e o imprime numa forma em que a sua estrutura
se torne claramente visvel.

Verificadores estaticos: Le um programa, analisa-o e tenta descobrir erros potenciais, sem


executa-lo.

Interpretadores: Em lugar de produzir um programa objeto como resultado da traducao,


um interpretador realiza as operacoes especificadas pelo programa fonte.

5
4 Analise
A analise compreende em tres fases:

1. Analise linear (lexica ou esquadrinhamento - scanning). Um fluxo de caracteres e lido da


esquerda para a direita e agrupado em tokens (sequencia de caracteres com um significado
coletivo).

2. Analise hierarquica (gramatical ou sintatica). Os tokens sao agrupados hierarquicamente


em colecoes aninhadas com significado coletivo (arvore gramatical)

3. Analise semantica: Verificacoes sao realizadas a fim de assegurar que componentes de um


programa se combinam de forma significativa;

Figura 2: Analise

6
5 Analise lexica
Nesta fase o arquivo de entrada e varrido caractere a caractere;

Smbolos especiais (espaco em branco, smbolos de pontuacao e new line) sao utilizados
para estabelecer os limites das palavras;

Eliminacao de espacos em branco e comentarios;

Durante a analise lexica, as palavras ou lexemas sao guardados na tabela de smbolos e


classificados de acordo com a linguagem, em palavras reservadas, comandos, variaveis e
tipos basicos;

Identificacao de erros lexicos (overflowde campo numerico, uso de smbolos nao perten-
centes ao alfabeto da linguagem).

5.1 Lex e Flex


O LEX / FLEX servem para gerar automaticamente programas (usualmente em C) fa-
zendo a leitura de uma entrada, de modo a varrer um texto e/ou programa (scanners) a fim de
obter uma sequencia de unidades lexicas (tokens). Os tokens gerados pelos programas criados
pelo LEX/FLEX serao usualmente processados posteriormente por um programa que realizara
a analise sintatica.

Lex: Gerador de analisadores lexicos (UNIX - Ex.: Lex AT&T, Berkeley BSD)

Flex: Gerador de analisadores lexicos (LINUX/Windows-DOS - GNU Lex)

Entrada e sada:

Entrada: Arquivo de descricao do analisador lexico

Sada: Programa na linguagem C que realiza a analise lexica (default: lexyy.c)

Outros geradores de analisadores:

TPly: TP Lex/Yacc: Gera um programa em PASCAL (scanner em Pascal)

JavaCC: Para linguagem Java

Flex++ ou Flexx: Para linguagem C++ (orientado a objetos)

Para mais dicas ler o material de apoio: exercicios-lex-flex.pdf

6 Estruturas basicas
Um programa fonte pode conter diversos modulos armazenados em arquivos diferentes. A
coleta desses modulos e feita pelo pre-processador. Alem disso, o pre-processador pode expandir
formas curtas (ex.: macros).
O contexto de um compilador:

7
Figura 3: Fases do compilador

Uma funcao essencial do compilador e registrar os identificadores usados no programa fonte e


coletar as informacoes sobre os seus diversos atributos. Uma tabela de smbolos e uma estrutura
de dados contendo um registro para cada identificador, com os campos contendo os atributos
do identificador. Quando o analisador lexico detecta um identificador, ele instala-o na tabela
de smbolos.

6.1 Deteccao de erros


Quando os caracteres de entrada nao foram qualquer token, ha um erro lexico encontrado.
A analise sintatica (gramatical) localiza erros no fluxo dos tokens, quando os tokens violam as
regras estruturais (sintaxe) da linguagem.

6.2 Fases do compilador


A entrada para um compilador pode ser produzida por um ou mais pre-processadores.
Posteriormente pode ser usado pelo compilador antes da geracao da linguagem de maquina.
Segue alguns tipos de pre-processadores:

1. Processamento de macros. Extensao de formas curtas (macros).

2. Inclusao de arquivos. (include do C, import do Python etc).

3. Pre-processadores racionais. Expandem linguagens mais antigas com features novas. Por
exemplo uma linguagem que nao possui WHILE, passa ter uma estrutura que e entendida
como um WHILE.

4. Extensores de linguagem. Tenta conferir maior poder a uma linguagem. Por exemplo
utilizar uma instrucao Python dentro de um programa em C. Essa instrucao e traduzida
para C;

8
Imagine um programa do tipo: a <- b + c*10
Fases:

Analisador lexico. Le os caracteres e os agrupa num fluxo de tokens. Uma sequencia de


caracteres que forma um token chama-se lexema. No exemplo acima, o analisador lexico le
os identificadores (a, b e c), gera os tokens (por exemplo, id1, id2 e id3, respectivamente)
e os insere na tabela de smbolos.

Analisador sintatico (gramatical). Gera uma estrutura hierarquica com o fluxo dos tokens
(arvores sintaticas). Na raz teramos a atribuicao. O filho esquerdo contem id1 e o filho
direito contem o somador dos filhos id2 (filho esquerdo) com o resultado da multiplicacao
do nvel 3 da arvore. No nvel 3 temos a id3 como filho esquerdo e 10 como filho direito.
Desenhe essa arvore, fica mais facil de entender.

Analise semantica.

Geracao de codigo intermediario. E uma linguagem abstrata definida pelo computador,


pronta para ser compilada para a linguagem alvo. Aqui trata-se construcoes do fluxo de
controle e chamadas de procedimentos.

Otimizacao de codigo. Tenta melhorar o codigo intermediario.

Geracao de codigo. As instrucoes intermediarias sao traduzidas para uma sequencia de


instrucoes na linguagem de maquina. As localizacoes na memoria sao selecionadas para
cada uma das variaveis usadas pelo programa. Ocorre a atribuicao de variaveis aos regis-
tradores (parte importante da geracao de codigo).

6.3 Em tempo de compilacao


6.4 Em tempo de execucao

7 Diagramas de execucao
7.1 Referencias
Livro do Tomasz, captulos:

Captulo 7, Diagramas de execucao;

Captulo 8, MEPA, instrucoes do apendice 3 (A,B), dado em prova;

Captulo 9, MEPA extendido (nao sera cobrado em prova);

8 MEPA
8.1 Introducao a MEPA
8.2 Instrucoes basicas
Instrucoes do apendice 3 (A,B) do livro do Thomasz (recebe na 1a prova e na final).
Veja a Figura 8.2:

9
Figura 4: Instrucoes MEPA

8.2.1 exemplo
Equacao:
A+(B DIV 9 -3)*C
a=0,0; b=0,1; c=0,2
CRVL 0,0 (carrega valor 0 em) CRVL 0,1 (carrega valor 1 em) CRCT 9 (carrega constante
9) DIVI (dividir) CRCT 3 (carrega constante 3) SUBT (subtrai) CRVL 0,2 (carrega 2 em)

10
MULT (multiplica) SOMA (soma)

8.3 Comandos de decisao e repeticao


8.3.1 IF e THEN c
Traducao de e;

DSVF l

traducao de c

l: nada

8.3.2 WHILE e do c
l1: nada

traducao de e

DSVF l2:

traducao de c

DSVS l1

l2: nada

8.3.3 IF e THEN c1 ELSE c2


Traducao de e

DSVF l1

traducao de c1

DSVS l2

l1: nada

traducao de c2

l2: nada

9 Procedimentos, Registro de ativacao


10 Tabela de smbolos
Uma funcao essencial do compilador e registrar os identificadores usados no programa
fonte e coletar as informacoes sobre os seus diversos atributos de um identificador, tais
como: memoria alocada, tipo, escopo;

Uma tabela de smbolos e uma estrutura de dados contendo um registro para cada iden-
tificador, com os campos contendo os atributos do identificador.

11
11 Passagem de parametros por valor e por referencia,
Funcoes
12 Rotulos e desvios incondicionais
13 Analise sintatica
E responsavel pela verificacao da bem formacao dos comandos da linguagem, de acordo
com as regras especificadas pela gramatica da linguagem;

Sentencas mal formadas, geralmente, interrompem o processo de compilacao e sao apre-


sentadas como mensagens de erro;

No fim da analise sintatica, temos a representacao do programa original de forma hierar-


quica, onde o programa e representado por uma arvore sintatica;

Uma arvore sintatica e uma representacao compactada de uma arvore PARSE na qual os
operadores aparecem como nos interiores e os operandos de um operador sao os filhos do
no daquele operador;

Os erros de sintaxe sao mais frequentes que os erros lexicos;

Identificacao e recuperacao de erros de sintaxe;

Construcoes lexicas nao requerem recursao, enquanto as sintaticas sim. As gramaticas


livres de contexto sao uma formalizacao de regras recursivas que podem ser usadas para
guiar a analise sintatica.

12
13.1 Yacc e Bison

14 Analise sintatica descendente


15 Gramaticas, Linguagens, Ambiguidade
16 Analise sintatica descendente recursiva com retro-
cesso
17 Analise sintatica descendente preditiva LL(1) e LL(k)
18 Fatoracao, Eliminacao de recursao a esquerda
19 Construcao de tabelas LL(1)
20 Implementacoes preditiva e recursiva
21 Diagramas sintaticos
22 Geradores de analisadores lexicos e sintaticos
23 Integracao Lex/Flex-Yacc/Bison
24 Analise sintatica ascendente
25 Construcao de tabelas SLR
26 Ambiguidade
27 Construcao de tabelas LR
28 Nocoes sobre construcao de tabelas LALR
29 Analise semantica
A analise semantica mais comum consiste na verificacao da consistencia de tipos dos ope-
randos envolvidos em operacoes aritmeticas ou dos parametros passados a procedimentos;
O codigo intermediario deve ser facil de produzir e facil de traduzir no programa objeto.

30 Tabela de smbolos
Uma funcao essencial do compilador e registrar os identificadores usados no programa
fonte e coletar as informacoes sobre os seus diversos atributos de um identificador, tais
como: memoria alocada, tipo, escopo).

13
Uma tabela de smbolos e uma estrutura de dados contendo um registro para cada iden-
tificador, com os campos contendo os atributos do identificador.

31 Verificacao de tipos, Acoes semanticas


32 Geracao de codigo
32.1 Codigo intermediario
Esta fase utiliza a representacao interna produzida pelo Analisador Sintatico e gera como
sada uma sequencia de codigo;

Esse codigo pode, eventualmente, ser o codigo objeto final mas, na maioria das vezes,
constitui-se num codigo intermediario, pois a traducao de codigo fonte para objeto em
mais de um passo apresenta algumas vantagens: possibilita a otimizacao de codigo inter-
mediario, de modo a obter-se o codigo objeto final mais eficiente;

Resolve, gradualmente, as dificuldades da passagem de codigo fonte para codigo objeto


(alto nvel para baixo nvel), ja que o codigo fonte pode ser visto como um texto conden-
sado que explore em inumeras instrucoes elementares de baixo nvel.

A geracao de codigo intermediario pode estar estruturalmente distribuda nas fases anteriores
(analise sintatica e semantica) ou mesmo nao existir (traducao direta para codigo objeto), no
caso de tradutores bem simples. A grande diferenca entre o codigo intermediario e o
codigo objeto final e que o intermediario nao especifica detalhes tais como quais
registradores serao usados, quais enderecos de memoria serao referenciados, etc.

14
Exemplo de codigo intermediario:

Figura 5: Codigo

Para o comando while apresentado anteriormente, o gerador de codigo intermediario, re-


cebendo a arvore de derivacao mostrada na figura, poderia produzir a seguinte sequencia de
instrucoes:

Figura 6: Arvore

Ha varios tipos de codigo intermediario: quadruplas, triplas, notacao polonesa pos-fixada,


etc. A linguagem intermediaria do exemplo acima e chamada codigo de tres enderecos, pois
cada instrucao tem no maximo tres operandos.
Resumindo:

No processo de traduzir um programa fonte para um codigo objeto, um compilador pode


produzir uma ou mais representacoes intermediarias, as quais podem ter diversas formas.

As arvores de sintaxe denotam uma forma de representacao intermediaria; elas normal-


mente sao usadas durante as analises sintatica e semantica.

Depois das analises sintatica e semantica do programa fonte, muitos compiladores ge-
ram uma representacao intermediaria explcita de baixo nvel ou do tipo linguagem de
maquina, que podemos imaginar como um programa para uma maquina abstrata.

15
Essa representacao intermediaria deve ter duas propriedades importantes: ser facilmente
produzida e ser facilmente traduzida para a maquina alvo.

Consideramos uma forma intermediaria, chamada codigo de tres enderecos, que consiste
em uma sequencia de instrucoes do tipo assembler com tres operandos por instrucao.

Cada operando pode atuar como um registrador.

A sada do gerador de codigo intermediario na Figura consiste em uma sequencia de instru-


coes ou codigo de tres enderecos...

16
Figura 7:

17
Figura 8: Arvore

18
Varios pontos precisam ser observados em relacao aos codigos de tres enderecos:

Primeiro, cada instrucao de atribuicao de tres enderecos possui no maximo um operador


do lado direito. Assim, essas instrucoes determinam a ordem em que as operacoes devem
ser realizadas; a multiplicacao precede a adicao no programa fonte;

Segundo, o compilador precisa gerar um nome temporario para guardar o valor computado
por uma instrucao de tres enderecos;

Terceiro, algumas instrucoes de tres enderecos, como a primeira e ultima na sequencia,


possuem menos de tres operandos.

32.2 Codigo objeto


O gerador de codigo gera, a partir do codigo intermediario (IR), o codigo para a maquina-
alvo;

Neste texto, escreveremos o codigo-alvo em forma de linguagem de montagem para facili-


tar o entendimento, embora a maioria dos compiladores gere diretamente o codigo-objeto;

E nessa fase da compilacao que as propriedades da maquina-alvo se tornam o fator prin-


cipal;

Nao e apenas necessario usar as instrucoes conforme apresentadas na maquina-alvo, mas


tambem as decisoes sobre a representacao de dados assume um papel importante, como,
por exemplo, quantos bytes ou palavras as variaveis de inteiros e de ponto flutuante
ocuparao na memoria;

Em nosso exemplo, precisamos decidir como armazenar inteiros para gerar o codigo de
indexacao de matrizes.

Por exemplo, uma sequencia de codigo possvel para a expressao dada poderia ser (em uma
linguagem de montagem hipotetica):

Figura 9: Sequencia de codigo

19
O primeiro operando de cada uma das instrucoes especifica um destino;

O F em cada uma das instrucoes nos diz que ela manipula numeros de ponto flutuante;

O codigo em (1.5) carrega o conteudo do endereco id3 no registrador R2, depois o multi-
plica pela constante de ponto flutuante 60.0;

O # significa que o valor 60.0 deve ser tratado como uma constante imediata;

A terceira instrucao move id2 para o registrador R1, e a quarta o soma com o valor
previamente calculado no registrador R2;

Finalmente, o valor no registrador R1 e armazenado no endereco de id1, portanto o codigo


mostrado implementa corretamente o comando de atribuicao;

Esta discussao sobre geracao de codigo ignorou a importante questao relativa a alocacao
de espaco na memoria para os identificadores do programa fonte;

A organizacao de memoria em tempo de execucao depende da linguagem sendo compilada;

Decisoes sobre a alocacao de espaco podem ser tomadas em dois momentos: durante a
geracao de codigo intermediario ou durante a geracao do codigo.

33 Traducao dirigida pela sintaxe


Sumario

20

Você também pode gostar