Buscar

Organização de computadores Aula 05

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 145 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 145 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 145 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

Organização e Arquitetura 
de Computadores 
Compiladores 
Prof. Wanderson Senra Michel 
wanderson.senra@udf.edu.br 
 Uma linguagem de programação é um conjunto de ferramentas, 
regras de sintaxe e símbolos ou códigos que nos permitem escrever 
programas de computador. 
 
 A primeira e mais primitiva linguagem de computador é a própria 
linguagem de máquina (0’s e 1’s). 
 
 Um programa era difícil, longo e principalmente caro de se 
construir. 
 
 Era também difícil de ser entendido por outros programadores. 
 
 Essa complexidade levou à necessidade de desenvolver novas 
técnicas e ferramentas. 
Programa em Linguagem de Máquina 
I. Execução de Programas 
• A resolução do problema passou pela criação de uma linguagem em 
que os códigos numéricos foram substituídos por mnemônicos. 
 
• O nome dessa linguagem é ASSEMBLY LANGUAGE. 
 
• Então será necessário um outro programa que leia o programa 
escrito nessa linguagem alternativa e o traduza para a linguagem 
nativa do computador!!! 
 
• O processo de tradução da linguagem de montagem para a linguagem 
de máquina é realizada por um programa chamado ASSEMBLER. 
 
 
Linguagem de Montagem 
I. Execução de Programas 
Linguagem Assembly 
I. Execução de Programas 
;****************************************************** 
;*** INTERFACE ROUTINES *** 
;****************************************************** 
; 
Read_Char PROC ;read char from input buffer 
; 
;Entry: CL = port number 
; 
;Exit: AL = char (z set if none) 
; 
 push cx ;preserve registers 
 push bx 
; 
 cmp cl,0 ;only port 0 allowed 
 jz rc_1 
; 
 _Int_Error 03301h ;invalid port number 
; 
rc_1: mov al,Rx_InPtr 
 sub al,Rx_OutPtr ;inptr - outptr = num. chars 
 jz rc_zex ;if zero exit 
• Foram desenvolvidas diversas linguagens de programação: 
• FORTRAN (1957) 
• ALGOL (1958) 
• COBOL (1959) 
• PASCAL (1963) 
• BASIC (1965) 
• ADA (1968) 
• DoD (1969) 
• C (1982) e mais tarde o C++ (1986) 
• Etc…. 
 
• Estas novas linguagens foram afastando cada vez mais o 
programador do nível de máquina. 
Linguagem de Programação 
I. Execução de Programas 
• Os programas em linguagem de alto nível também precisam ser 
traduzidos para linguagem de máquina. 
 
 
Tradução 
Código Fonte Código Objeto 
Tradução 
I. Execução de Programas 
• O processo de montagem traduz um programa 
escrito numa LP num programa equivalente em 
linguagem de máquina. 
Montagem 
Código Fonte 
Tradução 
Linguagem de Máquina 
Processo de Montagem 
I. Execução de Programas 
Mas com 
o quê ??? 
Todo o programa escrito numa 
linguagem de programação de alto 
nível precisa ser traduzido para a 
linguagem de máquina, 
para que o computador 
 possa executá-lo. 
Todo o programa escrito numa 
linguagem de programação de alto 
nível precisa ser traduzido para a 
linguagem de máquina, 
para que o computador 
 possa executá-lo. 
Com Compiladores ou 
Interpretadores 
• Um compilador tem a finalidade de converter uma 
linguagem – Linguagem Fonte – de fácil escrita e leitura 
para os programadores, numa linguagem – Linguagem alvo 
ou objeto – que possa ser executada pelas máquinas. 
 
• O código executável gerado pelo compilador é 
dependente do sistema operacional e da linguagem de 
máquina para o qual o código fonte foi traduzido. 
 
• A enorme variedade de compiladores existentes é bem 
vinda, visto que existem milhares de linguagens fonte, e 
as linguagens alvo são também muito variadas. 
II . Compiladores 
O que é um compilador 
• Os compiladores são por vezes classificados 
como uni-passo, multi-passo, otimizador, ou 
corretor de erros, dependendo da forma como 
foram construídos ou da funcionalidade para que 
são pretendidos. 
 
• Começaram a aparecer no início da década de 50. 
 
• Muito do trabalho inicial dos compiladores 
resumia-se a tradução de fórmulas aritméticas 
para código de máquina. 
II . Compiladores 
O que é um compilador 
• O primeiro compilador de FORTRAN, por exemplo, 
demorou 18 trabalhosos meses para ser implementado. 
 
• Boas linguagens de implementação, ambientes de 
programação e ferramentas de software estão sendo 
desenvolvidas a todo momento. 
 
• Com estes avanços, um bom compilador pode ser 
implementado por alunos num projeto de TCC de um curso 
de tecnologia. 
II . Compiladores 
O que é um compilador 
 Ilustração do funcionamento de um compilador: 
II . Compiladores www 
O que é um compilador 
• Podemos dividir o processo de compilação em duas fases: 
 
• Análise : parte o programa fonte em peças constituintes e cria 
uma representação intermediária do programa fonte. 
 
• Síntese : Constrói o programa alvo desejado (código de máquina) 
a partir da representação intermediária. 
 
• A parte da síntese é a que requer técnicas mais 
especializadas. 
II . Compiladores 
Modelo Análise- síntese da compilação 
II . Compiladores 
Modelo Análise- síntese da compilação 
Análise 
Análise 
Léxica 
Análise 
Sintática 
Análise 
Semântica 
II . Compiladores 
Modelo Análise- síntese da compilação 
Síntese 
Geração do 
Código 
Otimização 
do Código 
Muitos outros programas podem ser necessários para se 
criar um programa alvo executável. 
II . Compiladores 
Contexto de um compilador 
Biblioteca, arquivos de 
Objetos Realocados 
• Pré-processadores: produzem o input para os 
compiladores; 
• Montadores: Alguns compiladores produzem 
código Assembly que é passado para um 
montador para posterior processamento. 
• Alguns compiladores produzem o trabalho dos 
montadores; 
 
II . Compiladores 
Primos de um compilador 
• Montagens bi-passo: 
• Iº Passo - todos os identificadores que denotam localizações 
de armazenamento são encontrados e armazenados numa 
tabela de símbolos 
• IIº Passo - traduz cada código de operação para seqüência de 
bits representando essa operação na linguagem de máquina 
 
• Carregadores e editores de união (Linker): 
• Carregar consiste em tomar o restabelecimento do código 
máquina, alterando os endereços restabelecidos e colocando 
as instruções alteradas e dados na memória nas localizações 
convenientes. 
• O editor de união permite se fazer um único programa dos 
vários arquivos de código de máquina; 
II . Compiladores 
Primos de um compilador 
• Bibliotecas: 
 
• O desenvolvimento de um programa certamente utilizará 
diversas operações que são comuns a muitos outros programas. 
 
• Um programa de alto nível possivelmente conterá diversas 
chamadas de biblioteca. 
 
• Essas funções não devem ser confundidas com as instruções da 
linguagem – na realidade, são pequenos programas externos que 
são chamados através de instruções especiais chamado 
“biblioteca”. 
II . Compiladores 
Primos de um compilador 
• Na compilação a análise consiste em 3 
partes: 
 
• Análise Léxica ou Linear: 
• Em que a cadeia de caracteres que forma a estrutura 
do programa fonte é lido da esquerda para a direita e 
agrupado em tokens que são seqüências de 
caracteres tendo o sentido coletivo. 
 
• A sua função básica é o reconhecimento e a 
classificação das estruturas elementares ou classes 
sintáticas das linguagens. 
II . Compiladores 
Análise do programa fonte 
• Análise sintática ou hierárquica: 
• Na qual caracteres ou tokens são agrupados 
hierarquicamente em coleções aninhadas com sentido 
coletivo. 
 
• Verifica-se a estrutura geral do texto ou se o 
programa fonte está correto. 
II . CompiladoresAnálise do programa fonte 
• Análise semântica: 
• Na qual são executadas certas etapas para assegurar 
que os componentes de um programa são juntamente 
ajustados em sentido absoluto. 
 
• Verifica se o programa fonte tem erros semânticos e 
reúne a informação dos tipos para a fase de gerador 
de código subseqüente. 
 
• Um componente importante da análise semântica é a 
verificação do tipo. 
II . Compiladores 
Análise do programa fonte 
II . Compiladores 
Fases de um compilador 
II . Compiladores 
Fases de um compilador 
• Gerenciador da tabela de símbolos: 
• Uma função essencial de um compilador é registrar os 
identificadores usados no programa fonte e reunir 
informações sobre os vários atributos de cada 
identificador. 
 
• Uma tabela de símbolos é uma estrutura de dados 
contendo o registro de cada identificador, com 
campos para os atributos do identificador. 
II . Compiladores 
Fases de um compilador 
• Tabela de códigos: 
• É uma estrutura criada pela análise semântica de um 
compilador, que mantém registradas as linhas de 
código intermediário geradas por algum tempo. 
 
• Em geral as linhas de código geradas permanecem 
nesta tabela enquanto não estão totalmente 
preenchidas. 
 
 
II . Compiladores 
Fases de um compilador 
• Detecção de erros e aviso do erro: 
• Cada fase pode encontrar erros. Porém, depois de 
descobrir um erro, a fase tem que tratar de alguma 
maneira aquele erro, para que a compilação possa 
prosseguir. 
 
• As fases de análise sintática e semântica 
normalmente tratam de uma grande fração dos erros 
detectáveis pelo compilador. 
II . Compiladores 
Fases de um compilador 
 
• Geração de código intermediário: 
• Depois da análise sintática e semântica, alguns 
compiladores geram uma representação intermediária 
explícita do programa fonte. 
 
• Podemos pensar nesta representação intermediária 
como um programa para uma máquina abstrata. 
 
II . Compiladores 
Fases de um compilador 
• Otimização do código: 
• Esta fase tenta melhorar o código intermediário, de forma a que 
resulte num código de máquina mais rápido a ser executado. 
 
• Geração do código: 
• A fase final do compilador é a geração de código alvo, 
consistindo normalmente no restabelecimento no código máquina. 
• Neste ponto, após o programa fonte ter sido analisado e 
aprovado, segundo a sua sintaxe e livre de erros semânticos, o 
compilador tem condições de escrever um programa equivalente 
na linguagem alvo. 
II . Compiladores 
Fatores condicionantes da organização física dos compiladores 
• Dividir o processo de compilação em diversas fases "lógicas" 
permite um melhor entendimento do processo como um todo e leva a 
uma implementação mais estruturada. 
 
• A eficiência e os recursos disponíveis na máquina hospedeira do 
compilador influenciam de maneira decisiva. Um item 
importantíssimo na implementação de um compilador: o número de 
passos de compilação, para poder otimizar o tempo de compilação. 
 
 
 
 
III . Interpretadores 
Como funcionam os interpretadores 
• O funcionamento dos interpretadores é muito parecido ao 
dos compiladores. 
 
• O interpretador traduz o código linha a linha. 
 
• O código fonte não é totalmente traduzido antes de ser 
executado. 
 
• Não existem fases distintas nem se produz código 
intermediário. 
 
• Passa o tempo todo lendo e traduzindo código. 
III . Interpretadores 
Exemplos de interpretadores 
• Internet; 
 
• Excel, Word Basic, Access, ... ; 
 
• SmallTalk; 
 
• AutoLisp; 
 
• Lisp. 
IV . Comparação 
Vantagens Desvantagens 
 
Compiladores 
Execução mais rápida Várias etapas de tradução 
Permite estruturas de 
programação mais completas 
Programação final é maior, 
necessitando mais memória para a 
sua execução 
Permite a otimização do código 
fonte 
Processo de correção de erros e 
depuração é mais demorado 
 
Interpretadores 
Depuração do programa é mais 
simples 
Execução do programa é mais lenta 
Consome menos memória Estruturas de dados demasiado 
simples 
Resultado imediato do programa 
ou rotina desenvolvida 
Necessário fornecer o programa 
fonte ao utilizador 
V . Exemplos de Linguagens 
Compiladas e Interpretadas 
• Java; 
 
 
 
• Basic. 
Análise Léxica 
Prof. Wanderson Senra Michel 
wanderson.senra@udf.edu.br 
II – Análise léxica 
• Papel do analisador léxico. 
• Diagnóstico e recuperação de erros léxicos 
• Extensão de expressões regulares e definições 
regulares 
• Reconhecimento de tokens 
• Bibliografia aconselhada: 
– Aho, Sethi e Ullman – seções 3.1 e 3.3 
– Appel – seções 2.1, 2.2 e 2.3 
Papel do analisador léxico 
Tokens, padrões e lexemas 
• Token: representa um conjunto de sequências de 
caracteres com significado comum (número, 
identificador, etc...) 
• Padrão: conjunto de sequências que resultam 
num mesmo token 
• Lexema: sequência de caracteres que obedece a 
um padrão de token 
Exemplo 
• O token identificador tem como padrão uma 
sequência que começa numa letra seguida de 
letras e números. 
• Alguns lexemas possíveis: 
– var, x1, LFA9900. 
• Palavras reservadas 
– if, else, while, switch, int, long, etc... 
Atributos dos tokens 
• Alguns tokens, como as palavras chave, só têm 
um lexema possível 
• Quando existe mais do que um lexema possível é 
preciso guardar alguma informação suplementar: 
– um apontador para a tabela de símbolos, no caso de 
um identificador 
– o valor numérico, no caso de um número 
Erros léxicos 
• Um erro léxico ocorre quando o analisador léxico 
não pode prosseguir porque nenhum dos padrões 
foi encontrado 
• Muitas vezes é impossível na fase de análise 
léxica identificar alguns erros, porque o padrão 
encontrado e o pretendido são diferentes: a2 
(identificador) / 2a (número seguido de 
identificador) 
Recuperação de erros léxicos 
• Modo de pânico: apagar caracteres da entrada 
até conseguir um token válido 
• Apagar um caracter estranho 
• Inserir um caracter em falta 
• Substituir um caracter incorreto por um correto 
• Transposição de dois caracteres adjacentes 
Extensão de expressões regulares 
• Vamos estender as definições anteriores: 
– a | b  a + b 
– a+  aa* 
– [abc]  a + b + c  a | b | c 
– [a-z]  a + b + ... + z  a | b | ... | z 
– a?  a +   a |  
 
Definições regulares 
• Por conveniência podemos definir nomes para 
algumas expressões regulares. Por exemplo: [0-9] 
representa um dígito. 
• Uma definição regular tem a forma 
def  er onde def é a definição e er a expressão 
regular, podendo esta usar símbolos do alfabeto 
ou definições anteriores 
Exemplo 
• Definição de um identificador 
– letra  [A-Za-z] 
– dígito  [0-9] 
– identificador  letra (letra | digito)* 
• Definição de um número 
– dígito  [0-9] 
– dígitos  dígito+ 
– parte_fracionária  (. dígitos)? 
– expoente  (E [+-]? dígitos)? 
– num  dígitos parte_fracionária expoente 
Reconhecimento de tokens 
• Espaços em branco – caracteres que não são 
considerados para a formação de tokens 
• O caracter ' ' é desprezado quando se tenta 
determinar um token, mas é tido em conta 
quando se trata de uma string 
• Caracteres como os de tabulação e nova linha 
podem ser considerados espaços em branco 
Tokens e autómatos finitos 
• Os autómatos finitos, como reconhecem 
linguagens regulares, são uma boa forma de 
reconhecer tokens 
• Existem algoritmos que permitem transformar 
uma expressão regular num autómato finito 
determinístico 
Exemplode um analisador léxico 
• Crie um autômato finito determinístico que 
reconheça a seguinte linguagem: L=0 conjunto de 
todos os números binários {0,1} que tenham 01 
como subcadeia. 
Exemplo de um analisador léxico 
• Crie um autômato finito determinístico que 
reconheça a seguinte linguagem: L=0 conjunto de 
todos os números binários {0,1} que tenham 01 
como subcadeia. 
Exemplo de um analisador léxico 
• Vamos considerar um pequeno analisador léxico 
com: 
– espaços em branco: ' ', tabulação e nova linha 
– operadores relacionais: <, <=, >, >=, ==, != 
– identificadores: letra (letra | dígito)* 
– números: dígito+ (. dígito+)? (E [+-]? dígito+)? 
Operadores relacionais 
Identificadores 
Números 
II – Análise léxica 
• Conversão de expressões regulares em autómatos 
finitos determinísticos mínimos 
• Bibliografia aconselhada: 
– Aho, Sethi e Ullman – seções 3.6, 3.7 e 3.9 
– Crespo – subseções 3.1.2, 3.1.3 e 3.1.4 
– Appel – seção 2.4 
Expressão regular  autómato finito determinístico mínimo 
• Conversão expressão regular  autómato finito  
(Construção de Thompson) 
• Conversão autómato finito   autómato finito 
determinístico (construção de subconjuntos) 
• Conversão autómato finito determinístico com 
estados supérfluos  autómato finito 
determinístico mínimo 
ER  AF:  
ER  AF: a   
ER  AF: E1 + E2 
ER  AF: E1 E2 
ER  AF: E1
*
 
Propriedades do autómato final 
• O autómato criado tem no máximo duas vezes o 
número de símbolos da expressão regular 
• Existe um estado inicial e um final sem transições 
• Para cada estado, ou existe uma transição com 
um símbolo de  ou existem duas transições com 
 
Exemplo: (0+1)*(1+000) 
AF  AFD 
• fecha-(s): conjunto de estados alcançáveis a 
partir do estado s com transições de  
• fecha-(T): conjunto de estados alcançáveis a 
partir de estados s  T com transições de  
• mover(T,a): conjunto de estado para os quais 
existe uma transição a partir de estados s  T 
com o símbolo a   
Construção de subconjuntos 
• fecha-(i) é o primeiro estado não marcado 
 enquanto houver estados T não marcados 
– marcar T 
– para cada símbolo a   
• U = fecha-(mover(T,a)) 
• se U não pertence ao conjunto de estados 
– inserir U não marcado 
• adicionar transição (T,a,U) 
Exemplo: (0+1)*(1+000) 
• fecha-(0) = {0,1,2,4,7,8,10} = A 
• fecha-(mover(A,0)) = fecha-({3,11}) = 
{1,2,3,4,6,7,8,10,11} 
• fecha-(mover(A,1)) = fecha-({5,9}) = 
{1,2,4,5,6,7,8,9,10,14} = C 
• fecha-(mover(B,0)) = fecha-({3,11,12}) = 
{1,2,3,4,6,7,8,10,11,12} = D 
• fecha-(mover(B,1)) = fecha-({5,9}) = C 
Exemplo (cont.) 
• fecha-(mover(C,0)) = fecha-({3,11}) = B 
• fecha-(mover(C,1)) = fecha-({5,9}) = C 
• fecha-(mover(D,0))=fecha-({3,11,12,13}) = 
{1,2,3,4,6,7,8,10,11,12,13,14} = E 
• fecha-(mover(D,1)) = fecha-({5,9}) = C 
• fecha-(mover(E,0))=fecha-({3,11,12,13}) = E 
• fecha-(mover(E,1)) = fecha-({5,9}) = C 
Exemplo: AFD 
Minimizar número de estados 
• Partição inicial do conjunto de estados S em F e F\S 
• Em cada partição cria-se sucessivamente novas 
partições, mantendo juntos os estados que têm 
transições iguais entre partições 
• Os novos estados são: 
– estados iguais aos iniciais; 
– conjunto de estados com transições iguais entre partições. 
Exemplo: (0+1)*(1+000) 
• Partição inicial: (ABD)(CE) 
• Em (CE): o estado E tem a transição ((CE),0,(CE)), 
enquanto C tem a transição ((CE),0,(ABD))  (ABD)(C)(E) 
• Em (ABD): A e B têm a transição ((ABD),0,(ABD)), 
enquanto D tem a transição ((ABD),0,(E))  (AB)(C)(D)(E) 
• Em (AB): A tem a transição ((AB),0,(AB)), enquanto B tem 
a transição ((AB),0,(D))  (A)(B)(C)(D)(E) 
Exemplo: 0*(100*)*(1+ ) 
Exemplo: 0*(100*)*(1+ ) 
• fecha-(0) = {0,1,3,4,10,11,13,14,15} = A 
• fecha-(mover(A,0)) = fecha-({2}) = 
{1,2,3,4,10,11,13,14,15} = B 
• fecha-(mover(A,1)) = fecha-({5,12}) = {5,12,15} 
= C 
• fecha-(mover(B,0)) = fecha-({2}) = B 
• fecha-(mover(B,1)) = fecha-({5,12}) = C 
Exemplo: 0*(100*)*(1+ ) 
• fecha-(mover(C,0)) = fecha-({6}) = 
{4,6,7,9,10,11,13,14,15} = D 
• fecha-(mover(C,1)) = fecha-() =  
• fecha-(mover(D,0)) = fecha-({8}) = 
{4,7,8,9,10,11,13,14,15} = E 
• fecha-(mover(D,1)) = fecha-({5,12}) = C 
• fecha-(mover(E,0)) = fecha-({8}) = E 
• fecha-(mover(E,1)) = fecha-({5,12}) = C 
Exemplo: 0*(100*)*(1+ ) 
Exemplo: 0*(100*)*(1+ ) 
• Como todos os estados são finais, vamos ter 
inicialmente uma única partição (ABCDE) 
• Como C não tem transição com 1 para nenhum 
estado  (ABDE) (C) 
• Em (ABDE) todos os estados têm as mesmas 
transições: ((ABDE),0,(ABDE)) e ((ABDE),1,(C))  
não são feitas mais partições  (ABDE) (C) 
Exemplo: 0*(100*)*(1+ ) 
II – Análise léxica 
• lex: linguagem de especificação para analisadores 
léxicos 
• Implementação de simuladores de autómatos 
finitos 
• Bibliografia aconselhada: 
– lex & yacc 
– Aho, Sethi e Ullman – seção 3.5 e 3.8 
Linguagem lex 
• Linguagem de especificação de um analisador 
usando expressões regulares estendidas 
• As expressões regulares definem padrões para 
cada um dos tokens 
• As ações são especificadas para cada token em 
linguagem C 
Meta-símbolos 
• ‘-’ usado na definição de domínios (a-z) 
• ‘\’ caracter de escape usado como na linguagem C 
(\n, \t, etc...) e também para usar caracteres que 
são meta-símbolos (\- significa o caracter ‘-’) 
• “aspas” servem para delimitar caracteres 
• / operador de procura à frente (lookahead) 
Implementação do simulador AF 
• O lex usa um simulador de um autómato finito 
usando uma tabela de transições 
• Especificação em lex  compilador lex  tabela 
de transições 
• O simulador lê a entrada e, usando a tabela de 
transições, simula as transições dos autómatos 
executável 
Recapitulando 
Análise 
sintática 
Código fonte 
AST 
Análise 
semântica 
Geração de 
código 
AST decorada 
Análise 
léxica 
Tokens 
Análise Léxica - Definição 
• Fase da compilação responsável por extrair os tokens 
do código fonte de um programa. 
if (n == 0) { 
 return 1; 
} else { 
 ... 
} 
 
RPAR LCUR 
RCUR 
if LPAR 
return 
else 
 
"n" 
id 
"0" 
intLit 
assign 
"1" 
intLit ... comm 
Especificação 
• Os tokens de uma linguagem comumente são 
especificados através de Expressões Regulares 
 
– [a-z][a-z0-9]* identifier 
 
– [0-9]+ intLiteral 
 
Implementação 
• Autômatos finitos 
1 2 
a-z a-z 
0-9 
ID 
2 1 3 
i f 
IF 
Análise Sintática 
Prof. Wanderson Senra Michel 
wanderson.senra@udf.edu.br 
III – Análise sintática 
• Papel do analisador 
• Diagnóstico e recuperação de erros 
• Uso de gramáticas independentes de contexto 
• Bibliografia aconselhada: 
– Aho, Sethi e Ullman – seções 4.1 e 4.2 
– Appel – seção 3.1 
Papel do analisador 
Árvore 
Sintática 
Definição 
• Fase da compilação responsável por determinar se uma 
dada cadeia de entrada pertence ou não à linguagem 
definida por uma gramática 
 
• Tem como entrada os tokens processados pela análise 
léxica 
 
• Produz uma estrutura comumente denominada AST – 
abstract syntax tree 
 
Especificação 
• BNF - Backus-Naur form 
 
– S, A, B, C, D : não-terminais 
 
– a,b,d: terminais 
 
S ::= A | B 
A ::= C | D 
B ::= bba 
C ::= ab 
D ::= dab 
Produções 
Tipos de analisadores 
• Parsers universais: reconhecem todas as 
gramáticasindependentes de contexto mas são 
ineficientes 
• Parsers descendentes (Top-Down): 
reconhecimento feito da raíz para as folhas 
• Recursive-descent / LL(1) 
• Parsers ascendentes (Bottom-Up): 
reconhecimento feito das folhas para a raíz 
• LR, SLR, LALR, LR(k) 
 
Tipos de gramáticas 
• A maioria dos métodos trabalham apenas com 
subclasses de gramáticas, mas são suficientemente 
expressivas 
• Gramáticas LL(k) (Left-to-right parse, 
Leftmost-derivation, k-token lookahead): mais usado 
para parsers implementados diretamente 
• Gramáticas LR(k) (Left-to-right parse, 
Rightmost-derivation, k-token lookahead): mais usado 
com ferramentas automáticas 
Erros 
• A maioria dos erros no código fonte são erros S 
• Erros frequentes: 
– Tecla de shift 
– Carácter em falta ou em excesso 
– Erros resultantes de outros: variáveis mal declaradas 
não entram na tabela de símbolos 
Recursive descent 
• Algoritmo baseado em previsões 
 
• Funções mutuamente recursivas 
 
• Uma função para cada não-terminal 
 
• Uma cláusula para cada produção 
Recursive descent 
• Desenvolvendo um recursive descent parser 
 
– Cada não terminal 'X' dará origem a um 
método/função parseX(); 
 
– Produções do tipo 'A | B' darão origem a cláusulas 
cases 
Recursive descent 
A ::= aBcC 
 
B ::= CB | cC 
 
C ::= da 
parseA() { 
 accept(‘a’); 
 parseB(); 
 accept(‘c’); 
 parseC(); 
} 
parseB() { 
 case (d): 
 parseC(); 
 parseB(); 
 case (c): 
 accept(‘c’); 
 parseC(); 
} 
parseC() { 
 
 accept(‘d’); 
 accept(‘a’); 
} 
Recursive descent 
• Na prática constrói uma tabela de produções 
indexadas por não-terminais e terminais 
A ::= aBcC 
 
B ::= CB | cC 
 
C ::= da 
a c d 
A A::= 
aBcC 
B B::= cC B::= CB 
C C::= da 
Recursive descent 
A ::= aBcC 
 
B ::= CB | CA 
 
C ::= da 
parseA() { 
 accept(‘a’); 
 parseB(); 
 accept(‘c’); 
 parseC(); 
} 
parseB() { 
 case (d): 
 parseC(); 
 parseB(); 
 case (d): 
 parseC(); 
 parseA(); 
} 
parseC() { 
 
 accept(‘d’); 
 accept(‘a’); 
} 
Recursive descent 
• Na prática constrói uma tabela de produções 
indexadas por não-terminais e terminais 
A ::= aBcC 
 
B ::= CB | CA 
 
C ::= da 
a c d 
A A::= aBC 
B B::= CB 
B::= CA 
C C::= da 
Recursive descent 
• Vantagens 
– Fácil de implementar 
• Desvantagens 
– Performance 
– Gramática reconhecida possui restrições 
• Sem recursão à esquerda 
• Deve estar fatorada 
• ... 
 
Recursive descent 
A ::= aBC 
 
B ::= CB | CA 
 
C ::= da 
A ::= aBC 
 
B ::= CX 
 
X ::= B | A 
 
C ::= da 
a c d 
A A::= aBC 
B B::= CX 
C C::= da 
X X::=A X::=B 
Gramática 
LL(1) 
Gramáticas LL(1) 
Left-to-right parse Leftmost-derivation 1-symbol-lookahead 
LL(1) 
Algoritmos bottom-up 
• Algoritmos LL(k) precisam decidir que produção 
usar tendo visto apenas k tokens da entrada 
• Algoritmos bottom-up são baseados em técnicas 
LR(k) 
– Left-to-right parse, Right-most derivation, k-symbol-
lookahead 
Algoritmos bottom-up 
• Baseados no conceito de autômato a pilha 
– Pilha + lookahead 
– Duas tipos de ações 
• Shift: 
– Coloca o primeiro token da entrada no topo da pilha 
• Reduce: 
– Escolhe a regra X::= A B C 
– Retira C, B, A da pilha 
– Coloca X na pilha 
 
Gramáticas LR 
• LR(0) 
– Olham apenas para a pilha 
• SLR 
– Melhoramento sobre o LR(0) 
• LR(1) 
– Lookahead de 1 símbolo 
– Consegue descrever a maioria das linguagens de programação 
• LALR(1) 
– Melhoramento sobre o LR(1) 
• Diminuí o tamanho da tabela de parsing 
 
Gramáticas Ambíguas 
• Uma gramática é ambígua se a partir dela uma sentença 
pode dar origem a duas arvores de parsing 
 
• Problemáticas para a compilação 
 
• Eliminação de ambigüidade é quase sempre possível 
 
– Transformações na gramática 
Gramáticas Ambíguas 
• Caso clássico: gramática para expressões aritméticas 
E ::= intLiteral 
 | E '*' E 
 | E '/' E 
 | E '+' E 
 | E '-' E 
 |'(' E ')' 
 
 
Gramáticas Ambíguas 
1 + 2 * 3 
E 
E E 
E E 
* 
+ 3 
2 1 
E 
E E 
E E 
+ 
* 1 
2 3 
Gramáticas Ambíguas 
• Solução: 
– Transformar a gramática 
• * e / com maior precedência que + ou - 
• Operadores associativos a esquerda 
E ::= intLiteral 
 | E '*' E 
 | E '/' E 
 | E '+' E 
 | E '-' E 
 |'(' E ')' 
 
 
 
E ::= E '+' T 
 | E '–' T 
 | T 
T ::= T '*' F 
 | T '/' F 
 | F 
F ::= intLiteral 
 |'(' E ')' 
 
 
Parsing LR de Gramáticas Ambíguas 
• Gramáticas ambíguas ocasionam conflitos em 
parsers LR 
– Shift-reduce conflict 
• O parser não consegue decidir se empilha o próximo 
símbolo da entrada, ou se reduz para uma regra já 
disponível 
– Reduce-reduce conflict 
• O parser pode realizar uma redução para duas regras 
distintas 
Parsing LR de Gramáticas Ambíguas 
• Caso clássico: dangling-else 
 
S ::= 'if' E 'then' S 'else' S 
S ::= 'if' E 'then' S 
S ::= ... 
 
 
Parsing LR de Gramáticas Ambíguas 
if a then if b then s1 else s2 
if a then { if b then s1 else s2 } 
if a then { if b then s1 } else s2 
? 
Parsing LR de Gramáticas Ambíguas 
if a then if b then s1 else s2 Input: 
Stack: 
if a then if b then s1 
reduce St ? 
shift else ? 
St 
Optando pelo reduce... 
else s2 
Parsing LR de Gramáticas Ambíguas 
if a then if b then s1 else s2 Input: 
Stack: 
if a then if b then s1 
reduce St ? 
shift else ? 
else s2 
Optando pelo shift... 
St 
Parsing LR de Gramáticas Ambíguas 
• Solução: 
– Transformar a gramática 
• Introdução dos conceitos de matched e unmatched 
 
 
S ::= 'if' E 'then' S 'else' S 
S ::= 'if' E 'then' S 
S ::= ... 
 
 
S ::= M 
 | U 
 
M ::= 'if' E 'then' M 'else' M 
 | ... 
 
U ::= 'if' E 'then' S 
 | 'if' E 'then' M 'else' U 
LR(0) LL(0) 
SLR 
LALR(1) 
LL(k) 
LL(1) 
Gramáticas não-ambíguas Gramáticas 
ambíguas 
LR(1) 
LR(k) 
Sintaxe abstrata 
• Apenas reconhecer se uma sentença pertence ou não a 
linguagem especificada por uma gramática não é o 
suficiente 
 
• É necessário produzir uma estrutura que sirva de base 
para a próxima fase do processo de compilação 
– Parse trees nunca são montadas na prática 
 
AST – Abstract Syntax Tree 
• Capturam a essência da estrutura de uma gramática 
abstraindo não-terminais 
 
• Representação possível 
– Java: Classes que possam se relacionar a fim de montar uma 
árvore 
 
• Pode ser produzida através da inserção de ações 
semânticas no parser 
AST – Abstract Syntax Tree 
IfThenElse ::= 'if' expr 'then' comm1 'else' comm2 
return new IfThenElse(expr, comm1, comm2); 
Vantagem dos parsers LR 
• Reconhecem praticamente todas as construções 
de linguagens de programação expressas por 
gramáticas independentes de contexto 
• É o melhor método conhecido para parsers não 
recursivos com instruções shift-reduce, mantendo 
o grau de eficiência 
118 
Vantagem dos parsers LR (cont.) 
• A classe das gramáticas que podem ser derivadas 
com parsers LR contém a classe das gramáticas 
que podem ser derivadas usando parser 
preditivos 
• Pode detectar rapidamente erros, assim que seja 
possível numa pesquisa da entrada da esquerda 
para a direita 
119 
Yacc – gerador de parsers 
• YACC – Yet Another Compiler-Compiler 
• Tradução para um programaem C usando o 
método LALR 
• fich.y  compilador yacc  y.tab.c 
• y.tab.c  compilador C  a.out 
• entrada  a.out  saída 
120 
Programa em yacc 
 
 Declarações 
%% 
 Regras de tradução 
%% 
 Rotinas em C 
121 
Declarações 
• Declarações em C entre %{ e %} – tradução direta 
para C: 
enum{FALSE,TRUE}; 
• Declaração de tokens: 
%token INTEIRO REAL 
• Precedência e associatividade de operadores 
122 
Regras de tradução 
• X  Y1 | Y2 | ... | Yn 
• X : Y1 { Ação semântica 1 } 
 | Y2 { Ação semântica 2 } 
 ... 
 | Yn { Ação semântica n } 
 ; 
123 
Terminais e não terminais 
• Strings de letras e dígitos declarados como tokens 
são terminais 
• Caracteres na forma ‘c’ são terminais 
(correspondem a um token representado apenas 
pelo próprio carácter) 
• Todas as outras strings são não terminais 
124 
Valores de atributos 
• $$ – valor de atributo do lado esquerdo da regra 
• $i – valor de atributo do elemento de ordem i do 
lado direito 
• Exemplo: 
• Expr : expr ‘+’ term { $$ = $1 + $3 } 
 | term 
 ; 
125 
Interface com o analisador léxico 
• O yacc comunica com o analisador léxico 
chamando a função yylex 
• Os atributos são passados a partir da variável 
global yylval 
• Usando o lex/flex podemos gerar o analisador 
léxico, que poderá ser usado fazendo #include 
“lex.yy.c” 
• Compilação: gcc y.tab.c -ly -ll 
126 
Resolução de conflitos 
• Conflitos reduce/reduce – é usado o primeiro a 
ser listado na especificação do yacc 
• Conflitos shift/reduce – é resolvido escolhendo o 
shift 
 
127 
Associatividade e precedência 
• Associatividade: 
%left ‘+’ ‘-’ 
%right ‘^’ 
%noassoc ‘<‘ 
• Precedência: 
%prec terminal 
128 
Análise Semântica 
Prof. Wanderson Senra Michel 
wanderson.senra@udf.edu.br 
IV – Análise semântica 
• Associação de regras semânticas a produções 
• Bibliografia aconselhada: 
– Aho, Sethi e Ullman – capítulo 5.1 a 5.4 
 130 
Tradução orientada pela sintaxe 
• Existem duas formas de associar regras 
semânticas a produções 
– Definições orientadas pela sintaxe (gramática de 
atributos): especificações de alto nível para traduções 
– Esquemas de tradução: indicam a ordem de avaliação 
das regras semânticas 
 131 
Gramática de atributos 
• Gramática independente de contexto onde cada símbolo 
gramatical (nó na árvore de derivação) tem associado um 
conjunto de atributos 
• Estes atributos podem ser: 
– Sintetizados: valor obtido a partir dos filhos 
– Herdados: valor obtido a partir do pai ou outros descendentes 
deste 
• Os atributos podem ser qualquer coisa: número, tipo, 
endereço de memória, etc... 
 132 
Exemplo – calculadora 
 133 
PRODUÇÃO REGRA SEMÂNTICA 
L  E ‘\n’ Print(E.val) 
E  E1 + T E.val=E1.val+T.val 
E  T E.val=T.val 
T  T1 * F T.val=T1.val*F.val 
T  F T.val=F.val 
F  ( E ) F.val=E.val 
F  INT F.val=INT.val 
Exemplo – declaração de variável 
 134 
PRODUÇÃO REGRA SEMÂNTICA 
D  T L L.herd = T.tipo 
T  int T.tipo=inteiro 
T  real T.tipo=real 
L  L1 , ID L1.herd=L.herd 
ad_tipo(ID.entr,L.herd) 
L  ID ad_tipo(ID.entr,L.herd) 
Esquemas de tradução 
• Gramática independente de contexto onde 
existem atributos associados aos símbolos 
gramaticais e as ações semânticas são colocadas 
entre chaves 
• O valor do atributo tem que estar disponível 
quando for usado 
 135 
Exemplo – calculadora 
L  E ‘\n’ { Print(E.val) } 
E  E1 + T { E.val = E1.val+T.val } 
E  T { E.val = T.val } 
T  T1 * F { T.val = T1.val*F.val } 
T  F { T.val = F.val } 
F  ( E ) { F.val = E.val } 
F  INT { F.val = INT.val } 
 136 
Exemplo – declaração de variável 
D  T {L.herd = T.tipo} L 
T  int {T.tipo = inteiro} 
T  real {T.tipo = real} 
L  {L1.herd = L.herd} L1 , ID 
 { ad_tipo(ID.entr,L.herd) } 
L  ID { ad_tipo(ID.entr,L.herd) } 
 137 
Exemplo – notação pós fixa 
E  T E’ 
E’  + T { Print(‘+’) } E’ |  
T  F T’ 
T’  * F { Print(‘*’) } T’ |  
F  INT { Print(INT.val) } | ( E ) 
 138 
IV – Análise semântica 
• Implementação da tabela de símbolos 
• Verificação estática 
• Bibliografia aconselhada: 
– Aho, Sethi e Ullman – capítulo 6 
– Crespo – capítulo 6 
 139 
Tabelas de símbolos 
• Inserção e pesquisa eficiente dos identificadores 
• Capacidade de armazenar atributos para 
diferentes tipos de identificadores 
• Visibilidade nas várias fases da compilação 
• Versatilidade na verificação de tipos 
 140 
Árvores binárias 
• Inserção ordenada de 
informação 
• Pesquisa de informação 
eficiente 
• Existência de algoritmos 
de balanceamento de 
árvores binárias 
 141 
Tabelas de hashing 
• Divisão da informação em 
várias parcelas 
• Função de hashing: 
Função simples que faz 
essa divisão e acessa 
rapidamente o local de 
armazenamento 
 142 
Verificação estática 
• Verificação de tipos: verificar se os tipos de duas 
variáveis numa operação são compatíveis 
• Verificação de fluxo de controle: verificar se 
algumas instruções de fluxo de controle estão 
bem definidas: 
– goto label só é possível se existir label 
– break só é possível dentro de um ciclo 
 143 
Verificação estática (cont.) 
• Unicidade: verificação da unicidade de algumas 
construções: 
– funções definidas uma única vez 
– switch-case com alternativas únicas 
• Verificação relacionada com nomes 
 144 
Verificação de tipos 
• Verificação de tipos pode ser realizada em: 
– Expressões 
– Instruções 
– Funções 
• Equivalência de tipos 
• Conversão de tipos 
• Tipos polimórficos 
• Funções polimórficas 
 145

Outros materiais