Compilador
Software que traduz o texto (linguagem
fonte) que representa um programa para
código máquina(linguagem alvo) capaz de
ser executado pelo computador
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.
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 Linguagem de Programação através de
uma gramática livre 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.
Pré-processador
Analisador Léxico
Analisador Sintático
front-end
Analisador Semântico
Gerador de Código
(intermediário)
Otimizador
back-end
Gerador de Código
final = (nota1 + nota2) / 2;
Analisador Léxico
Id1 = (Id2 + Id3) / 2
Analisador Sintático
=
Id1
Tabela de Símbolos
/
2
+
Id2
Id3
Id1
final
double
...
Id2
nota1
double
...
Id3
nota2
double
...
...
Analisador Semântico
=
Id1
/
intToDouble(2)
+
Id2
Id3
Gerador de Código
(intermediário)
Tabela de Símbolos
Id1
final
double
...
Id2
nota1
double
...
temp2 = Id3 * temp1
Id3
nota2
double
...
temp3 = Id2 / temp2
...
temp1 = intToDouble(2)
Id1 = temp3
Otimizador de Código
Temp1 = id3 *2.0
Id1 = id2 / temp1
Gerador de Código
MOVF ID3, R2
MULF #2.0, R2
MOVF ID2, R1
DIVF R2, R1
MOVF R1, ID1
Tabela de Símbolos
Id1
final
double
...
Id2
nota1
double
...
Id3
nota2
double
...
...
Fases de um Compilador
Gerenciamento da tabela de símbolos:
uma estrutura de dados contendo um
registro para cada identificador, com os
campos contendo os atributos do
identificador.
Quando o analisador léxico detecta um
identificador, instala-o na tabela de
símbolos.
A estrutura de dados permite encontrar
rapidamente cada registro e armazenar ou
recuperar dados do mesmo.
Fases de um Compilador
Um compilador não deve parar quando encontrar
algum erro e sim continuar para detectar todos.
A análise léxica substituir a estrutura por tokens e
acrescenta na tabela de símbolos
A análise sintática transforma um texto na entrada
em uma estrutura de dados, em geral uma árvore,
o que é conveniente para processamento posterior
e captura a hierarquia implícita desta entrada
A análise semântica verifica os erros semânticos,
(por exemplo, uma multiplicação entre tipos de
dados diferentes) no código fonte e coleta as
informações necessárias para a próxima fase da
compilação que é a geração de código objeto
Compilador simples de uma passagem
Uma linguagem de programação pode ser
definida pela descrição da aparência de seus
programas (a sintaxe da linguagem) e do que os
mesmos significam (a semântica da linguagem)
Para especificar a sintaxe de uma linguagem,
apresentamos uma notação amplamente aceita,
chamada gramática livre de contexto ou BFN
(Forma Backus-Naur)
Para especificar a semântica de uma linguagem
usaremos descrições informais e exemplos
sugestivos.
Gramáticas
Uma linguagem consiste essencialmente de uma
seqüência de strings ou símbolos com regras
para definir quais seqüências de símbolos são
válidas na linguagem, ou seja, qual a sintaxe da
linguagem.
A interpretação do significado de uma seqüência
válida de símbolos corresponde à semântica da
linguagem.
Existem meios formais para definir a sintaxe de
uma linguagem - a definição semântica é um
problema bem mais complexo.
A sintaxe de linguagens é expressa na forma de
uma gramática, que será introduzida na
seqüência.
Estrutura da vanguarda de um
compilador
Fluxo de
caracteres
de entrada
Analisador
léxico
Fluxo
de
tokens
Tradutor
dirigido
pela
sintaxe
Representação
intermediária
Definição da Sintaxe
Uma gramática descreve a estrutura
hierárquica de muitas construções das
linguagens de programação.
O comando if tem a estrutura em C
If (expressão) comando else comando
O comando é if, um parêntese à esquerda,
uma expressão, um parêntese à direita,
um comando, a palavra else e outro
comando
cmd -> if (expr) cmd else cmd
Gramáticas
Um conjunto de regras de produção, é um
símbolo de partida. Uma regra de
produção tem o formato , onde
representa o nome da construção sintática
e representa uma forma possível dessa
construção:
<expressão> <expressão> +
<expressão>
Definição da Sintaxe
A regra é chamada de produção
If e parênteses são tokens
As variáveis expr e cmd são sequências de
tokens e não terminais
Elementos de uma gramática livre de
contexto
Conjunto de tokens (símbolos terminais)
Conjunto não-terminais
Conjunto de produções, onde a produção
consiste em um não-terminal, chamado de
lado esquerdo da produção, uma seta e
uma sequência de tokens e/ou nãoterminais, chamado de lado direito da
produção
Uma designação a um dos não terminais
como símbolo de partida
Gramáticas
<expr> <expr> + <expr>
| <expr> – <expr>
| (<expr>)
| <const>
<const>
<const><const>
|0|1|2|3|4|5|6|7|9
Derivação
A verificar se uma frase faz parte da
linguagem gerada pela gramática,
envolve sucessivas substituições da
cadeia de símbolos que ocorre do lado
esquerdo da produção pela sua
construção sintática correspondente,
partindo do símbolo inicial.
Essa substituição é chamada derivação
sendo normalmente denotada pelo
símbolo .
Derivação
<expressão>
<expr> + <expr>
(<expr>) + <expr>
(<expr> - <expr>) + <expr>
(<const> - <expr>) + <expr>
(<const><const> - <expr>) + <expr>
(1<const> - <expr>) + <expr>
(10 - <expr>) + <expr>
(10 - <const>) + <expr>
...
(10 - 2) + 3
Árvore Gramatical
(10 – 2) + 3
<expr>
<expr>
<expr>
(<expr>)
+
<const>
<expr> - <expr>
<const>
<const>
10
2
3
Gramática Ambíguas
10 – 2 + 3
<expr>
<expr>
<expr>
-
<expr>
<expr>
<expr> + <expr>
10
2
3
+
<expr>
<expr> - <expr>
10
2
3
Precedência de Operadores
Como saber quem precede entre * e +.
Para tal criamos mais dois não terminais
Precedência de Operadores
<expr> <expr> + <termo>
| <expr> - <termo>
| <termo>
<termo> (<expr>)
| <const>
<expr>
<expr>
+
<expr> - <termo>
<expr>
<expr> + <termo>
<expr> - <termo> + <termo> 10
2
<termo> - <termo> + <termo>
10 – 2 + 3
<termo>
3
Precedência de Operadores
<expr>
<termo>
<fator>
<expr> + <termo>
| <expr> - <termo>
| <termo>
<termo> * <fator>
| <termo> / <fator>
| <fator>
(<expr>)
| <const>
1+2*3
<expr>
<expr>
+
<termo>
<termo> * <fator>
3
2
3
Gramática
<expr>
<termo>
<fator>
<expr> + <termo>
| <expr> - <termo>
| <termo>
<termo> * <fator>
| <termo> / <fator>
| <fator>
(<expr>)
| <const>
1+2*3
<expr>
<termo>
<termo> * <fator>
Tradução Dirigida pela Sintaxe
Programa Fonte
Analisador Léxico
token
Solicita token
Analisador Sintático
Analisador Semântico
Código Intermediário
Tabela de Símbolos
...
Notação Posfixa
(9-5)+2 => 95-2+
9-(5+2) => 952+ Os parênteses são desnecessários na
notação posfixa porque a posição e a
aridade (número de argumentos) dos
operadores permitem somente um
decodificação de uma expressão posfixa
Definição dirigida pela Sintaxe
A definição dirigida pela sintaxe usa
gramática livre de contexto para
especificar a estrutura sintática de
entrada.
Cada símbolo da gramática associa um
conjunto de atributos e cada produção
associa um conjunto de regras semânticas
para computar os valores dos atributos
associados aos símbolos que figuram
naquela produção.
Atributos Sintetizados
O valor em um nó da árvore gramatical é
determinado a partir dos valores dos
atributos dos filhos daquele nó.
Os atributos sintetizados possuem a
desejável propriedade de que podem ser
avaliados durante um único
caminhamento bottom-up (final para
início) da árvore gramatical
Análise Léxica
O Analisador Léxico (scanner) examina o
programa fonte caractere por caractere
agrupando-os em conjuntos com um
significado coletivo (tokens):
palavras chave (if, else, while, int, etc),
operadores (+, -, *, /, ^, &&, etc),
constantes (1, 1.0, ‘a’, 1.0f, etc),
literais (“Projeto Mono”),
símbolos de pontuação (; , {, }),
labels.
Token
Tokens, ou lexemas, é uma sequência de
caracteres que podem ser tratados como
uma unidade na gramática de uma
linguagem de programação
Análise Léxica
Entrada: arquivo texto
Saída: sequência de tokens
Conta número de linhas
Remove espaços em branco e comentários
Apresenta símbolos ilegais
Produz a tabela de símbolos
Por que análise léxica?
Simplifica a análise sintática
Simplifica a definição da linguagem
Modularidade
Reusabilidade
Eficiência
Análise Léxica
constanteInt
digito digito*
constanteDouble
digito digito*. digito*
digito
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
X* Representa uma seqüência de zero ou mais X.
Autômatos Finitos
Autômatos finitos, ou máquina de estados
finitos
autômatos finitos determinísticos
autômatos finitos não-determinísticos
Compiladores
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 Linguagens de Programação, 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.
Autômato Finito Determinístico
Definição. Um autômato finito
determinístico é uma quíntupla
M = (K, Σ, *, s, F), onde
K é um conjunto finito de estados
Σ é um alfabeto
s € K é o estado inicial
F está contido K é o conjunto de estados finais
é a função de transição, K x Σ para K
Exemplo de um AFD
Exemplo. Seja M o
autômato finito
determinístico (K, Σ,
*, s, F), onde
K = {q0, q1}
Σ = {a,b}
s = q0
F = K {q0}
q
(q, )
q0
q0
q1
q1
a
b
a
a
q0
q1
q1
q0
Valido no AFD
(q0, abba)
(q0, bba)
(q1, ba)
(q0, a)
q
(q0 ,€)
q0
q0
Portanto, (q0, aabba)
q1
é aceita por M
q1
T T T
T
(q0, aabba)
T
a
b
a
a
(q, )
q0
q1
q1
q0
Autômato Finito Não-Determinístico
Definição. Um autômato finito nãodeterminístico é uma quíntupla
M = (K, Σ, ), s, F), onde
K é um conjunto finito de estados
Σ é um alfabeto
s € K é o estado inicial
F está contido K é o conjunto de estados finais
é a função de transição, K x (Σ c {,}) para K
Exemplo de um AFND
Exemplo. Seja M o
autômato finito nãodeterminístico (K, Σ,
), s, F), onde
K = {q0, q1, q2, q3, q4}
Σ = {a,b}
s = q0
F = K {q4}
q
(q, )
q0
a
q0
q0
b
q0
q0
b
q1
q1
b
q2
q1
a
q3
q2
€
q4
q3
b
q4
q4
a
q4
q4
b
q4
Entrada para um AFND
T
(q1, ababab)
(q3 babab)
(q4, abab)
(q4, bab)
(q4 ab)
(q4, b)
(q4, €)
T
T
T
T
T
(q0, bababab)
T
Portanto, (q0, bababab)
é aceita por M
Análise Sintática
Verifica se as frases obedecem as regras
sintáticas da linguagem:
Por exemplo, uma expressão pode ser
definida como:
expressão + expressão
expressão – expressão
(expressão)
constante
Gramáticas
Um conjunto de regras de produção, é um
símbolo de partida. Uma regra de
produção tem o formato , onde
representa o nome da construção sintática
e representa uma forma possível dessa
construção:
<expressão> <expressão> +
<expressão>
Gramáticas
<expr> <expr> + <expr>
| <expr> – <expr>
| (<expr>)
| <const>
<const>
<const><const>
|0|1|2|3|4|5|6|7|9
Derivação
A verificar se uma frase faz parte da
linguagem gerada pela gramática,
envolve sucessivas substituições da
cadeia de símbolos que ocorre do lado
esquerdo da produção pela sua
construção sintática correspondente,
partindo do símbolo inicial.
Essa substituição é chamada derivação
sendo normalmente denotada pelo
símbolo .
Derivação
<expressão>
<expr> + <expr>
(<expr>) + <expr>
(<expr> - <expr>) + <expr>
(<const> - <expr>) + <expr>
(<const><const> - <expr>) + <expr>
(1<const> - <expr>) + <expr>
(10 - <expr>) + <expr>
(10 - <const>) + <expr>
...
(10 - 2) + 3
Árvore Sintática
(10 – 2) + 3
<expr>
<expr>
<expr>
(<expr>)
+
<const>
<expr> - <expr>
<const>
<const>
10
2
3
Linguagens Regulares
Gerada a partir de uma gramática regular.
Pode ser representada através de uma expressão
regular.
Pode ser reconhecida por um Autômato Finito.
Considerando linguagens compostas por símbolos
0 e 1 podemos afirmar:
a linguagem L01 ={0n1n| n 1} não é regular;
a linguagem L01 ={0n1m | n 1, m 1} é regular;