Fases do compilador com exemplo: processo e etapas de compilação
Quais são as fases do design do compilador?
Compilador opera em várias fases, cada fase transforma o programa fonte de uma representação para outra. Cada fase recebe entradas de seu estágio anterior e alimenta sua saída para a próxima fase do compilador.
Existem 6 fases em um compilador. Cada uma dessas fases auxilia na conversão da linguagem de alto nível em código de máquina. As fases de um compilador são:
- Análise léxica
- Análise de sintaxe
- Análise semântica
- Gerador de código intermediário
- Otimizador de código
- Gerador de código
Todas essas fases convertem o código-fonte dividindo-o em tokens, criando árvores de análise e otimizando o código-fonte por diferentes fases.
Fase 1: Análise Lexical
A Análise Lexical é a primeira fase em que o compilador verifica o código-fonte. Este processo pode ser feito da esquerda para a direita, caractere por caractere, e agrupar esses caracteres em tokens.
Aqui, o fluxo de caracteres do programa de origem é agrupado em sequências significativas, identificando os tokens. Ele faz a entrada dos tickets correspondentes na tabela de símbolos e passa esse token para a próxima fase.
As principais funções desta fase são:
- Identifique as unidades lexicais em um código-fonte
- Classifique as unidades lexicais em classes como constantes, palavras reservadas e insira-as em tabelas diferentes. Irá ignorar comentários no programa de origem
- Identifique o token que não faz parte do idioma
Exemplo:
x = y + 10
Tokens
X | identificador |
= | Operador de atribuição |
Y | identificador |
+ | Operador de adição |
10 | Sessão |
Fase 2: Análise de Sintaxe
A análise de sintaxe trata da descoberta da estrutura do código. Determina se um texto segue ou não o formato esperado. O principal objetivo desta fase é garantir que o código-fonte escrito pelo programador esteja correto ou não.
A análise de sintaxe é baseada nas regras baseadas na linguagem de programação específica, construindo a árvore de análise com a ajuda de tokens. Também determina a estrutura do idioma de origem e a gramática ou sintaxe do idioma.
Aqui está uma lista de tarefas realizadas nesta fase:
- Obtenha tokens do analisador léxico
- Verifica se a expressão está sintaticamente correta ou não
- Reportar todos os erros de sintaxe
- Construa uma estrutura hierárquica conhecida como árvore de análise
Exemplo
Qualquer identificador/número é uma expressão
Se x for um identificador e y+10 for uma expressão, então x= y+10 é uma instrução.
Considere a árvore de análise para o exemplo a seguir
(a+b)*c
Na árvore de análise
- Nó interior: registro com um arquivo de operador e dois arquivos para filhos
- Folha: registros com 2/mais campos; um para token e outras informações sobre o token
- Garantir que os componentes do programa se encaixem de forma significativa
- Reúne informações de tipo e verifica a compatibilidade de tipo
- Os operandos de verificação são permitidos pelo idioma de origem
Fase 3: Análise Semântica
A análise semântica verifica a consistência semântica do código. Ele usa a árvore de sintaxe da fase anterior junto com a tabela de símbolos para verificar se o código-fonte fornecido é semanticamente consistente. Também verifica se o código está transmitindo um significado apropriado.
O Semantic Analyzer verificará incompatibilidades de tipo, operandos incompatíveis, uma função chamada com argumentos impróprios, uma variável não declarada, etc.
As funções da fase de análise semântica são:
- Ajuda você a armazenar informações de tipo coletadas e salvá-las na tabela de símbolos ou na árvore de sintaxe
- Permite realizar verificação de tipo
- No caso de incompatibilidade de tipos, onde não existem regras exatas de correção de tipos que satisfaçam a operação desejada, um erro semântico é mostrado
- Coleta informações de tipo e verifica a compatibilidade de tipo
- Verifica se o idioma fonte permite ou não os operandos
Exemplo
float x = 20.2; float y = x*30;
No código acima, o analisador semântico irá converter o inteiro 30 para float 30.0 antes da multiplicação
Fase 4: Geração de Código Intermediário
Terminada a fase de análise semântica, o compilador gera código intermediário para a máquina alvo. Representa um programa para alguma máquina abstrata.
O código intermediário está entre a linguagem de alto nível e a linguagem de máquina. Esse código intermediário precisa ser gerado de forma que seja fácil traduzi-lo no código de máquina de destino.
Funções na geração de código intermediário:
- Deve ser gerado a partir da representação semântica do programa fonte
- Contém os valores calculados durante o processo de tradução
- Ajuda você a traduzir o código intermediário para o idioma de destino
- Permite manter a ordem de precedência do idioma de origem
- Ele contém o número correto de operandos da instrução
Exemplo
Por exemplo, nos
total = count + rate * 5
O código intermediário com a ajuda do método de código de endereço é:
t1 := int_to_float(5) t2 := rate * t1 t3 := count + t2 total := t3
Fase 5: Otimização de Código
A próxima fase é a otimização do código ou código intermediário. Esta fase remove linhas de código desnecessárias e organiza a sequência de instruções para acelerar a execução do programa sem desperdiçar recursos. O principal objetivo desta fase é melhorar o código intermediário para gerar um código que rode mais rápido e ocupe menos espaço.
As principais funções desta fase são:
- Ajuda você a estabelecer uma compensação entre velocidade de execução e velocidade de compilação
- Melhora o tempo de execução do programa alvo
- Gera código simplificado ainda em representação intermediária
- Removendo código inacessível e livrando-se de variáveis não utilizadas
- Removendo instruções que não são alteradas do loop
Exemplo:
Considere o seguinte código
a = intofloat(10) b = c * a d = e + b f = d
Pode se tornar
b =c * 10.0 f = e+b
Fase 6: Geração de Código
A geração de código é a última e última fase de um compilador. Ele obtém entradas das fases de otimização do código e, como resultado, produz o código da página ou do objeto. O objetivo desta fase é alocar armazenamento e gerar código de máquina relocável.
Também aloca locais de memória para a variável. As instruções do código intermediário são convertidas em instruções de máquina. Esta fase converte o código otimizado ou intermediário na linguagem de destino.
A linguagem alvo é o código de máquina. Portanto, todos os locais de memória e registros também são selecionados e alocados durante esta fase. O código gerado por esta fase é executado para receber entradas e gerar saídas esperadas.
Exemplo
uma = b + 60.0
Seria possivelmente traduzido para registros.
MOVF a, R1 MULF #60.0, R2 ADDF R1, R2
Gerenciamento de tabela de símbolos
Uma tabela de símbolos contém um registro para cada identificador com campos para os atributos do identificador. Este componente torna mais fácil para o compilador pesquisar o registro do identificador e recuperá-lo rapidamente. A tabela de símbolos também auxilia no gerenciamento do escopo. A tabela de símbolos e o manipulador de erros interagem com todas as fases e a tabela de símbolos é atualizada de forma correspondente.
Rotina de tratamento de erros
No processo de design do compilador, pode ocorrer erro em todas as fases fornecidas abaixo:
- Analisador léxico: tokens escritos incorretamente
- Analisador de sintaxe: parênteses ausentes
- Gerador de código intermediário: operandos incompatíveis para um operador
- Code Optimizer: quando a instrução não está acessível
- Code Generator: Quando a memória está cheia ou os registros adequados não estão alocados
- Tabelas de símbolos: erro de vários identificadores declarados
Os erros mais comuns são sequências de caracteres inválidas na varredura, sequências de tokens inválidas no tipo, erro de escopo e análise na análise semântica.
O erro pode ser encontrado em qualquer uma das fases acima. Após encontrar os erros, a fase precisa lidar com os erros para continuar com o processo de compilação. Esses erros precisam ser relatados ao manipulador de erros que trata o erro para executar o processo de compilação. Geralmente, os erros são relatados na forma de mensagem.
Resumo
- O compilador opera em várias fases, cada fase transforma o programa fonte de uma representação para outra
- Seis fases de design do compilador são 1) Análise lexical 2) Análise sintática 3) Análise semântica 4) Gerador de código intermediário 5) Otimizador de código 6) Código Generator
- A Análise Lexical é a primeira fase em que o compilador verifica o código-fonte
- A análise de sintaxe trata de descobrir a estrutura do texto
- A análise semântica verifica a consistência semântica do código
- Assim que a fase de análise semântica terminar no compilador, gere o código intermediário para a máquina de destino
- A fase de otimização do código remove linhas de código desnecessárias e organiza a sequência de instruções
- A fase de geração de código obtém entradas da fase de otimização de código e produz o código da página ou código do objeto como resultado
- Uma tabela de símbolos contém um registro para cada identificador com campos para os atributos do identificador
- A rotina de tratamento de erros lida com erros e relatórios durante muitas fases