O processo de tradução de uma linguagem para outra realizado por um compilador é muito complexo, sua implementação lógica é feita seguindo muitas fases que são sub-processos da compilação.
Geralmente as análises são consideradas como front-ends. Nelas o programa é recebido e analisado resultando na tradução para uma estrutura gramatical que mostra a sintaxe final do programa nessas fases os objetivos são entender o código fonte, verificar erros, falhas e inconsistências, e representá-lo em uma estrutura intermediária. As análises podem ser subdivididas em análise léxica, análise sintática e análise semântica.
Já a síntese realiza um trabalho de back end realizando a tradução dessa representação e entregando o resultado para a máquina ela é composta por três etapas de Geração de código intermediário, otimização de código e geração de código final (ou código de máquina), sendo somente esta última etapa obrigatória.
Análise Léxica
A varredura ou também chamada de análise léxica traduz uma sequência de caracteres em uma sequência de tokens, identificando as palavras ou lexemas da linguagem. Exemplos típicos de tokens são palavras-chave como while, for, if e else.
De acordo com LOUDEN (pg 31, 2004), a análise léxica é “a fase de um compilador que lê o programa-fonte como um arquivo de caracteres e o separa em marcas".
Para gerar a sequência de tokens/marcas, a análise léxica precisa utilizar métodos de reconhecimento de padrões como expressões regulares e autômatos finitos.
Segundo SEBESTA (pg 155, 2003) " O analisador léxico coleta caracteres em grupamentos lógicos e atribui códigos internos aos grupamentos de acordo com sua estrutura" Tecnicamente a análise léxica faz a analise sintática no nível mais baixo da estrutura.
Análise Sintática
Análise sintática é responsável pela análise da sintaxe e é conhecida como parsing. SEBESTA (pg 159, 2003) diz que " A análise sintática é feita usando árvores de análise e as derivações incluem toda a informação sintática necessária para o processamento da linguagem".
Nesta fase de um compilador o principal objetivo é verificar se dado programa ou linguagem está sintaticamente correto. E ao encontrar um erro é necessário que envie uma mensagem de diagnóstico e retomar a análise, isso é exigido para que o compilador encontre o maior número de erros possíveis. Outra característica desta fase é a produção de uma árvore de análise completa que será usada como base para a tradução do linguagem alvo.
Existem duas formas de árvores de análise, estão classificadas com base na direção que são criadas: cima-baixo onde a construção começa da raiz para as folhas, esse tem como tarefa encontrar a próxima sentença. Uma árvore baixo-cima produz uma árvore ao contrario começando das folhas para as raízes. Ele produz o inverso de uma derivação mais a direita.
Os algoritmos de análise são ineficientes e complexos, gastam tempo para a análise e estão sempre passando por renovações. Todos os algoritmos de análise sintática usados em compiladores são complexos e seu uso se torna mais ineficiente conforme o comprimento da cadeia a ser analisada.
Análise Semântica
A análise semântica tem por objetivo verificar se as construções identificadas pela análise sintática estão em acordo com as “regras semânticas” da linguagem sendo compilada. É através da semântica que são extraídas as informações que possibilitam a posterior geração de código.
Um exemplo de funcionamento da análise semântica é a definição de como as variáveis devem ser declaradas, é necessário uma parte que diga que uma variável é int e outra é float. A semântica trabalha nesse nível atuando entre partes distintas do programa verificando o fluxo de controle e a unicidade da declaração de variáveis e em algumas linguagens fazem-se necessários outros tipos de verificação devido ao seu nível de complexidade e detalhamento.
Gerador de código intermediário
É o primeiro sub-processo da síntese ele usa os dados analisados anteriormente para criar uma cadeia de instruções simples.
A maioria dos compiladores atuais realizam a geração de uma representação intermediária entre o código fonte e o código de máquina, por exemplo a possível geração de uma versão intermediária do código em assembly. Essa representação pode ser vista como uma máquina abstrata e classificadas como independente de máquina, quando podem ser aplicadas antes da geração do código na linguagem assembly, ou dependentes de máquina se aplicada antes.
Otimização de código
Realiza a otimização do código intermediário tal que o programa seja mais eficiente e eficaz, o resultado é outro programa em código intermediário que faz a mesma tarefa do original, mas com melhor qualidade.
A otimização dos compiladores, além de manter as características e significados do programa original devem captar a maior parte das possibilidades de melhoria do código dos esforços gastos para tal fim. É comum, os compiladores permitirem a especificação do esforço desejado no processo de otimização.
Para otimizar o código e entregar um produto final com melhor qualidade são usadas diversas técnicas de otimização que tem como princípio básico algumas características: Eliminação de código redundante que tem como objetivo detectar traduções de duas e instruções cuja execução repetida não geram nenhum resultado; Eliminação de código não-alcançável, códigos que não fazem sentido na execução e não tem definido com clareza suas atribuições; O uso de propriedades algébricas tem como objetivo substituir operações de alto custo de execução por operações mais simples; A movimentação de código é usado com frequência para otimizar operações usando laços de repetição que usam variáveis que não tem interação direta.
Referências
Sebesta, Robert W. (2003), “Linguagens de Programação”. 5ª Edição. Artmed Editora.
Louden, Kenneth C. (2005), “Compiladores – Princípios e práticas”. Editora Thomson Pioneira.