Projeto de Compiladores - DEINF/UFMAcsalles/comp/comp_parte1.pdf · • GRUNE, DICK et al. Projeto...
Transcript of Projeto de Compiladores - DEINF/UFMAcsalles/comp/comp_parte1.pdf · • GRUNE, DICK et al. Projeto...
Slide 1 Compiladores I
CP 5017.9 – Compiladores ICP 5017.9 – Compiladores IProf. Msc. Carlos de SallesProf. Msc. Carlos de Salles
1 - EMENTAO Processo de Compilação. Deteção e Recuperação de Erros. Introdução à geração de Código Intermediário. Geração de Código de Máquina. Otimização. Uma visão sobre alguns compiladores. A construção de um compilador.
2 - O PROCESSO DE COMPILAÇÃO 2.1 - Introdução 2.2 - Aspectos do Processo de Compilação
3 - DEFINIÇÃO DE LINGUAGEM 3.1 - Introdução 3.2 - Sintaxe e Semântica 3.3 - Gramáticas - Definição Formal de Linguagem de Programação 3.4 - O Problema da Análise
4 - ANÁLISE LÉXICA 4.1 - Introdução 4.2 - Construção Manual de Analisadores 4.3 - Construção Sistemática de Analisadores 4.4 - Saídas do Analisador Léxico 4.5 - Implementação 4.6 - Erros
5 - GRAMÁTICAS LIVRES DO CONTEXTO 5.1 - Sintaxe e Semântica 5.2 - Gramáticas - Definição Formal de Linguagens de Programação 5.3 - Gramáticas Livres do Contexto
Slide 2 Compiladores I
CP 5017.9 – Compiladores I CP 5017.9 – Compiladores I Prof. Msc. Carlos de SallesProf. Msc. Carlos de Salles
Ementa (continuação)
6 - ANÁLISE SINTÁTICA DESCENDENTES 6.1 - Formalização 6.2 - Análise com Recuperação 6.3 - Análise sem Recuperação 6.3.1 - Analisadores Recursivos 6.3.2 - Analisadores Preditivos
7 - ANÁLISE SNTÁTICA ASCENDENTE 7.1 - Formalização 7.2 - Analisadores de Precedência
8 - TRADUÇÃO DIRIGIDA POR SINTAXE 8.1 - Esquemas da Tradução Dirigida por Sintaxe 8.2 - Implementação de Tradutores Dirigidos por Sintaxe
9 - TABELA DE SÍMBOLOS 9.1 - Os Conteúdos da Tabela de Símbolo 9.2 – Organizações de Tabelas de Símbolos
10 - ORGANIZAÇÃO DE MEMÓRIA11 - CONSTRUÇÃO AUTOMÁTICA DE
ANALISADORES EFICIENTES
Bibliografia• GRUNE, DICK et al. Projeto Moderno de Compiladores.
Editora Campus.• AHO, Alfred; SETHI, Ravi; ULLMAN, Jeffrey D. Compiladores
– Princípios, Técnicas e Ferramentas. Editora Guanabara. Editora LTC.
Slide 3 Compiladores I
Paradigma análise/síntese
código fonte interface devanguarda
código intermediário geradorde código
código alvo
Estrutura básica de um compilador
Slide 4 Compiladores I
Interface de vanguarda do compilador
• Engloba as fases de análise• Gera uma representação semântica intermediária
• Análise léxica– Converte uma seqüência de caracteres no(s)
arquivo(s) de entrada com o código fonte em uma seqüência de tokens equivalente;
– Cada token representa um elemento atômico da linguagem;
• Análise sintática– Transforma a seqüência de tokens em uma árvore
sintática que representa o código fonte;– É dividido em dois grupos de métodos: os top-down
e os bottom-up;
• Tratamento de contexto– Avalia erros de tipagem e nos identificadores;– Baseia-se em informações coletadas numa
estrutura chamada tabela de símbolos.
Slide 5 Compiladores I
Geração de código X Interpretação
• Geração de código– Transforma a representação semântica intermediária em
código executável em uma linguagem alvo;– Características:
• Processamento considerável;• A forma intermediária resultante, que é de código binário
específico da máquina, é de baixo-nível;• O mecanismo de interpretação é o próprio hardware da
CPU;• A execução do programa é relativamente rápida;
• Interpretação– Ao invés de traduzir para uma linguagem alvo, realiza as
ações semânticas diretamente;– Vantagens:
• Geralmente um interpretador é escrito em uma linguagem de mais alto nível, portanto irá rodar em várias máquinas diferentes. Um gerador de código é voltado para uma máquina específica;
• Escrever um interpretador exige esforço bem menor;• Executar as ações diretamente da representação semântica
permite tanto uma melhor verificação quanto informação de erros;
• Melhor segurança (explorado por Java)– Características:
• O processamento do programa é de mínimo a moderado;• A forma intermediária resultante, alguma estrutura de dados
específica de sistema, de médio a alto nível;• O mecanismo de interpretação é um programa;• A execução do programa é relativamente lenta.
Slide 6 Compiladores I
Por que estudar Compiladores?
• Estruturação eficaz do problema– Compiladores analisam sua entrada, constroem uma
representação semântica intermediária e sintetizam sua saída a partir disso;
– Esse paradigma análise-síntese é muito poderoso e amplamente aplicável;
– Usando a mesma representação semântica intermediária, um compilador pode ser escrito para L linguagens e M máquinas, bastando escrever as L interfaces de vanguarda diferentes e os M sintetizadores diferentes;
• Uso eficiente de formalismos– Expressões regulares – análise léxica– Gramáticas livres de contexto – análise sintática– Gramáticas de atributo – tratamento de contexto e
extensão para geração de código e interpretação– Casamento de padrões e técnicas de programação
dinâmica – geração de código• Uso de ferramentas de geração de programas
– A entrada para um gerador de programas é de um nível de abstração mais alto que de uma linguagem de programação;
– Flexibilidade e mutabilidade ampliadas;– Código pré-construído pode ser incluído em um programa
gerado, aumentando seu poder a quase nenhum custo;– Uma descrição formal pode ser usada para gerar mais de
um tipo de programa;– Exemplo: Flex/Bison; Lex/Yacc.
Slide 7 Compiladores I
Por que estudar Compiladores?
• A construção de compiladores tem uma ampla aplicabilidade;– O uso de técnicas de construção de compiladores
são aplicadas em outros escopos– Exemplos:
• Conversão de formatos diferentes de arquivos;• Leituras de arquivos HTML ou PostScript.
• Compiladores são baseados em algoritmos gerais úteis– As estruturas de dados e algoritmos
implementados são didaticamente importantes– Exemplos:
• Árvores sintáticas;• Tabelas hash;• Tabelas pré-processadas ;• Coletor de lixo;• Programação dinâmica, etc.
• Enfim, estudar compiladores é solidificar o conhecimento de programação!
Slide 8 Compiladores I
Propriedades de um bom compilador
• Gerar código correto;– De que adianta um compilador que erra uma vez a
cada um milhão;• Estar de acordo com a especificação da
linguagem;– Nada de super ou sub-conjuntos da linguagem;
• Ser escalável, ou seja, ser capaz de tratar códigos fonte de tamanhos arbitrários;– Atualmente isso é muito mais simples já que a
quantidade de memória disponível é cada vez maior;
• Velocidade de compilação não é mais importante;– Nada mais de cartões – compilar agora é rápido;
• Algoritmos desejavelmente lineares – O(n);– Não é interessante a existência de algoritmos não-
lineares no processo de compilação;– Problema: a otimização de código não é linear;
• Outras características importantes:– Portabilidade do compilador;– Portabilidade do código alvo;
Slide 9 Compiladores I
Histórico de compiladores
• 1945 – 1960: Geração de Código– Linguagens sendo desenvolvidas lentamente;– O uso de compiladores era chamado de
“programação automática”;– Linguagens “de alto nível” não eram bem vistas;
• Se o compilador gerava código inferior àquele gerado manualmente com assemblers, para que uma linguagem de alto nível?
• 1960 – 1975: Parsing– Proliferação de novas linguagens;– Nasce a idéia que é melhor ter ferramentas para
gerar um compilador rapidamente que gerar código mais eficiente;
– Mudança no paradigma: maior enfoque na análise que na síntese;
– Aparecimento de técnicas formais notadamente voltadas para a geração de parsers;
• 1975 – dias atuais: Geração e Otimização de Código; Novos Paradigmas– Número de novas linguagens e de novas máquinas
caem;– Demanda por interfaces com o usuário mais
amigáveis (nascimento dos IDEs);– Novos paradigmas de programação, como
linguagens funcionais e lógicas e programação distribuída;
– A ênfase agora é “o que compilar” e não mais “como compilar”.
Slide 10 Compiladores I
Análise Léxica
• O objetivo da análise léxica é ler o(s) arquivo(s) fonte de entrada, caractere a caractere, e transformá-lo(s) numa seqüência de símbolos léxicos chamados de tokens;
• Tokens são elementos indivisíveis da linguagem como palavras reservadas, constantes, cadeias de caracteres, identificadores, operadores e outros delimitadores
• Internamente, um token pode ser expresso por três informações:– Classe do Token: define seu tipo;
• Exemplos: operador, palavra reservada, identificador etc.
– Valor do Token: dependendo da classe do token, assume um valor que o representa;
• Exemplos: “i” para um identificador; “123” para uma constante numérica etc;
– Posição do Token: define o arquivo e a posição (linha e coluna) dentro daquele arquivo do respectivo token.
Slide 11 Compiladores I
Implementação de um Analisador Léxico
• Como ler o arquivo texto de entrada?– Memória não é mais um problema;– A entrada por dois buffers (atual e anterior);– Leitura do arquivo de entrada colocando-o em
memória em uma única chamada ao sistema operacional;
• O problema da nova linha– No Windows/DOS, uma nova linha é marcada
pela seqüência de caracteres ASCII 13 e 10;– No Linux, uma nova linha é marcada pelo
caractere ASCII 10;– No OS/2, cada linha é um vetor de caracteres;
• O que é um token exatamente?– Simplificação: se você pode colocar espaços
na esquerda e direita sem alterar o significado, trata-se de um token;
• Comentários não são tokens. A análise léxica os remove da seqüência de tokens gerada como saída.
Slide 12 Compiladores I
Interface de Programação (API) Léxica
• Funções públicas– void startLex(string fileName);
• Abre o arquivo fonte de entrada fileName e o copia por inteiro para a memória
• Caso o compilador manipule múltiplos arquivos, utiliza uma pilha para armazenar a posição em que estava manipulando o arquivo atual e abre o novo arquivo
– Token nextToken();• Avalia o arquivo fonte atual, caractere por
caractere, e retorna o próximo token• Implementação de autômatos
• Algumas funções privadas úteis– char nextChar();
• Retorna o próximo caractere do arquivo atual, contando as linhas e colunas;
– void skipLayoutAndComment();• Percorre o fonte ignorando comentários
até encontrar um caractere que não seja espaço, tabulação ou pulo de linha
Análise LéxicaArquivos fonte Tokens
Slide 13 Compiladores I
Expressões Regulares (ER)
Padrões Básicos: Significado:x O caractere x. Qualquer caractere, exceto nova linha[xyz...] Quaisquer caracteres x, y, z, ...
Operadores de repetição:R? Um R ou nada (opcionalmente R)R* Zero ou mais ocorrências de RR+ Uma ou mais ocorrências de R
Operadores de composição:R1R2 Um R1 seguido de um R2R1|R2 Ou R1, ou R2
Agrupamento:(R) R por si só
Exemplos:inteiro := [0..9]+real := [0-9]*{.}[0-9]+identificador := [a-zA-Z][a-zA-Z0-9_]*
Slide 14 Compiladores I
Autômatos expressando ER's
• Número inteiro – [0-9]+
• Número real – [0-9]*{.}[0-9]+
• Identificador – [a-zA-Z][a-zA-Z0-9_]*
• Exemplo mais complexo [a][b|c]*[d]+{e}
1 0-9
0-9
2
1 0-9
0-9
2 3 0-9
0-9
4
.
.
1 a-zA-Z
a-zA-Z0-9_
2
1 a e 42 d
bc
3
d
Slide 15 Compiladores I
Análise Sintática
• Responsável pela construção da árvore sintática abstrata (AST) com base nos tokens de entrada;
• Há dois tipos de métodos de parsing (verificação sintática):– Top-down: a árvore sintática é construída da
raiz para as folhas– Bottom-up: a partir das folhas da árvore são
construídas hipóteses que montam recorrentemente seus pais, de forma que a raiz da árvore é o último nó a ser construído
• Uma linguagem de programação é formalmente definida por uma gramática, tipicamente livre de ambigüidade, de forma que uma única árvore sintática pode representar o fonte;
• Linguages de programação geralmente são expressas por meio de uma notação chamada BNF (Backus Naur Form) ou sua extensão E-BNF (Extended-BNF) que acrescenta o uso de expressões regulares
Análise SintáticaÁrvore SintáticaTokens
Slide 16 Compiladores I
Árvore sintática – expressão b*b-4*a*c
Árvore sintática abstrata
Árvore Abstrata Anotada
Slide 17 Compiladores I
Parser Recursivo Descendente
• Método top-down baseado no mecanismo de recursão das linguagens de programação;
• O processo inicia com a construção da raiz da árvore sintática, representando um não-terminal N;
• A decisão da alternativa correta a ser considerada de N é feita com base em um processo recursivo que tenta as alternativas de N;
• A alternativa escolhida é a primeira possível, de forma a evitar backtracking, o que simplifica o processo mas cria alguns problemas;
• Exemplo de gramática:entrada → expressao EOFexpressao → termo resto_expressaotermo → IDENT | parenteses_expparenteses_exp → '(' expressao ')'resto_expressao → operador expressao | εoperador → '+' | '-' | '*' | '/'
• Tokens dessa linguagem:IDENT := [a-zA-Z][a-zA-Z0-9_]*EOF, '(', ')', '+', '*', '-', '/'
Slide 18 Compiladores I
Parser Recursivo Descendentebool entrada() {
return expressao() && tk(EOF);}bool expressao() {
return termo() && resto_expressao();}bool termo() {
return tk(IDENT) || parenteses_exp();}bool parenteses_exp() {
return tk('(') && expressao() && tk(')');
}bool resto_expressao() {
return (operador() && expressao() ) || true;
}bool operador() {
return tk('+') || tk('-') || tk('*') || tk('/');
}bool tk(Token tt) {
if(tokenAtual!=tt) return false;getNextToken(); return true;
}
Slide 19 Compiladores I
Conjunto FIRST de uma gramática
• entrada { IDENT ’(’ }• expressao { IDENT ’(’ }• termo { IDENT ’(’ }• parenteses_exp { ’(’ }• resto_expressao { ’+’ ε }
Data definitions:1. Token sets called FIRST sets for all terminals, non-terminals and alternatives of non-terminals in G.2. A token set called FIRST for each alternative tail in G; an alternative tail is a sequence of zero or more grammar symbols α if A α is an alternative or alternative tail in G.Initializations:1. For all terminals T, set FIRST(T) to {T}.2. For all non-terminals N, set FIRST(N) to the empty set.3. For all non-empty alternatives and alternative tails α, set FIRST(α) to the empty set.4. Set the FIRST set of all empty alternatives and alternative tails to {ε}.Inference rules:1. For each rule N→α in G, FIRST(N) must contain all tokens in FIRST(α), including ε if FIRST(α) contains it.2. For each alternative or alternative tail α of the form Aβ, FIRST(α) must contain all tokens in FIRST(A), excluding ε, should FIRST(A) contain it.3. For each alternative or alternative tail α of the form Aβ and FIRST(A) contains ε, FIRST(α) must contain all tokens in FIRST(β), including ε if FIRST(β) contains it.