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