720 likes | 1.09k Views
Introdução a Compiladores. Prof. Alexandre Monteiro Baseado em material cedido pelo Prof. Euclides Arcoverde Recife. Contatos. Prof. Guilherme Alexandre Monteiro Reinaldo Apelido: Alexandre Cordel E-mail/ gtalk : alexandrecordel@gmail.com greinaldo@fbv.edu.br
E N D
Introdução a Compiladores Prof. AlexandreMonteiro Baseadoem material cedidopelo Prof. EuclidesArcoverde Recife
Contatos • Prof. Guilherme Alexandre Monteiro Reinaldo • Apelido: Alexandre Cordel • E-mail/gtalk: alexandrecordel@gmail.com greinaldo@fbv.edu.br • Site: http://www.alexandrecordel.com.br/fbv • Celular: (81) 9801-1878
Agenda • História • Linguagens de Alto Nível • Introdução a Compiladores
Objetivo • O principal objetivo é responder à seguinte pergunta: • Como criar uma linguagem computacional? • Para responder a pergunta, estudaremos: • Como especificar uma linguagem • Como construir um compilador para ela
História • O surgimento dos compiladores está fortemente ligado à história das linguagens de programação, que, por sua vez, está ligada à história dos computadores modernos
História dos Computadores • Computadores com “programas fixos” – todos até a década de 40 • Programa implícito no hardware • A maioria tinha propósito específico, como acontece com as calculadoras modernas • Apenas uma minoria podia ser reprogramada, o que era feito de maneira mecânica • Ligando e desligando fios
História dos Computadores • Computadores de programas armazenados a partir da década de 40 com o SSEM – Manchester Small-Scale Experimental Machine • São os computadores modernos, de propósito geral • Diferentes programas podem ser criados e carregados na memória • Programas escritos usando um conjunto de instruções, representadas em código binário • Linguagem de máquina!
Fonte: http://en.wikipedia.org/wiki/Manchester_Small-Scale_Experimental_Machine
ENIAC • Pesava 30 toneladas, media 5,50 m de altura e 25 m de comprimento e ocupava 180 m² de área construída. • Foi construído sobre estruturas metálicas com 2,75 m de altura e contava com 70 mil resistores e 17.468 válvulas a vácuo ocupando a área de um ginásio desportivo, diodos de cristal de 7200, 1.500 relés, 70.000 resistores, 10.000 capacitores e cerca de 5 milhões de juntas soldadas à mão. • Segundo Tom Forester, quando acionado pela primeira vez, o ENIAC consumiu tanta energia que as luzes de Filadélfia piscaram. • Processador de 20 ciclos de clock de 0,1MHz na unidade. • Era incapaz de armazenar programa em memória (cartões perfurados) – Em 1970 DRAM de 1KB, mais tarde 256KB SDRAM.
Linguagem de Máquina • A linguagem de máquina do SSEM pode ser entendida (grosseiramente) como a primeira linguagem de programação • Essa linguagem define a associação entre uma instrução em binário e o efeito dela sobre o hardware • Linguagem entendida diretamente pelo processador • Cada arquitetura de computador define a sua própria linguagem
1011100 0001 0010 0011 adição reg. 1 reg. 2 reg. 3 História das Linguagens • Exemplo de instrução • Somar o valor dos registradores 1 e 2 e guardar o resultado no registrador 3 • Exemplo de código de máquina (fictício) • Podemos ver que escrever um programa em linguagem de máquina não é trivial...
História das Linguagens • Programadores definiram uma notação simbólica para representar seus programas • Usada para anotar seus programas em papel antes de passar para linguagem de máquina • Essa notação definia símbolos para representar cada instrução, tais como: • ADD – operação de adição • LOAD – operação de leitura da memória • R1 – representa o registrador 1 • ...
História das Linguagens • A nova notação simbólica recebeu o nome de linguagem assembly ou linguagem de montagem • Exemplo de instrução em assembly • ADD R1 R2 R3 • Representa a mesma operação dada antes em código de máquina
História das Linguagens • Não demorou até alguém ter a idéia de criar um programa para traduzir... • De um texto digitado na linguagem assembly • Para um arquivo em linguagem de máquina • Este tipo de programa passou a ser conhecido pelo nome de assembler ou montador • Um compilador primitivo
História das Linguagens • Programar em linguagem de montagem, porém, ainda tinha muitas das desvantagens da própria linguagem de máquina • O principal problema era a necessidade de pensar não só no algoritmo em si, mas no funcionamento do hardware
História das Linguagens • Começaram a surgir outras linguagens mais elaboradas • Fortran (1957) • LISP (1959) • COBOL (1960) • BASIC (1964) • C (1972) • etc.
História das Linguagens • Facilitaram a vida dos programadores ao tornar a notação mais intuitiva e mais distante dos detalhes de hardware • Por exemplo, o comando Fortran temp = 2 + 3 • Engloba várias operações de hardware: • Carregar os operadores em registradores • Operar a adição, guardando o resultado em algum registrador • Copiar o conteúdo do registrador de resultado para a memória
História das Linguagens • As novas linguagens que surgiram foram chamadas de linguagens de alto nível • Em contraste, as linguagens assembly e de montagem passaram a ser chamadas de linguagens de baixo nível
Linguagens de Alto Nível • O nome “alto nível” tem o sentido de “alto nível de abstração”, pois essas linguagens abstraem detalhes operacionais pouco relevantes • Assim, o programador pode (tenta) focar só no algoritmo • Veremos agora dois exemplos para comparar as linguagens de nível alto e baixo
Exemplo em Baixo Nível • Programa Hello World em assembly x86 DOSSEG .MODEL SMALL .DATA Msgdb "Hello World.",13,10,"$" .CODE Start: mov AX, @DATA mov DS, AX lea DX, Msg mov AH, 9 int 21h mov ah, 4ch int 21h END Start
Exemplo em Alto Nível • Programa Hello World em C #include <stdio.h> intmain() { printf(“Hello World”); return 0; }
Linguagens de Baixo Nível • Características gerais • Dependentes de arquitetura • Oferecem instruções primitivas simples (operações sobre o hardware) • Programas extensos e pouco legíveis • Visam oferecer mais controle sobre o hardware
Linguagens de Alto Nível • Características gerais • Especificadas independentemente de qualquer arquitetura • Oferecem comandos mais intuitivos • Dizem mais “o que” deve ser feito do que “como” deve ser feito • Mais fácil de programar e de ler códigos prontos • Restringem o uso do hardware para evitar bugs • Controle da alocação de memória em Java
História dos Compiladores • A criação das linguagens de alto nível provocou o surgimento dos compiladores • Lêem um código de entrada e, como saída, geram um arquivo em código de máquina • O arquivo gerado pode ser executado para produzir os resultados desejados • Depois surgiriam também os interpretadores • Lêem um código de entrada e imediatamente executam o que está descrito no código
compilação execução interpretação Compilação x Interpretação • Compilação • Interpretação código fonte código de máquina resultados código fonte resultados
História dos Compiladores • Tanto compiladores como interpretadores são responsáveis por tornar "real” uma linguagem • Sem eles, a linguagem não tem utilidade • Nos anos 60 e 70 essa área amadureceu, causando um grande crescimento do número de linguagens de alto nível • Hoje a área está bem estabelecida...
História dos Compiladores • Atualmente as técnicas são aplicadas não só em linguagens de programação, mas também em outros contextos da computação • Linguagens de consulta, como SQL • Linguagens de descrição de dados, como XML • Ferramentas auxiliares de desenvolvimento • Syntaxhighlighting, outline do código, etc.
O que é um Compilador? • Um compilador é um programa que lê um programa escrito em uma linguagem (linguagem fonte) e a traduzem um programa equivalente em outra linguagem (linguagem alvo). Aho, Sethi, Ullman. Compilador Programa Fonte Programa Objeto
O que é um Compilador? • Exemplo de linguagem fonte: C, Pascal, Java, etc. • Exemplo de linguagem destino: • Linguagem de máquina (assembler) de um processador • Linguagem de uma máquina virtual (Java) • Qualquer outra linguagem (C)
O que é um Compilador? • Nesse processo de tradução, há duas tarefas básicas a serem executadas por um compilador: • análise, em que o texto de entrada (na linguagem fonte) é examinado, verificado e compreendido • síntese, ou geração de código, em que o texto de saída (na linguagem objeto) é gerado, de forma a corresponder ao texto de entrada
O que é um Compilador? • A fase de análise normalmente se subdivide em análise léxica, análise sintática e análise semântica • É possível representar completamente a sintaxe de uma LP através de uma gramática sensível ao contexto • Mas como não existem algoritmos práticos para tratar essas gramáticas, a preferência recai em usar gramáticas livres de contexto • Deixa-se para a análise semântica a verificação de todos os aspectos da linguagens que não se consegue exprimir de forma simples usando gramáticas livres de contexto
O que é um Compilador? • A implementação de reconhecedores de linguagens regulares (autômatos finitos) é mais simples e mais eficiente do que a implementação de reconhecedores de linguagens livres de contexto (autômatos de pilha) • Nesse caso, é possível usar expressões regulares para descrever a estrutura de componentes básicos das LP, tais como identificadores, palavras reservadas, literais numéricos, operadores e delimitadores, etc. • Essa parte da tarefa de análise (análise léxica) é implementada separadamente, pela simulação de autômatos finitos
O que é um Compilador? • Um dos modelos possíveis para a construção de compiladores faz a separação total entre o front-end, encarregado da fase de análise, e o back-end, encarregado da geração de código, de forma que: • O front-end e back-end se comunicam apenas através de uma representação intermediária • O front-end depende exclusivamente da linguagem fonte • O back-end depende exclusivamente da linguagem objeto
O que é um Compilador? • Essa idéia visa simplificar a implementação de várias linguagens de programação para várias máquinas: • Basta escrever um front-end para cada linguagem, e um back-end para cada máquina • Ou seja, para implementar m linguagens em n máquinas, precisamos fazer mfront-ends e nback-ends, em vez de mn compiladores completos
O que é um Compilador? • Um compilador típico consiste de algumas fases onde cada uma passa sua saída para as fases seguintes • As principais fases são: • análise léxica (ou scanner) • análise sintática (ou parser) • análise semântica • otimização • gerador de código • otimização (novamente!)
Fases da Compilação Programa Fonte Analisador Léxico Analisador Sintático e Semântico Manipulador de erros Tabela de Símbolos Gerador de Código Intermediário Otimizador de Código Gerador de código Programa Objeto
Análise Léxica • Também chamada de scanner • Agrupa caracteres em símbolos (ou tokens) • Token: <nome-token, valor-atributo> • Entrada: fluxo de caracteres • Saída: fluxo de símbolos • Símbolos são: • Palavras reservadas, identificadores de variáveis e procedimentos, operadores, pontuação, etc. • Expressões regulares usadas para reconhecimento • Scanner é implementado como uma MEF (Método dos Elementos Finitos) • Lex/Flex (J) são ferramentas para gerar scanners
Análise Léxica • Por exemplo, os caracteres na instrução de atribuição position= initial + rate * 60 • seriam agrupados nos seguintes tokens: • O identificador position • O símbolo de atribuição = • O identificador initial • O símbolo de adição + • O identificador rate • O símbolo de multiplicação * • O número 60 <id, 1> <=> <id, 2> <+> <id, 3> <*> <60>
Análise Sintática • Também chamada de parser • Agrupa símbolos em unidades sintáticas • Ex.: os 3 símbolos A+B podem ser agrupados em uma estrutura chamada de expressão • Expressões depois podem ser agrupados para formar comandos ou outras unidades • Saída: representação de árvore sintátca do programa • Gramática livre de contexto é usada para definir a estrutura do programa reconhecida por um parser • Yacc/Bison (J) são ferramentas para gerar parsers
Análise Sintática • Regras sintáticas (1) • Qualquer identificador é uma expressão • Qualquer número é uma expressão • Se expressão1 e expressão2 são expressões, então também são expressões • expressão1+ expressão2 • expressão1* expressão2 • ( expressão1)
Análise Sintática • Regras sintáticas (2) • Se identificador1 é um identificador e expressão2 é uma expressão, então identificador1= expressão2é um comando (statement) • Se expressão1 é uma expressão e statement2 é um comando (statement), entãowhile( expressão1) { statement2 }if( expressão1) { statement2 } são comandos (statements)
Análise Sintática position = initial + rate * 60 comando de atribuição identificador = expressão position expressão expressão + * expressão expressão identificador initial identificador número rate 60
Análise Semântica • Verifica se estruturas sintáticas, embora corretas sintaticamente, têm significado admissível na linguagem • Por exemplo, não é possível representar em uma gramática livre de contexto uma regra como “todo identificador deve ser declarado antes de ser usado”, e a verificação de que essa regra foi aplicada cabe à análise semântica • Um importante componente é checagem de tipos • Considerando “A + B”, quais os possíveis problemas semânticos? • Saída: árvore sintática anotada
Análise Semântica • Não existe um modelo matemático inteiramente adequado para descrever o que deve ser verificado na análise semântica, mas mecanismos como gramáticas de atributos tem sido usados • Durante o parser, informações sobre variáveis (e outros objetos) são armazenados em uma tabela de símbolos e são utilizadas na checagem
Análise Semântica • Análise semântica insere uma conversão de inteiro para real = = position position + + initial * initial * rate rate 60 int2real 60
Gerador de Código Intermediário • Usa as estruturas produzidas pelo analisador sintático e verificadas pelo analisador semântico para criar uma sequência de instruções simples (código intermediário) • Está entre a linguagem de alto nível e a linguagem de baixo nível