11.07.2015 Views

Compiladores: PASCALjr - WWW2 - Udesc

Compiladores: PASCALjr - WWW2 - Udesc

Compiladores: PASCALjr - WWW2 - Udesc

SHOW MORE
SHOW LESS
  • No tags were found...

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

<strong>Compiladores</strong>: P ASCAL jrRogério Eduardo da Silva, M.Sc.2005/2


Sumário1 Introdução 11.1 Evolução das Linguagens de Programação . . . . . . . . . . . . . . . . . . 11.2 Introdução à Compilação . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.2.1 Fases da Compilação . . . . . . . . . . . . . . . . . . . . . . . . . . 31.3 Ferramentas para Geração de <strong>Compiladores</strong> . . . . . . . . . . . . . . . . . 62 Um Compilador Simples de uma Passagem 72.1 Definição da Sintaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.2 Análise Gramatical . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.2.1 Exercícios Propostos . . . . . . . . . . . . . . . . . . . . . . . . . . 92.3 Características da linguagem P ASCAL jr . . . . . . . . . . . . . . . . . . . 102.3.1 Exercícios Propostos . . . . . . . . . . . . . . . . . . . . . . . . . . 123 Análise Léxica 133.1 O Papel do Analisador Léxico . . . . . . . . . . . . . . . . . . . . . . . . . 133.2 Buferização de Entrada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143.3 Gramáticas e Linguagens Regulares . . . . . . . . . . . . . . . . . . . . . . 153.3.1 Exercícios Propostos . . . . . . . . . . . . . . . . . . . . . . . . . . 163.4 Especificação e Reconhecimento de Tokens . . . . . . . . . . . . . . . . . . 173.4.1 Trabalho Prático #1 . . . . . . . . . . . . . . . . . . . . . . . . . . 194 Análise Sintática 214.1 O Papel do Analisador Sintático . . . . . . . . . . . . . . . . . . . . . . . . 214.2 Análise Sintática Ascendente - BOTTOM UP . . . . . . . . . . . . . . . . 234.2.1 Algoritmo “Empilhar-e-Reduzir” . . . . . . . . . . . . . . . . . . . 234.3 Análise Sintática Descendente - TOP DOWN . . . . . . . . . . . . . . . . 244.3.1 Análise Sintática Preditiva . . . . . . . . . . . . . . . . . . . . . . . 254.3.2 Exercícios Propostos . . . . . . . . . . . . . . . . . . . . . . . . . . 264.4 Reconhecedor de Gramáticas Preditivas Descendentes . . . . . . . . . . . . 274.4.1 Algoritmo para Construção da Tabela de Análise . . . . . . . . . . 294.4.2 Projeto de uma Gramática para um Analisador Sintático PreditivoAscendente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304.4.3 Projeto de uma Gramática para um Analisador Sintático PreditivoDescendente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314.4.4 Exercícios Propostos . . . . . . . . . . . . . . . . . . . . . . . . . . 384.4.5 Trabalho Prático #2 . . . . . . . . . . . . . . . . . . . . . . . . . . 38i


5 Análise Semântica 415.1 Tradução Dirigida pela Sintaxe . . . . . . . . . . . . . . . . . . . . . . . . 415.1.1 Definições L-Atribuídas . . . . . . . . . . . . . . . . . . . . . . . . . 435.1.2 Verificações de Contexto . . . . . . . . . . . . . . . . . . . . . . . . 445.2 Tabela de Símbolos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465.2.1 Atributos dos Nomes dos Identificadores . . . . . . . . . . . . . . . 475.2.2 Hashing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475.3 Projeto das Regras Semânticas . . . . . . . . . . . . . . . . . . . . . . . . . 505.3.1 Trabalho Prático #3 . . . . . . . . . . . . . . . . . . . . . . . . . . 566 Geração de Código Intermediário 596.1 Linguagens Intermediárias . . . . . . . . . . . . . . . . . . . . . . . . . . . 596.1.1 Representações Gráficas . . . . . . . . . . . . . . . . . . . . . . . . 596.1.2 Notação Pós (e Pré) Fixadas . . . . . . . . . . . . . . . . . . . . . . 606.1.3 Código de Três-Endereços . . . . . . . . . . . . . . . . . . . . . . . 616.2 BackPatching (Retrocorreção) . . . . . . . . . . . . . . . . . . . . . . . . . 647 Otimização de Código 677.1 Otimização Peephole . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 677.2 Otimização de Blocos Sequenciais através de grafos . . . . . . . . . . . . . 687.2.1 Algoritmo para Construir o GAD de um bloco . . . . . . . . . . . . 697.2.2 Algoritmo para Ordenação de um GAD . . . . . . . . . . . . . . . . 708 Geração de Código Objeto 718.1 Máquina Objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 728.1.1 Regras para Geração de Código Objeto . . . . . . . . . . . . . . . . 768.1.2 Trabalho Prático #4 . . . . . . . . . . . . . . . . . . . . . . . . . . 84ii


Lista de Figuras1.1 Processo de Compilação . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.2 Fases da Compilação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.3 Árvore resultante da análise de um comando de atribuição em PASCAL . . 42.1 Representação da árvore gramatical da produção A→XYZ . . . . . . . . . 82.2 Ambigüidade Gramatical . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83.1 O papel do analisador léxico . . . . . . . . . . . . . . . . . . . . . . . . . . 133.2 Buffer de entrada para um analisador léxico . . . . . . . . . . . . . . . . . 153.3 Autômato finito de reconhecimento de números inteiros e reais . . . . . . . 173.4 AFD de reconhecimento de identificadores simples . . . . . . . . . . . . . . 183.5 AFD de reconhecimento de strings . . . . . . . . . . . . . . . . . . . . . . 184.1 Exemplo de Árvore Sintática . . . . . . . . . . . . . . . . . . . . . . . . . . 224.2 Derivação à Esquerda e à Direita . . . . . . . . . . . . . . . . . . . . . . . 224.3 Análise descendente com backtracking . . . . . . . . . . . . . . . . . . . . 254.4 Exemplos de Recursão à Esquerda e à Direita . . . . . . . . . . . . . . . . 274.5 Funcionamento de um Analisador Sintático Descendente . . . . . . . . . . 285.1 Exemplo de Árvore Decorada para a Expressão 3*5+4 . . . . . . . . . . . 425.2 Grafo de Dependências . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435.3 Tipos Simples e Construtor de Tipos . . . . . . . . . . . . . . . . . . . . . 445.4 Hashing com Encadeamento . . . . . . . . . . . . . . . . . . . . . . . . . . 496.1 Exemplo de Representação Gráfica de Operadores para a=b*c+b*2 . . . . 606.2 Backpatching para expressões lógicas . . . . . . . . . . . . . . . . . . . . . 657.1 Grafo Acíclico Dirigido - GAD . . . . . . . . . . . . . . . . . . . . . . . . . 70iii


Capítulo 1IntroduçãoEntende-se por linguagem como uma forma eficiente de comunicação entre pessoas. Naverdade a linguagem é um conjunto de palavras usadas, segundo certas regras, para aformação de frases compreensíveis por ambos os interlocutores (falantes).Quando um dos interlocutores é o computador, se faz necessário o uso de uma linguagemespecial denominada linguagem de programação que permite a comunicação entrehomem e máquina através da definição de comandos.Uma L. P. é ser dita de baixo nível, se esta somente aceitar comandos na próprialinguagem da máquina (0’s e 1’s) que é de difícil aplicação. Já as linguagens ditas dealto nível, são representadas por ações próximas ao problema a ser resolvido que são,posteriormente, traduzidas para a linguagem de máquina, através de um agente especialdenominado compilador ou interpretador.Concluindo: compilador é um programa capaz de traduzir um certo programa fonte(escrito em uma linguagem fonte) para outro programa objeto (escrito em uma linguagemobjeto) geralmente a própria linguagem de máquina.1.1 Evolução das Linguagens de ProgramaçãoCronologicamente, as L. P.’s são classificadas em cinco gerações: (1 a ) linguagens demáquina; (2 a ) linguagens simbólicas (Assembly); (3 a ) linguagens orientadas ao usuário;(4 a ) linguagens orientadas à aplicação e (5 a ) linguagens de conhecimento.As duas primeiras são consideradas linguagens de baixo nível, enquanto que as demaisde alto nível.Os primeiros computadores só podiam ser programados através da sua própria linguagemde máquina (código binário), onde cada operação possuía sua representação bináriaque era passada à máquina através de circuitos elétricos. Esse processo, além de extremamentedifícil e cansativo, era altamente sujeito a erros devido a sua grande complexidadede execução.A seguir, como uma primeira tentativa de simplificação, surgem as linguagens simbólicasou de montagem (Assembly). Agora, extensas seqüências binárias são substituídas pormnemônicos que são “palavras especiais” que representam certas ações básicas. ExemploMOV, JMP, etc. Os mnemônicos precisavam ser traduzidos para a linguagem de máquinaantes da sua execução.1


A 3 a geração surgiu na década de 60, com as linguagens procedimentais como FOR-TRAN, PASCAL e ALGOL e declarativas como LISP e PROLOG. Nas linguagens procedimentais,um programa especifica uma seqüência de passos a serem seguidos para asolução do problema. Já as linguagens declarativas são subdivididas em funcionais elógicas. A programação funcional se baseia na teoria das funções recursivas, enquantoque, as linguagens lógicas se baseiam em proposições da lógica de predicados (fatos eregras).Devido ao fato de programas escritos em linguagens de 3 a geração serem muito extensose de difícil manutenção, surgiram as linguagens de aplicação (4 a geração), onde odesenvolvedor deixa de se preocupar com “atividades secundárias” e trata apenas da codificaçãodo problema (foco do programador deixa de ser a codificação para ser a análisedo problema). Aspectos como: interface de entrada e saída, relatórios, etc. são resolvidospela própria linguagem através de um banco de dados e dicionários associados àsaplicações desenvolvidas.A 5 a geração das linguagens de programação atua em problemas altamente complexosonde a representação de conhecimento se faz necessária para sua solução, como osproblemas enfrentados pela inteligência artificial. A linguagem PROLOG é aceita comopertencente a esta geração.1.2 Introdução à CompilaçãoConforme já dito, um compilador nada mais é do que um programa tradutor responsávelpor converter uma certa linguagem fonte em outra linguagem objeto (ver Figura 1.1).Usualmente a linguagem objeto é a própria linguagem de máquina, mas não necessariamente.ProgramaFonteCOMPILADORProgramaObjetoMensagemde ErroFigura 1.1: Processo de CompilaçãoExistem dois tipos básicos de tradutores: os compiladores e os interpretadores. Osprimeiros fazem uma análise completa sobre o programa fonte, caso não encontre errosfaz a tradução de todo o código fonte para a linguagem objeto que será posteriormenteexecutado em uma máquina capaz de fazê-lo. Já os interpretadores não têm essa preocupaçãoholística (análise completa) sobre o programa fonte. Um interpretador traduzum comando fonte por vez e o executa em uma máquina virtual (programa que simula ofuncionamento de um computador) sem a necessidade da criação do programa objeto.Interpretadores são mais simples de serem implementados, porém, compiladores geramexecuções mais rápidas de programas, pois não há a perda de tempo de traduções virtuaisa cada nova instrução executada.2


1.2.1 Fases da CompilaçãoO processo de compilação pode ser dividido em dois grupos de etapas: as etapas de análisee as etapas de síntese. Na análise, o programa fonte é percorrido em busca de errosde programação (inconsistências com a linguagem fonte), já na etapa de síntese (após averificação da corretude do programa de origem), efetua-se a tradução, propriamente dita,do código fonte para a linguagem objeto em questão. A figura 1.2 abaixo ilustra todo oprocesso:Programa FonteAnálise LéxicaAnálise SintáticaANÁLISETabela deSímbolosAnálise SemânticaGeração de Código IntermediárioManipuladorde ErrosOtimização de CódigoSÍNTESEGeração de Código ObjetoPrograma ObjetoFigura 1.2: Fases da CompilaçãoA análise léxica ou scanning é a primeira etapa do processo de compilação. Elaé responsável por analisar linearmente os caracteres do programa fonte e agrupá-los emunidades léxicas denominadas tokens. O token é o elemento mais básico da programação;ele é representado por um conjunto de caracteres que apresentam um significado claropara o programa. Exemplo: Para o seguinte código em PASCAL:Media := Nota1 + Nota2 * 2Os caracteres poderiam ser agrupados da seguinte forma:1. O identificador “Media”2. O símbolo de atribuição “:=”3. O identificador “Nota1”4. O sinal de adição “+”5. O identificador “Nota2”6. O sinal de multiplicação “*”3


7. O número “2”Os espaços em branco presentes na sentença são ignorados durante a análise.O resultado da análise léxica é uma lista contendo todos os tokens encontrados noprograma fonte. Essa lista léxica é então o elemento de entrada para a análise sintáticaou análise gramatical (parsing), onde é verificado se os tokens podem ser agrupados emsentenças válidas (comandos, expressões, etc.) da linguagem fonte. Normalmente, essesagrupamentos são realizados através da construção de uma árvore sintática conforme éapresentado na figura 1.3:Comando deAtribuiçãoIdentificadorSímbolo deAtribuiçãoExpressãoMedia :=ExpressãoOperadorAritméticoExpressãoNota1 +ExpressãoOperadorAritméticoExpressãoNota2 * 2Figura 1.3: Árvore resultante da análise de um comando de atribuição em PASCALA estrutura hierárquica de um programa é usualmente expressa por regras recursivas.Por exemplo, poderíamos ter as seguintes regras como parte definição de expressões:1. Qualquer identificador é uma expressão2. Qualquer número é uma expressão3. Se expressão 1 e expressão 2 são expressões válidas, então expressão 1 “op. aritmético”expressão 2 também éA estrutura utilizada para a representação dessas regras é a gramática livre de contexto(GLC), normalmente apresentada na Forma Normal de Backus (BNF).Exemplo:〈comando〉 ::= 〈while〉 | 〈atribuição〉 | . . .〈while〉 ::= while 〈expr bool〉 do 〈comando〉〈atribuição〉 ::= identificador := 〈expr aritm〉〈expr bool〉 ::= 〈expr aritm〉 op.Lógico 〈expr aritm〉〈expr aritm〉 ::= 〈expr aritm〉 op.Aritm 〈termo〉 | 〈termo〉〈termo〉 ::= número | identificadorApós a análise sintática, tem-se a certeza de que o programa está escrito corretamente(respeita as regras gramaticais da linguagem fonte), porém, será que o programa escritofaz algum sentido? Ou seja, executa de forma apropriada?4


A análise semântica tem por objetivo validar os comandos e expressões através deanálises como compatibilidade de tipos e escopo de identificadores. Esta etapa analisa,por exemplo, se um identificador declarado como variável é usado como tal, ou se umaexpressão atribuída a uma variável retorna um tipo compatível com o qual foi declaradaa variável (em algumas linguagens, uma variável inteira não pode receber uma expressãoreal).Até aqui foi realizada a etapa de análise do programa fonte, ou seja, a procura porerros de programação. Caso nenhum erro seja encontrado, o processo de compilação passaentão para a etapa de síntese, ou seja, a construção do programa objeto.A geração do código intermediário é a primeira fase da construção do programaobjeto. O que ela faz é a representação do programa fonte em uma linguagem intermediáriasimplificada (máquina abstrata), o que permite a realização da próxima etapamais facilmente.A próxima etapa é a otimização de código, que tem por objetivo tentar modificaro código intermediário no intuito de melhorar a velocidade de execução, bem como autilização do espaço de memória, fazendo com isso, um uso mais racional dos recursos damáquina.A última etapa do processo de compilação é a geração de código objeto propriamentedita. Esta fase tem como objetivos: produção de código objeto, reserva de memóriapara constantes e variáveis, seleção de registradores, etc. É a fase mais difícil, pois requeruma seleção cuidadosa das instruções e dos registradores da máquina alvo a fim deproduzir código objeto eficiente.Exemplo de geração de código para o código fonte:While I < 100 do I := J + ICódigo Intermediário Otimização Código ObjetoL0: if I


Os dados a serem armazenados dependem do projeto do tradutor, mas os mais comunssão: identificador, classe (variável, parâmetro, procedimento, etc.), tipo, endereço,tamanho.A tabela de símbolos deve ser estruturada de uma forma que permita rápida inserçãoe extração de informações, porém deve ser tão compacta quanto possível.O módulo de manipulação de erros tem por objetivo “tratar os erros” que são detectadosem todas as fases de análise do programa fonte e deve dispor de mecanismos(recuperação de erros) que permitam que o processo de análise prossiga mesmo que errostenham sido detectados.1.3 Ferramentas para Geração de <strong>Compiladores</strong>Existem diversas ferramentas para auxiliar a construção de compiladores chamadas degeradores de compiladores ou sistemas de escritas de tradutores. A seguir são apresentadosalguns exemplos:Geradores de Analisadores Gramaticais responsáveis por desenvolver analisadoressintáticos, normalmente a partir de entrada baseada numa gramática livre de contexto.Geradores de Analisadores Léxicos geram automaticamente analisadores léxicos apartir de uma especificação baseada em expressões regulares.Dispositivos de tradução dirigida pela sintaxe produzem coleções de rotinas quepercorrem uma árvore gramatical, gerando código intermediário.Geradores automáticos de código tal ferramenta toma uma coleção de regras quedefinem a tradução de cada operação da linguagem intermediária para linguagemalvo. Tais regras precisam incluir detalhamento suficiente para que possamos lidarcom os diferentes métodos de acesso possíveis para os dados.Dispositivos de fluxo de dados Ferramentas que auxiliam na etapa de otimização decódigo.Não é de escopo desta disciplina o estudo de ferramentas de implementação de compiladores,mais detalhes podem ser obtidos na bibliografia de apoio.6


Capítulo 2Um Compilador Simples de umaPassagem2.1 Definição da SintaxeA especificação da sintaxe de uma linguagem de programação pode ser obtida através deuma gramática livre de contexto.Exemplo: Seja o comando condicional da forma:IF Expressão THEN Comando ELSE Comandose Expr denotar a construção de uma expressão e Cmd denotar um comando (ou enunciado),pode-se usar as regras de produção de uma GLC 1 para representar tal estruturada seguinte forma:< Cmd >⇒ IF < Expr > THEN < Cmd > ELSE < Cmd >as palavras-chave como IF, THEN e ELSE representam os símbolos terminais, enquantoque os termos Cmd e Expr, representam os não-terminais.Exemplo de uma GLC simples para definir expressões aritméticas baseadas apenas emadição e subtração:< Lista > ⇒ < Lista > + < Digito >< Lista > ⇒ < Lista > − < Digito >< Lista > ⇒ < Digito >< Digito > ⇒ 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9onde os símbolos 0 a 9 e + ou - são os elementos terminais, enquanto que Lista e Digitorepresentam os não-terminais. Convencionalmente, o primeiro não-terminal representa oaxioma da gramática. Expressões exemplo: 1+1, 3-6+9, 1+2+3+4+5+61 Gramática Livre de Contexto7


2.2 Análise GramaticalA análise gramatical é feita através de derivações de cadeias a partir do axioma dagramática. Se um não-terminal A possui uma produção A ⇒ XYZ então, uma árvoregramatical pode ter um nó rotulado de A com 3 filhos X, Y e Z da esquerda para a direita,conforme a figura 2.1.AXYZFigura 2.1: Representação da árvore gramatical da produção A→XYZFormalmente, segundo uma GLC, a árvore gramatical resultante apresenta as seguintespropriedades:• A raiz é rotulada pelo símbolo de partida (axioma);• Cada folha é rotulada por um terminal ou por ε;• Cada nó interno é rotulado por um elemento não-terminal;• Se A ⇒ X 1 X 2 . . . X n é uma produção então, algum nó interno da árvore será rotuladopor A sendo X 1 X 2 . . . X n os rótulos dos filhos desse nó.AmbiguidadeUma gramática pode ter mais de uma árvore gramatical gerando uma dada cadeia, nestecaso, ela é dita ser ambígua. Ambos os exemplos da figura 2.2 geram a sentença 9-5+2.CadeiaCadeiaCadeia+CadeiaCadeia-CadeiaCadeia-Cadeia29Cadeia+Cadeia9 55 2Figura 2.2: Ambigüidade Gramatical8


Associatividade de OperadoresConvencionalmente, 9+5+2 é equivalente à (9+5)+2, pois, ao analisarmos o operando 5precisamos decidir qual operação será realizada primeiro. Pela convenção da matemática aadição é associativa à esquerda, sendo assim o resultado (9+5)+2 é obtido. Na maioria daslinguagens de programação, as quatro operações básicas (adição, subtração, multiplicaçãoe divisão) são associativas à esquerda.A exponenciação é um exemplo de operador associativo à direita (em Fortran) 5**2**3é equivalente a 5**(2**3). Outro exemplo é o operador de atribuição, onde a expressãoa=b=c (em linguagem C) é tratada como a=(b=c).Precedência de OperadoresConsidere a expressão 9+5∗2. Existem duas interpretações possíveis: (9+5)∗2 e 9+(5∗2).Quando mais de um tipo de operadores estiverem presentes em uma expressão é necessáriose definir a ordem de precedência entre eles.Na aritmética, os operadores ∗ e / tem precedência mais alta do que + e -; assim, naexpressão anterior o operador de multiplicação é capturado antes da adição.2.2.1 Exercícios Propostos1. Prova, através da construção da árvore de derivação, que os exemplos anteriores sãoválidos para a gramática de expressões aritméticas vista.2. Considere a gramática livre de contexto: S → SS+ | SS∗ | a(a) Mostre que a cadeia aa+a∗ pode ser gerada por esta gramática.(b) Construa a árvore gramatical para esta cadeia.(c) Qual é a linguagem gerada por esta gramática? Justifique sua resposta.3. Quais são as linguagens geradas pelas seguintes gramáticas?(a) S → 0S1 | 01(b) S → +SS | -SS | a(c) S → S(S)S | ε(d) S → aSbS | bSaS | ε(e) S → a | S+S | SS | S* | (S)4. Construa uma gramática livre de contexto para os números romanos (1 a 10).5. Construa uma G.L.C. para as expressões aritméticas de inteiros e identificadorescom as quatro operações básicas (+, -, ∗, /).9


2.3 Características da linguagem P ASCAL jr1. Não é caso sensitivo (‘A’ = ‘a’)2. Suporta os tipos: integer, real, char, string e boolean3. Comandos:• Atribuição com operadores: “:=”, “+=”, “-=”, “*=”, “/=”, “++”, “- -”• Entrada com o comando read( )• Saída com os comandos write( ) e writeln()• Condicional com o comando if - then - else• Repetições:– Pré teste com o comando while - do– Pós teste com o comando repeat - until– Contada com o comando for - to - do• Sub-rotinas através dos comando procedure e function.– Retorno de funções com o comando result– Nome de identificador de subrotinas inicia obrigatoriamente com “ ”(ex.: Tela)4. Constantes caracteres delimitados por (‘ ’) e constantes strings por (“ ”)5. Operadores relacionais: “=”, “>=”, “”, “=”, “”, “


11. Bloco de comandos delimitados por “begin” e “end”12. Comentário de linha com operador “//”13. Comentário de bloco com os delimitadores “{” e “}”14. Lista de Palavras Reservadas: var, const, while, do, for, read, write, writeln, if,then, else, true, false, integer, real, char, string, boolean, result, procedure,function,and,or,xor,not,to,repeat,until,program,downtoExemplos de programas a serem reconhecidos pela linguagem P ASCAL jr :{}PILOTO.TXTExemplo completo de programa na linguagem <strong>PASCALjr</strong>Desenvolvido por Rogerio Eduardo da SilvaAgosto, 2005Program Piloto;// declaraç~oes de variaveis e constantes globaisvar: integer cont; real Nota1, Nota2, Media_das_medias, med;const: integer total = 10;// Subrotina de preenchimento de telaprocedure _Tela()beginwriteln("******** ENTRADA DE DADOS ***************");writeln("Digite os valores da entrada:");end;// Calculo da media aritmetica entre duas notasfunc real _Media(real a , b)var: real media;beginmedia := (a+b)/2.0;result := media;end;// Inicio do Programa PrincipalbeginMedia_das_medias := 0;for cont=0 to total dobegin_Tela();read(Nota1, Nota2);med := _Media(Nota1, Nota2);Media_das_medias += med;11


write("Media = ",med);end;write("Media Geral = ",Media_das_medias/total);end.2.3.1 Exercícios PropostosUsando a linguagem P ASCAL jr faça:1. Um programa para cálculo do fatorial de N.2. Um programa para cálculo de N-ésimo termo da série de Fibonacci12


Capítulo 3Análise Léxica3.1 O Papel do Analisador LéxicoA análise léxica é a primeira fase de um compilador e tem por objetivo fazer a leiturado programa fonte, caracter a caracter, e traduzi-lo para uma seqüência de símbolosléxicos denominados tokens, os quais são utilizados pelo analisador sintático. Exemplosde tokens são os identificadores, palavras reservadas, operadores da linguagem, etc.A interação entre análise léxica e sintática é normalmente implementada fazendo-secom que o analisador léxico seja uma sub-rotina ou co-rotina do parser (ver figura 3.1).Ao receber do parser um comando do tipo “obter próximo token”, o analisador léxico lêos caracteres de entrada até que possa identificar o próximo token.ProgramaFonteAnalisadorLéxicoTokenObter PróximoTokenAnalisadorSintáticoTabela deSímbolosFigura 3.1: O papel do analisador léxicoUm analisador léxico clássico pode ser entendido como um sistema de estados finitose, portanto, utiliza-se um autômato finito para sua implementação. As principaiscaracterísticas desse autômato:• O alfabeto de entrada são os caracteres pertencentes ao arquivo fonte• Cada estado final reconhece uma classe específica de tokens da linguagem fonteÉ denominado erro léxico a qualquer evento (durante o processo de análise léxica)que impossibilite a interpretação de um token.Uma lista de tokens é o resultado do processo de análise léxica, caso nenhum erroléxico tenha sido encontrado.13


Porque efetuar análise léxica?Simplificação de Projeto é mais simples implementar dois analisadores distintos (paratarefas distintas) do que um analisador sintático que faça todo trabalho de formaunificada;Melhor Eficiência a análise léxica é potencialmente mais lenta que a sintática (poisefetua leitura de caracteres em disco). Técnicas de buferização de leitura podemacelerar significativamente este processo;Portabilidade as peculiaridades do alfabeto de entrada de cada linguagem podem sertratadas exclusivamente pelo scanner.Tokens, Padrões e LexemasUm token é um símbolo terminal da gramática da linguagem fonte sob análise. Em geral,existem diversas cadeias de caracteres para as quais o mesmo token é gerado.Essas cadeias respeitam um determinado padrão ou regra associada a esse token.Um lexema é um conjunto de caracteres que é reconhecido pelo padrão de um determinadotoken.Exemplo:const pi = 3.14159;a subcadeia pi é um lexema para o token “identificador”, pois respeita o padrão para osidentificadores (letra)(letra | digito) ∗ .Atributos para os tokensUm token é comumente representado como um par [LEXEMA, CLASSE], onde a classeindica qual foi o padrão utilizado para reconhecer o lexema.Outras informações adicionais podem ser incorporadas à descrição do token, de acordocom as necessidades das fases subseqüentes, como por exemplo, número da linha e colunaonde o token foi reconhecido no arquivo fonte e número de caracteres lidos até oreconhecimento, seria exemplos de informações adicionais úteis caso um erro léxico sejadetectado.3.2 Buferização de EntradaConforme já visto, o processo de análise léxica é normalmente realizado efetuando-se umaleitura do arquivo fonte de entrada, caracter a caracter, o que resulta em um processosignificativamente lento.Existem 3 alternativas de implementação de analisadores léxicos (listados em ordemcrescente de complexidade de implementação):1. Usar ferramentas de construção de analisadores léxicos (como o Lex), através deexpressões regulares;14


2. Escrever um programa numa linguagem de programação convencional, usando seusrecursos de entrada e saída;3. Escrever um programa numa linguagem de montagem e manipular explicitamentea entrada e a saída.Alguns aspectos a serem considerados no projeto de implementação de um scanner:BufferEm muitas linguagens, existem momentos que o analisador léxico precisa examinar várioscaracteres à frente do lexema, antes que seja anunciado um reconhecimento.Os caracteres que foram lidos e não foram “aproveitados” no lexema sob análise, sãoentão, devolvidos ao fluxo de entrada para que possam ser lidos novamente na análise deoutro lexema posterior.Assim sendo, um buffer de entrada que acumula vários caracteres é criado, conformea figura 3.2. O processo de análise léxica é realizado sobre este buffer. Os tokens queforam reconhecidos são eliminados do buffer e novos caracteres são adicionados a ele atéque todo o arquivo fonte seja lido e analisado.E = m * c * c eofapontadorFigura 3.2: Buffer de entrada para um analisador léxicoEm casos mais simples, a entrada pode ser realizada caracter a caracter, contendoapenas um buffer de armazenamento dos caracteres lidos.3.3 Gramáticas e Linguagens RegularesA seguir, serão revisados alguns conceitos importantes da disciplina linguagens formais emáquinas (LFM) para então prosseguir na análise léxica.GramáticaUma gramática é um mecanismo gerador de sentenças de uma dada linguagem. É definidapela quádrupla (V N , V T , P, S), onde: V N representa o conjunto de símbolos não-terminaisda linguagem; V T representa o conjunto de símbolos terminais ou alfabeto; P é um conjuntode regras de produção e S é o axioma da gramática (símbolo inicial).As regras de produção são definidas na forma α ⇒ β 1 | β 2 | . . . | β N , onde α representaum símbolo não-terminal e os β N representam sentenças podendo conter tanto símbolosterminais quanto não-terminais.15


Seqüência de DerivaçãoEntende-se por derivação ao processo de substituição de α por um dos β N na regra deprodução, desta forma obtendo-se uma nova sentença que por sua vez, pode ser novamentederivada por outra regra. Uma seqüência de derivação é uma série de derivações sucessivasque permitem a geração de uma determinada sentença da linguagem.Gramática RegularUma gramática é dita ser regular se todas as suas regras de produção respeitam a formaA → αB ou A → α, onde A,B são símbolos não-terminais e α é uma sentença contendosomente símbolos terminais.Gramática Linearmente à Esquerda e à DireitaQuando uma regra de produção é da forma A → αB, ou seja, novos símbolos não-terminaissão inseridos à direita da sentença, diz-se se tratar de uma gramática linearmente à direita.Se a produção for da forma A → Bα denomina-se como linearmente à esquerda.Expressões RegularesUma expressão regular representa uma determinada linguagem através de ‘fórmulas’ indutivas.Simbologia adotada:ε = sentença vazia (comprimento = 0);a | b = representa uma seleção entre a sentença a ou b;A ∗ = conjunto de todas as sentenças de comprimento ≥ 0 sobre A;A + = A ∗ − {ε} = fechamento positivos sobre AA ? = representa que a expressão A ocorre zero ou uma vez.Exemplos: Digito(Digito) ∗ = representa a descrição de números inteiros.Letra(Letra|Digito) ∗ = representa a descrição de identificadores.3.3.1 Exercícios Propostos1. Defina expressões regulares e sua respectiva gramática regular para as seguinteslinguagens:• todas as palavras contendo a e/ou b.• todas as palavras contendo a e/ou b com sufixo aa.• todas as palavras contendo a e/ou b com aaa como sub-palavra.• todas as palavras contendo a e/ou b com exatamente dois b.2. Para a gramática G=({S,A,B},{0,1},P,S), indique a linguagem reconhecida.16


P: S → 0S | AA → A1 | BB → 0 | 1 | ε3.4 Especificação e Reconhecimento de TokensA especificação de tokens é feita através de expressões regulares e reconhecida através dosreconhecedores de gramáticas regulares chamados de autômatos finitos.Exemplo:< Numero > → < Digitos >< Frac Opc >< Exp Opc >< Frac Opc > → . < Digitos >| ε< Exp Opc > → (E | e)(+ | − | ε) < Digitos >| ε< Digitos > → < Digito >< Digitos >|< Digito >< Digito > → 0 | 1 | 2 | . . . | 9esta gramática é capaz de reconhecer números inteiros como 1, 100, 1234, etc. e tambémnúmeros reais expressos ou não por notação exponencial como: 1.5, 10.34, 1.3e15, 1E+2;porém, é incapaz de reconhecer números como 1., sem a parte fracionária. A figura 3.3reconhece esta gramática, enquanto que a figura 3.4 reconhece identificadores simples 1 .OUTROINICIODÍGITODÍGITODÍGITO0 1DIGITO .2 DIGITO 3E | e + | -4 5DIGITO6OUTRO7 * Retornar(Num_Real,Obter_Token())OUTROE | eDIGITO8* Retornar(Num_Inteiro, Obter_Token())Figura 3.3: Autômato finito de reconhecimento de números inteiros e reaisO reconhecimento de strings é apresentado na figura 3.5, onde “caracteres válidos”representa o alfabeto válido para strings, geralmente letras, números, espaços e sinaisortográficos.Exercício: Criar um AFD capaz de reconhecer os tokens da linguagem P ASCAL jr :símbolos (Dois Pontos, Ponto e Vírgula, Vírgula, Abre e Fecha Parênteses, Atribuição),Operadores Relacionais e Aritméticos, Constante Caracter e identificadores de sub-rotinas1 Exceto identificadores de sub-rotinas17


INICIOLETRA OU DÍGITOLETRAOUTRO0 1 2* Retornar(ID, Obter_Token())Figura 3.4: AFD de reconhecimento de identificadores simplesINICIOCARACTERESVÁLIDOS"0 1"2 Retornar(String,Obter_Token())Figura 3.5: AFD de reconhecimento de strings(iniciam obrigatoriamente com ‘ ’ e tem pelo menos 2 caracteres), e ainda, ser capaz detratar os caracteres nulos: espaços, enter, tab e comentários, sem reconhecer token.Reconhecendo palavras reservadas como identificadores simples:Criar uma função de identificação de palavras reservadas (enumeração) que retorna aclasse palavra reservada ou identificador.USO: Retornar(ObterClasse(Lexema),Lexema)Erros Léxicos1. ‘Caracter Inválido’ : uso de um caracter (simbolo) de entrada (arquivo fonte)que não pertença ao alfabeto da linguagem. Exemplo: # ou %2. ‘Delimitador Não Balanceado’ : definição de uma cadeia literal (ou constantecaracter) sem o correto balanceamento das aspas. Exemplo: “Entrada de Dados3. ‘Número Real Inválido’ : definição incorreta ou incompleta de um número real.Exemplos: 1., 1.0e3, .8, 1e+O código abaixo apresenta algum erro léxico? Apresente a lista léxica.begin ; media==10.5E-5/===//%_ Média 1.PTeste?Solução:begin Palavra Reservada; Símbolo Ponto e Virgula18


Operador Relacional Diferentemedia Identificador= Operador Relacional de Igualdade= Operador Relacional de Igualdade10.5E-5 Número Real/= Símbolo de Atribuição= Operador Relacional de Igualdade= Operador Relacional de IgualdadeTeste Identificador? Símbolo Interrogação3.4.1 Trabalho Prático #1Implementar um módulo (sub-rotina) analisador léxico para um protótipo de compiladorpara a linguagem P ASCAL jr vista em aula.Características:Do módulo scanner:• A sub-rotina retorna um token (classe e lexema) cada vez que for chamada.• Considera que o programa fonte para análise já está aberto.• Não retorna nada quando atingir o fim de arquivo (flag de controle).• Implementa um AFD para o reconhecimento de tokens.Do programa a ser criado:• Abre um arquivo fonte para análise.• Chama (sucessivas vezes) a rotina de scanner e exibe o valor do token.• Fecha o arquivo fonte ao final da compilação.• Pára o processo de compilação caso um erro seja encontrado.• Exibe erros de compilação (se ocorrerem) ou mensagem de sucesso.Critérios de Avaliação:• Implementação usando linguagem C ou C++.• Entrega de fontes e executável (em um arquivo zipado) via disquete/CD ou e-mail:rsilva@joinville.udesc.br ou professor.rogerio@gmail.com19


• Grupo de 02 alunos (máximo).• Valor do trabalho: 10.0 (25% da nota prática).• Data de Entrega: A Definir• Punições:– de 10% por cada análise incorreta.– de 20% do valor do trabalho por dia de atraso.– de 20% do valor do trabalho para a entrega não conforme dos arquivos pedidos.– de 50% do valor do trabalho para o caso de não executar ou travar (após testeem 2 computadores, sendo um o do professor).– de 100% do valor do trabalho para o caso de cópias (mesmo de trabalhos desemestres anteriores).• Prazo máximo para defesa e arguição sobre o trabalho: 5 dias letivos após entrega.• Punições:– de 25% para arguição não respondida ou respondida incorretamente. Obs.: Aarguição é individual.– de 33% ponto por dia de atraso da defesa.20


Capítulo 4Análise Sintática4.1 O Papel do Analisador SintáticoA análise sintática constitui a segunda etapa de um tradutor. Sua função é verificarse as construções usadas no programa estão gramaticalmente corretas. Normalmente, asestruturas sintáticas válidas são especificadas através de uma gramática livre de contexto.Dada uma GLC e uma sentença (programa fonte) s, o objetivo do parser é verificarse s pertence a GLC, através da construção de uma árvore de derivação.O processo de construção dessa árvore pode ser feito de forma explícita (construindoseo TDA) ou implícita, através de chamadas recursivas das rotinas que aplicam as regrasde produção da gramática durante o reconhecimento.Existem duas estratégias básicas: Descendente (Top-Down) e Ascendente (Bottom-Up). Na estratégia top-down constrói-se a árvore a partir da raiz em direção às folhas(tokens), enquanto que na bottom-up, o processo é invertido e a construção é realizadapartindo-se das folhas, agrupando-se os tokens até que a raiz da árvore seja gerada.A árvore gramatical é então a saída para as próximas fases da compilação.Revisão sobre Gramáticas Livre de Contexto (GLC)Uma gramática livre de contexto é qualquer gramática da forma: A → α, onde A é umsímbolo não-terminal e α um elemento pertencente a (V N ∪ V T ) ∗ .Exemplo de produções de uma G.L.C.: S → SS+ | SS∗ | a.Árvores de DerivaçãoÁrvore de derivação é a representação gráfica de uma derivação de sentença.Exemplo: Considerando a gramática abaixo, gerar árvore de derivação que comprovaque a sentença 45 é válida (ver Figura 4.1).21


Numero > → < Num >< Num > → < Num >< Digito >|< Digito >< Digito > → 0 | 1 | 2 | . . . | 954Figura 4.1: Exemplo de Árvore SintáticaDerivação mais à Esquerda e mais à DireitaDerivação mais à esquerda é obtida por gramáticas que geram inicialmente, os símbolosmais à esquerda da sentença sob análise; analogamente para as derivação mais à direita.Exemplo: Seja a gramática: E → E + E | E − E | E ∗ E | E/E | (E) | x. Pode obtera expressão x+x*x de duas formas, conforme a figura 4.2:EEE*EE +EE+EXXE *EXXXXFigura 4.2: Derivação à Esquerda e à DireitaExercício: Para a gramática G = ({S,A},{0,1},P,S) sendo P: S → 0S | A A → 1A | 1,provar as seguintes sentenças: 0001, 01, 0011.22


4.2 Análise Sintática Ascendente - BOTTOM UPA criação da árvore gramatical é realizada no sentido folhas → raiz, ou seja, geração desentenças é feita através do processo de empilhar e reduzir. A idéia é “reduzir” a sentençaoriginal até o axioma da gramática através de sucessivas substituições por não-terminais.Exemplo: S → aABe A → Abc | b B → dVerificar se a sentença abbcde pode ser reduzida pela gramática:abbcde↓aAbcde↓aAde↓aABe↓S4.2.1 Algoritmo “Empilhar-e-Reduzir”Este procedimento de análise sintática ascendente consiste de dois passos:1. Escolha de um candidato α a redução (handle);2. Redução do candidato pelo não-terminal A à esquerda da produção A → α;3. Repetir os passos 1 e 2 até que a sentença tenha sido reduzida ao axioma dagramática.Um candidato é uma subcadeia que reconhece o lado direito de uma produção e cujaredução ao não-terminal do lado esquerdo da produção representa um passo ao longo dopercurso de uma derivação.É denominado de “poda do candidato” ao processo de substituí-lo pelo não-terminalà esquerda da regra de produção, obtendo desta forma, uma redução na sentença sobanálise.Uma forma conveniente de implementar um analisador sintático de empilhar e reduziré usar uma pilha para guardar os símbolos gramaticais. O analisador sintático operaempilhando zero ou mais símbolos até que um candidato surja no topo da pilha. Umapoda do candidato é então feita. Repete-se este processo até que no topo da pilha estejao axioma da gramática ou um erro seja encontrado (nenhuma poda seja possível).Exemplo: E → E + E | E − E | E ∗ E | E/E | (E) | id.Sentença sob análise: id + id * id23


Entrada Pilha Açãoid+id*id $ empilhar+id*id $id reduzir E → id+id*id $E empilharid*id $E+ empilhar*id $E+id reduzir E → id*id $E+E reduzir E → E + E*id $E empilharid $E* empilhar$ $E*id reduzir E → id$ $E*E reduzir E → E ∗ E$ $E aceitarSão apenas 4 as operações possíveis por este método: empilhar, reduzir, aceitar ouerro.Conflitos durante a Análise Sintática de Empilhar e ReduzirExistem gramáticas livres de contexto para as quais o procedimento empilhar-e-reduzirnão pode ser utilizado, porque, em certos casos, o analisador pode atingir um estado tal,que:• Mesmo conhecendo toda a pilha e o próximo símbolo de entrada, não pode decidirentre empilhar e reduzir. Isto é chamado de conflito empilhar/reduzir.• Outro conflito possível, o reduzir/reduzir, ocorre quando não é possível optar entreas diversas reduções possíveis.4.3 Análise Sintática Descendente - TOP DOWNA análise sintática top-down pode ser vista como uma tentativa de se encontrar umaderivação mais à esquerda para uma cadeia de entrada, ou ainda, como de se construir aárvore gramatical a partir da raiz em direção às folhas.O processo de análise pode ser feito de forma recursiva ou não, onde a forma recursivapode ser realizada com ou sem retrocesso (backtracking), dependendo das regras deprodução gramática.Análise Sintática Recursiva com RetrocessoA construção da árvore é feita a partir da raiz, expandindo sempre o não-terminal mais àesquerda primeiro. Quando existe mais de uma regra de produção para o não-terminal aser expandido, a opção escolhida é função do símbolo corrente na fita de entrada (tokensob análise). Se o token não define a produção a ser usada, então todas as alternativasvão ser tentadas até que se obtenha sucesso (ou todas falhem).Exemplo 1:S → cAd A → ab | a.Verificar se a gramática gera a sentença cad.Exemplo 2: S → cA A → aB B → D | bD D → d24


Sc A dfalha!SSabc A dSc A dsucesso!aFigura 4.3: Análise descendente com backtrackingA análise sintática é dita ser uma análise sintática preditiva caso não seja necessário arealização de retrocesso no processo e pode ser implementada de forma recursiva ou não(através da utilização de uma pilha).4.3.1 Análise Sintática PreditivaO processo de análise preditiva (sem retrocesso) exige modificações na gramática originalpara análise:• eliminação de recursão à esquerda;• fatoração à esquerda das regras de produção;• os não-terminais que apresentarem mais de uma regra de produção, tenham o primeiroterminal derivável único (capaz de identificar a produção a ser analisada).Ou seja, deve ser possível determinar, para um dado símbolo a, qual das produçõesdeve ser derivada.Exemplo: No exemplo 2 visto acima a produção B → D | bD D → d apresenta duasalternativas de derivação. A escolha é feita a partir do primeiro terminal para cada regra(d ou b).O conjunto de símbolos terminais que iniciam sentenças deriváveis a partir de umaprodução b é denominado FIRST(β) ou PRIMEIRO(β).Exemplo: FIRST(S) = {c}; FIRST(A) = {a}; FIRST(B) = {b, d}; FIRST(D) = {d}.As regras que definem o conjunto FIRST são:25


• Se β → ε, então ε é um elemento de FIRST.• Se β → aδ, sendo a um símbolo terminal, então a pertence a FIRST.• Se β → X 1 X 2 . . . X N , sendo X 1 X 2 . . . X N elementos não-terminais, então FIRST(β)= FIRST(X 1 ). Se em FIRST(X 1 ) constar o elemento ε, então incluir FIRST(X 2 )em FIRST(β) e assim por diante.Eliminação da Recursão à EsquerdaÉ possível que um analisador gramatical descendente recursivo execute indefinidamente.O problema ocorre em produções recursivas à esquerda, tais como: A → A0 | 1.Este tipo de produção gera uma árvore que cresce recursivamente à esquerda até queum terminal 1 seja gerado à esquerda da seqüência de 0’s.Para se evitar isso deve-se substituir o elemento causador da recursão à esquerda, que édo tipo A → Aα | β, onde α, β representam outras seqüências de terminais e não-terminaisnão iniciadas por A .Para eliminar a recursão à esquerda deve-se reescrever essa produção, da seguinteforma: A → βA ′ e A ′ → αA ′ | ε. A figura 4.4 apresenta as árvors de derivação para umasentença qualquer da forma βαααα.4.3.2 Exercícios Propostos• Para as gramáticas abaixo elimine sua recursão à esquerda.1. G=({S,A,B},{a,b},P,S) onde P: S → Sa | Sb | A | B A → Aa | a B → bB | b2. G=({S,A},{0,1,2},P,S) onde P: S → S0 | S1 | A | 0 A → S23. G=({S,A,B},{0,1},P,S) onde P: S → SA | A A → A0B | 0 B → B1 | ε4. G=({A},{0,1},P,A) onde P: A → A0A | 1• Apresente a cláusula First para as produções das gramáticas abaixo:1. G=({S,X,Y,Z},{0,1,2,3},P,S) onde P: S → XY Z X → OXO | 1 Y → 2Y 2 |3 Z → 0Z1 | ε2. G=({S,A,B,C},{a,b,c},P,S) onde P: S → Sa | aA A → aA | Bb B → cB | ε3. G=({S,X,Y,Z},{0,1},P,S) onde P: S → XY Z X → 0X | 1Y | ε Y → 1Y |ε Z → 01Z | εFatoração à EsquerdaA fatoração à esquerda é uma transformação gramatical útil para a criação de umagramática adequada à análise sintática preditiva. A idéia básica está em, quando nãoestiver claro qual das duas produções alternativas usar para expandir um não-terminal A,estarmos capacitados a reescrever as produções A e postergar a decisão até que tenhamosvisto o suficiente da entrada para realizarmos a escolha certa.Exemplo: S → aXbY cZ | aXbY26


AAAβAαAαAααβαA'A'α A'α A'α A'εFigura 4.4: Exemplos de Recursão à Esquerda e à DireitaAo analisarmos o token a não há como saber qual das duas alternativas utilizar (ocomando com ou sem o “cZ”). Quando houver duas produções A → αβ 1 | αβ 2 , devemospostergar a decisão expandindo A para αA ′ e então expandir A’ para β 1 | β 2 . Fatorandoesta gramática temos: S → aXbY S ′ S ′ → cZ | ε.4.4 Reconhecedor de Gramáticas Preditivas DescendentesUm reconhecedor preditivo descendente (orientado por tabela) compreende uma fita deentrada, uma pilha e uma tabela de análise, conforme é mostrado na figura 4.5. A fitacontém a sentença a ser analisada seguida de $. A pilha contém os símbolos utilizadosdurante o processo de análise. A tabela de análise é uma matriz com n linhas (correspondendoaos símbolos não-terminais) e t+1 colunas (correspondendo aos símbolos terminaismais o símbolo especial $).Considerando X o elemento no topo da pilha e a o símbolo de entrada sob análise, oanalisador executa uma de três ações possíveis:1. se X = a = $, o analisador pára, aceitando a sentença;2. se X = a ≠ $, o analisador desempilha a e avança o cabeçote de leitura para opróximo símbolo na fita de entrada;3. se X é um símbolo não-terminal, o analisador consulta a tabela M[X,a] da tabelade análise. Essa entrada poderá conter uma produção da gramática ou ser vazia.Supondo M[X,a] = { X → XY Z }, o analisador substitui X (no topo da pilha) porZYX (ficando X no topo). Se M[X,a] for vazio isto é um erro sintático.Na implementação de um analisador sintático, a maior dificuldade está na construçãoda tabela de análise. Para construir essa tabela, é necessário computar duas funçõesassociadas à gramática: FIRST e FOLLOW.27


a + b $XYZParserTabela deAnáliseFigura 4.5: Funcionamento de um Analisador Sintático DescendenteO algoritmo para calcular a função FIRST já foi visto anteriormente. O algoritmopara calcular a função FOLLOW é apresentado a seguir:1. Se S é o símbolo inicial da gramática e $ é o marcador de fim de sentença, então $está em FOLLOW(S);2. Se existe produção do tipo A → αXβ, então todos os terminais de FIRST(β), fazemparte de FOLLOW(X);3. Se existe produção do tipo A → αX, ou A → αXβ, sendo que β → ε, então todosos terminais que estiverem em FOLLOW(A) fazem parte de FOLLOW(X).Dada a gramática G = ({E, E ′ , T, T ′ , F }, {∨, ∧, ¬, id}, P, E) para expressões lógicas:Cláusula FirstE → T E ′E ′ → ∨T E ′ | εT → F T ′T ′ → ∧F T ′ | εF → ¬F | idConvém iniciar o processo pelos não-terminais que gerem conjuntos triviais. No exemplo,temos os não-terminais F, E’ e T’ que só geram elementos terminais (ou vazio):F = {¬, id}E ′ = {∧, ε}T ′ = {∨, ε}Como T deriva apenas em FT’ e F não leva em vazio, conclui-se que FIRST(T) =FIRST(F). E ainda, FIRST(E) = FIRST(T) = FIRST(F) = {¬, id}.28


Cláusula FollowPela regra 1 temos que FOLLOW(E) = {$}. Pela regra 3 tem-se que FOLLOW(E)= FOLLOW(E’). FOLLOW(T) é obtido a partir da união dos conjuntos obtidos pelaaplicação da regra 2 em (E ′ → ∨T E ′ ) e regra 3 em (E ′ → ε). Sendo assim temos:FOLLOW(T) = FIRST(E’) + FOLLOW(E’) = {∨, $}.FOLLOW(T’) = FOLLOW(T) pela aplicação da regra 3 em T → F T ′ . E finalmente,FOLLOW(F) = FIRST(T’) + FOLLOW(T’). Aplicação das regras 2 e 3 em T ′ → ∧F T ′ |ε, ou seja FOLLOW(F) = {∨, ∧, $}.4.4.1 Algoritmo para Construção da Tabela de AnáliseMétodo:• Para cada produção X → α, execute os passos 2 e 3 (para criar a linha X da tabelaM);• Para cada terminal a de FIRST(α), adicione a produção X → α a M[X,a];• Se FIRST(α) inclui a palavra vazia, então adicione X → α a M[X,b] para cada bem FOLLOW(X);Aplicando-se o algoritmo acima à gramática de expressões lógicas temos:Para E → T E ′ tem-se FIRST(TE’) = {¬, id} então, M[E, ¬] = M[E,id] = E → T E ′ .Para E ′ → ∨T E ′ tem-se FIRST(∨T E ′ ) = {∨} então, M[E’, ∨] = E ′ → ∨T E ′ .Para E ′ → ε tem-se FOLLOW(E’) = {$} então, M[E’, $] = E ′ → ε.Para T → F T ′ tem-se FIRST(FT’) = {¬, id} então, M[T, ¬] = M[T,id] = T → F T ′ .Para T ′ → ∧F T ′ tem-se FIRST(∧F T ′ ) = {∧} então, M[T’, ∧] = T ′ → ∧F T ′ .Para T ′ → ε tem-se FOLLOW(T’) = {∨, $} então, M[T’, ∨] = M[T’,$] = T ′ → ε.Para F → ¬F tem-se FIRST(¬F ) = {¬} então, M[F, ¬] = F → ¬F .Para F → id tem-se FIRST(id) = {id} então,M[F,id] = F → id.id ∨ ∧ ¬ $E E → T E ′ E → T E ′E’ E ′ → ∨T E ′ E ′ → εT T → F T ′ T → F T ′T’ T ′ → ε T ′ → ∧F T ′ T ′ → εF F → id F → ¬idSe, em cada entrada da Tabela de Análise, existe apenas uma produção, então agramática que originou a tabela é dita ser do tipo LL(1), ou seja: as sentenças geradaspela gramática são passíveis de serem analisadas da esquerda para a direita (Left toRight), produzindo uma derivação mais à esquerda (Leftmost Derivation), levandoem conta apenas um símbolo da entrada.Exercício: Considerando a gramática para a linguagem a ser reconhecida pelo protótipode compilador para análise descendente, construir a tabela de análise resultante.29


4.4.2 Projeto de uma Gramática para um Analisador SintáticoPreditivo AscendenteAnalisa gramáticas do tipo LR(k), ou seja, left-to-right e rightmost derivation com ksímbolos lidos da entrada a cada etapa de análise.Porque usar análise sintática ascendente LR?• porque é possível ser elaborados reconhecedores para todas as GLC, sem restrição;• porque o método de análise LR é tão eficiente quanto os demais métodos de análise;• porque um analisador LR consegue encontrar um erro sintático o mais cedo possívelem uma análise da esquerda para a direita.As gramáticas GLC para as quais é viável a implementação manual de reconhecedoresascendentes (devido à complexidade de implementação) apresentam as seguintesrestrições:• nenhum lado direito das produções seja ε• nenhum lado direito tenha dois não-terminais adjacentes (gramática de operadores)Exemplo: E → E + E | E − E | E ∗ E | E/E | (E) | −E | idUm forma simples de se implementar um reconhecedor ascendente é através da análisede precedência de operadores, porém, justamente devido à sua simplicidade, uma série derestrições estão associadas a estes:• dificuldades de analisar operadores com mais de um significado semântico (ex.: operadorunário e binário de subtração)• somente uma pequena classe de linguagens pode ser analisada por esta alternativa,apesar disso, já foram desenvolvidos analisadores de precedência para linguagensinteiras.Na análise de precedência temos definidos as relações de precedência entre os operadores,sendo (a < • b) onde a confere precedência a b; (a = b) onde a possui a mesmaprecedência de b e (a • > b) a tem precedência sobre b.Seja o exemplo da gramática anterior onde a precedência dos operadores é dada por:id + * $id • > • > • >+ < • • > < • • >* < • • > • > • >$ < • < • < •Analisando a expressão: id+id*id temos as seguintes relações de precedência:$ < • id • > + < • id • > ∗ < • id • > $O algoritmo para se determinar o handle para redução é:30


1. Percorrer a cadeia, a partir da esquerda até que o primeiro • > seja encontrado.2. Percorrer, então, de volta (para a esquerda) por sobre quaisquer relações (=) atéque < • seja encontrado.3. O handle contém tudo à esquerda do primeiro • > e à direita do < •, incluindoquaisquer não-terminais presentes.No exemplo acima, o primeiro handle é dado pelo primeiro id encontrado que podeser reduzido para o não-terminal E (segundo a gramática vista), seguido pelos próximosdois ids da sentença. A seguir, a sentença obtida ficaria $ E + E * E $; removendo-se osnão-terminais e acrescentando-se as relações de precedência temos: $ < • + < • ∗ • > $,indicando que a próxima redução deve ser realizada sobre o operador “*” (e seus respectivosoperandos associados “E* E”).Devido ao fato da sua implementação não ser trivial, a solução de implementação maisviável para este tipo de gramática é fazer uso de um gerador de analisadores sintáticos,como o YACC ou BISON.4.4.3 Projeto de uma Gramática para um Analisador SintáticoPreditivo DescendenteConsiderando o uso de analisadores sintáticos descendentes preditivo algumas preocupaçõesquanto a gramática a ser utilizada, devem ser tomadas: eliminar ambigüidade, eliminaras recursões à esquerda e fatorar à esquerda a gramática.Analisando um Programa SimplesA seguir, é apresentado um exemplo de um programa simples na linguagem P ASCAL jr :var: float N1, N2, M; int Ct;const: int Qtde = 10;func float _Media(float a,float b)float media;{media = (a+b)/2.0;return media;}main( ) {Ct = 0;do {print("Digite duas notas:");scanf(N1,N2);printl("Media = ",_Media(N1,N2));Ct ++;} while(Ct != Qtde);}Todo programa em P ASCAL jrrespeita a seguinte estrutura:31


[ Declaração de Variáveis e Constantes ][ Declaração de Sub-Rotinas ]onde: [ ] indica seção opcional e indica seção obrigatória.Pode-se descrever esta estrutura na forma de uma regra de produção de uma GLC daseguinte forma, onde o não-terminal Programa será o axioma da gramática da linguagemP ASCAL jr :Programa → AreaDecl AreaSubRot PrincipalAnalisando a Seção de Declaração de Variáveis e ConstantesEsta seção declara todas as variáveis e constantes utilizadas pelo programa. É possível adeclaração de várias áreas de declaração de variáveis e/ou constantes simultaneamente.Um programa pode ainda não conter esta seção.AreaDecl → AreaDeclVar AreaDecl |AreaDeclConst AreaDecl | εAreaDeclVar → prVar DoisPt DeclVarsDeclVars → Tipo ListaID PtVirg DeclVars’DeclVars’ → Tipo ListaID PtVirg DeclVars’ | εTipo → prInt | prFloat | prChar | prString | prBoolListaID → Identificador ListaID’ListaID’ → Virg Identificador ListaID’ | εAreaDeclConst → prConst DoisPt DeclConstsDeclConsts → Tipo ListaIDConst PtVirg DeclConsts’DeclConsts’ → Tipo ListaIDConst PtVirg DeclConsts’ | εListaIDConst → Identificador Atrib Valor ListaIDConst’ListaIDConst’ → Virg Identificador Atrib Valor ListaIDConst’ | εValor → OpAritSubt Numeros | Numeros |ConstChar | ConstString | prTrue | prFalseNumeros → NumeroInteiro | NumeroRealAnalisando a Seção de Declaração de Procedimentos e FunçõesA seção de declaração de procedimentos e funções declara todas as sub-rotinas utilizadaspelo programa. É possível a declaração de várias áreas de declaração de sub-rotinassimultaneamente. Um programa pode ainda não conter esta seção.32


AreaSubRot → AreaProc AreaSubRot |AreaFunc AreaSubRot | εAreaProc → prProc IdentSR AbrePar ListaParamFechaPar AreaDecl BlocoComListaParam → Tipo Identificador ListaParam’ | εListaParam’ → Virg Tipo Identificador ListaParam’ | εAreaFunc → prFunc Tipo IdentSR AbrePar ListaParamFechaPar AreaDecl BlocoComAnalisando o Programa PrincipalO programa principal é o ponto onde inicia-se a execução do código fonte. Ela é definidapela função “main”. Apesar da linguagem P ASCAL jr não permitir a passagem deparâmetros para esta função, ainda sim utilizar-se-ão os parênteses “(” “)” na sintaxe docomando meramente por uma questão didática. Esta seção é obrigatória em qualquerprograma.Principal → prMain AbrePar FechaPar BlocoComAnalisando um Bloco de ComandosUm bloco de comandos pode ser entendido como um comando composto por uma listade outros comandos simples (ou outros blocos) podendo (em alguns casos) ser separadospor “;” e delimitados por “{” e “}”. Sendo assim:BlocoCom → AbreChaves ListaCom FechaChavesListaCom → Comando ListaCom | εComando → Condicional | RepetPre | RepetPos PtVirg |RepetCont | Entrada PtVirg | Saida PtVirg |Atrib PtVirg | SubRot PtVirg | BlocoCom |Retorno PtVirg | εAnalisando o comando AtribuiçãoPode ser realizado através de 7 diferentes operadores:= atribuição simples+= atribuição após adição (X+ = Y ⇔ X = X + Y )33


-= atribuição após subtração (X− = Y ⇔ X = X − Y )*= atribuição após multiplicação (X∗ = Y ⇔ X = X ∗ Y )/= atribuição após divisão (X/ = Y ⇔ X = X/Y )obs.:Não prevê divisão por zero++ atribuição incremental (X + + ⇔ X = X + 1)- - atribuição decremental (X − − ⇔ X = X − 1)Exemplo de uma gramática que reconhece esses comandos:Atrib → Identificador SimbAtrib Expr |Identificador SimbAtribSoma Expr |Identificador SimbAtribSubt Expr |Identificador SimbAtribMult Expr |Identificador SimbAtribDivi Expr |Identificador SimbIncr | Identificador SimbDecrporém, temos problemas de fatoração. Fatorando à esquerda estas produções temos:Atrib → Identificador Atrib’Atrib’ → SimbAtrib Expr |SimbAtribSoma Expr |SimbAtribSubt Expr |SimbAtribMult Expr |SimbAtribDivi Expr |SimbIncr | SimbDecrAnalisando o comando CondicionalO comando condicional (sem fatoração) ficaria:Condic → prIf AbrePar Expr FechaPar Comando |prIf AbrePar Expr FechaPar ComandoprElse Comandoe após fatoração teremos:Condic → prIf AbrePar Expr FechaPar Comando Condic’Condic’ → prElse Comando | ε34


Analisando os comandos de RepetiçãoOs comandos de repetição podem ser reconhecidos por:RepetPos → prDo ListaCom prWhile AbrePar Expr FechaParRepetPre → prWhile AbrePar Expr FechaPar ComandoRepetCont → prFor AbrePar Atrib PtVirg Expr PtVirg AtribFechaPar ComandoAnalisando os comandos para chamada a Sub-RotinasOs comandos para chamadas a sub-rotinas incluem o comando < Retorno > que deveser usado nas chamadas a funções.SubRot → IdentSR AbrePar ListaExpr FechaParRetorno → prReturn ExprAnalisando os comandos de Entrada e SaídaPara os comandos de entrada e saída temos:35


Entrada → prScanf AbrePar ListaVar FechaParSaida → prPrint AbrePar ListaExpr FechaPar |prPrintl AbrePar ListaExpr FechaParListaExpr → Expr ListaExpr’ | εListaExpr’ → Virg Expr ListaExpr’ | εAnalisando Expressões Lógicas e AritméticasPara descrever sentenças que formam expressões aritméticas compostas das cinco operaçõesbásicas (adição, subtração, multiplicação, divisão e potenciação), tendo como operandos:identificadores de variáveis e constantes, números inteiros e reais, chamadas asub-rotinas e ainda permitir o uso de parênteses e do operador unário de sinal “-”; arepresentação mais simples possível seria:ExprAr → ExprAr OpAdic ExprAr |ExprAr OpSubt ExprAr |ExprAr OpMult ExprAr |ExprAr OpDivi ExprAr |ExprAr OpPote ExprAr |AbrePar ExprAr FechaPar |OpSubt ExprAr | SubRot |Identificador | NumeroInteiro | NumeroReal |ConstCaracter | ConstStringExercício: Montar a árvore gramatical para a expressão 2 ∗ (X − 5.0) + 10/BApesar de que, com esta gramática, é possível gerar qualquer expressão aritmética simples,esta não leva em consideração todas as restrições já estudadas para a implementaçãode reconhecedores de gramática TOP-DOWN.O primeiro problema que se percebe é o fato da gramática anterior não considerar aquestão da precedência de operadores. Para resolver este problema deve-se inserir novoselementos não-terminais à gramática:ExprAr → ExprAr OpAdic TermoAr |ExprAr OpSubt TermoAr | TermoArTermoAr → TermoAr OpMult FatorAr |TermoAr OpDivi FatorAr | FatorArFatorAr → FatorAr OpPote ElementoAr | ElementoArElementoAr → AbrePar ExprAr FechaPar |36


OpSubt ExprAr | SubRot |Identificador | NumeroInteiro | NumeroReal |ConstCaracter | ConstStringExercício: Montar a árvore gramatical para a expressão 2 ∗ (X − 5.0) + 10/BA idéia é gerar os elementos de menor precedência mais próximos à raiz da árvoresintática e os de maior precedência, mais próximos às folhas.Novamente temos problemas com a solução proposta: recursão à esquerda. A novagramática após realizado o processo (já estudado) de eliminação da recursão à esquerda,temos:ExprAr → TermoAr ExprAr’ExprAr’ → OpAdic TermoAr ExprAr’ |OpSubt TermoAr ExprAr’ | εTermoAr → FatorAr TermoAr’TermoAr’ → OpMult FatorAr TermoAr’ |OpDivi FatorAr TermoAr’ | εFatorAr → ElementoAr FatorAr’FatorAr’ → OpPote ElementoAr FatorAr’ | εElementoAr → AbrePar ExprAr FechaPar |OpSubt ExprAr | SubRot |Identificador | NumeroInteiro | NumeroReal |ConstCaracter | ConstStringExercício: Montar a árvore sintática para a expressão: 2 ∗ (X − 5.0) + 10/B.Analisando Expressões LógicasUma expressão lógica é, na verdade, uma comparação entre resultados de expressõesaritméticas, ou ainda, a união de duas expressões aritméticas através de um operadorrelacional.São possíveis ainda, expressões lógicas mais complexas através da união de duas expressõeslógicas simples por operadores lógicos.Expr → TermoLog Expr’ TernarioTernario → Interrog Expr DoisPt Expr | εExpr’ → OpLogAnd TermoLog Expr’ |OpLogOr TermoLog Expr’ |OpLogXor TermoLog Expr’ | εTermoLog → FatorLog TermoLog’37


TermoLog’ → OpRelacMaior FatorLog |OpRelacMenor FatorLog |OpRelacMenorIgual FatorLog |OpRelacMaiorIgual FatorLog |OpRelacIgual FatorLog |OpRelacDifer FatorLog | εFatorLog → ExprAr | OpLogNeg Expr |prTrue | prFalsee ainda, ElementoAr → AbrePar Expr FechaPar.4.4.4 Exercícios Propostos1. Montar a árvore sintática da expressão (A + 5 == B/C − 2) && D || ! A >= B2. Criar a cláusula FIRST para as produções da gramática preditiva descendente comoforma de verificar sua implementação.3. Criar a árvore gramatical para o programa abaixo:var: float A, B, C;main() {scanf(A,B);C=A+B*2;print(C);}4.4.5 Trabalho Prático #2Implementar um módulo (sub-rotina) analisador sintático descendente para um protótipode compilador para a linguagem P ASCAL jr vista em aula.Características:Do módulo parser:• A sub-rotina retorna um flag indicando sucesso ou não da análise sintática.• Utiliza os tokens provenientes do módulo scanner (já implementado) para simular alista léxica.• Implementa uma pilha de execução (física ou por recursividade) para análise dasregras de produção da gramática vista.Do programa a ser criado:• Abre um arquivo fonte para análise.38


• Executa a análise da produção axioma da gramática.• Fecha o arquivo fonte ao final da compilação.• Pára o processo de compilação caso um erro seja encontrado.• Exibe erros de compilação (se ocorrerem) ou mensagem de sucesso.Critérios de Avaliação:• Implementação usando linguagem C ou C++.• Entrega de fontes e executável (em um arquivo zipado) via e-mail:rsilva@joinville.udesc.br ou professor.rogerio@gmail.com• Obrigatoriamente o mesmo grupo do trabalho anterior.• Valor do trabalho: 10.0 (25% da nota prática).• Data de Entrega: A definir• Punições:– de 10% por erro sintático não analisado corretamente.– de 12.5% por erro léxico não analisado corretamente.– de 20% do valor do trabalho por dia de atraso.– de 20% do valor do trabalho para a entrega não conforme dos arquivos e/ouformato pedidos.– de 50% do valor do trabalho para o caso de não executar ou travar (após testeem 2 computadores, sendo um o do professor).– de 100% do valor do trabalho para o caso de cópias (mesmo de trabalhos desemestres anteriores).• Prazo máximo para defesa e arguição sobre o trabalho: 5 dias letivos após entrega.• Punições:– de 25% para arguição não respondida ou respondida incorretamente. Obs.: Aarguição é individual.– de 33% ponto por dia de atraso da defesa.39


Capítulo 5Análise Semântica5.1 Tradução Dirigida pela SintaxeA idéia é associar informações aos símbolos gramaticais que representam a construção deuma LLC; tais informações são atributos dos símbolos segundo certas “regras semânticas”associadas às produções da gramática.Existem duas notações para associar regras semânticas às produções: definições dirigidaspela sintaxe e esquemas de tradução.Definições Dirigidas pela SintaxeUma definição dirigida pela sintaxe é uma GLC na qual cada símbolo gramatical possuium conjunto associado de atributos, particionados em dois subconjuntos: atributossintetizados e herdados.Um atributo é dito ser sintetizado se seu valor foi obtido a partir da computaçãodos valores dos filhos daquele nó da árvore e dito ser herdado se foi obtido a partir dacomputação dos irmãos e pai do respectivo nó.Uma árvore gramatical mostrando os valores dos atributos a cada nó é denominadade uma árvore gramatical anotada ou decorada.Para cada produção do tipo A → α, temos associado a ela uma regra semântica daforma b := f(c 1 , c 2 , . . . , c n ), onde f é uma função que:• Ou b é um atributo sintetizado de A e c 1 , c 2 , . . . , c n são atributos pertencentes aossímbolos gramaticais da produção ou,• b é um atributo herdado, pertencente a um dos símbolos gramaticais do lado direitode produção e c 1 , c 2 , . . . , c n são atributos pertencentes aos símbolos da produção.Numa definição dirigida pela sintaxe, assume-se que os terminais tenham apenas atributossintetizados visto que para os mesmos não existem regras semânticas. Se, para todasas produções, só forem utilizados atributos sintetizados diz-se se tratar de uma definiçãoS-atribuída.Exemplo: Seja a gramática abaixo responsável pelo funcionamento de uma calculadorasimples e tendo como único atributo sintetizado o valor val responsável pelo armazenamentode um número inteiro, então as regras semânticas (para determinação do valorresultante de uma expressão aritmética) são:41


ProduçãoL → EE → E + TE → TT → T ∗ FT → FF → (E)F → inteiroRegras Semânticasimprimir(E.val)E.val = E’.val+T.valE.val = T.valT.val = T’.val*F.valT.val = F.valF.val = E.valF.val = inteiro.lexemaLEImprimir(19)E.val = 15+4 = 19E.val = 15E+TT.val = 4T.val = 3*5 = 15TFF.val = 4T.val = 3T*FF.val = 54Inteiro.Lexval = 4F.val = 3F5Inteiro.Lexval = 53Inteiro.Lexval = 3Figura 5.1: Exemplo de Árvore Decorada para a Expressão 3*5+4Atributos herdados são convenientes para expressar a dependência de uma construçãode linguagem de programação no contexto em que a mesma figurar. Por exemplo, podemosusar um atributo herdado para controlar se um identificador aparece ao lado esquerdo oudireito de um comando de atribuição a fim de decidirmos se é necessário usar o endereçoou o valor do mesmo.Exemplo: Declaração de variáveis: int x,y,zProduçãoD → T LT → intT → floatL → L ′ , idL → idRegras SemânticasL.in = T.tipoT.tipo = inteiroT.tipo = realL’.in = L.inincluir tipo(id, L.in)incluir tipo(id, L.in)Grafos de DependênciaSe um atributo b a um nó da árvore depender de um atributo c, a regra semântica para bàquele nó precisa ser avaliada após a regra semântica para c. As interdependências entreos atributos herdados e sintentizados são delineadas através de um grafo de dependência.42


Exemplo: Supondo que a produção A → XY tenha uma regra semântica da formaA.a = f(X.x, Y.y), ou seja, o atributo sintentizado a em A depende dos atributos x e y.A representação para esta dependência em um grafo de dependência ficaria (conforme afigura 5.2):1. Existem três nós A.a, X.x e Y.y2. Existe um arco partindo de X.x para A.a pois a depende de x3. Existe um arco partindo de Y.y para A.a pois a depende de yA.aX.xY.yFigura 5.2: Grafo de Dependências5.1.1 Definições L-AtribuídasO nome L provém de left, onde a análise gramatical é feita sempre a partir do filho maisà esquerda na árvore sintática através do seguinte algoritmo:Algoritmo de pesquisa em profundidade (depth-first order):Procedimento Visitar(n: nó);InicioPara cada filho m de n da esquerda para a direita façaInicioAvaliar os atributos herdados de m;Visitar(m);FimAvaliar os atributos sintetizados de m;FimUma definição dirigida pela sintaxe é L-atribuída se cada atributo herdado de X j ,i ≤ j ≤ n, do lado direito de A → X 1 , X 2 , . . . , X n depender somente:• Dos atributos dos símbolos X 1 , X 2 , . . . , X j−1 à esquerda de X j na produção e• Dos atributos herdados de A.Note-se que cada definição S-atribuída é L-atribuída porque as restrições (1) e (2) seaplicam somente aos atributos herdados.43


5.1.2 Verificações de ContextoUm compilador precisa fazer uma verificação estática das estruturas do programa, paraassegurar que o mesmo esteja livre de certos tipos de erros, tais como:Verificação de Tipos verifica se um operador está sendo aplicado a operandos de tiposincompatíveis. Exemplo: 10 + TRUE.Verificação de Fluxo de Controle os enunciados de fluxo de controle (repetições, p.ex.) precisam ter algum local de retorno para onde transferir o controle. Exemplo:um comando tipo break em C precisa estar envolvido por algum comando de fluxode controle (while, for ou switch).Verificações de Unicidade verifica se os identificadores foram declarados de formaunívoca, ou seja, sem duplicidade.Verificações relacionadas aos nomes existem linguagens que apresentam particularidadesacerca dos identificadores do programa. Essa etapa da análise verifica se essasparticularidades foram respeitadas. Exemplo: A linguagem ADA exige que blocosde comandos comecem e terminem com o mesmo identificador.Sistema de TiposDefine-se como um sistema de tipos ao conjunto de regras de aplicabilidade entre os tiposdos operandos e os operadores da linguagem. Exemplo: Seja o comando X = A + B. Umsistema de tipos iria validar se os tipos associados aos operandos A e B são válidos para aadição e, em caso afirmativo, qual seria o tipo resultante, e ainda, se esse tipo resultanteé compatível com o tipo associado à variável X.Todo o processo de análise semântica para verificação de tipos é realizado a partir daconstrução de expressões de tipo, que podem ser compostas: ou por um tipo simples oupor um construtor de tipos (tipo resultante da aplicação de um operador).Voltando no exemplo: X = A+B; supor que X e B sejam declarados do tipo inteiro eA seja do tipo real, assim temos:===(inválido)X +int +int +(float)ABfloatintfloatintTipo SimplesConstrução de TiposFigura 5.3: Tipos Simples e Construtor de TiposO processo de verificação de tipos consiste da aplicação de regras semânticas àsproduções da gramática sob análise. Usualmente, um atributo sintetizado tipo é suficientepara tal análise.44


Exemplo1: Para uma produção do tipo E → id teríamos uma regra semântica como{ E.tipo = id.tipo }.Exemplo2: Imagine uma linguagem que permita apenas operações com tipos idênticos,então para uma produção do tipo E → E + T teríamos a regra semântica { E.tipo = seE.tipo = T.tipo então E.tipo senão inválido }Verificação de Tipos em ExpressõesAs seguintes regras semânticas podem ser abstraídas de forma geral:E → tipo simplesE → idE → E 1E → E 1 op E 2{E.tipo = tipo simples}{E.tipo = ProcurarTS(id).tipo}{E.tipo = E 1 .tipo}{E.tipo = SistemaT ipos[E 1 .tipo, op, E 2 , tipo]}Construção de um Sistema de Tipos para OperadoresApresenta as relações de compatibilidade, para cada operador, em função dos operandos.Exemplo na linguagem Pascal:Op 1 Op 2 + - * /integer integer integer integer integer realinteger real real real real realinteger char Erro Erro Erro Errointeger string Erro Erro Erro Errointeger boolean Erro Erro Erro Erroreal integer real real real realreal real real real real realreal char Erro Erro Erro Erroreal string Erro Erro Erro Erroreal boolean Erro Erro Erro Errochar integer Erro Erro Erro Errochar real Erro Erro Erro Errochar char String Erro Erro Errochar string String Erro Erro Errochar boolean Erro Erro Erro Errostring integer Erro Erro Erro Errostring real Erro Erro Erro Errostring char String Erro Erro Errostring string String Erro Erro Errostring boolean Erro Erro Erro Erroboolean integer Erro Erro Erro Erroboolean real Erro Erro Erro Erroboolean char Erro Erro Erro Erroboolean string Erro Erro Erro Erroboolean boolean Erro Erro Erro Erro45


Exercício: Construir as tabelas de sistema de tipos para todos os operadores utilizadosna gramática da linguagem P ASCAL jr .Operadores Aritméticos: +, -, *, /, **Operadores Relacionais: ==, !=, >, =,


sendo, essas tarefas são realizadas nas fases de análise sintática e/ou semântica, onde sereferencia à tabela, cada vez que um identificador for encontrado no programa.Os problemas enfrentados ao se projetar uma tabela de símbolos são:• a quantidade de símbolos armazenados na tabela, depende do programa fonte sobanálise, ou seja, é desejável uma estrutura dinâmica de alocação de memória paraa tabela. Se uma estrutura estática for construída, esta deve ter um tamanhosuficientemente grande para suportar qualquer programa (limitação de tamanho);• a quantidade de acesso à tabela (inclusões e consultas), pode ser bastante grande,dependendo do programa. Pode-se organizar os dados na tabela através de listaslineares, árvores binárias ou tabelas hash. O mecanismo linear, apesar de simples, éineficiente para programas grandes, já o hashing tem melhor desempenho mas exigemaior esforço de programação.• cada entrada na TS está associada a um nome de identificador. Estas entradaspodem não ser uniformes, ou seja, os atributos de um identificador de variávelnão são os mesmos de um identificador de função, por exemplo. O uso de registrosvariantes pode ser uma alternativa elegante para se organizar a estrutura que conterátais atributos.Enfim, ao se projetar uma tabela de símbolos deve se levar em conta: a quantidade dedados a serem armazenados, a natureza desses dados, o tempo de acesso e a facilidade demodificação da estrutura de armazenamento desses dados. Essas preocupações poderãoestar relacionadas à aplicação que se deseja desenvolver.5.2.1 Atributos dos Nomes dos IdentificadoresDe maneira geral, qualquer informações acerca dos identificadores definidos em um programafonte, podem (e devem) ser armazenados em uma tabela de símbolos. O conjuntode atributos é inerente às características da linguagem sendo reconhecida.Atributos como nome, tipo, uso no programa (isto é: variável, constante, procedimento,função, rótulo), tamanho de memória a ser alocada (em bytes), endereço (onde foialocada), escopo, etc.Para efeito da disciplina (implementação do protótipo) apenas os atributos: nome,tipo, endereço, natureza (var ou const).5.2.2 HashingA função de espalhamento é responsável por determinar qual endereço (na tabela Hash),uma determinada chave k deve ser inserida.Exemplo: int Hash(int Key){return Key%K T AM HASH; } onde, K TAM HASHindica o tamanho (n ◦ de posições) do vetor hash.A função hash acima gera valores entre 0 e K TAM HASH-1 em função do valor deKey (chave a ser inserida).Problema!: Caso exista duas chaves que possuam o mesmo valor de chave, a funçãohash irá gerar o mesmo endereço de vetor. Isto é chamado de colisão ou conflito deespalhamento.47


Existem dois métodos básicos para manipular colisões de espalhamento: reespalhamentoou encadeamento.Solucionando colisões através de reespalhamentoRequer o uso de uma função de espalhamento secundária sobre a chave, sucessivas vezesaté que um endereço válido (disponível) seja encontrado para inserção do elemento.No processo de busca ocorre idéia semelhante, exemplo: desejase localizar uma determinadachave k:1. usa-se a função de espalhamento principal2. caso não encontrado, usa-se a função de espalhamento secundária3. caso não encontrado, repete-se o passo 2.1. Inserindo elemento MEDIA: Hash(MEDIA)=6123456 MEDIA2. Inserindo elemento X: Hash(X)=212 X3456 MEDIA3. Inserindo elemento MEDIA FINAL: Hash(MEDIA FINAL)=6 (conflito!)Hash2(MEDIA FINAL)=1 (endereço válido)1 MEDIA FINAL2 X3456 MEDIA4. Inserindo elemento CONT: Hash(CONT)=1 (conflito!)Hash2(CONT)=2 (conflito!)Hash2(CONT)=3 (endereço válido)1 MEDIA FINAL2 X3 CONT456 MEDIA48


O método mais simples de solucionar colisões de espalhamento é colocar o registro napróxima posição disponível no vetor.OBS.: Deve-se trabalhar os dados no vetor da mesma forma que em uma lista circular,ou seja, a próxima posição após o último elemento é novamente o primeiro elemento.Solucionando colisões através de encadeamentoUsa-se uma lista encadeada para armazenar os elementos conflitantes no local do conflito.Cada posição do vetor é o inicio de uma lista de elementos.Sempre que um conflito na inserção de dados ocorrer, o novo elemento (conflitante) éinserido em uma lista encadeada na posição.MediaX1X2AResBCContFigura 5.4: Hashing com Encadeamento49


5.3 Projeto das Regras SemânticasOs símbolos semânticos no protótipo P ASCAL jr armazenarão as seguintes informações:Lexema, Tipo, Endereço, Natureza (VAR ou CONST ), Valor (No caso de constantes).O TDA (Hashing) é acessado através dos seguintes métodos:CriaTS aloca a tabela de símbolos vaziaInsereTS insere um novo símbolo na tabela de símbolosBuscaTS retorna as informações de um determinado símbolo na tabelaDestroiTS desaloca toda a tabela de símbolosOs possíveis erros semânticos são:1. Identificador Não Declarado2. Identificador de Variável Esperado3. Tipos Incompatíveis4. Identificador Duplicado5. Retorno de Função Esperado6. Uso Incorreto de Chamada de Sob-RotinaAs seguintes regras semânticas serão aplicadas às regras da gramática da linguagemP ASCAL jr . Obs.: Não esquecer de considerar os grafos de dependência entre os atributossemânticos.50


Programa → AreaDecl AreaSubRot Principal{ CriaTS(); EnderecoLivre=0; }AreaDecl → AreaDeclVar AreaDecl | AreaDeclConst AreaDecl | ε{ Nenhuma Ação Semântica }AreaDeclVar → prVar DoisPt DeclVars{ Nenhuma Ação Semântica }DeclVars → Tipo ListaID PtVirg DeclVars’{ ListaID.Acao = DeclV ar; ListaID.T ipo = Tipo.T ipo }DeclVars’ → Tipo ListaID PtVirg DeclVars’{ ListaID.Acao = DeclV ar; ListaID.T ipo = Tipo.T ipo }DeclVars’ → ε { Nenhuma Ação Semântica }Tipo → prInt { Tipo.T ipo = Inteiro }Tipo → prFloat { Tipo.T ipo = Real }Tipo → prChar { Tipo.T ipo = Caracter }Tipo → prString { Tipo.T ipo = Cadeia }Tipo → prBool { Tipo.T ipo = Logico }ListaID → Identificador ListaID’{ ListaID’.Acao = ListaID.Acao; ListaID’.T ipo = ListaID.T ipo;if(ListaID.Acao==DeclVar){if(!InsereT S(VAR, Identificador.Lexema, ListaID.T ipo, EnderecoLivre + +)ERRO SEM 4; }else Entrada=BuscaTS(Identificador.Lexema);if(!Entrada) ERRO SEM 1 else if(Entrada.Natureza != VAR) ERRO SEM 2 }ListaID’ 1 → Virg Identificador ListaID’ 2{ ListaID’ 2 .Acao = ListaID’ 1 .Acao; ListaID’ 2 .T ipo = ListaID’ 1 .T ipo;if(ListaID’ 1 .Acao==DeclVar){if(!InsereT S(VAR, Identificador.Lexema, ListaID.T ipo, EnderecoLivre + +)ERRO SEM 4; }else Entrada=BuscaTS(Identificador.Lexema);if(!Entrada) ERRO SEM 1 else if(Entrada.Natureza != VAR) ERRO SEM 2 }ListaID’ → ε { Nenhuma Ação Semântica }AreaDeclConst → prConst DoisPt DeclConsts{ Nenhuma Ação Semântica }DeclConsts → Tipo ListaIDConst PtVirg DeclConsts’{ ListaIDConst.T ipo = Tipo.T ipo }DeclConsts’ → Tipo ListaIDConst PtVirg DeclConsts’{ ListaIDConst.T ipo = Tipo.T ipo }DeclConsts’ → ε { Nenhuma Ação Semântica }ListaIDConst → Identificador Atrib Valor ListaIDConst’{ ListaIDConst’.T ipo = ListaIDConst.T ipo;InsereT S(CONST, Identificador.Lexema, ListaIDConst.T ipo,EnderecoLivre + +, Valor.V alor) }51


ListaIDConst’ 1 → Virg Identificador Atrib Valor ListaIDConst’ 2{ ListaIDConst’ 2 .T ipo = ListaIDConst 1 .T ipo;InsereT S(CONST, Identificador.Lexema, ListaIDConst 1 .T ipo,EnderecoLivre + +, Valor.V alor) }ListaIDConst’ → ε { Nenhuma Ação Semântica }Valor → OpAritSubt Numeros { Valor.V alor = −Numeros.V alor }Valor → Numeros { Valor.V alor = Numeros.V alor }Valor → ConstCaracter { Valor.V alor = ConstCaracter.Lexema }Valor → ConstString { Valor.V alor = ConstString.Lexema }Valor → prTrue { Valor.V alor = true }Valor → prFalse { Valor.V alor = false }Numeros → NumeroInteiro { Numeros.V alor = NumeroInteiro.Lexema }Numeros → NumeroReal { Numeros.V alor = NumeroReal.Lexema }BlocoCom → AbreChaves ListaCom FechaChaves{ Nenhuma Ação Semântica }ListaCom → ComSimp ListaCom’ { Nenhuma Ação Semântica }ListaCom → ε { Nenhuma Ação Semântica }ListaCom’ → PtVirg ComSimp ListaCom’ { Nenhuma Ação Semântica }< ListaCom’ >→ ε { Nenhuma Ação Semântica }< ComSimp >→< Atrib > { Nenhuma Ação Semântica }< ComSimp >→< RepetPre > { Nenhuma Ação Semântica }< ComSimp >→< RepetPos >{ Nenhuma Ação Semântica }< ComSimp >→< RepetCont >{ Nenhuma Ação Semântica }< ComSimp >→< Entrada >{ Nenhuma Ação Semântica }< ComSimp >→< Saida >{ Nenhuma Ação Semântica }< ComSimp >→< Condic >{ Nenhuma Ação Semântica }< ComSimp >→< BlocoCom >{ Nenhuma Ação Semântica }< ComSimp >→ ε{ Nenhuma Ação Semântica }< Atrib >→ Identificador < Atrib’ >{Entrada = BuscaT S(Identificador);if(!Entrada)ERRO SEM 1 else if(Entrada.Natureza! = V AR)ERRO SEM 2;< Atrib’ > .T ipo = Entrada.T ipo;}52


Atrib’ >→ SimbAtrib < Expr >{if(!SistemaT ipos(< Atrib’ > .T ipo, SimbAtrib, < Expr > .T ipo))ERRO SEM 3}< Atrib’ >→ SimbAtribSoma < Expr >{if(!SistemaT ipos(< Atrib’ > .T ipo, SimbAtribSoma, < Expr > .T ipo))ERRO SEM 3}< Atrib’ >→ SimbAtribSubt < Expr >{if(!SistemaT ipos(< Atrib’ > .T ipo, SimbAtribSubt, < Expr > .T ipo))ERRO SEM 3}< Atrib’ >→ SimbAtribMult < Expr >{if(!SistemaT ipos(< Atrib’ > .T ipo, SimbAtribMult, < Expr > .T ipo))ERRO SEM 3}< Atrib’ >→ SimbAtribDivi < Expr >{if(!SistemaT ipos(< Atrib’ > .T ipo, SimbAtribDivi, < Expr > .T ipo))ERRO SEM 3}< Atrib’ >→ SimbIncr{if(!SistemaT ipos(< Atrib’ > .T ipo, SimbIncr))ERRO SEM 3}< Atrib’ >→ SimbDecr{if(!SistemaT ipos(< Atrib’ > .T ipo, SimbDecr))ERRO SEM 3}< Condic >→ prSe < Expr > prEntao < ComSimp >< Condic’ >{if(< Expr > .T ipo! = Logico)ERRO SEM 3}< Condic’ >→ prSenao < ComSimp >{ Nenhuma Ação Semântica }< Condic’ >→ ε{ Nenhuma Ação Semântica }< RepetPos >→ prRepita < ListaCom > prAte < Expr >{if(! < Expr > .T ipo == Logico)ERRO SEM 3}< RepetPre >→ prEnquanto < Expr > prFaca < CompSimp >{if(! < Expr > .T ipo == Logico)ERRO SEM 3}< RepetCont >→ prPara Identificador SimbAtribValor’ > prAte< Expr > prFaca < RepetCont’ >< ComSimp >{Entrada = BuscaT S(Identificador);if(Entrada.T ipo! =< Valor’ > .T ipo || Entrada.T ipo! = Inteiro)ERRO SEM 3elseif(< Expr > .tipo! = inteiro)ERRO SEM 3< RepetCont’ >→ prPasso < Expr >if(< Expr > .tipo! = inteiro)ERRO SEM 3< RepetCont’ >→ ε{ Nenhuma Ação Semântica }< Valor’ >→ NumeroInteiro{ < Valor’ > .T ipo = inteiro }< Valor’ >→ Identificador{ < Valor’ > .T ipo = BuscaT S(Identificador).T ipo }< Entrada >→ prLeia AbrePar < ListaID > FechaPar{ < ListaID > .Acao = Leia; }< Saida >→ prImprima Abrepar < ListaExpr > FechaPar{ Nenhuma Ação Semântica }53


ListaExpr >→< Expr >< ListaExpr’ >{ Nenhuma Ação Semântica }< ListaExpr’ >→ Virg < Expr >< ListaExpr’ >{ Nenhuma Ação Semântica }< ListaExpr’ >→ ε{ Nenhuma Ação Semântica }< Expr >→< TermoLog >< Expr’ >< Ternario >{< Expr’ > .in =< TermoLog > .out; < Ternario > .in =< Expr’ > .out;< Expr > .tipo =< Ternario > .out; }< Ternario >→ Interrog < Expr > 1 DoisPt < Expr > 2{if(< Ternario > .in ! = Logico)ERRO SEM 3 elseif(< Expr > 1 .T ipo ! = < Expr > 2 .T ipo) ERRO SEM 3 else< Ternario > .out =< Expr > 1 .T ipo}< Ternario >→ ε{< Ternario > .out =< Ternario > .in}< Expr’ >→ OpLogAnd < TermoLog >< Expr’ > 1{< Expr’ > 1 .in = SistemaT ipos[< Expr’ > .in, OpLogAnd, < TermoLog > .out];< Expr’ > .out =< Expr’ > 1 .out}< Expr’ >→ OpLogOr < TermoLog >< Expr’ >{< Expr’ > 1 .in = SistemaT ipos[< Expr’ > .in, OpLogOr, < TermoLog > .out];< Expr’ > .out =< Expr’ > 1 .out}< Expr’ >→ OpLogXor < TermoLog >< Expr’ >{< Expr’ > 1 .in = SistemaT ipos[< Expr’ > .in, OpLogXor, < TermoLog > .out];< Expr’ > .out =< Expr’ > 1 .out}< Expr’ >→ ε{< Expr’ > .out =< Expr’ > .in}< TermoLog >→< FatorLog >< TermoLog’ >{< TermoLog’ > .in =< FatorLog > .out;< TermoLog > .out =< TermoLog’ > .out; }< TermoLog’ >→ OpRelacMaior < FatorLog >{< TermoLog’ > .out = SistemaT ipos[< TermoLog’ > .in, OpRelacMaior,< FatorLog > .out]; }< TermoLog’ >→ OpRelacMenor < FatorLog >{< TermoLog’ > .out = SistemaT ipos[< TermoLog’ > .in, OpRelacMenor,< FatorLog > .out]; }< TermoLog’ >→ OpRelacMenorIgual < FatorLog >{< TermoLog’ > .out = SistemaT ipos[< TermoLog’ > .in, OpRelacMenorIgual,< FatorLog > .out]; }< TermoLog’ >→ OpRelacMaiorIgual < FatorLog >{< TermoLog’ > .out = SistemaT ipos[< TermoLog’ > .in, OpRelacMaiorIgual,< FatorLog > .out]; }54


TermoLog’ >→ OpRelacIgual < FatorLog >{< TermoLog’ > .out = SistemaT ipos[< TermoLog’ > .in, OpRelacIgual,< FatorLog > .out]; }< TermoLog’ >→ OpRelacDifer < FatorLog >{< TermoLog’ > .out = SistemaT ipos[< TermoLog’ > .in, OpRelacDifer,< FatorLog > .out]; }< TermoLog’ >→ ε{< TermoLog’ > .out =< TermoLog’ > .in}< FatorLog >→< ExprAr >{< FatorLog > .out =< ExprAr > .out}< FatorLog >→ OpLogNeg < Expr >{< FatorLog > .out = SistemaT ipos[< Expr > .out, OpLogNeg]; }< FatorLog >→ prVerdadeiro{< FatorLog > .out = V erdadeiro}< FatorLog >→ prFalso{< FatorLog > .out = F also}< ExprAr >→< TermoAr >< ExprAr’ >{< ExprAr’ > .in =< TermoAr > .out;< ExprAr > .out =< ExprAr’ > .out; }< ExprAr’ >→ OpAdic < TermoAr >< ExprAr’ > 1{< ExprAr’ > 1 .in = SistemaT ipos[< ExprAr’ > .in, OpAdic, < TermoAr > .out];< ExprAr’ > .out =< ExprAr’ > 1 .out; }< ExprAr’ >→ OpSubt < TermoAr >< ExprAr’ > 1{< ExprAr’ > 1 .in = SistemaT ipos[< ExprAr’ > .in, OpSubt, < TermoAr > .out];< ExprAr’ > .out =< ExprAr’ > 1 .out; }< ExprAr’ >→ ε{< ExprAr’ > .out =< ExprAr’ > .in}< TermoAr >→< FatorAr >< TermoAr’ >{< TermoAr’ > .in =< FatorAr > .out;< TermoAr > .out =< TermoAr’ > .out; }< TermoAr’ >→ OpMult < FatorAr >< TermoAr’ > 1{< TermoAr’ > 1 .in = SistemaT ipos[< TermoAr’ > .in, OpMult, < FatorAr > .out];< TermoAr’ > .out =< TermoAr’ > 1 .out; }< TermoAr’ >→ OpDivi < FatorAr >< TermoAr’ > 1{< TermoAr’ > 1 .in = SistemaT ipos[< TermoAr’ > .in, OpDivi, < FatorAr > .out];< TermoAr’ > .out =< TermoAr’ > 1 .out; }< TermoAr’ >→ ε{< TermoAr’ > .out =< TermoAr’ > .in}< FatorAr >→< ElementoAr >< FatorAr’ > 1{< FatorAr’ > 1 .in =< ElementoAr > .out;< FatorAr > .out =< FatorAr’ > 1 .out; }55


FatorAr’ >→ OpPote < ElementoAr >< FatorAr’ > 1{< FatorAr’ > 1 .in = SistemaT ipos[< FatorAr’ > .in, OpPote,< ElementoAr > .out];< FatorAr’ > .out =< FatorAr’ > 1 .out; }< FatorAr’ >→ ε{< FatorAr’ > .out =< FatorAr’ > .in}< ElementoAr >→ AbrePar < Expr > FechaPar{< ElementoAr > .out =< Expr > .out; }< ElementoAr >→ OpSubt < ExprAr >{< ElementoAr > .out = SistemaT ipos[< ExprAr > .out, OpSubt]; }< ElementoAr >→ Identificador{< ElementoAr > .out = BuscaT S(Identificador).T ipo; }< ElementoAr >→ NumeroInteiro{< ElementoAr > .out = Inteiro}< ElementoAr >→ NumeroReal{< ElementoAr > .out = Real}< ElementoAr >→ ConstCaracter{< ElementoAr > .out = Caracter}< ElementoAr >→ ConstString{< ElementoAr > .out = Cadeia}5.3.1 Trabalho Prático #3Implementar um módulo analisador semântico para um protótipo de compilador para alinguagem P ASCAL jr (simplificada) vista em aula.Características:Do módulo semântico:• Cada chamada a elementos não-terminais processa as ações semânticos necessáriaspara cada produção da gramática.• Utiliza a árvore gramatical criada no módulo parser.• Implementa uma tabela de símbolos (Hashing) a fim de armazenar os símbolosreconhecidos durante a compilação.Do programa a ser criado:• Abre um arquivo fonte para análise.• Executa a análise semântica a partir da produção axioma da gramática.• Fecha o arquivo fonte ao final da compilação.• Pára o processo de compilação caso um erro seja encontrado.• Exibe erros de compilação (se ocorrerem) ou mensagem de sucesso.Critérios de Avaliação:56


• Implementação usando linguagem C ou C++.• Entrega de fontes e executável (em um disquete identificado).• Grupo de 02 alunos (máximo).• Valor do trabalho: 10.0 (25% da nota prática).• Data de Entrega: 08/11/2004.• Punições:– de 20% do valor do trabalho por dia de atraso.– de 20% por erro léxico não analisado corretamente.– de 15% por erro sintático não analisado corretamente.– de 10% por erro semântico não analisado corretamente.– de 20% do valor do trabalho para a entrega não conforme dos arquivos pedidos.– de 50% do valor do trabalho para o caso de não executar ou travar (após testeem 2 computadores, sendo um o do professor).– de 100% do valor do trabalho para o caso de cópias (mesmo de trabalhos desemestres anteriores).• Prazo máximo para defesa e arguição sobre o trabalho: 7 dias letivos após entrega.• Punições:– de 33% ponto por dia de atraso da defesa.– de 25% para arguição não respondida ou respondida incorretamente. Obs.: Aarguição é individual.57


Capítulo 6Geração de Código IntermediárioÉ a primeira fase da etapa de síntese, responsável por transformar a árvore de derivaçãoem um trecho de código (que pode eventualmente ser o próprio código objeto final).Freqüentemente porém, o código gerado não especifica detalhes da máquina alvo, taiscomo quais registradores serão usados ou quais endereços de memória serão referenciados,etc.Existem vantagens e desvantagens de se usar a etapa de geração de código intermediário.Vantagens:• Permite otimizações de código, a fim de tornar o código final mais eficiente;• Simplifica a implementação do compilador;• Possibilita que um mesmo código intermediário possa ser traduzido para diferenteslinguagens objeto.Desvantagens:• Uma etapa a mais é executada durante o processo de compilação, tornando o processomais lento.6.1 Linguagens IntermediáriasSão divididas em 3 categorias:• Representações gráficas;• Notação pós (ou pré) fixadas;• Código de três-endereços.6.1.1 Representações GráficasÉ uma forma condensada de árvore de derivação na qual somente os operandos da linguagemaparecem como folhas; os operadores constituem nós interiores da árvore (figura6.1).Exercício: Gerar as representações gráfica para as expressões abaixo:59


=a +* *b c b2Figura 6.1: Exemplo de Representação Gráfica de Operadores para a=b*c+b*21. a = b + c ∗ d/42. exp = b ∗ b − 4 ∗ a ∗ c >= 0 && a! = 03. d = (a ∗ a) − (b ∗ b)6.1.2 Notação Pós (e Pré) FixadasDada uma certa expressão E1 q E2, onde E1 e E2 são os operandos e q um operador, aexpressão pós-fixada é representada por E1 E2 q, enquanto que a representação pré-fixadapor q E1 E2.Exemplos:Infixa Pós-Fixada Pré-Fixada(a + b) ∗ c ab + c∗ ∗ + abca ∗ (b + c) abc + ∗ ∗a + bca + b ∗ c abc ∗ + +a ∗ bca = b ∗ c + d abc ∗ d+ = = a + ∗bcdExercícios: Gerar as notações pré e pós fixas das expressões abaixo:1. a + a ∗ b + b2. a + b ∗ 4/d − c3. (a + a) ∗ (b + b)4. a = b + c ∗ d/45. exp = b ∗ b − 4 ∗ a ∗ c >= 0 && a! = 06. d = (a ∗ a) − (b ∗ b)A seguir é apresentado um esquema de tradução para expressões pós fixadas:E → E1 + T {E.cod = E1.cod T.cod +}E → T {E.cod = T.cod}T → T 1 ∗ F {T.cod = T 1.cod T.cod *}T → F {T.cod = F.cod}F → id {F.cod = id.nome}60


6.1.3 Código de Três-EndereçosCada instrução faz referência, no máximo, a três variáveis (endereços de memória). Asinstruções dessa linguagem intermediária são:A = B op CA = op BA = Bgoto Lif A oprel B goto LExemplo: A = X+Y*ZT1 = Y*ZT2 = X+T1A = T2Um código de três-endereços pode ser implementado através de quadruplas (um operador,dois operandos e um resultado) ou triplas (um operador e dois operandos), conformeos exemplos abaixo.Exemplo: A = B*(-C+D)oper arg 1 arg 2 result(0) - C T1(1) + T1 D T2(2) * B T2 T3(3) = T3 Aoper arg 1 arg 2(0) - C(1) + (0) D(2) * B (1)(3) = A (2)Na representação por triplas existentes apontadores para a própria estrutura, evitandoassim o uso de temporários.Esquema de Tradução para um comando de atribuiçãoAtrib → ID=Expr {Atrib.cod = Expr.cod;Geracod(ID.nome=Expr.nome); }Expr → Expr 1 +Expr 2 {Expr.nome = GeraT emp;Expr.cod = Expr 1 .cod || Expr 2 .cod ||Geracod(Expr.nome=Expr 1 .nome+Expr 2 .nome); }Expr → Expr 1 *Expr 2 {Expr.nome = GeraT emp;Expr.cod = Expr 1 .cod || Expr 2 .cod ||Geracod(Expr.nome=Expr 1 .nome*Expr 2 .nome); }Expr → (Expr 1 ) {Expr.nome = Expr 1 .nome; Expr.cod = Expr 1 .cod; }Expr → ID {Expr.nome = ID.nome; Expr.cod = “ ′′ }61


O atributo nome armazena o nome de uma variável (ou temporário). O atributocod armazena o código fonte gerado para o comando. A função geracod gera um textocorrespondente a(s) instrução(ões) fornecida(s) como parâmetro. A função geratemp gerao nome de uma variável temporária.Exemplo para o comando A=X+Y*Z gera o seguinte código:T1 = Y * ZT2 = X + T1A = T2Esquema de Tradução para Expressões LógicasExistem dois métodos principais: representação numérica e representação por fluxo decontrole.Representação NuméricaCodifica numericamente as constantes true (=1) e false (=0) e avalia o resultado lógiconuma variável temporária.Exemplo: Supondo que o código gerado seja armazenado a partir da quadrupla 100 ocomando A < B seria traduzido para:099: . . .100: if A < B goto 103101: T1=0102: goto 104103: T1=1104: . . .Expr → Expr 1 || Expr 2 {Expr.nome = GeraT emp;Geracod(Expr.nome = Expr 1 .nome || Expr 2 .nome)}Expr → Expr 1 && Expr 2 {Expr.nome = GeraT emp;Geracod(Expr.nome = Expr 1 .nome && Expr 2 .nome)}Expr →! Expr 1{Expr.nome = GeraT emp;Geracod(Expr.nome = ! Expr 1 .nome); }Expr → (Expr 1 ) {Expr.nome = Expr 1 .nome; }Expr → ID 1 opRel ID 2 {Expr.nome = GeraT emp;Geracod(if ID 1 .nome opRel ID 2 .nome goto P roxq + 3);Geracod(Expr.nome =0); Geracod(goto P roxq + 2);Geracod(Expr.nome =1); }Expr → true {Expr.nome = GeraT emp; Geracod(Expr.nome=1); }Expr → false {Expr.nome = GeraT emp; Geracod(Expr.nome=0); }A variável proxq indica o índice da próxima quádrupla disponível.Exemplo: A < B || C < D && E < F62


100: if A < B goto 103 107: T2=1101: T1=0 108: if E < F goto 111102: goto 104 109: T3=0103: T1=1 110: goto 112104: if C < D goto 107 111: T3=1105: T2=0 112: T4=T2 && T3106: goto 108 113: T5 = T1 || T4O valor final da expressão é sempre no último temporário gerado (no exemplo T5). Onome desse temporário é então armazenado no não-terminal expressão da gramática.Esquema de Tradução para o comando EnquantoO comando < Enquanto >→ prEnquanto < Expr > prFaca < ComSimp >, segueo seguinte esquema de tradução:Através da seguinte regra de tradução:Inicio: Expr.codif Expr.nome == 0 goto ProxComSimp.codgoto InicioProx: . . .S → prEnquanto Expr prFaca {S.inicio = GeraRotulo; S.prox = GeraRotulo;ComSimp S.cod = Expr.cod ||Geracod(if Expr.nome == 0goto S.prox);ComSimp.cod; || Geracod(goto S.inicio); }Representação por Fluxo de ControleEste método traduz expressões lógicas para um código formado por instruções if-goto.São gerados rótulos true e false, que armazenam os endereços de desvio de execução casoa avaliação resulte em true ou false respectivamente. Por este motivo, é mais eficiente queo método de avaliação numérica.Tradução de expressões lógicas por fluxo de controleExpr → Expr 1 || Expr 2 {Expr 1 .true = Expr.true; Expr 1 .false = GeraRotulo;Expr 2 .true = Expr.true; Expr 2 .false = Expr.false;Expr.cod = Expr 1 .cod || Expr 2 .cod; }Expr → Expr 1 && Expr 2 {Expr 1 .true = GeraRotulo; Expr 1 .false = Expr.false;Expr 2 .true = Expr.true; Expr 2 .false = Expr.false;Expr.cod = Expr 1 .cod || Expr 2 .cod; }Expr →! Expr 1{Expr 1 .true = Expr.false; Expr 1 .false = Expr.true;Expr.cod = Expr 1 .cod; }Expr → (Expr 1 ){Expr 1 .true = Expr.true; Expr 1 .false = Expr.false;Expr → ID 1 opRel ID 2Expr.cod = Expr 1 .cod; }{Expr.cod = Geracod(if ID 1 .nome opRel ID 2 .nomegoto Expr.true); Geracod(goto Expr.false); }Expr → true {Expr.cod = Geracod(goto Expr.true); }Expr → false {Expr.cod = Geracod(goto Expr.false); }63


Exemplo: A < B || C < D && E < FL1L2if A < B goto RTgoto L1if C < D goto L2goto RFif E < F goto RTgoto RFSupondo que os atributos true e false tenham recebido os rótulos RT e RF.Esquema de Tradução para Comandos de Controle de FluxoS → prSe Expr prEntao S 1S → prSe Expr prEntao S 1prSenao S 2S → prEnquanto Expr S 1{Expr.true = GeraRotulo; Expr.false = S.prox;S 1 .prox = S.prox; S.cod = Expr.cod || S 1 .cod; }{Expr.true = GeraRotulo; Expr.false = GeraRotulo;S 1 .prox = S.prox; S 2 .prox = S.prox;S.cod = Expr.cod || S 1 .cod; ||Geracod(goto S.prox); || S 2 .cod; }{S.inicio = GeraRotulo; Expr.true = GeraRotulo;Expr.false = S.prox; S 1 .prox = S.inicio;S.cod = Expr.cod || S 1 .cod; ||Geracod(goto S.inicio); }6.2 BackPatching (Retrocorreção)O principal problema, na geração de código, é que o código gerado deve incluir comandosde desvio para endereços que, em geral, ainda não são conhecidos. Isso inviabiliza ageração de código num único passo.A solução é utilizar geração de códigos incompletos para os comandos (sem os endereços)que serão devidamente completados quando o endereço destino for conhecido.Chama-se de backpatching ao preenchimento desses endereços não resolvidos.São necessárias três funções para isso:makelist(i) cria uma lista contendo i (um único elemento) e retorna um ponteiro paraa lista criada; o elemento i é um índice do vetor de quádruplas;merge(p1,p2) concatena as listas apontadas por p1 e p2, e retorna um ponteiro paralista resultante;backpatching(p,i) insere i (rótulo destino) no campo de endereço de cada uma dasquádruplas da lista apontada por p.64


Backpatching para Expressões LógicasFoi acrescentado o símbolo não-terminal M, que tem como objetivo guardar o endereçoda próxima quádrupla disponível no momento em que M é empilhado (após um “&&” ou“||”).E → E 1 || E 2 {backpatching(E 1 .ListaF alse, M.quad);E.ListaT rue = merge(E 1 .ListaT rue, E 2 .ListaT rue);E.ListaF alse = E 2 .ListaF alse; }E → E 1 && E 2 {backpatching(E 1 .ListaT rue, M.quad);E.ListaT rue = E 2 .ListaF alse;E.ListaF alse = merge(E 1 .ListaF alse, E 2 .ListaF alse); }E →!E 1 {E.ListaT rue = E 1 .ListaF alse; E.ListaF alse = E 1 .ListaT rue; }E → (E 1 ) {E.ListaT rue = E 1 .ListaT rue; E.ListaF alse = E 1 .ListaF alse; }E → ID 1 opRel ID 2 {E.ListaT rue = makelist(P roxq);E.ListaF alse = makelist(P roxq + 1);Geracod(if ID 1 .nome opRel ID 2 .nome goto );Geracod(goto ); }E → ID{E.ListaT rue = makelist(P roxq);E.ListaF alse = makelist(P roxq + 1);Geracod(if ID.nome goto );Geracod(goto ); }M → ε {M.quad = P roxq; }Neste esquema, E.ListaTrue e E.ListaFalse são atributos sintetizados (listas) que indicamquádruplas com comandos de desvio incompletos. Os endereços são preenchidoscom o valor armazenado em M.quad quando um backpatching ocorre na lista.Exemplo: a < b || c < d && e < fEListaTrue=100,104ListaFalse=103,105ListaTrue=100ListaFalse=101E||MQuad=102EListaTrue=104ListaFalse=103,105A < BListaTrue=102ListaFalse=103E&&MQuad=104EListaTrue=104ListaFalse=105C < DE < FFigura 6.2: Backpatching para expressões lógicas100: if A < B goto 100: if A < B goto101: goto 101: goto 102102: if C < D goto 102: if C < D goto 104103: goto 103: goto104: if E < F goto 104: if E < F goto105: goto 105: goto65


Backpatching para Comandos de ControleEntendendo-se o esquema de tradução anterior, é introduzido um não-terminal N antesdo comando “senao” para ocasionar um salto sobre o bloco do “senao” se for o caso.S → Se E entao M S 1 {backpatching(E.ListaT rue, M.quad);S.prox = merge(E.ListaF alse, S 1 .prox); }S → Se E entao M 1 S 1 N {backpatching(E.ListaT rue, M 1 .quad);senao M 2 S 2backpatching(E.ListaF alse, M 2 .quad);S.prox = merge(S 1 .prox, merge(N.go, S 2 .prox)); }N → ε {N.go = makelist(P roxq); Geracod(goto ); }S → Enquanto M 1 E Faca {backpatching(S 1 .prox, M 1 .quad);M 2 S 1backpatching(E.ListaT rue, M 2 .quad);S.prox = E.ListaF alse; Geracod(goto M 1 .quad); }S → {L}{S.prox = L.prox;S → A {S.prox = makelist(NULL); }A → ID = EAções semânticas já vistas em aulaL → L 1 ; M S {backpatching(L 1 .prox, M.quad); L.prox = S.prox; }L → S {L.prox = S.prox; }P → S. {backpatching(S.prox, EOF ); }O endereço indicado por EOF representa o fim do código fonte e o retorno ao sistemaoperacional.Exercício: Gere o código para o trecho de programa abaixo (supor primeira instruçãono endereço 000):Enquanto A < B entaose C < D entaoX = Y+ZsenaoX = Y-ZCódigo resultante:000: if A < B goto 002 006: goto 000001: goto EOF 007: T2 = Y-Z002: if C < D goto 004 008: X = T2003: goto 007 009: goto 000004: T1 = Y+Z EOF:005: X = T166


Capítulo 7Otimização de CódigoTrata-se do problema da geração de código eficiente: uso racional da memória e rapidezna execução do código fonte. Porém, muitas vezes esses aspectos são conflitantes, ou seja,apela-se para um maior tempo de execução para se conseguir um ganho no consumo dememória e vice-versa.<strong>Compiladores</strong> que aplicam transformações de melhorias no código gerado são denominadoscompiladores otimizantes.Normalmente este processo é feito em duas fases: otimizações do código intermediárioe otimizações do código objeto. No código intermediário pode-se eliminar atribuiçõesredundantes, sub-expressões comuns, temporários desnecessários, etc., no intuito de diminuio código intermediário, enquanto que no código objeto é feita uma substituição deinstruções por equivalentes mais rápidos que permitem melhor uso dos registradores.7.1 Otimização PeepholeUma técnica simples para melhorar localmente o código gerado é a otimização peephole,que trabalha substituindo seqüências de instruções (peepholes) por outras mais eficientes.As principais ações da otimização peephole são:• eliminação de instruções redundantes• otimizações de fluxo de controle• simplificações algébricasEliminação de Instruções RedundantesUma seqüência de instruções do tipo a = b e b = a, pode ser substituído somente por a= b, pois o resultado lógico é o mesmo.Otimização de Fluxo de ControleOs algoritmos de geração de código intermediário freqüentemente produzem: (1) desviospara desvios, (2) desvios para desvios condicionais ou (3) desvios condicionais para desvios.Uma otimização peephole pode remover tais instruções redundantes:Exemplo 1:67


goto L1 goto L2. . . . . .L1: goto L2 L1: goto L2Neste caso, se não houverem outras instruções que levem a L1, esta instrução podeser removida do código.Exemplo 2:if a < b goto L1 if a < b goto L2. . . . . .L1: goto L2 L1: goto L2Exemplo 3:goto L1if a < b goto L2. . . goto L3L1: if a < b goto L2 . . .L3: . . . L3: . . .Simplificação AlgébricaRemoção de redundâncias que ocorrem em expressões algébricas, tais como: x=x+0 ouy=y*1.7.2 Otimização de Blocos Sequenciais através de grafosO uso de grafos acíclicos dirigidos (GAD) para se representar uma seqüência de instruções,permite mais facilmente rearranjar a ordem das instruções a fim de reduzir o código objetofinal.Exemplo: (a+b)-(e-(c+d))68


Código IntermediárioT1 = a+bT2 = c+dT3 = e-T2T4 = T1-T3T2 = c+dT3 = e-T2T1 = a+bT4 = T1-T3Código ObjetoMOV a, R0ADD b, R0MOV c, R1ADD d, R1MOV, R0, T1MOV E, R0SUB R1, R0MOV T1, R1SUB R0, R1MOV R1, T4MOV c, R0ADD d, R0MOV e, R1SUB R0, R1MOV a, R0ADD b, R0SUB R1, R0MOV R0, T47.2.1 Algoritmo para Construir o GAD de um blocoO algoritmo supõe que cada instrução (de três-endereços) segue um dos seguintes trêsformatos: (1) x = y op z; (2) x = op y; (3) x = y. Instruções if-goto são tratadas como ocaso (1).Passos:1. Se o nó y ainda não existe no grafo, crie uma folha para y (e para z se for o caso 1);2. No caso 1, verifique se existe um nó op com filhos y e z (nessa ordem). Se sim,chame-o, também de x; senão, crie um nó op com nome x e dois arcos dirigidos donó op para y e z. No caso 2, verifique se existe um nó op com um único filho y. Senão existir, cria tal nó e um arco para y; chame de x o nó criado ou encontrado. Nocaso 3, chame também de x o nó y.Exemplo: y = ((a+b)*(a-b))+((a+b)*(a-c))T1 = a+bT2 = a-bT3 = T1*T2T4 = a+bT5 = a-cT6 = T4*T5T7 = T3+T6y = T769


+T7,yT3* *T6+T1,T4 T2 T5--abcFigura 7.1: Grafo Acíclico Dirigido - GAD7.2.2 Algoritmo para Ordenação de um GADCria-se uma lista de ordenação dos nós internos do GAD através do algoritmo a seguir:Enquanto existirem nós interiores n~ao listados faça inicio1. Selecionar um nó n n~ao listado do qual todos os pais já foram listados;2. Listar n;Enquanto o filho mais à esquerda m de n tiver todos os pais listadose n~ao for uma folha faça inicio3. Listar m;4. n := mfimfimO código é então gerado através da ordem inversa da obtida pela lista resultante.Exemplo para o código intermediário visto anteriormente: y (ou T7), T3, T6, T1 (ouT4), T5, T2.70


Capítulo 8Geração de Código ObjetoÉ a fase final de um modelo de compilador. Recebe como entrada a representação intermediáriado programa-fonte e produz como saída um programa-alvo equivalente.Por ser impraticável a criação de um gerador de código ótimo, contenta-se com acriação (através de heurísticas) de bons geradores de código. A maioria dos problemasde geração de código envolve aspectos como gerência de memória, seleção de instruções,alocação de registradores e ordem de avaliação.As principais características de um bom gerador de código: deve ser correto (essencial),usar de forma eficiente os recursos da máquina e o próprio gerador deve ser implementadode forma eficiente.Uma entrada usual para um gerador de código é o código intermediário juntamentecom as informações armazenadas na tabela de símbolos. Já a saída pode ser uma linguagemabsoluta de máquina (carrega em uma posição fixa de memória antes da execução),linguagem relocável (cria módulos que podem ou não, serem executados em conjunto,sendo para tal necessário uma etapa de linkedição dos módulos) ou linguagem de montagem(gera instruções simbólicas que são traduzidas através de um montador). A terceiraopção será a escolhida para a implementação do trabalho prático #4.Seleção de InstruçõesSe não nos importarmos com a eficiência do código-objeto gerado, a seleção de instruções éum processo direto. Para cada tipo de instrução intermediária (código de três-endereços)gerada, projeta-se um esqueleto de código final equivalente. Exemplo: x = y + z.MOV y, R0 (carregar y no registrador R0)ADD z,R0 (adicionar z a R0)MOV R0, x (armazenar R0 em x)Infelizmente, o código final gerado por esta alternativa freqüentemente é extenso eineficiente. Exemplo:a = b+cd = a+eMOV b, R0ADD c, R0MOV R0, aMOV a, R0ADD e, R0MOV R0, d71


Operação com RegistradoresO uso de registradores no processo de execução de instruções é interessante pois:• Operação com registrador é mais rápida• Conjunto de registradores é pequeno• Utilização eficiente = otimizaçãoDesvantagem: a ordem das instruções é muito importante no aspecto eficiência.8.1 Máquina ObjetoPara se criar um gerador de código para uma determinada máquina objeto, primeiramenteé necessário conhecer seu conjunto de instruções.Por motivos didáticos optou-se por uma máquina hipotética (MAQHIPO), que temas seguintes características:• É baseada em uma pilha;• Trabalha com os mesmos tipos de dados das fases anteriores;• Sua memória é dividida em duas partes:Área de Código contém uma lista (CODIGO) das instruções geradas pelo compilador(código de três-endereços);Área de Dados composta por uma pilha (DADOS) que conterá os registradoresmanipulados pelas instruções da máquina (só existe em tempo de execução deum programa) e uma lista (MEMO) que armazena as variáveis e constantesmanipuladas pelo programa. Para efeito de simplificação, todas as variáveisterão tamanho 1;• Possui um registrador especial (PROXINST) para a próxima instrução (na área decódigo) a ser executada, outro (TOPODADOS) que indica o topo da pilha de dadose TOPOMEMO a quantidade de memória alocada;O funcionamento da MAQHIPO é bastante simples:1. Carrega-se o programa-objeto (gerado pelo compilador) na área de código.2. Todas as instruções (indicadas pelo ponteiro PROXINST) são executadas seqüencialmenteaté a instrução de parada ou até que ocorra algum erro de execução;3. A execução de cada instrução incrementa o valor de PROXINST (exceto para instruçõesde desvio);O conjunto de instruções válidas para a MAQHIPO é:Inicialização e Finalização72


INIP inicializa a execução do programa {T opoMemo = T opoDesvio = 0; T opoDados =−1; }FIMP encerra a execução do programa {}Alocação de MemóriaALME m t Aloca m posições de memória, todas do tipo t{for(i = T opoMemo; i < T opoMemo + m; i + +)Memo[i].tipo = t;T opoMemo+ = m; }DEAL m Desloca m posições de memória{T opoMemo− = m; }Comandos de E/SENTR Entrada de dados {gets(Dados[+ + T opoDados]); }IMPR Impressão de valores {printf(“%s ′′ , Dados[T opoDados − −]); }Comandos para ExpressõesCRCT k t Carrega uma constante do tipo t. {Dados[+ + T opoDados] = (k, t); }CRVL n t Transporta o conteúdo do endereço de memória n para a pilha de dados.{Dados[+ + T opoDados] = Memo[n]; }SOMA substitui os dois elementos mais ao topo da pilha por sua soma.{Dados[T opoDados − 1]+ = Dados[T opoDados − −]; }SUBT substitui os dois elementos mais ao topo da pilha por sua diferença.{Dados[T opoDados − 1]− = Dados[T opoDados − −]; }MULT substitui os dois elementos mais ao topo da pilha por seu produto.{Dados[T opoDados − 1]∗ = Dados[T opoDados − −]; }DIVI substitui os dois elementos mais ao topo da pilha por seu quociente.{Dados[T opoDados − 1]/ = Dados[T opoDados − −]; }POTE substitui os dois elementos mais ao topo da pilha pela sua potência.{Dados[T opoDados − 1] = pow(Dados[T opoDados − 1], Dados[T opoDados − −]); }INVE inverte o sinal do elemento no topo da pilha.{Dados[T opoDados]∗ = −1; }CONJ substitui os dois elementos mais ao topo da pilha por sua conjunção (&&).{Dados[T opoDados − 1] = Dados[T opoDados − 1] && Dados[T opoDados];T opoDados − −; }DISJ substitui os dois elementos mais ao topo da pilha por sua disjunção (||).{Dados[T opoDados − 1] = Dados[T opoDados − 1] + Dados[T opoDados] == 1;T opoDados − −; }73


DISX substitui os dois elementos mais ao topo da pilha por sua disjunção exclusiva (& |).{Dados[T opoDados − 1] = Dados[T opoDados − 1] & | Dados[T opoDados];T opoDados − −; }NEGA inverte o valor lógico do elemento no topo da pilha .{Dados[T opoDados] =!Dados[T opoDados]; }CPME substitui os dois elementos mais ao topo da pilha pela comparação de menor.{Dados[T opoDados − 1] = Dados[T opoDados − 1] < Dados[T opoDados];T opoDados − −; }CMAI substitui os dois elementos mais ao topo da pilha pela comparação de maior.{Dados[T opoDados − 1] = Dados[T opoDados − 1] > Dados[T opoDados];T opoDados − −; }CPIG substitui os dois elementos mais ao topo da pilha pela comparação de igualdade.{Dados[T opoDados − 1] = Dados[T opoDados − 1] == Dados[T opoDados];T opoDados − −; }CDIF substitui os dois elementos mais ao topo da pilha pela comparação de desigualdade.{Dados[T opoDados − 1] = Dados[T opoDados − 1] ! = Dados[T opoDados];T opoDados − −; }CPMI substitui os dois elementos mais ao topo da pilha pela comparação de menor ouigual.{Dados[T opoDados − 1] = Dados[T opoDados − 1] = Dados[T opoDados];T opoDados − −; }74


Comando de AtribuiçãoARMZ n Transporta o conteúdo do topo da pilha para o endereço de memória n.{Memo[n] = Dados[T opoDados − −]; }Comandos Condicionais e IterativosDSVF p Desvio se condicional falso para a instrução p.{P roxInst = Dados[T opoDados]?P roxInst + 1 : p; }DSVI p Desvio incondicional para a instrução p. {P roxInst = p; }EXEC n Executa a chamada a uma sub-rotina com inicio na instrução n e empilha oendereço de retorno{Desvios[+ + T opoDesvio] = P roxInst + 1; P roxInst = n; }RETR Retorna a execução para a próxima instrução após a chamada à sub-rotina{P roxInst = Desvios[T opoDesvio − −]; }Exemplo de Geração de Código Objeto na Linguagem Hipo:Programa FontePrograma ObjetoVar: inteiro F, N; 01 INIP02 ALME 1 i03 ALME 1 iInicioLeia(N); 04 ENTR05 ARMZ 1F=1; 06 CRCT 1 i07 ARMZ 0Enquanto N >= 1 faca Inicio 08 CRVL 1 i09 CRCT 1 i10 CPMA11 DSVF 21F ∗ = N; 12 CRVL 0 i13 CRVL 1 i14 MULT15 ARMZ 0N − −; 16 CRVL 1 i17 CRCT 1 i18 SUBT19 ARMZ 1Fim 20 DSVI 8Escreva(F); 21 CRVL 0 i22 IMPRFim 23 FIMP75


8.1.1 Regras para Geração de Código ObjetoPrograma → AreaDecl AreaSubRot Principal{ Geracod(INIP);Gerar código para AreaDeclQuad = ProxQ; Geracod(DSVI);Gerar código para AreaSubRotBackpatching(Quad, ProxQ);Gerar código para Principalif(TS.Qtde 0) Geracod(DEAL TS.Qtde); Geracod(FIMP);}AreaDecl → AreaDeclVar AreaDecl{ Gerar código para AreaDeclVarGerar código para AreaDecl }AreaDecl → AreaDeclConst AreaDecl{ Gerar código para AreaDeclConstGerar código para AreaDecl }AreaDecl → ε {}AreaDeclVar → prVar DoisPt DeclVars{ Gerar código para DeclVars }DeclVars → Tipo ListaID PtVirg DeclVars’{ ContIds=0;Gerar código para ListaIDGerar código para DeclVars’ }DeclVars’ → Tipo ListaID PtVirg DeclVars’{ ContIds=0;Gerar código para ListaIDGerar código para DeclVars’ }DeclVars’ → ε {}Tipo → prInt {}Tipo → prFloat {}Tipo → prChar {}Tipo → prString {}Tipo → prBool {}76


ListaID → Identificador ListaID’{ if(Acao == DeclV ar)ContIds + +;else{Geracod(ENT REntrada.T ipo); Geracod(ARMZ Entrada.Endereco Entrada.T ipo); }Gerar código para ListaID’ }ListaID’ → Virg Identificador ListaID’{ if(Acao == DeclV ar)ContIds + +;else{Geracod(ENT REntrada.T ipo); Geracod(ARMZ Entrada.Endereco Entrada.T ipo); }Gerar código para ListaID’ }ListaID’ → ε{ if(Acao == DeclV ar)Geracod(ALME ContIds); }AreaDeclConst → prConst DoisPt ListaConst{ Gerar código para ListaConst }ListaConst → Tipo ListaIDConst PtVirg ListaConst’{ Gerar código para ListaIDConstGerar código para ListaConst’ }ListaConst’ → Tipo ListaIDConst PtVirg ListaConst’{ Gerar código para ListaIDConstGerar código para ListaConst’ }ListaConst’ → ε {}ListaIDConst → Identificador Atrib Valor ListaIDConst’{ Geracod(ALME 1 Identificador.tipo);Gerar código para ValorGeracod(ARMZ Identificador.Endereco); Gerar código para ListaIDConst’ }ListaIDConst’ → Virg Identificador Atrib Valor ListaIDConst’{ Geracod(ALME 1 Identificador.tipo);Gerar código para ValorGeracod(ARMZ Identificador.Endereco); Gerar código para ListaIDConst’ }ListaIDConst’ε {}Valor → OpAritSubt Numeros{ Gerar código para NumerosGeracod(INVE); }Valor → Numeros{ Gerar código para Numeros }77


Valor → ConstCaracter{ Geracod(CRCT ConstCaracter.lexema c); }Valor → ConstString{ Geracod(CRCT ConstString.lexema s); }Valor → prTrue{ Geracod(CRCT true b); }Valor → prFalse{ Geracod(CRCT False b); }Numeros → NumeroInteiro{ Geracod(CRCT NumeroInteiro.lexema i); }Numeros → NumeroReal{ Geracod(CRCT NumeroReal.lexema f); }AreaSubRot → AreaProc AreaSubRot{ Gerar código para AreaProcGerar código para AreaSubRot }AreaSubRot → AreaFunc AreaSubRot{ Gerar código para AreaFuncGerar código para AreaSubRot }AreaSubRot → ε { }AreaProc → prProc IdentSR AbrePar ListaParam FechaParAreaDecl BlocoCom{ Gerar código para ListaParamGerar código para AreaDeclGerar código para BlocoComif(TS.qtde 0) Geracod(DEAL TS.qtde);Geracod(RETR); }ListaParam → Tipo Identificador ListaParam’{ Geracod(ARMZ Identificador.endereco);Gerar código para ListaParam’ }ListaParam → ε { }ListaParam’ → Virg Tipo Identificador ListaParam’{ Geracod(ARMZ Identificador.endereco);Gerar código para ListaParam’ }78


ListaParam’ → ε { }AreaFunc → prFunc Tipo IdentSR AbrePar ListaParam FechaParAreaDecl BlocoCom{ Gerar código para ListaParamGerar código para AreaDeclGerar código para BlocoComif(TS.qtde 0) Geracod(DEAL TS.qtde);Geracod(RETR); }Principal → prMain Abrepar FechPar BlocoCom{ Gerar código para BlocoCom }BlocoCom → AbreChaves ListaCom FechaChaves{ Gerar código para ListaCom }ListaCom → Comando ListaCom{ Gerar código para ComandoGerar código para ListaCom }ListaCom → ε {}Comando → Atrib PtVirg{ Gerar código para Atrib }Comando → RepetPre{ Gerar código para RepetPre }Comando → RepetPos PtVirg{ Gerar código para RepetPos }Comando → RepetCont{ Gerar código para RepetCont }Comando → Entrada PtVirg{ Gerar código para Entrada }Comando → Saida PtVirg{ Gerar código para Saida }Comando → Condic{ Gerar código para Condic }Comando → BlocoCom{ Gerar código para BlocoCom }79


Comando → Retorno PtVirg{ Gerar código para Retorno }Comando → SubRot PtVirg{ Gerar código para SubRot }Atrib → Identificador Atrib’{ Gerar código para Atrib’; Geracod(ARMZ Identificador.Endereco) }Atrib’ → SimbAtrib Expr{ Gerar código para Expr }Atrib’ → SimbAtribSoma Expr{ Geracod(CRVL Entrada.Endereco Entrada.tipo)Gerar código para ExprGeracod(SOMA) }Atrib’ → SimbAtribSubt Expr{ Geracod(CRVL Entrada.Endereco Entrada.tipo)Gerar código para ExprGeracod(SUBT) }Atrib’ → SimbAtribMult Expr{ Geracod(CRVL Entrada.Endereco Entrada.tipo)Gerar código para ExprGeracod(MULT) }Atrib’ → SimbAtribDivi Expr{ Geracod(CRVL Entrada.Endereco Entrada.tipo)Gerar código para ExprGeracod(DIVI) }Atrib’ → SimbIncr{ Geracod(CRVL Entrada.Endereco Entrada.tipo)Geracod(CRCT 1 i)Geracod(SOMA) }Atrib’ → SimbDecr{ Geracod(CRVL Entrada.Endereco Entrada.tipo)Geracod(CRCT 1 i)Geracod(SUBT) }Condic → prIf AbrePar Expr FechPar Comando Condic’{ Gerar código para Expr; Quad1 = ProxqGeracod(DSVF); Gerar código para ComandoGerar código para Condic’ }80


Condic’ → prElse Comando{ Quad2 = ProxQ; Geracod(DSVI)Backpatching(Quad1, ProxQ); Gerar código para ComandoBackpathing(Quad2, ProxQ); }Condic’ → ε{ Backpatching(Quad1, ProxQ) }RepetPos → prDo BlocoCom prWhile AbrePar Expr FechaPar{ Quad1 = ProxQ; Gerar código para BlocoComGerar código para Expr; Geracod(DSVF ProxQ+2); Geracod(DSVI Quad1) }RepetPre → prWhile AbrePar ExprFechaPar Comando{ Quad1 = ProxQ; Gerar código para ExprQuad2 = ProxQ; Geracod(DSVF); Gerar código para ComandoGeracod(DSVI Quad1); Backpatching(Quad2, ProxQ) }RepetCont → prFor AbrePar Atrib PtVirg Expr PtVirg Atrib FechaParComando{ Gerar código para Atrib; Quad1 = ProxQ;Gerar código para Expr; Quad2 = ProxQGeracod(DSVF); Buffer = Gerar código para AtribGerar código para ComandoDescarrega o buffer temporário; Geracod(DSVI Quad1)Backpatching(Quad2, ProxQ) }Entrada → prScanf AbrePar ListaID FechaPar{ Gerar código para ListaID }Saida → prPrint Abrepar ListaExpr FechaPar{ Gerar código para ListaExpr }Saida → prPrintl Abrepar ListaExpr FechaPar{ Gerar código para ListaExpr }ListaExpr → Expr ListaExpr’{ Gerar código para Expr; Gerar código para ListaExpr’ }ListaExpr’ → VirgExpr ListaExpr’{ Gerar código para Expr; Gerar código para ListaExpr’ }ListaExpr’ → ε{}Retorno → prReturn Expr{ Gerar código para Expr }81


SubRot → IdentSR AbrePar ListaArg FechaPar{ Gerar código para ListaArg; Geracod(EXEC IdentSR.Endereco) }ListaArg → ListaExpr{ Gerar código para ListaExpr }ListaArg → ε{}Expr → TermoLog Expr’ Ternario{ Gerar código para TermoLog; Gerar código para Expr’Gerar código para Ternario }Ternario → Interrog Expr 1 DoisPt Expr 2{ Quad1 = ProxQ; Geracod(DSVF); Gerar código para Expr 1Quad2 = ProxQ; Geracod(DSVI); Backpatching(Quad1, ProxQ)Gerar código para Expr 2 ; Backpatching(Quad2, ProxQ) }Ternario → ε{}Expr’ → OpLogAnd TermoLog Expr’{ Gerar código para TermoLog; Geracod(CONJ)Gerar código para Expr’ }Expr’ → OpLogOr TermoLog Expr’{ Gerar código para TermoLog; Geracod(DISJ)Gerar código para Expr’ }Expr’ → OpLogXor TermoLog Expr’{ Gerar código para TermoLog; Geracod(DISX)Gerar código para Expr’ }Expr’ → ε {}TermoLog → FatorLog TermoLog’{ Gerar código para FatorLog; Gerar código para TermoLog’; }TermoLog’ → OpRelacMaior FatorLog{ Gerar código para FatorLog; Geracod(CMAI); }TermoLog’ → OpRelacMenor FatorLog{ Gerar código para FatorLog; Geracod(CPME); }TermoLog’ → OpRelacMenorIgual FatorLog{ Gerar código para FatorLog; Geracod(CPMI); }82


TermoLog’ → OpRelacMaiorIgual FatorLog{ Gerar código para FatorLog; Geracod(CPMA); }TermoLog’ → OpRelacIgual FatorLog{ Gerar código para FatorLog; Geracod(CPIG); }TermoLog’ → OpRelacDifer FatorLog{ Gerar código para FatorLog; Geracod(CDIF); }TermoLog’ → ε{}FatorLog → ExprAr{ Gerar código para ExprAr; }FatorLog → OpLogNeg Expr{ Gerar código para Expr; Geracod(NEGA); }FatorLog → prTrue{ Geracod(CRCT true l); }FatorLog → prFalse{ Geracod(CRCT false l); }ExprAr → TermoAr ExprAr’{ Gerar código para TermoAr; Gerar código para ExprAr’; }ExprAr’ → OpAdic TermoAr ExprAr’{ Gerar código para TermoAr; Geracod(SOMA);Gerar código para ExprAr’; }ExprAr’ → OpSubt TermoAr ExprAr’{ Gerar código para TermoAr; Geracod(SUBT);Gerar código para ExprAr’; }ExprAr’ → ε{}TermoAr → FatorAr TermoAr’{ Gerar código para FatorAr; Gerar código para TermoAr’; }TermoAr’ → OpMult FatorAr TermoAr’{ Gerar código para FatorAr; Geracod(MULT);Gerar código para TermoAr’; }TermoAr’ → OpDivi FatorAr TermoAr’{ Gerar código para FatorAr; Geracod(DIVI);Gerar código para TermoAr’; }83


TermoAr’ → ε{}FatorAr → ElementoAr FatorAr’{ Gerar código para ElementoAr; Gerar código para FatorAr’; }FatorAr’ → OpPote ElementoAr FatorAr’{ Gerar código para ElementoAr; Geracod(POTE);Gerar código para FatorAr’; }FatorAr’ → ε{}ElementoAr → AbrePar Expr FechaPar{ Gerar código para Expr; }ElementoAr → OpSubt ExprAr{ Gerar código para ExprAr; Geracod(INVE); }ElementoAr → Identificador{ Geracod(CRVL identificador.Endereco); }ElementoAr → Numeros{ Gerar código para Numeros }ElementoAr → SubRot{ Gerar código para SubRot }ElementoAr → ConstCaracter{ Geracod(CRCT ConstCaracter.lexema c); }ElementoAr → ConstString{ Geracod(CRCT ConstString.lexema s); }8.1.2 Trabalho Prático #4Implementar um módulo analisador semântico para um protótipo de compilador para alinguagem P ASCAL jr (simplificada) vista em aula.Características:Do módulo gerador de código:• Cada chamada a elementos não-terminais processa esquemas de tradução de códigonecessários para cada produção da gramática.• Utiliza a árvore decorada criada no módulo semântico.• Utiliza a tabela de símbolos (altera e consulta dados) construída na fase anterior.84


Do programa a ser criado:• Abre um arquivo fonte para análise.• Executa todas as fases de análise e, caso nenhum erro seja encontrado, armazena asequência de instruções em linguagem HIPO na lista CODE da MaqHipo.• Fecha o arquivo fonte ao final da compilação.• Pára o processo de compilação caso um erro seja encontrado.• Exibe erros de compilação (se ocorrerem) ou mensagem de sucesso.• Inicia o processo de execução da máquina objeto, segundo as regras definidas paracada instrução.• Pára o processo de execução ao atingir a instrução FIMP (neste caso informa fim deexecução) ou algum erro de execução seja detectado (neste caso informa mensagemde erro).Critérios de Avaliação:• Implementação usando linguagem C ou C++.• Entrega de fontes e executável (em um disquete identificado ou e-mail).• Grupo de 02 alunos (máximo).• Valor do trabalho: 10.0 (25% da nota prática).• Data de Entrega: 30/06/2005.• Punições:– de 20% do valor do trabalho por dia de atraso.– de 1.75 por erro léxico não analisado corretamente.– de 1.5 por erro sintático não analisado corretamente.– de 1.25 por erro semântico não analisado corretamente.– de 1.0 por erro semântico não analisado corretamente.– de 0.5 do valor do trabalho para a entrega não conforme dos arquivos pedidos.– de 50% do valor do trabalho para o caso de não executar ou travar (após testeem 2 computadores, sendo um o do professor).– de 100% do valor do trabalho para o caso de cópias (mesmo de trabalhos desemestres anteriores).85

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!