1
Revisão Compiladores – AP1
Prof. Alexandre MonteiroBaseado em material cedido pelo Prof. Euclides
Arcoverde
Recife
Contatos Prof. Guilherme Alexandre Monteiro Reinaldo Apelido: Alexandre Cordel E-mail/gtalk: [email protected]
[email protected] Site: http://www.alexandrecordel.com.br/fbv Celular: (81) 9801-1878
3
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 das Linguagens
5
História das Linguagens Começaram a surgir outras linguagens
mais elaboradas• Fortran (1957)• LISP (1959)• COBOL (1960)• BASIC (1964)• C (1972)• etc.
Linguagens de Alto Nível
7
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
8
Exemplo em Baixo Nível
Programa Hello World em assembly x86DOSSEG.MODEL SMALL.DATA Msg db "Hello World.",13,10,"$".CODEStart: mov AX, @DATA mov DS, AX lea DX, Msg mov AH, 9 int 21h mov ah, 4ch int 21hEND Start
9
Exemplo em Alto Nível
Programa Hello World em C
#include <stdio.h>
int main() { printf(“Hello World”); return 0;}
10
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
11
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
Introdução a Compiladores
13
Compilação x Interpretação Compilação
Interpretação
compilaçãocódigofonte
códigode
máquina
execuçãoresultados
interpretaçãocódigofonte resultados
14
O que é um Compilador? Um compilador é um programa que lê um
programa escrito em uma linguagem (linguagem fonte) e a traduz em um programa equivalente em outra linguagem (linguagem alvo). Aho, Sethi, Ullman.
CompiladorProgramaFonte
ProgramaObjeto
15
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
16
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
17
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
18
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
19
Etapas da Compilação
Front-End(Análise)
Back-End(Síntese)
Análise Léxica
Análise Sintática
Analise Semântica
Geração de CódigoIntermediário
Geração de CódigoFinal
20
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!)
21
Fases da Compilação
Programa Fonte
Analisador Léxico
Analisador Sintático eSemântico
Gerador de CódigoIntermediário
Otimizador de Código
Gerador de código
Manipulador de erros
Tabela deSímbolos
Programa Objeto
22
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
23
Análise Léxica Por exemplo, os caracteres na instrução
de atribuiçãoposition = 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>
24
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ática 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
25
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 )
26
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ão
while ( expressão1 ) { statement2 }if ( expressão1 ) { statement2 }
são comandos (statements)
27
Análise Sintática
position = initial + rate * 60
initial
rate
*
+
=
position
60
comando de atribuição
identificador expressão
expressão expressão
expressãoidentificador
identificador
expressão
número
28
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
29
Gerador de Código Intermediário Considere que temos um único
registrador acumulador Considere o comando de atribuiçãox := a + b * cpode ser traduzido em:
t1 := b * ct2 := a + t1x := t2
Pode-se fazer um gerador de código relativamente simples usando regras como:
30
Gerador de Código Intermediário
31
Gerador de Código Intermediário O comando de atribuiçãox := a + b * c
Gera o códigoLoad b { t1 := b * c }Mult cStore t1Load a { t2 := a + t1 }Add t1Store t2Load t2 { x := t2 }Store x
32
Otimizador de Código Independente da máquina Melhora o código intermediário de modo
que o programa objeto seja menor (ocupe menos espaço de memória) e/ou mais rápido (tenha tempo de execução menor)
A saída do otimizador de código é um novo código intermediário
33
Otimizador de Código
Otimizador por sub-expressões comuns
34
Gerador de Código Produz o código objeto final Toma decisões com relação à:
• Alocação de espaço para os dados do programa;
• Seleção da forma de acessá-los• Definição de quais registradores serão usados,
etc. Projetar um gerador de código que
produza programas objeto eficientes é uma das tarefas mais difíceis no projeto de um compilador
35
Gerador de Código Várias considerações têm que ser feitas:
• Há vários tipos de instruções correspondendo a vários tipos de dados e a vários modos de endereçamento
• Há instruções de soma específicas, por exemplo para incrementar/decrementar de 1
• Algumas somas não foram especificadas explicitamente
- Cálculo de endereço de posições em vetores• Incremento/decremento registrador de topo
pilha• Local onde armazenar variáveis• Alocação de registradores
36
Gerador de Códigotemp1 = id3 * 60.0id1 = id2 + temp1
MOVF id3, R2MULF #60.0, R2MOVF id2, R1ADDF R2, R1MOVF R1, id1
37
Tabela de Símbolos É uma estrutura de dados usada
para guardar informações a respeito de todos os nomes usados pelo programa e registrar informações importantes associadas a cada um, tais como seu tipo (inteiro, real, etc.), tamanho, escopo, etc.
38
Manipulador de Erros É ativado sempre que for detectado um
erro no programa fonte Deve avisar o programador da ocorrência
do erro emitindo uma mensagem, e ajustar-se novamente à informação sendo passada de fase a fase de modo a poder completar o processo de compilação• Mesmo que não seja mais possível gerar
código objeto, a análise léxica e sintática deve prosseguir até o fim
39
Bibliografia AHO, A., LAM, M. S., SETHI, R., ULLMAN, J. D.,
Compiladores: princípios, técnicas e ferramentas. Ed. Addison Wesley. 2a Edição, 2008 (Capítulo 1)
40
Adiantando... Lexema: sequência de caracteres com
significado interligado Token: classificação dada ao lexema
• Geralmente retornado junto com o próprio lexema ou outro atributo, como um ponteiro
Padrão: é uma descrição da forma que os lexemas de um token podem tomar. •Ex. sequência de caracteres que formam palavra-chave como um token.
41
Exemplos
42
Especificando Tokens Geralmente são especificados com expressões
regulares
Cada token é associado a uma expressão regular que representa seus lexemas válidos• Padrão que representa várias palavras (dizemos que as palavras “casam” com o padrão)
43
Especificando Tokens
Expressões Regulares• Formalismo utilizado para definir o conjunto de
aceitação de uma linguagem• Principais operadores utilizados pelas ERs
Expressão Reconhece ε A cadeia de caractedes vazia “” “str” A string “str” A | B Todas as cadeias reconhecidas por A ou B A . B Cadeias formadas pela concatenação das
cadeias reconhecidas por A e B A+ Reconhece cadeias formadas pela
concatenação de um número finito de cadeias reconhecidas por A
44
Especificando Tokens
Operadores derivadosExpressão Equivale a
( A ) Agrupamento de operadores A* A+ | ε A? A | ε [A-Z] A | B | ... | Y | Z A{N} A . A . A ... } N vezes A{M,N} A . A . A ... } entre M e N vezes AB A . B abc String “abc”
45
Especificando Tokens Exercícios
• Defina expressões para expressar:- Número IP: \d{3}.\d{3} .\d{3} .\d{3}- Números naturais (e inteiros): \d{n} ou [0-9]{n}- Números de telefone (com DDD opcional): \([0-9]{2}\).[0-9]{4}.
[0-9]{4}- Horas: [012]\d:[0-5]\d- E-mails: [a-zA-Z0-9\._-]@[A-Za-z]+\\.[A-Za-z]+- Placa de Carro: [A-Z]{3}-\d{4}- CEP: \d{5}-\d{3} ou \d\d\d\d\d- URLs:
- Com http - (http|https)://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
- Sem http - ([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
46
Análise Sintática Alguns autores consideram que a análise sintática
envolve tudo que diz respeito à verificação do formato do código fonte
• Inclui a análise léxica como subfase
• Visão mais coerente, porém o livro texto que usamos tem outra visão...
47
Análise Sintática Entenderemos análise sintática como
sendo apenas a segunda fase dessa verificação de formato:
• “É a fase que analisa os tokens para descobrir a estrutura gramatical do código fonte”
• Também chamada “Reconhecimento” (Parsing)
• Porém, um nome melhor seria “Análise Gramatical”
48
Gramáticas São usadas para organizar os tokens em “frases” de
sentido lógico
Definem regras de formação recursivas
expressão → CTE_INT | ID | expressão + expressão
49
Gramáticas As gramáticas livres de contexto possuem quatro
elementos:
• Símbolos terminais• Símbolos não-terminais • Símbolo inicial• Produções! ou Regras de Produção!
50
Gramáticas Elementos das gramáticas livres de contexto:
• Símbolos terminais: - Símbolos assumidos como atômicos, indivisíveis- Em compiladores, são os tokens (int, +, -, /, identificador,
valores literais, etc)
• Símbolos não-terminais: - Usados para organizar os tokens em “frases”- Representam elementos abstratos do programa (expressões,
termos, etc)
51
Gramáticas Elementos das gramáticas livres de
contexto:
• Símbolo inicial: - Não-terminal a partir do qual são derivadas
“cadeias” de símbolos terminais- Geralmente é um não-terminal chamado programa
• Produções: - São as regras de formação - Definem as “frases” de terminais válidas na
linguagem
52
Derivação Seja a gramática BNF anterior
• Derivar a cadeia “i+i+x”
• Consideremos que esta é uma derivação mais à esquerda
<expressao>Þ <termo> + <expressao>Þ i + <expressao>Þ i + <termo> + <expressao>Þ i + i + <expressao>Þ i + i + <termo>Þ i + i + x
53
Derivação Seja a gramática BNF mostrada antes
• Agora a Derivação mais à direita da cadeia “i+i+x”
<expressao>Þ <termo> + <expressao> Þ <termo> + <termo> + <expressao>Þ <termo> + <termo> + <termo>Þ <termo> + <termo> + xÞ <termo> + i + xÞ i + i + x
54
Derivação O processo de derivação consiste em
substituir cada ocorrência de um não-terminal pelo lado direito (corpo) de alguma de suas produções• Derivação mais à esquerda: substituir sempre
o não-terminal mais à esquerda• Derivação mais à direita: análogo
A derivação pára quando sobrarem apenas terminais e o resultado é a cadeia
55
Árvore de Derivação
A árvore dos exemplos anteriores
expressao
expressao
i
+
i
+
x
termo
termo expressao
a cadeia gerada pode ser percebida nas folhas, analisando-as da esquerda para a
direita
termo
56
Árvore de Derivação
A mesma árvore reorganizada
expressao
expressao
i + i + x
termo
termo expressao
a seqüência de terminais (cadeia) gerada
termo
57
Gramáticas Ambíguas Uma gramática é dita ambígua se ela puder gerar
duas árvores de derivação distintas para uma mesma cadeia
Veremos uma gramática equivalente à anterior, porém ambígua...
58
Gramáticas Ambíguas Exemplo
• Mostrar duas árvores de derivação para “i+i+x”
<expressao> ::= <expressao> "+" <expressao> | "x" | "i"
59
Gramáticas Ambíguas Gramáticas ambíguas são, em geral, inadequadas
para uso em compiladores• Dificultam a construção do analisador sintático
Em alguns casos, são usadas gramáticas ambíguas junto com restrições adicionais• Exemplo: precedência e associatividade
60
Tratando Ambiguidades A seguinte de definição de comando apresenta
ambigüidade
Exemplo: “if (x) if (x) outro else outro”• A qual “if” está ligado o “else”?
cmd ::= if ( expr ) cmd else cmd | if ( expr ) cmd | outro
expr ::= x
61
Tratando Ambiguidades Solução
cmd ::= matched-cmd | open-cmd
matched-cmd ::= | if ( expr ) matched-cmd else matched-cmd | outro
open-cmd ::= | if ( expr ) cmd | if ( expr ) matched-cmd else open-cmd
62
Análise de Descida Recursiva É uma análise sintática top-down ou
descendente
É um parser LL(1)• Left-to-right – a ordem de leitura dos tokens é
da esquerda para a direita
• Leftmost derivation – segue a ordem típica de uma derivação mais à esquerda
• Só olha 1 token por vez
63
Análise de Descida Recursiva É útil para construir parsers manualmente, por
ser fácil de implementar Princípio Básico
• Se eu quero produzir um não-terminal e sei qual o terminal corrente eu devo saber que regra deve ser aplicada.
Porém, requer uma gramática muito bem ajustada
• Veremos os ajustes necessários Mostraremos como usar a técnica, assumindo
que o parser final será uma classe Java
64
Exemplo de Reconhecedor Gramática:Terminais = {a,b,c}Não-terminais = {S, X}Produções = {
S a X bS b X aS cX b XX a
}Não-terminal inicial = S
a b cS a X
bb X a c
X a b X Error
65
Conjunto FIRST FIRST(α)
• Aplicado a cadeias α quaisquer
É o conjunto de terminais que podem iniciar a cadeia α dada• Se a cadeia derivar vazio, também inclui ε
Como calcular?
66
Conjunto FIRST Se α for a cadeia vazia (α = ε)
• FIRST(ε) = { ε }
Se for um terminal a qualquer• FIRST(a) = { a }
Se for um não-terminal N com as produções N → α | β• FIRST(N) = FIRST(α) FIRST(β)
67
Conjunto FIRST Se for uma cadeia de símbolos α =
X1X2...Xk • Depende de X1
Se X1 não pode gerar vazio• FIRST (X1X2...Xk) = FIRST(X1)
Se X1 pode gerar vazio• FIRST (X1X2...Xk) = FIRST(X1)
FIRST(X2...Xk)• Calcula para X1 e continua o cálculo
recursivamente para o restante da cadeia
68
Múltiplas Produções Calculando o FIRST para as duas produções de
comando ...• comando ::= atribuição | declaração
FIRST(atribuição) = FIRST(IDENTIFICADOR = <expressao> ;) = { IDENTIFICADOR }
FIRST(declaração) = FIRST(tipo IDENTIFICADOR ;) = FIRST(tipo) = { int , char, float }
69
Eliminação de Fatoração Direta:
X a b cX a d e
Sem fatoração:X a YY b cY d e
IndiretaX a bX Y cY a d
Elimina-se a indireção:X a bX a d cY a d
Sem fatoração:X a ZY a dZ bZ d c