770 likes | 953 Views
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
E N D
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 linguagem-fonte (é 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
Programa fonte Programa objeto Compilador Esquematicamente: Mensagens de erro
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] do parallel i = 1, n 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] + 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 Yaccsã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 Assemblyde uma máquina • Para a implementação, a linguagem-fonte é Lex eYacce 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 Bibliotecas e outros arquivos com endereçamento deslocado Editor de Ligações Código de máquina executável com endereçamento deslocado Código de máquina executável com endereçamento correto Carregador
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: if (EHPAR(a+b)) --------------; if (valor max) ERRO(“valor muito grande”); O resultado do pré-processamento é: if ((((a+b)%2)?0:1)) ....... ; if (valor > max) printf(“ERRO:%s\n”,“valor muito grande”); O pré-processador substitui a primeira parte do #define pela segunda, realizando inclusive passagem de argumentos
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
C1: CONST 1 C2: CONST 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); } Primeiramente, reserva de espaço para as constantes 1 e 2
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 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); } Em seguida, reserva de espaço para as variáveis n, i, fat
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 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: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ 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); } Na realidade, a tradução de scanf é algo mais complexo: É uma chamada de subprograma
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST 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: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever JUMP 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); } “escrever” é o rótulo da instrução logo após JUMP
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP 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); }
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE 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); } Na realidade, a tradução de printf é algo mais complexo: É uma chamada de subprograma
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic 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: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END 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: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic O Assembler monta uma tabela de rótulos para ajudar a preencher o programa em linguagem de máquina
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic
C1: CONST 1 C2: CONST 2 n: CONST 0 fat: CONST 0 i: CONST 0 inic: READ n LD C1 ST fat LD C2 ST i loop: SUB n JP escrever LD fat MULT i ST fat LD i ADD C1 ST i JUMP loop escrever:WRITE fat STOP END inic Endereço inicial da execução: 5 Essa informação deve acompanhar o programa em linguagem de máquina
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
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
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 de3000 posições) • Isso tem de ser corrigido antes da execução Endereço inicial da execução: 3005
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
Programa-fonte (caracteres) while (i < n) i = i + j; Analisador léxico while ( i < n ) Sequência de átomos Analisador sintático i = i + j ; Árvore sintática Analisador semântico while i int - - - int - - - n < = Gerador de código intermediário Tabela de símbolos int - - - j Código objeto + i n i load i R1: sub n JZ R2 JP R2 load i add j st i J R1 R2: - - - - - R1: T1 = i < n JF T1 R2 T2 = i + j i = T2 JUMP R1 R2: - - - - - i j R1: T1 = i < n JF T1 R2 i = i + j JUMP R1 R2: - - - - - Otimizador de código intermediário Código intermediário Gerador de código objeto Exemplo
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