UNIVERSIDADE DO VALE DO ITAJAÍ CENTRO DE CIÊNCIAS ...siaibib01.univali.br/pdf/Marcos Roberto...

86
UNIVERSIDADE DO VALE DO ITAJAÍ CENTRO DE CIÊNCIAS TECNOLÓGICAS DA TERRA E DO MAR CURSO DE CIÊNCIA DA COMPUTAÇÃO GERADOR DE CÓDIGO OBJETO PARA O HAPPY PORTUGOL Área de Compiladores por Marcos Roberto Fischer André Luís Alice Raabe, Dr. Orientador Itajaí (SC), junho de 2006

Transcript of UNIVERSIDADE DO VALE DO ITAJAÍ CENTRO DE CIÊNCIAS ...siaibib01.univali.br/pdf/Marcos Roberto...

UNIVERSIDADE DO VALE DO ITAJAÍ CENTRO DE CIÊNCIAS TECNOLÓGICAS DA TERRA E DO MAR

CURSO DE CIÊNCIA DA COMPUTAÇÃO

GERADOR DE CÓDIGO OBJETO PARA O HAPPY PORTUGOL

Área de Compiladores

por

Marcos Roberto Fischer

André Luís Alice Raabe, Dr. Orientador

Itajaí (SC), junho de 2006

UNIVERSIDADE DO VALE DO ITAJAÍ CENTRO DE CIÊNCIAS TECNOLÓGICAS DA TERRA E DO MAR

CURSO DE CIÊNCIA DA COMPUTAÇÃO

GERADOR DE CÓDIGO OBJETO PARA O HAPPY PORTUGOL

Área de Compiladores

por

Marcos Roberto Fischer Relatório apresentado à Banca Examinadora do Trabalho de Conclusão do Curso de Ciência da Computação para análise e aprovação. Orientador: André Luís Alice Raabe, Dr.

Itajaí (SC), junho de 2006

ii

SUMÁRIO

LISTA DE ABREVIATURAS..................................................................iv

LISTA DE FIGURAS.................................................................................v

LISTA DE TABELAS...............................................................................vi RESUMO...................................................................................................vii ABSTRACT..............................................................................................viii 1 INTRODUÇÃO ......................................................................................9 1.1 PROBLEMATIZAÇÃO................................................................................. 10 1.1.1 Formulação do Problema ............................................................................ 10 1.1.2 Solução Proposta .......................................................................................... 10 1.2 OBJETIVOS ................................................................................................... 11 1.2.1 Objetivo Geral.............................................................................................. 11 1.2.2 Objetivos Específicos.................................................................................... 11 1.3 METODOLOGIA........................................................................................... 12 1.4 ESTRUTURA DO TRABALHO ................................................................... 13

2 FUNDAMENTAÇÃO TEÓRICA ......................................................14 2.1 HAPPY PORTUGOL..................................................................................... 14 2.1.1 Modelo de Casos de Uso............................................................................... 15 2.1.2 Demais Funcionalidades .............................................................................. 17 2.1.3 Tecnologias utilizadas .................................................................................. 18 2.2 CONSTRUÇÃO DE COMPILADORES ...................................................... 21 2.2.1 Análise léxica ................................................................................................ 21 2.2.2 Análise sintática ........................................................................................... 22 2.2.3 Análise semântica......................................................................................... 22 2.3 GERAÇÃO DE CÓDIGO.............................................................................. 23 2.3.1 Código de triplo endereço ............................................................................ 24 2.3.2 Otimização.................................................................................................... 27 2.3.3 Código Objeto ou Código Final ................................................................... 28 2.4 WIN32ASM..................................................................................................... 35 2.4.1 Definições gerais........................................................................................... 35 2.4.2 Alocação de memória ................................................................................... 35 2.4.3 Registradores................................................................................................ 36 2.4.4 Principais comandos ou mnemônicos.......................................................... 36 2.4.5 Visão geral de um programa Win32asm..................................................... 39 2.4.6 Uso de operandos ......................................................................................... 42 2.4.7 Tipos de operandos possíveis ....................................................................... 42 2.4.8 Chamada a procedimentos .......................................................................... 43 2.5 MONTADORES............................................................................................. 44

iii

2.5.1 Comandos de alto nível ................................................................................ 45 2.5.2 Exemplos de programas............................................................................... 47

3 PROJETO.............................................................................................49 3.1.1 Ações semânticas para geração de código para declaração de variáveis .. 50 3.1.2 Ações semânticas para geração de código para atribuições e expressões . 50 3.1.3 Ações semânticas para geração de código para desvios condicionais simples .................................................................................................................... 51 3.1.4 Ações semânticas para geração de código para desvios condicionais compostos................................................................................................................ 51 3.1.5 Ações semânticas para geração de código para laço de repetição ............. 52 3.1.6 Outros comandos disponíveis no Happy Portugol ..................................... 52

4 DESENVOLVIMENTO ......................................................................54 4.1 CORREÇÃO DE BUGS DO HAPPY PORTUGOL .................................... 54 4.2 IMPLEMENTAÇÃO DO GERADOR DE CÓDIGO .................................. 54 4.2.1 Ações semânticas para geração de código para declaração de variáveis .. 55 4.2.2 Ações semânticas para geração de código para atribuições e expressões . 56 4.2.3 Ações semânticas para geração de código para leitura de dados do console 57 4.2.4 Ações semânticas para geração de código para escrita de dados no console 58 4.2.5 Ações semânticas para geração de código para desvios condicionais simples .................................................................................................................... 59 4.2.6 Ações semânticas para geração de código para laço de repetição “para” 59 4.3 INTEGRAÇÃO DO WIN32ASM AO HAPPY PORTUGOL...................... 60 4.4 TESTES........................................................................................................... 63 4.5 LIMITAÇÕES E RESTRIÇÕES .................................................................. 67

5 Conclusões.............................................................................................68 5.1 TRABALHOS FUTUROS ............................................................................. 68

REFERÊNCIAS BIBLIOGRÁFICAS ...................................................70

iv

LISTA DE ABREVIATURAS

API Application Programming Interface ASA Árvore Sintática Abstrata ASCII American Standard Code for Information Interchange C3E Código de Triplo Endereço EXE Executável GLC Gramática Livre de Contexto TCC Trabalho de Conclusão de Curso UNIVALI Universidade do Vale do Itajaí YACC Yet Another Compiler-Compiler

v

LISTA DE FIGURAS

Figura 1. Passos para transformação de um código fonte em executável ........................................10 Figura 2. Interface que o Happy Portugol disponibiliza para a criação dos programas....................15 Figura 3. Modelo de casos de uso da ferramenta Happy Portugol...................................................16 Figura 4. Estrutura de um programa portugol.................................................................................19 Figura 5. Esquema de geração de código alvo................................................................................21 Figura 6. Árvore sintática da expressão aritmética A[index] = 4 + 2 ..............................................22 Figura 7. Árvore sintática abstrata da expressão aritmética 2*a + (b-3) ..........................................24 Figura 8. Representação da expressão em uma linguagem de alto nível 2 * a +(b-3) em C3E.........25 Figura 9. Seqüência de instruções para chamada de rotinas............................................................26 Figura 10. Declaração de estrutura na linguagem C .......................................................................30 Figura 11. Ilustração da alocação da variável x em memória..........................................................31 Figura 12. Organização típica de código para um comando if-else .................................................32 Figura 13. Organização típica de código para um comando while ..................................................33 Figura 14. Esqueleto de um programa Win32asm ..........................................................................39 Figura 15. Estrutura da seção .CODE ............................................................................................41 Figura 16. Exemplo da seção .CODE.............................................................................................41 Figura 17. Exemplo do uso dos operandos.....................................................................................42 Figura 18. Exemplos de uso dos tipos de operandos possíveis nas operações .................................43 Figura 19. Chamada de procedimento usado call ...........................................................................43 Figura 20. Chamada de procedimento usado invoke .......................................................................43 Figura 21. Forma de criação de procedimentos internos.................................................................44 Figura 22. Exemplo de use de .IF, .ELSE e .ELSEIF .....................................................................45 Figura 23. Exemplo de use de.REPEAT ........................................................................................46 Figura 24. Exemplo de use de .WHILE..........................................................................................46 Figura 25. Programa Portugol que exibe o maior de cinco números digitados pelo usuário ............47 Figura 26. Programa Win32asm que exibe o maior de cinco números digitados pelo usuário.........48 Figura 27. Gramática resumida do Happy Portugol........................................................................50 Figura 28. Estrutura da tabela de símbolos.....................................................................................55 Figura 29. Procedimento para a geração de código para declaração de variáveis. ...........................56 Figura 30. Nova interface que o Happy Portugol disponibiliza para a criação dos programas.........62 Figura 31. Implementações realizadas sobre a ferramenta Happy Portugol ....................................63

vi

LISTA DE TABELAS

Tabela 1. Representação em C3E da instrução em alto nível z = (1 + c) * (a / -c) por triplos..........27 Tabela 2. Representação em C3E da instrução em alto nível z = (1 + c) * (a / -c) por quádruplos...27 Tabela 3. Principais mnemônicos utilizados na programação Win32asm (Parte I)..........................37 Tabela 4. Principais mnemônicos utilizados na programação Win32asm (Parte II) ........................38 Tabela 5. Tipos de dados que podem ser utilizados na identificação das variáveis..........................41 Tabela 6. Comparação entre os montadores ...................................................................................44 Tabela 7. Operadores que podem ser usados nas condições das instruções.....................................46 Tabela 8. Alguns procedimentos ou macros existentes nas bibliotecas ...........................................47 Tabela 9. Ações semânticas para geração de código para declaração de variáveis ..........................50 Tabela 10. Ações semânticas para geração de código para atribuições e expressões.......................50 Tabela 11. Ações semânticas para geração de código para desvio condicional simples ..................51 Tabela 12. Ações semânticas para geração de código para desvio condicional composto ...............51 Tabela 13. Ações semânticas para geração de código para laço de repetição ..................................52 Tabela 14. Tradução dos principais comandos portugol para Win32asm........................................53 Tabela 15. Ações semânticas para geração de código para declaração de variáveis ........................55 Tabela 16. Ações semânticas para geração de código para atribuições e expressões.......................57 Tabela 17. Ações semânticas para geração de código para leitura de dados do console ..................58 Tabela 18. Ações semânticas para geração de código para escrita de dados no console ..................58 Tabela 19. Ações semânticas para geração de código para desvio condicional simples ..................59 Tabela 20. Ações semânticas para geração de código para laço de repetição “para” .......................60 Tabela 21. Exemplo de programa que escreve“helloworld” na tela. ...............................................64 Tabela 22. Exemplo de programa calcula a média e diz se o aluno foi aprovado ou não.................65 Tabela 23. Exemplo de programa que informa o maior de cinco valores digitados pelo aluno........66

vii

RESUMO

FISCHER, Marcos Roberto. Gerador de Código Objeto para o Happy Portugol. Itajaí, 2006 86 f. Trabalho de Conclusão de Curso (Graduação em Ciência da Computação)–Centro de Ciências Tecnológicas da Terra e do Mar, Universidade do Vale do Itajaí, Itajaí, 2006. O desenvolvimento da lógica algorítmica e a aquisição de familiaridade com ambientes de programação são exigidos dos acadêmicos dos cursos de Ciência da Computação já nos primeiros semestres de estudo, pois são habilidades fundamentais ao longo do curso. Para auxiliar neste processo, normalmente utiliza-se alguma ferramenta de auxílio a aprendizagem de programação. No caso da disciplina de Algoritmos e Programação do Curso de Ciência da Computação da Univali, os alunos utilizam atualmente a ferramenta Happy Portugol que disponibiliza uma linguagem com comandos em português para criação de algoritmos. Porém, esta ferramenta internamente gera um código C e é dependente do compilador Turbo C 1.01 para gerar código objeto e para criar um programa executável. Desta forma, este projeto visa eliminar esta dependência desenvolvendo um gerador de código objeto para o Happy Portugol compatível com a plataforma Windows 32 bits. Para isso o código será gerado em uma linguagem de montagem denominada Win32asm, tornando-o independente de outro compilador e possibilitando a posterior criação de um programa executável. Neste sentido este trabalho está focalizado no estudo e implementação das técnicas para geração de código objeto. Palavras-chave: Compiladores. Gerador de Código Objeto. Win32asm.

viii

ABSTRACT

The development of the algorithmic logic and the acquisition of familiarity with programming

environments are demanded of the academics of the Computer Science`s Courses already in the

first semesters of study, therefore they are basic abilities to the long of the course. To assist in this

process, normally some tool is used to aid in the programming learning. In the case of the

discipline of Algorithms and Programming of the Computer Science`s Course of the Univali, the

pupils currently use the tool Happy Portugol that supplies a language with commands in

Portuguese for creation of algorithms. However, this tool internally generates C code and is

dependent of the Turbo C 1.01 compiler to generate object code and to create an executable file.

This project aims to eliminate this dependence developing a object code generator for the Happy

Portugol compatible with the Windows 32 bits platform. This code will be generated in a Assembler

language called Win32asm becoming it independent of another compiler and making possible the

latter creation of one executable file. In this direction this work is focused in the study and

implementation of the techniques for code object generation.

Keywords: Compilers. Code Object Generator. Win32asm.

1 INTRODUÇÃO

Geralmente nos cursos de Ciência da Computação, os alunos iniciam a aprendizagem de

lógica de programação utilizando para isso alguma ferramenta. Esta ferramenta pode ser um

compilador comercial ou versões simplificadas voltadas ao público iniciante. O objetivo é permitir

aos alunos criarem programas simples a fim de desenvolver a lógica algorítmica e se habituarem a

um ambiente de programação.

Um algoritmo é a lógica de um programa de computador. A representação desta lógica em

uma linguagem de programação (composta por instruções e comandos pré-definidos) cria um

programa. Segundo Price e Toscani (2001), cronologicamente, as linguagens podem ser

classificadas em cincos gerações: (1ª) linguagens de máquina, (2ª) linguagens simbólicas

(Assembly), (3ª) linguagens orientadas ao usuário, (4ª) linguagens orientadas a aplicação e (5ª)

linguagens de conhecimento. As linguagens de 1ª e 2ª gerações são consideradas de baixo nível; as

demais linguagens são classificadas como de alto nível. As linguagens de programação mais

utilizadas são as de alto nível por serem consideradas mais próximas das linguagens naturais.

Segundo Aho, Sethi e Ullman (1995), para que um algoritmo codificado em uma linguagem

de programação possa ser transformado em um programa executável, é necessário um compilador.

Compiladores são programas que recebem um código fonte escrito em uma determinada linguagem

de programação, analisam esse código fonte e geram um código objeto, para que seja interpretado e

executado pelos computadores. Para este código objeto ser executado ele ainda passará por um

montador e um linker.

Um montador basicamente traduz o código fonte Assembly para linguagem de máquina na

qual o programa será executado. A principal característica deste tipo de tradutor é que para cada

instrução Assembly será gerada uma única instrução de máquina.

O linker ou ligador é responsável por coletar programas traduzidos separadamente como

bibliotecas e ligá-los em um único módulo, normalmente denominado módulo absoluto de carga ou

simplesmente programa executável (o EXE). A Figura 1 ilustra a seqüência de passos para um

código fonte se tornar executável.

10

Figura 1. Passos para transformação de um código fonte em executável

1.1 PROBLEMATIZAÇÃO

1.1.1 Formulação do Problema

Na disciplina de Algoritmos e Programação do Curso de Ciência da Computação da Univali,

os alunos utilizam atualmente a ferramenta Happy Portugol para aprender noções básicas de

construção de programas. O Happy Portugol oferece a possibilidade de utilizar uma linguagem com

comandos em português para criação de algoritmos (chamado de portugol), porém esta ferramenta

utiliza um gerador de código externo, criando uma dependência do compilador Turbo C 1.01.

1.1.2 Solução Proposta

Este trabalho propõe construir um gerador de código objeto para o Happy Portugol

tornando-o independente de um outro compilador.

Pretende-se que o código objeto gerado seja compatível com as novas versões do sistema

operacional Microsoft Windows 32 Bits (posteriores ao Windows 95), sendo necessário para isso

11

utilizar o conjunto de instruções denominado Win32asm. A geração do executável será feita através

de uma ferramenta externa (montador). O objeto de interesse deste trabalho é gerar o código

Win32asm para que este seja convertido em um executável por esta ferramenta.

A realização deste trabalho também se justifica em nível de Trabalho de Conclusão de Curso

para o Curso de Ciência da Computação, pois trata do desenvolvimento de um projeto

computacional que faz uso de várias tecnologias, conceitos e teorias relevantes a essa área, e por ser

um trabalho precursor em geração de código Win32asm. Além disso, com o aprendizado e domínio

desta linguagem está se contribuindo para a inclusão deste tópico como conteúdo de ensino para a

disciplina de Compiladores, e possibilitando aos alunos de Algoritmos uma alternativa mais rica em

recursos para apoiar a aprendizagem da lógica de programação.

Cabe salientar que não é objeto deste trabalho realizar a análise de trabalhos similares que

apóiam a aprendizagem de algoritmos e programação, pois o foco está na aplicação de uma

tecnologia (Win32asm) que ainda não possui referências em projetos de natureza acadêmica.

1.2 OBJETIVOS

1.2.1 Objetivo Geral

O objetivo geral deste trabalho é construir um gerador de código objeto Win32asm para o

Happy Portugol.

1.2.2 Objetivos Específicos

Os objetivos específicos deste trabalho são:

• Compreender o funcionamento de um gerador de código;

• Delimitar o subconjunto da linguagem portugol que será implementado;

• Conhecer e utilizar o conjunto de instruções Win32asm para implementar o gerador de

código utilizando o compilador C++ Builder;

• Selecionar uma ferramenta para montagem e link-edição do programa executável; e

• Realizar testes de funcionamento do gerador de código integrado ao Happy Portugol.

12

1.3 Metodologia

A metodologia para a realização deste trabalho consiste em duas grandes etapas: (i) Estudo;

e (ii) Projeto para tradução.

A etapa de estudo consiste em estudar e entender o funcionamento dos geradores de código

e demais tecnologias envolvidas, a fim de adquirir o conhecimento necessário para possibilitar a

conclusão do trabalho. Nesta etapa realizaram-se os seguintes estudos:

• Happy Portugol: uma vez que a geração de código objeto é para a ferramenta Happy

Portugol, realizou-se um estudo a fim de conhecer o funcionamento da mesma;

• Construção de compiladores: buscou-se mostrar noções gerais da construção e

funcionamento dos compiladores;

• Geração de código intermediário: sendo esta uma etapa que pode existir num

compilador, realizou-se um estudo sobre a geração de código intermediário;

• Código objeto ou código final: tendo em vista que a proposta deste projeto é a geração

de código objeto para o Happy Portugol, buscou-se mostrar os procedimentos

necessários para a geração de código objeto ou código final;

• Win32asm: como o código objeto gerado será Win32asm, foram realizados estudos que

abrangem seu conceito, uso de registradores, conjunto de instruções e outros aspectos

pertinentes; e

• Definição do montador: sendo necessário para a geração do EXE o uso de um montador,

foi definido o montador e estudou-se as funcionalidades que este dispõe.

A etapa de projeto de tradução consiste em mostrar como os comandos em Portugol serão

traduzidos para os comandos Win32asm. Nesta etapa, foram realizadas as seguintes tarefas:

• Construção da tabela de tradução: esta tabela mostra como os comandos escritos em

Portugol serão traduzidos para Win32asm;

• Apresentação de ações semânticas para geração de código: as principais ações

semânticas que permitirão ao analisador sintático realizar a geração do código

Win32asm foram apresentadas; e

13

• Integração do Gerador de Código com o Happy Portugol: através da interface do sistema

foram realizadas chamadas ao gerador de código e o resultado do processo (código

Win32asm gerado) é exibido na tela. As chamadas do montador e do executável da

aplicação também tiveram que ser construídas.

1.4 Estrutura do trabalho

Este trabalho está estruturado em 5 capítulos: (i) Introdução; (ii) Fundamentação Teórica;

(iii) Projeto; (iv) Desenvolvimento; e (v) Conclusões.

O Capítulo 1 (Introdução) apresenta o contexto do projeto, descreve sucintamente o tema e

aborda a importância e os objetivos que se pretende alcançar com o trabalho.

O Capítulo 2 (Fundamentação Teórica) apresenta o estudo sobre geradores de código, uma

descrição da ferramenta Happy Portugol, descreve o funcionamento e o conjunto de instruções

Win32asm, além de apresentar o montador escolhido e seu funcionamento.

O Capítulo 3 (Projeto) apresenta de forma detalhada o projeto e suas funcionalidades,

mostrando exemplos de implementação e como será feita a geração do código Win32asm a partir do

portugol.

O Capítulo 4 (Desenvolvimento) apresenta como os comandos escritos em portugol foram

traduzidos para os comandos Win32asm e mostra como o Happy Portugol foi integrado com o

código Win32asm gerado.

O Capítulo 5 (Conclusões) apresenta o relacionamento entre o projeto e seus objetivos, além

de mostrar os resultados obtidos com a conclusão do projeto.

14

2 FUNDAMENTAÇÃO TEÓRICA

Existem várias ferramentas que podem ser usadas para criação de algoritmos. Um algoritmo

é a lógica de um programa e essa lógica é representada por uma linguagem de programação que

segundo Crespo (1998), é um conjunto de frases formado por um alfabeto e um conjunto de regras

que restrinjam as combinações possíveis das frases.

Aho, Sethi e Ullman (1995) afirmam que, para que um algoritmo escrito em uma linguagem

de programação possa ser transformado em um programa executável, é necessário um compilador.

Existem várias fases que constituem um compilador, cada uma com suas peculiaridades.

Como descrito no Capítulo 1, o intuito deste projeto é gerar código objeto para o Happy

Portugol. A geração de código objeto é uma fase ou passo de um compilador, e para ser possível seu

desenvolvimento é necessário ter o conhecimento também de suas outras fases. Assim nas seções

subseqüentes serão descritas informações necessárias ao desenvolvimento deste projeto. A Seção

2.1 mostra uma visão geral do Happy Portugol, onde também é feita uma descrição desta

ferramenta. A Seção 2.2 descreve uma noção geral da construção de compiladores. Na Seção 2.3

serão mostrados algoritmos de geração de código e na Seção 2.3.3 algoritmos de geração de código

objeto. A Seção 0 mostra o conjunto de instruções Win32asm que serão utilizadas na geração do

código objeto. E por fim, para que o código objeto seja transformado num executável, é necessária a

utilização de um montador, que será descrito na Seção 2.5.

2.1 Happy Portugol

Happy Portugol é uma ferramenta para auxiliar na aprendizagem da lógica de programação.

Esta ferramenta oferece a possibilidade de utilizar uma linguagem com comandos em português

estruturado para criação de algoritmos (chamado de portugol).

Após o usuário escrever seu programa em portugol, a ferramenta converte este Portugol para

Turbo C++, compila este código convertido e gera o programa executável com o compilador Turbo

C++ 1.01 da Borland.

Vale lembrar que não será removido o conversor de código Portugol para C++ existente hoje

na ferramenta Happy Portugol.

15

A interface do Happy Portugol foi escrita utilizando a linguagem C++ Builder da Borland, e

a Figura 2 mostra a interface disponibilizada para a criação dos programas.

Figura 2. Interface que o Happy Portugol disponibiliza para a criação dos programas

2.1.1 Modelo de Casos de Uso

Os casos de uso representam as funcionalidades do sistema. Os diagramas de casos de uso

especificam os relacionamentos entre os casos de uso e os atores e estes relacionamentos indicam a

existência de comunicação entre os atores e os casos de uso (PAULA FILHO, 2001).

A Figura 3 apresenta o modelo de casos de uso da ferramenta Happy Portugol.

16

Figura 3. Modelo de casos de uso da ferramenta Happy Portugol

2.1.1.1 UC01.01 - Cria Programa

Permite que um usuário crie um programa no Happy Portugol.

Cenários

Cria Programa {Principal}.

1. O Happy portugol apresenta a tela de desenvolvimento.

2. O usuário escreve o programa na linguagem portugol.

2.1.1.2 UC01.02 - Compila Programa

Permite que um usuário compile um programa que escreveu no Happy Portugol.

Cenários

Compila Programa {Principal}.

1. O usuário solicita ao Happy Portugol a conversão para C++.

17

2. O Sistema faz a análise léxica, sintática e semântica e converte o programa escrito em

portugol para C++.

Erros {Exceção}.

1. Caso no passo 2 do cenário Compila Programa, o Happy Portugol encontre erros no

programa portugol, ele mostrará mensagens de erros para o usuário e não fará a conversão para

C++.

2.1.1.3 UC01.03 - Testa Programa

Permite que um usuário teste o programa que escreveu no Happy Portugol.

Cenários

Testa Programa {Principal}.

1. O usuário solicita ao Happy Portugol o teste do programa convertido.

2. O Sistema verifica se o programa escrito em portugol foi convertido para C++ e executa o

programa convertido.

Nao convertido {Exceção}.

1. Caso no passo 2 do cenário Testa Programa, o Happy Portugol verificar que o programa

escrito em portugol não foi convertido para C++, ele mostrará uma mensagem de erro dizendo: "É

necessário converter para C++ antes de testar" e não executará o programa.

2.1.2 Demais Funcionalidades

Além das funcionalidades para criar, compilar e testar os programas, a interface do Happy

Portugol oferece outras funcionalidades em sua barra de ferramentas, conforme segue:

• Novo – cria um novo programa.

• Abrir – abre um programa salvo anteriormente.

• Salvar – salva o programa atual.

• Imprimir – imprime o programa atual.

18

• Desfazer – desfaz uma digitação.

• Refazer – refaz uma digitação que foi desfeita.

• Copiar – copia um texto selecionado para área de transferência.

• Colar – cola um texto que estava na área de transferência.

Além da barra de ferramentas o Happy Portugol disponibiliza também um menu com as

opções:

• Arquivo – opções Novo, Abrir, Salvar, Salvar Como, Imprimir e Sair.

• Editar – opções Desfazer, Refazer, Copiar e Colar.

• Testar – opções Converter e Executar.

• Ajuda – opções Sintaxe do Portugol F1, Limitações e Sobre.

2.1.3 Tecnologias utilizadas

Para ser possível escrever um programa em Happy Portugol foi definida uma gramática que

determina quais símbolos e de que forma estes podem ser agrupados ou produzidos, para que

posteriormente possam ser reconhecidos pelos analisadores léxico, sintático e semântico. Assim, um

programa em Happy Portugol deve possuir a estrutura conforme a Figura 4. A definição completa

consta no Anexo IV.

19

programa <nome_do_programa> Declarações <definição_de_constantes> <declarações_de_variáveis> inicio <instruções_do_programa> fim Por exemplo: programa soma declarações inteiro a, b, soma inicio a <- 5 b <- 4 soma <- a+b escreva (soma) fim Ou programa <nome_do_programa> I nicio <instruções_do_programa> fim por exemplo: programa ola inicio escreva ("olá") fim

Figura 4. Estrutura de um programa portugol

Quando o usuário solicita ao Happy Portugol para fazer a conversão do programa portugol

para o Turbo C++, o programa portugol é analisado. Nesta etapa são, caso hajam, identificados os

erros presentes no programa. Esta etapa é subdividida em três fases de análise: Léxica, Sintática e

Semântica, cada uma delas tratando de um aspecto do programa fonte.

Na análise léxica são vistos os aspectos léxicos do programa. Aqui entra em cena o

analisador léxico, também conhecido como scanner. Ele lê o texto do programa e o separa em

tokens, que são os símbolos básicos de uma linguagem de programação. Eles representam palavras

reservadas, literais numéricos e de texto, identificadores, operadores e pontuações.

Para a análise léxica o Happy Portugol utiliza o LEX. O LEX é um dos mais tradicionais

geradores de analisadores léxicos, (Gesser, 2003). Trata-se originalmente de um programa UNIX

que lê uma especificação léxica formada de expressões regulares, que neste caso é o programa

escrito em portugol, e gera um código fonte de análise léxica na linguagem C.

Na análise sintática é vista a estrutura sintática do programa, com base na definição da

gramática. São lidos os tokens gerados pelo analisador léxico e é feito o agrupando de acordo com a

estrutura sintática da linguagem. O resultado desta análise é chamado de árvore de derivação.

20

Para a análise sintática o Happy Portugol utiliza o YACC (Yet Another Compiler-

Compiler). O YACC é utilizado em parceria com o LEX e é um dos mais utilizados geradores de

analisadores sintáticos. (Crespo, 1998). Recebe como entrada uma especificação das características

sintáticas da linguagem definidas formalmente através de uma gramática livre de contexto (GLC),

com ações semânticas inseridas, e como o LEX, gera um programa fonte na linguagem C para a

análise sintática. A gramática do portugol com os tokens utilizados pela ferramenta Happy Portugol

encontra-se no ANEXO II.

Como saída o YACC gera uma tabela de análise sintática LALR, uma rotina de controle que

executa em cima da tabela, e as ações semânticas que foram especificadas durante a definição da

linguagem no arquivo de entrada.

O analisador é gerado para trabalhar em conjunto com um programa de análise léxica gerada

pelo LEX, o que permite uma fácil integração entre os analisadores léxico (gerado pelo LEX),

sintático e semântico (gerados pelo YACC).

Na análise semântica são vistos aspectos semânticos do programa, levando-se em

consideração o sentido do programa. São feitas análises como a verificação de tipos, verificação de

fluxo de controle, verificação de unicidade e verificações relacionadas aos nomes de subrotinas. É

através da análise semântica que são extraídas as informações que possibilitam a posterior geração

de código.

No fim de cada produção pode ser escrita uma ação semântica a ser tomada quando uma

produção for identificada pelo analisador sintático. A ação é escrita diretamente na linguagem

utilizada para desenvolver o Happy, apenas a chamada é feita pelo analisador.

Apesar de o Happy Portugol fornecer uma linguagem com comandos em português

estruturado, o que torna mais fácil a criação de algoritmos, ele tem a limitação de depender de um

compilador externo para poder gerar o programa executável. A geração do código objeto torna o

Happy Portugol independente de outro compilador para a geração de um programa executável e traz

mais uma opção para os alunos verem como os comandos escritos em uma linguagem de alto nível

se transformam em uma linguagem de baixo nível, mas próxima ao que os computadores realmente

entendem ou executam.

21

2.2 Construção de Compiladores

Compiladores são programas que fazem tradução de uma linguagem para outra. Um

compilador recebe um código fonte escrito em uma determinada linguagem de programação,

analisa esse código fonte e gera um código objeto ou código alvo, para que seja interpretado e

executado pelos computadores, (Aho, Sethi e Ullman, 1995). Esquematicamente este processo pode

ser visto conforme a Figura 5.

Figura 5. Esquema de geração de código alvo

Fonte: Adaptado de Louden (2004).

Um compilador é internamente constituído por vários passos ou fases. Essas fases podem ser

entendidas como partes separadas do compilador, e também podem ser implementadas

separadamente, embora na prática elas sejam normalmente agrupadas. Estas fases são a análise

léxica, análise sintática, análise semântica, geração de código intermediário, otimização e geração

de código alvo.

Estas etapas serão explicadas brevemente para uma melhor contextualização da etapa de

geração de código objeto, que é o foco deste projeto.

2.2.1 Análise léxica

Nesta etapa é feita a leitura do código-fonte. Segundo Louden (2004), geralmente o código

fonte é fornecido como uma seqüência de caracteres e essa seqüência é organizada em unidades

significativas chamadas marcas, que podem ser entendidas como palavras em uma linguagem

natural.

Por exemplo, considerando a linha de código a seguir, que poderia pertencer a um programa

escrito em C:

A[index] = 4 + 2

Este código contém 12 caracteres diferentes de espaço, porém somente 8 marcas;

22

A identificador

[ colchete à esquerda

index identificador

] colchete à direita

= atribuição

4 número inteiro

+ sinal de adição

2 número inteiro

Cada marca pode ser constituída de um ou mais caracteres, que são agrupados em unidades

para prosseguir o processamento.

2.2.2 Análise sintática

O analisador sintático recebe do analisador léxico o código fonte na forma de marcas e faz a

análise sintática que consiste em determinar a estrutura do programa. A análise sintática identifica

os elementos estruturais do código fonte e seus relacionamentos e tem seus resultados geralmente

representados na forma de uma árvore de análise sintática ou uma árvore sintática, (Louden, 2004).

A Figura 6 mostra a árvore sintática da expressão A[index] = 4 + 2.

Figura 6. Árvore sintática da expressão aritmética A[index] = 4 + 2

2.2.3 Análise semântica

Segundo Aho, Sethi e Ullman (1995), nesta fase, baseado na estrutura hierárquica

determinada pela análise sintática, é verificado se o código fonte segue as convenções estáticas e

23

semânticas da linguagem fonte e são capturadas informações para a fase subseqüente de geração de

código. Esta verificação é chamada de verificação estática e inclui:

• Verificação de tipos: relata um erro se um operador for aplicado a um operando

incompatível, por exemplo, a soma de um número com um caracter;

• Verificação de fluxo de controle: enunciados que fazem o fluxo de controle deixar uma

construção devem ter um local para transferir o controle, por exemplo, um enunciado

break em C deixa o while, for ou switch envolvente mais interno, um erro será exibido

se existir um break e não estiver dentro de um while, for ou switch;

• Verificação de unicidade: há situações em que um objeto deve ser definido uma única

vez, por exemplo, em pascal, um identificador precisa ser declarado uma única vez; e

• Verificações relacionadas aos nomes: algumas vezes, o mesmo nome precisa aparecer

duas ou mais vezes, por exemplo, em Ada, um laço ou bloco precisa ter um nome que

apareça no início e no final da construção.

2.3 Geração de Código

Segundo Louden (2004), a geração de código é a parte mais complexa de um compilador,

porque não depende somente das características da linguagem do programa-fonte, mas também de

detalhes da arquitetura alvo, da estrutura do ambiente de execução e do sistema operacional alvo.

Em virtude desta complexidade de geração de código, normalmente um compilador quebra

essa fase em vários passos, que utilizam diversas estruturas de dados intermediárias e algum tipo de

código abstrato, denominado código intermediário. Conforme Price e Toscani (2001), a grande

diferença entre o código intermediário e o código objeto final é que o código intermediário não

especifica detalhes da máquina alvo, como quais endereços da memória serão referenciados ou

quais registradores serão usados.

Segundo Louden (2004), o código intermediário é muito útil quando o objetivo do

compilador é produzir um código alvo bastante eficiente e quando se deseja facilitar o

redirecionamento de um compilador para outras máquinas alvo, pois se o código intermediário for

independente da máquina alvo, para gerar o código objeto final para uma máquina alvo diferente,

basta reescrever o tradutor do código intermediário para o código alvo, o que geralmente é mais

fácil que reescrever o gerador de código completo.

24

Conforme Crespo (1998), há várias formas para representação de código intermediário,

porém as mais utilizadas na implementação dos compiladores são a notação em árvore sintática

abstrata, a notação pós-fixada e o código de triplo endereço. Existem ainda outras representações

como listas, grafo de sintaxe e P-código.

Uma árvore sintática abstrata (ASA) representa a estrutura hierárquica natural de um código

fonte. A árvore sintática é gerada a partir da determinação dos elementos estruturais do programa

fonte e seus relacionamentos, feitos pelo analisador sintático. Essa árvore é um recurso bastante útil

para visualizar a sintaxe de um programa ou elemento de programa. Considerando a expressão

aritmética 2*a + (b-3), a árvore sintática para a expressão ficaria da forma que mostra a Figura 7.

Figura 7. Árvore sintática abstrata da expressão aritmética 2*a + (b-3)

Fonte: Adaptado de Aho, Sethi e Ullman (1995).

A notação pós-fixada é uma representação linear de uma árvore sintática e é particularmente

adaptada para representação e cálculo de expressões. Na notação pós-fixada os operandos são

indicados primeiro e o operador é indicado em ultimo lugar, por exemplo, a expressão 2*a + (b-3),

em notação pós-fixada é representada como 2 a * + b 3 -.

2.3.1 Código de triplo endereço

Na representação por Código de Triplo Endereço (C3E), cada instrução faz referência, no

máximo, a três variáveis ou endereços de memória. Segundo Crespo (1998), uma instrução

representada pelo C3E, formada por um operador e três operandos possui o formato NNN

oper(arg1, arg2, res), onde NNN representa o endereço abstrato da C3E, oper é um operador como

+,- ou cpy que indicaria cópia de literal ou variável, arg1 e arg2 são operandos que podem ser

endereços de variáveis ou literais e res constitui o resultado da instrução e pode ser um endereço de

uma variável ou um endereço de uma instrução C3E, mas não pode ser uma constante ou literal. Por

exemplo, considerando a instrução z = x + 1, em uma linguagem de programação de alto nível, na

notação C3E a mesma instrução poderia ser descrita por 100 +(x, 1, z).

25

Quando as instruções C3E utilizarem somente parte dos operandos, convenciona-se atribuir

aos operandos não utilizados o valor nulo, que pode ser representado por _. Por exemplo,

considerando a instrução z = - x , em uma linguagem de programação de alto nível, na notação C3E

a mesma instrução poderia ser descrita por 100 -(x, _, z).

Diz Louden (2004), que em várias situações o C3E requer que o compilador gere nomes

para variáveis temporárias, por exemplo, a expressão em uma linguagem de alto nível 2 * a +(b-3),

poderia ser representada em C3E como mostra a Figura 8.

t1 = 2 * a t2 = b – 3 t3 = t1 + t2

Figura 8. Representação da expressão em uma linguagem de alto nível 2 * a +(b-3) em C3E

A forma como essas variáveis temporárias devem ser alocadas na memória não são

especificadas por esse código, em geral, elas são atribuídas a registradores ou podem também ser

mantidas em registros temporários.

Conforme Crespo (1998), as instruções que existem no C3E são semelhantes as existentes

no código alvo. Algumas instruções típicas do C3E necessárias em um código intermediário são:

• Instruções de Operadores Binários e Unários: operadores aritméticos usuais (soma,

subtração, multiplicação e divisão), o operador negação aritmética e operadores

relacionais (igual, maior, menor);

• Instruções de Cópia de Literal ou Variável: Esta instrução é necessária para representar

em C3E instruções da linguagem de alto nível do tipo x = a ou X = 1. Vale lembrar que

esta instrução não é estritamente necessária, uma vez que pode ser substituída por uma

operação com operando nulo. Por exemplo, a instrução de cópia do valor da variável x à

variável z, cpy(x, _, z), é equivalente a instrução C3E +(x, 0, z). A instrução de cópia de

literal ou variável é disponibilizada por razões de otimização de código;

• Instruções de Acesso a Elementos de Uma Tabela: Essas instruções de acesso indexado

a elementos de uma tabela possuem a forma idxr(x, i, z) e idxl(x, i, z) e correspondem,

respectivamente, às atribuições z = x[i] e z[i] = x. O índice i determina o elemento

relativo à primeira posição da tabela;

26

• Instruções de Salto: Instruções de salto condicional tem o formato ifoper(x, y, l) no qual

ifoper é um operador relacional e para saltos incondicionais o C3E disponibiliza a

instrução jmp(_, _, l) que implementa o salto incondicional para a instrução de endereço

l;

• Instruções de Chamada e Retorno de Rotinas: Uma instrução de chamada a rotina numa

linguagem de alto nível P(A1,A2,...,An) é traduzida para C3E conforme a seqüência de

instruções mostradas na Figura 9:

param(A1, _, _) param(A2, _, _) ... param(An, _, _) call(P, _, _)

Figura 9. Seqüência de instruções para chamada de rotinas

O retorno de rotinas é codificado pela instrução ret(val, _, _), onde val indica o valor

de retorno; e

• Instrução de Referenciação e de Dereferenciação de Ponteiros: No C3E são

disponibilizadas três instruções de referenciação e de dereferenciação de ponteiros. Elas

possuem a forma addr(x, _, z), locr(x, _, z) e locl(x, _, z) e correspondem,

respectivamente, às instruções da linguagem de alto nível C z = &x, z = *x e *z = x.

A estratégia de implementação de um C3E depende da representação explícita, ou não, das

variáveis temporárias. No primeiro caso a implementação é feita usando quádruplos e a segunda

usando triplos. A vantagem dos quádruplos é a facilidade na implementação e na otimização de

código e a dos triplos é a poupança de memória, tanto no número de campos nos registros C3E

quanto na representação de variáveis temporárias.

Nos quádruplos o operador e os três operandos são campos de registros de C3E, já nos

triplos, a diferença é que o operando res não é campo dos registros do C3E. Nos triplos não há

criação explícita de variáveis temporárias. Sempre que uma instrução C3E tiver um operando arg

com referência a uma variável temporária, no seu lugar é indicado o endereço da instrução C3E

onde o valor foi calculado.

Considerando a instrução em alto nível z = (1 + c) * (a / -c), sua representação por triplos é

mostrada na Tabela 1.

27

Tabela 1. Representação em C3E da instrução em alto nível z = (1 + c) * (a / -c) por triplos

Triplos Endereço oper arg1 arg2 100 + 1 c 101 uminus c 102 / a (101) 103 * (100) (102)

104 Cpy (103) z

Fonte: Crespo (1998).

Considerando a mesma instrução em alto nível z = (1 + c) * (a / -c), sua representação por

quádruplos é mostrada na Tabela 2.

Tabela 2. Representação em C3E da instrução em alto nível z = (1 + c) * (a / -c) por quádruplos

Quádruplos Endereço oper arg1 arg2 res 100 + 1 c t1 101 uminus c t2 102 / a t2 T3 103 * t1 t3 t4 104 cpy t4 z

Fonte: Crespo (1998).

2.3.2 Otimização

Segundo Crespo (1998), o processo de geração de código tende a produzir instruções:

• Inacessíveis: Instruções que nunca serão executadas;

• Redundantes: Instruções em que a execução não altera o estado da aplicação;

• Lentas: Instruções para as quais existem blocos de código ou comandos equivalentes que

são mais rápidos e produzem os mesmos resultados; e

• Pesadas: Instruções para as quais existem blocos de código ou comandos equivalentes

que ocupam menos recursos de memória RAM e utilizam menos registradores do

processador.

28

Crespo (1998), diz ainda que etapa de otimização tem por objetivo substituir o código

produzido por outro código equivalente, porém, que tenha maior velocidade e ocupe menor espaço

de memória. Existem diversas técnicas para a otimização de código, entretanto, dificilmente se

consegue atingir um código ótimo no sentido matemático, o que acontece na maioria das vezes é

uma melhoria de código. Vale ressaltar que um programador dificilmente irá ou deva utilizar todas

as técnicas de otimização de código, mas sim analisar e julgar que técnicas melhor se aplicariam a

uma determinada situação para resultar em melhoras significativas no código com menor impacto

na complexidade do gerador.

2.3.3 Código Objeto ou Código Final

Conforme Crespo (1998), a etapa de geração de código objeto ou código final recebe como

entrada uma representação intermediária do programa fonte ou o próprio programa fonte e produz

como saída um programa semanticamente equivalente e executável numa máquina alvo. Esta etapa

é estruturada em três componentes:

1. Determinação das estruturas de dados: O código intermediário pode possuir várias

estruturas de dados, tais como, estruturas do programa fonte e variáveis temporárias,

assim, nesta fase são atribuídas as estruturas de dados do código intermediário os

recursos da máquina alvo, como registradores, localizações de memória e localizações

de pilha do processador;

2. Seleção das instruções da máquina alvo: São selecionadas as instruções, de linguagem de

máquina, que transcrevem a representação intermediária para a linguagem da máquina

alvo; e

3. Criação do ambiente de execução: Na geração de código final também são identificadas

as estruturas de dados necessárias para a formação do ambiente de execução do

programa.

2.3.3.1 Geração de código para variáveis e referências a estrutura de dados

Há diversas situações que exigem cálculos de endereços para localizar um determinado

endereço, e esses cálculos devem ser expressos diretamente. Esses cálculos ocorrem na indexação

de matrizes, nos campos de registros e nas referências de ponteiros.

29

2.3.3.2 Referências de matrizes

Uma referência de matriz requer a indexação de uma variável de matriz por uma expressão

para chegar a uma referência ou valor de um único elemento da matriz. Por exemplo, no código em

alto nível da linguagem C:

int a[SIZE]; int i, j;

a[i + 1] = a[j * 2] + 3;

Nesta atribuição, a indexação de a pela expressão i + j produz o endereço alvo da atribuição,

e a indexação de a pela expressão j * 2 produz o valor no endereço calculado pelo tipo de endereço

de a, neste caso int. Como as matrizes são armazenadas seqüencialmente na memória, cada

endereço deve ser calculado a partir do endereço base de a e de um deslocamento que dependa

linearmente do valor do índice. Quando se deseja obter o valor em vez do endereço, um passo

adicional precisa ser feito para capturar o valor no endereço calculado.

O deslocamento é calculado a partir do valor do índice, da seguinte forma: inicialmente,

deve-se ajustar o valor do índice caso o intervalo de índices não comece por zero. Depois, o valor

do índice ajustado deve ser multiplicado por um fator de escala, que é igual ao tamanho de cada

elemento da matriz na memória e por ultimo, o índice resultante é somado ao endereço de base para

obter o endereço final do elemento da matriz. Por exemplo, no caso da linguagem C, onde o nome

de uma matriz denota seu endereço base, o endereço de referência da matriz a[i + 1] é: a + (i + 1) *

sizeof(int).

De maneira geral, o endereço de um elemento de matriz a[t] em qualquer linguagem é

endereço_base(a) + (t – limite_inferior(a)) * tamanho_elemento(a).

2.3.3.3 Referências de matrizes multidimensionais

Um fator complicador para o cálculo de endereços de matrizes é a existência, na maioria das

linguagens, de matrizes com múltiplas dimensões. Por exemplo, na linguagem C, uma matriz com

duas dimensões e tamanhos diferentes de índices pode ser declarada como: int a[15][10].

30

Essas matrizes podem ser indexadas de forma parcial, levando à matrizes de dimensões

menores, por exemplo, com a declaração acima de a em C, a expressão a[i] indexa parcialmente a,

levando a uma matriz unidimensional de inteiros, e a expressão a[i][j] indexa totalmente a e leva a

um valor do tipo inteiro. Assim, para a obtenção do endereço de uma variável de tipo matriz parcial

pode-se utilizar a aplicação recursiva das técnicas descritas para o caso das matrizes

unidimensionais.

2.3.3.4 Estruturas de registros e referências de ponteiros

O cálculo do endereço de um campo de registro ou estrutura é similar ao cálculo de um

endereço da matriz indexada. Primeiramente, o endereço da base da variável precisa ser calculado,

em seguida, o deslocamento do campo com o nome é encontrado, e por fim, os dois são somados

para obter o endereço resultante. A Figura 10 mostra um exemplo as declaração de estrutura na

linguagem C:

Typedef struct x { int i; char c; int j; } Rec … … … rec x;

Figura 10. Declaração de estrutura na linguagem C

Normalmente, a variável x é alocada na memória de acordo como mostra a Figura 11, e cada

campo(i, c e j) tem um deslocamento a partir do endereço base de x.

31

Figura 11. Ilustração da alocação da variável x em memória

Fonte: Louden, (2004).

Os campos são alocados linearmente, o deslocamento de cada campo é uma constante e o

primeiro campo (x.i) tem deslocamento zero. Vale observar que os deslocamentos dos campos

dependem dos tamanhos dos diferentes tipos de dados na máquina alvo, mas não é necessário usar

nenhum fator de escala como acontece nas matrizes.

As estruturas de registros são tipicamente usadas em conjunto com ponteiros e memória

dinâmica, para implementar estruturas de dados dinâmicas como listas e árvores. Assim, um

ponteiro estabelece um nível adicional de referência indireta.

2.3.3.5 Geração de código para declarações de controle e expressões lógicas

Segundo Louden (2004), as principais formas de declarações de controle são as declarações

estruturadas if e while. Existem ainda outras formas como break, goto, repeat, for e a declaração

case ou switch.

2.3.3.6 Geração de código para declarações if e while

A maior dificuldade para gerar código para essas declarações é traduzir as características de

controle estruturado em uma forma equivalente “sem estrutura” ou linear com saltos. Os

compiladores devem gerar código para essas declarações em uma forma padrão que permite o uso

eficiente de um subconjunto de saltos possíveis considerando a arquitetura de uma máquina alvo.

32

Abaixo são mostradas as organizações possíveis para cada uma das declarações. Em cada uma

dessas organizações, há apenas dois tipos de saltos, saltos incondicionais e saltos nos quais a

condição é falsa. O caso verdadeiro é sempre um caso de “seguir em frente”, ou seja, não requer

saltos. Isto reduz a quantidade de saltos que o compilador tem que gerar.

A Figura 12 mostra a organização típica de código para um comando de desvio condicional

(if-else).

Figura 12. Organização típica de código para um comando if-else

Fonte: Adaptado de Aho, Sethi e Ullman (1995).

No caso de um laço de repetição os saltos devem possibilitar que determinadas instruções

sejam realizadas repetitivamente, sendo neste caso um salto para uma instrução anteriormente

realizada. A Figura 13 mostra a organização típica de código para um laço de repetição com teste

lógico no início (while).

33

Figura 13. Organização típica de código para um comando while

Fonte: Adaptado de Aho, Sethi e Ullman (1995).

2.3.3.7 Geração de rótulos e ajuste retroativo (backpatching)

Um problema que pode acontecer durante a geração de código para declarações de controle

é que, em alguns casos, os saltos para um rótulo podem ser gerados antes da definição do próprio

rótulo. Durante a geração do código alvo, esses rótulos podem ser passados para um montador se

for gerado código de montagem, mas caso o código gerado seja executável, esses rótulos precisam

ser resolvidos como localizações absolutas ou relativas (Louden, 2004).

Um método para gerar esses saltos para adiante é deixar um espaço em branco no código em

que o salto deve ocorrer ou gerar uma instrução temporária de salto para uma localização fictícia.

Então, quando o salto é determinado, a localização é usada para ajustar retroativamente o código

que ficou faltando. Para isso é necessário que o código gerado fique armazenado em um repositório

na memória, para que o ajuste de código retroativo seja feito durante o processo, ou que o código

seja gerado num arquivo temporário e, posteriormente o código seja relido deste arquivo e seja

ajustado retroativamente quando for necessário. Para os dois casos, os ajustes retroativos podem ser

armazenados numa pilha que age como repositório ou mantidos localmente em procedimentos

recursivos.

34

É necessário cuidado adicional no processo de ajuste retroativo, pois pode ocorrer um

problema, porque muitas arquiteturas têm dois tipos de salto, um curto, ou ramificação, e um longo

que exige mais espaço de código. Neste caso, o gerador pode inserir instruções nop (not operation)

para encurtar os saltos ou efetuar várias passadas para condensar o código.

2.3.3.8 Geração de código de expressões lógicas

Segundo Louden (2004), a geração de código alvo para expressões lógicas geralmente

requer que valores booleanos sejam representados aritmeticamente, pois a maioria das arquiteturas

não apresenta um tipo de dado booleano interno. A forma padrão é representar o valor booleano

falso com zero e o valor booleano verdadeiro com um. Isto requer que o resultado das operações de

comparação como < ou > sejam normalizados para zero ou um.

Pode acontecer em algumas arquiteturas como na linguagem C, de uma expressão ser um

curto-circuito, ou seja, quando não se pode avaliar seu segundo argumento. Por exemplo, se a for

uma expressão booleana calculada como falsa, a expressão booleana a and b pode ser determinada

de imediato como falsa sem avaliar b. Neste caso, é necessário o uso adicional dos saltos

condicionais.

As operações de curto-circuito são extremamente úteis para o codificador, pois a avaliação

da segunda expressão causaria um erro se um operador não fosse de curto-circuito. Por exemplo, na

linguagem C é comum escrever:

If ((p!=NULL) && (p->val= =0))

onde a avaliação de p->val, quando p for nulo, provocaria um erro de memória.

2.3.3.9 Geração de código para chamadas de procedimentos e funções

A complexidade da geração de código para esses mecanismos é um pouco maior, em

decorrência de diferentes máquinas alvo utilizarem mecanismos consideravelmente diferentes para

as ativações dos procedimentos e das funções e também porque as ativações dependem da

organização do ambiente de execução. Desta forma a chamada de procedimentos e funções será

apresentada em detalhes na seção pertinente ao assembly do Win32asm.

35

2.4 Win32asm

Não haviam bibliografias oficiais a disposição para realização desta fundamentação teórica.

A aquisição de obras foi avaliada, mas em virtude do custo, prazo e principalmente da qualidade das

informações encontradas em Iczelion (2005) decidiu-se utilizar este autor como única referência.

Muitos sites na internet apontam o material deste autor como um dos mais completos e explicativos,

desta forma, assumiu-se que este possui autoridade sobre o assunto.

2.4.1 Definições gerais

Win32asm é o termo usado para descrever o uso da linguagem assembly para criar

programas para Windows 32 bits. A API(Application Programming Interface) do Windows fornece

uma biblioteca de rotinas e procedimentos bastante grande que podem ser chamadas pelos

programas escritos em Win32asm, auxiliando assim os programadores no desenvolvimento das

aplicações (MICROSOFT, 2005). Depois de criados, os programas passam por um montador, para

que ele analise o código e gere o arquivo executável. O montador também pode fornecer um

conjunto de instruções de alto nível a fim de facilitar ainda mais o desenvolvimento dos programas,

sendo que essas instruções variam de montador para montador e podem incluir macros para controle

de fluxo, laços de repetição, processamento de strings, entrada e saída de dados, entre outros.

2.4.2 Alocação de memória

Os programas Win32asm são executados em modo protegido no Windows e cada programa

é executado num espaço virtual separado. Isso significa que cada programa terá seus próprios 4 GB

de espaço de endereços de memória. Entretanto, isto não significa que cada programa tem 4GB da

memória física, somente que o programa pode se dirigir a qualquer endereço nessa escala. O

Windows fará o necessário para que os programas se referenciem corretamente a memória. Os

programas devem seguir às regras definidas pelo Windows, por exemplo, não tentar gravar

informações numa parte de memória de outro programa, caso contrário, causarão uma falha de

proteção. Cada programa Win32asm está sozinho em seu espaço de memória, ao contrário da

situação em Win16, onde todos os programas podem se enxergar entre si.

O modelo de memória no Win32asm também é diferente do modelo Win16. No Win32asm,

não é necessária a preocupação com o modelo ou os segmentos da memória. Há somente um

modelo de memória, não há mais os segmentos de 64K. A memória é um espaço contínuo de 4 GB.

Também não é necessário se preocupar com os registradores de segmentos, pode-se usar qualquer

36

registrador para dirigir-se a qualquer ponto no espaço de memória. Esta é uma grande ajuda aos

programadores e torna bastante fácil a programação.

2.4.3 Registradores

Registradores são áreas de trabalho especiais dentro do processador e são mais rápidas que

operandos de memória. Estas áreas foram projetadas para trabalharem com códigos operacionais,

que são instruções embutidas nos circuitos dos processadores.

Na programação Win32asm devem-se conhecer algumas regras importantes. Uma delas é

que o Windows usa os registradores ESI, EDI, EBP e o EBX internamente e não espera que os

valores destes registradores mudem, assim, se for utilizado algum destes quatro registradores em

alguma chamada de função, é necessário sempre restaurá-los antes de devolver o controle para o

Windows.

Os registradores de um processador com 32 bits apresentam um recurso muito limitado

quando se escreve assembly. Existem apenas 8 registradores de uso geral: EAX, EBX, ECX, EDX,

ESI, EDI, ESP e EBP. Na maioria dos casos, ESP e EBP não são ou não deveriam ser utilizados,

porque são usados principalmente para entrada e saída de procedimentos. Na prática, isto significa

que existem apenas 6 registradores de 32 bits para escrever o programa.

Existem também oito registradores de 80 bits que são utilizados para cálculos de ponto

flutuante e para a execução de instruções MMX de 64 bits. Processadores mais atuais possuem

também oito registradores XMM de 128 bits.

Algumas instruções usam determinados registradores para realizar tarefas em particular,

tornando-as mais rápidas. Existem algumas regras tradicionais que foram estabelecidas ao longo

dos anos e acabaram ditando o modo como os registradores devem ser utilizados. Não são leis, mas

são regras que podem melhorar o desenvolvimento, e estão descritas no Anexo III.

2.4.4 Principais comandos ou mnemônicos

A Tabela 3 mostra os principais comandos ou mnemônicos utilizados na programação

Win32asm. A lista completa consta no Anexo I.

37

Tabela 3. Principais mnemônicos utilizados na programação Win32asm (Parte I)

Sintaxe Exemplo Descrição ADD destino, fonte ADD eax, Valor Soma o destino à fonte e põe o resultado no

destino. Ambos os operandos são binários. AND destino, fonte AND eax,ebx Realiza uma operação lógica AND bit a bit nos

seus operandos e põe o resultado no destino. Há 5 modos diferentes de usar AND entre dois operandos: AND dois registradores. AND um registrador com uma variável. AND uma variável com um registrador. AND um registrador com uma constante. AND uma constante com um registrador.

CMP destino, fonte cmp eax, contador Compara a fonte ao destino, porém não armazena o resultado, é usado para comparar operandos antes de usar alguma instrução de salto condicional. A fonte pode ser um registrador, um endereço de memória ou um valor. O destino pode ser um registrador ou um endereço de memória.

DEC destino dec eax ou dec contador

Decrementa o valor de um registrador ou de uma variável em 1.

INC destino inc eax ou inc contador

Incrementa o valor de um registrador ou de uma variável em 1.

JE Mov eax, contador: carrega o valor da variável contador para eax. Cmp eax, 10: compara eax com a literal 10. JE: salta se eax for igual a 10

Salto condicional caso igual, usado depois de fazer uma comparação com o comando CMP.

JMP rótulo JMP sair Salto incondicional para o rótulo. MOV destino, fonte Mov eax,contador Copia um byte ou word do operando fonte para o

operando destino. A instrução MOV transfere ou move o conteúdo da fonte para o destino. Ao se executar a transferência, o conteúdo da fonte fica preservado e o conteúdo do destino é substituído pelo conteúdo da fonte. Há vários modos de usar MOV entre dois operandos. MOV dois registradores. MOV uma variável com um registrador. MOV um registrador com uma variável. MOV um registrador com uma constante. MOV uma variável com uma constante.

38

Tabela 4. Principais mnemônicos utilizados na programação Win32asm (Parte II)

Sintaxe Exemplo Descrição NOT destino not eax É uma operação lógica que alterna o valor de cada

bit individual, passando o que esta em 0 para 1 e 1 para 0.

OR destino, fonte or edx, 10000000b: ativa o bit da posição 7

OR realiza uma operação lógica OR INCLUSIVE bit a bit nos seus operandos e põe o resultado no destino. Todos os bits ativos em qualquer dos operandos estará ativo no resultado e os restantes não sofrem alteração:

POP destino pop edx Transfere o word do topo da pilha (SS:SP) para o destino e incrementa SP em dois para apontar para o novo topo da pilha. O destino pode ser um registrador ou um endereço de memória. A pilha é uma área de memória que armazena dados temporariamente. O registrador SP (stack pointer) sempre contém o endereço da localização que corresponde ao "topo" da pilha.

PUSH fonte Decrementa SP pelo tamanho do operando e transfere a fonte para o topo da pilha (SS:SP). A fonte pode ser um registrador, um endereço de memória ou um valor literal.

SUB destino, fonte sub contador, 5 Subtrai a fonte do destino e o resultado é armazenado no destino.

XOR destino, fonte xor, eax,eax Realiza uma operação lógica OR EXCLUSIVE bit a bit nos seus operandos e põe o resultado no destino. XOR retorna 1 quando ambos os operandos forem diferentes, caso contrário retorna 0, é muito utilizado para zerar operandos.

39

2.4.5 Visão geral de um programa Win32asm

A Figura 14 mostra o esqueleto de um programa Win32asm e logo após é mostrado o

detalhamento de cada parte.

.386

.MODEL FLAT, STDCALL

.DATA

<dados inicializados>

......

.DATA?

<dados não inicializados>

......

.CONST

<as constantes>

......

.CODE

<label>

<programa fonte>

end <label>

Figura 14. Esqueleto de um programa Win32asm

2.4.5.1 Orientação ao montador (.386)

Esta é uma diretriz de orientação para o montador, dizendo a ele para usar o conjunto de

instruções 80386. Pode-se usar também usar o .486, .586 ou .686. Há atualmente duas formas quase

idênticas para cada modelo de processador, .386/.386p, 486/.486p, as versões com "p" são

necessárias somente quando o programa usar instruções privilegiadas. As instruções privilegiadas

são as instruções reservadas pelo processador e pelo sistema operacional quando este opera em

modo privilegiado, são usados por programas privilegiados, como os dispositivos de drive virtual.

Na maioria das vezes, os programas são executados em modo não privilegiado.

2.4.5.2 Modelo de memória e chamada de subrotina (.MODEL)

Esta é uma diretriz de orientação para o montador, que especifica o modelo de memória do

programa. Em Win32asm, há somente um modelo, o modelo FLAT.

40

STDCALL especifica a convenção da passagem de parâmetro. Esta convenção especifica a

ordem da passagem de parâmetro, da esquerda-à-direita ou direita-à-esquerda, e também quem

balanceará o quadro da pilha após a chamada de função. Utilizando STDCALL os parâmetro serão

passados da direita-à-esquerda e a função chamada é responsável pelo balanceamento da pilha após

a chamada.

2.4.5.3 Definição de dados (.DATA, .DATA?, .CONST)

Estas três diretrizes definem seções lógicas. Não existem segmentos em Win32asm, mas

pode-se dividir o espaço de memória em seções lógicas. O começo de uma seção denota o fim da

seção precedente. Existem dois tipos de seção: dados e código. As seções dos dados são divididas

em 3 categorias: .DATA, .DATA? e .CONST.

• .DATA: contêm dados inicializados do programa. Por exemplo: contador word 0,

onde “contador” é o nome da variável, “word” é o tipo de dado e 0 é a inicialização da

variável com zero.

• .DATA?: contêm os dados não inicializados do programa. É utilizada quando se quer

apenas pré-alocar um espaço da memória, mas não se quer inicializá-lo. A vantagem de

não inicializar os dados é que reduz o tamanho do executável. Por exemplo, se você

alocar 10.000 bytes na seção .DATA?, o executável não será gerado com 10.000 bytes,

seu tamanho permanecerá o mesmo. Só é dito ao montador quanto espaço o programa

necessita quando ele for carregado na memória. Por exemplo: texto dword ?, onde

“texto” é o nome da variável, “dword” é o tipo de dado e ? indica que a variável não está

inicializada.

• .CONST: contêm a declaração das constantes usadas pelo programa. As constantes nesta

seção nunca poderão ser modificadas no programa. Por exemplo: pi dword 3.14159,

onde “pi” é o nome da variável, “dword” é o tipo de dado e 3.14159 é o valor fixo da

variável que não poderá ser alterado por nenhum código do programa.

Não é necessário usar todas as três seções num programa. Podem-se declarar somente as

seções que forem utilizadas.

41

2.4.5.4 Tipos de Dados

Há vários tipos de dados que podem ser utilizados na identificação das variáveis descritas

nas seções .DATA, .DATA? e .CONST. A Tabela 5 mostra os tipos que podem ser utilizados.

Tabela 5. Tipos de dados que podem ser utilizados na identificação das variáveis

Tipo Abreviação Tamanho Faixa Tipos Permitidos BYTE DB 1 byte -128 a +255 Caracter, string WORD DW 2 bytes -32768 a +65535 16-bit near ptr, 2 characters, double-

byte character DWORD DD 4 bytes -2GB a +4GB-1 16-bit far ptr, 32-bit near ptr, 32-bit

long word FWORD DF 6 bytes -- 32-bit far pointer QWORD DQ 8 bytes -- 64-bit long word TBYTE DT 10 bytes

-- BCD, 10-byte binary numbers

REAL4 DD 4 bytes -- Single-precision floating point REAL8 DQ 8 bytes -- Double-precision floating point REAL10 DT 10 bytes -- 10-byte floating point

2.4.5.5 Seção de código (.CODE)

Há somente uma seção para o código.

.CODE: nesta seção ficam os programas fonte e sua estrutura é mostrada na Figura 15.

<label> <programa fonte> end <label> Onde: <label> é um nome usado para especificar o início do programa fonte e end <label> define o fim do programa anteriormente iniciado. Ambos nomes devem ser idênticos e todos os programas devem residir entre < label > e end < label >.

Figura 15. Estrutura da seção .CODE

A Figura 16 mostra um exemplo da seção .CODE.

.CODE

inicio: print chr$("Ola, este é um simples exemplo.",13,10)

exit

end inicio

Figura 16. Exemplo da seção .CODE

42

2.4.6 Uso de operandos

Os operandos são os parâmetros usados com mnemônicos ou comandos do montador. Um

mnemônico pode ter de zero a dois operandos. Em mnemônicos lógicos ou aritméticos com dois

operandos, o operando direito é a origem ou fonte e o operando esquerdo é o destino. A Figura 17

mostra um exemplo do uso dos operandos.

Mnemônico destino, origem mov eax, 1 Onde: MOV é um nome ou comando reservado para uma família dos opcodes. EAX é o operando de destino. 1 é o operando de origem.

Figura 17. Exemplo do uso dos operandos

O exemplo mostrado na Figura 17 copia o operando 1, neste caso literal, para o operando

"EAX". Deve-se lembrar que outros montadores para outros processadores podem usar outra ordem

dos operandos.

2.4.7 Tipos de operandos possíveis

Há três tipos básicos de operandos que podem ser utilizados nas operações: imediato,

memória ou um outro registrador.

• Imediato: Um operando imediato é geralmente um número, mas pode também ser um

literal caracter como "a" que é convertido pelo montador para seu equivalente do ASCII

(American Standard Code for Information Interchange);

• Memória: Um operando de memória é um endereço na memória de algum dado; e

• Registrador: Um operando registrador é um registrador com um valor nele.

A Figura 18 apresenta exemplos do uso dos tipos de operandos possíveis utilizados nas

operações.

43

Tipo de operando Registrador mov ecx, edx ; copia EDX para ECX

Tipo de operando Imediato

mov eax, "a" ; literal string mov edx, 0 ; literal numérico

Tipo de operando Memória mov edx, [esi] ; copia o valor no endereço em ESI para EDX mov edx, Memvar ; copia o valor da variável Memvar para EDX

Figura 18. Exemplos de uso dos tipos de operandos possíveis nas operações

2.4.8 Chamada a procedimentos

A notação tradicional para chamar procedimentos é através da utilização do comando

"push", para empilhar os parâmetros da função e do comando “call” para chamar a função ou

procedimento. A Figura 19 mostra um exemplo de chamada de procedimento usando call.

push offset parametro4

push parâmetro3

push offset parâmetro2

push parâmetro1

call Nome_da_Função

mov valor_de_retorno, eax

Figura 19. Chamada de procedimento usado call

Há também um método alternativo para chamada de funções e procedimentos que tem

vantagens nos termos de velocidade e de confiabilidade. Este método pode ser usado através do

comando “invoke”. Um procedimento chamado pelo “INVOKE” faz verificação a um protótipo para

conferir se o tamanho e o número de parâmetros estão corretos. A Figura 20 mostra um exemplo de

chamada de procedimento usando invoke.

invoke Nome_da_Função parâmetro1,ADDR parâmetro2, parâmetro3, ADDR parâmetro4

mov valor_de_retorno, eax

Figura 20. Chamada de procedimento usado invoke

Tanto call quanto o invoke podem ser usados para chamar procedimentos internos,

desenvolvidos pelo programador, ou externos, que já estejam disponíveis em alguma biblioteca ou

API do Windows.

Os procedimentos internos são criados na seção .CODE e sua forma de criação está descrita

na Figura 21.

44

main proc código do procedimento.... main endp

Onde: “main” é o nome do procedimento, “proc” diz que é um procedimento e “endp” diz que chegou o fim do procedimento.

Figura 21. Forma de criação de procedimentos internos

2.5 Montadores

Segundo Price e Toscani (2001), montadores (assemblers) são tradutores que recebem um

conjunto de instruções em linguagem simbólica e as transformam em instruções de linguagem de

máquina, geralmente, numa relação de uma-para-uma, ou seja, uma instrução em linguagem

simbólica é transformada em uma instrução de linguagem de máquina.

Existem também tradutores que transformam instruções simbólicas numa relação de uma-

para-muitas, estes tradutores são conhecidos como macro-montadores ou macro-assemblers. Neste

caso existem macros ou procedimentos criados para facilitar a programação, que são transformados

em uma seqüência de comandos simbólicos antes de ser feita a tradução efetiva para a linguagem de

máquina.

Os montadores podem prover uma interface para a escrita do programa assembly ou podem

simplesmente receber um arquivo texto como entrada e processá-lo para fazer as traduções dos

comandos.

Existem vários montadores para Win32asm, O MASM (da MicroSoft), NASM (da equipe

liderada por Simon Tatham e Julian Hall), o TASM (da Borland) e o GoAsm (de Jeremy Gordon).

A Tabela 6 mostra uma breve comparação entre alguns desses montadores.

Tabela 6. Comparação entre os montadores

Opção / Montador MASM NASM GoASM Compila e forma .EXE Sim Não Sim Suporta macros tipo .if, .else, .endif Sim Não Não Use endereçamento de memória FLAT Sim Sim Sim Permite uso e criação de macros Sim Sim Sim É de uso livre, desde que não inserido em pacotes comerciais

Sim Sim Sim

Decidiu-se optar pelo MASM por este oferecer uma documentação bastante completa, com

vários exemplos que ajudam no desenvolvimento dos programas e facilitam a compreensão, o

45

MASM também oferece suporte para macros tipo .if, que facilitam a geração de programas. Além

disso, o MASM não possui custo de licença, desde que a aplicação não seja vendida nem inclusa em

algum pacote comercial. Sendo assim abaixo serão descritas algumas de suas características.

O MASM disponibiliza uma outra forma para alocar espaço para variáveis não inicializadas

dentro dos programas, usando o comando LOCAL. Porém estas variáveis são locais, diferente das

variáveis alocadas nas seções .DATA e .DATA? e CONST que são globais.

No MASM, a alocação destas variáveis é feita no começo de um procedimento antes de

escrever o código no procedimento. Como a alocação destas variáveis é local, elas somente poderão

ser usadas dentro desse procedimento e serão liberadas no fim ou saída do procedimento. Por

Exemplo:

LOCAL MinhaVariavel:DWORD ; aloca um espaço de 32 bits na memória

LOCAL Buffer[128]:BYTE ; aloca 128 bytes de espaço na memória

A criação de variáveis desta forma tem grande vantagem, pois é fácil e rápido para criá-las e

ocupa a memória somente pelo tempo que o procedimento está sendo usado.

2.5.1 Comandos de alto nível

Como citado anteriormente no texto, o montador pode fornecer um conjunto de instruções a

fim de facilitar o desenvolvimento dos programas. O MASM fornece um conjunto de instruções de

alto nível para comparação de valores e construção de laços de repetição, sendo algumas delas: .IF,

.ELSE, .ELSEIF, .ENDIF,.REPEAT, .WHILE, .BREAK.

.IF é usado para criar blocos de comandos e pode ser utilizado com .ELSE e .ELSEIF e

termina com .ENDIF. A Figura 22 mostra um exemplo do uso de .IF, .ELSE e .ELSEIF.

.IF condição

comandos do if

[.ELSEIF] condição

Comandos do elseif

[.ELSE]

commandos do else

.ENDIF

Figura 22. Exemplo de use de .IF, .ELSE e .ELSEIF

46

.REPEAT é utilizado para formar laços de repetição e é executado pelo menos uma vez. A

Figura 23 mostra um exemplo do uso de .REPEAT.

.REPEAT

comandos do repeat

.UNTIL condição

Figura 23. Exemplo de use de.REPEAT

.WHILE é utilizado para formar laços de repetição e é executado somente se a condição for

verdadeira. A Figura 24 mostra um exemplo do uso de .WHILE.

.WHILE condição

comandos do while

.ENDW

Figura 24. Exemplo de use de .WHILE

.BREAK é usado para sair de um laço de repetição .REPEAT ou .WHILE e pode vir

acompanhado da condição .IF, neste caso só irá sair do laço caso a condição do .IF seja verdadeira.

Por exemplo: .BREAK [IF condição].

A Tabela 7 mostra os operadores que podem ser usados nas condições das instruções.

Tabela 7. Operadores que podem ser usados nas condições das instruções

Operador Descrição = = Igual != Não Igual > Maior que >= Maior ou igual < Menor que <= Menor ou igual & Teste de Bit, na forma: expressão & numero_do_bit ! Não Lógico && E lógico || Ou lógico

Além dos comandos vistos até agora, têm-se disponíveis bibliotecas com funções da API do

Windows e procedimentos e macros disponibilizados pelo montador, que podem ser utilizados para

facilitar a programação Win32asm. Estas funções, procedimentos e macros podem ser utilizadas

incluindo-se no programa antes da seção .DATA as diretivas do montador INCLUDE e

INCLUDELIB mais a biblioteca que se deseja utilizar. Por exemplo:

47

• include \masm32\include\masm32.inc

• includelib \masm32\lib\masm32.lib

• include \masm32\include\macros.inc

A Tabela 8 mostra alguns procedimentos e macros existentes nas bibliotecas.

Tabela 8. Alguns procedimentos ou macros existentes nas bibliotecas

Procedimento Exemplo Descrição EXIST EXIST nome_do_arquivo Retorna no registrador eax 0

caso o arquivo não exista e 1 caso ele exista.

INPUT mov texto, input("Digite o texto aqui : ") Obtém o texto de um console para uma variável alocada localmente ou nas seções .DATA, .DATA?

CHR$ mov texto, chr$("Teste",13,10) Formata uma string ou numero ASCII e retorna um ponteiro para os dados que foram como parâmetro

PRINT print $CHR("Ola, isto é um texto",13,10)

mostra um texto num console

2.5.2 Exemplos de programas

A Figura 25 mostra um exemplo de um programa em Portugol que exibe o maior de cinco

números digitados pelo usuário.

programa maior declarações real val, maior inteiro cont inicio limpa() escreva ("Este programa descobre o maior de cinco valores digitados") cont <- 1 maior <- 0 enquanto (cont <= 5) faca escreva ("Informe o ", cont, "o numero: ") leia (val) se (val > maior) então maior <- val fimse cont <- cont + 1 fimenquanto escreva ("O maior valor eh: ", maior) aguarda () fim

Figura 25. Programa Portugol que exibe o maior de cinco números digitados pelo usuário

48

A Figura 26 mostra um exemplo de um programa Win32asm que exibe o maior de cinco

números digitados pelo usuário.

.486 ; create 32 bit code

.model flat, stdcall ;definição do modelo de memória e chamada de procedimentos option casemap :none ; caso sensitivo include \masm32\include\windows.inc ; bibliotecas do windows include \masm32\macros\macros.asm ; macros para o MASM ; ----------------------------------------------------------------- ; inclui arquivos que tem os protótipos de funções do MASM ; ----------------------------------------------------------------- include \masm32\include\masm32.inc include \masm32\include\gdi32.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc ; ------------------------------------------------ ; bibliotecas com definições de funções ; ------------------------------------------------ includelib \masm32\lib\masm32.lib includelib \masm32\lib\gdi32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib .data maior dword 0 espera dword 0 .code ; inicia a seção de código inicio: ; inicio do programa cls ; limpa a tela print chr$("Este programa descobre o maior de cinco valores digitados",13,10) call main ; chama a procedure main print chr$(" O maior valor eh: ",13,10) print str$(maior) print chr$(" ",13,10) mov espera, input("Tecle enter para sair") exit ; main proc ; inicio do procedimento main LOCAl valor :dword LOCAL cont :dword mov cont,0 mov ebx,0 .WHILE cont <= 5 mov valor, sval(input("Digite um numero: ")) inc cont .IF valor > ebx mov ebx,valor .ENDIF .ENDW mov maior,ebx ret main endp ;fim do procedimento main end inicio ; fim do código

Figura 26. Programa Win32asm que exibe o maior de cinco números digitados pelo usuário

49

3 PROJETO

A construção de um gerador de código trata de algoritmos que trabalham de forma integrada

a análise sintática e são chamados de ações semânticas ou esquemas de tradução. As ações

semânticas são partes de código fonte que podem ser associados com as regras da gramática.

Quando o parser determina que uma regra da gramática foi reconhecida ou usada, executará a ação

semântica associada.

Para o desenvolvimento do gerador de código, foram definidas ações semânticas que foram

integradas ao analisador sintático gerado pela ferramenta YACC. Não foi encontrada na bibliografia

nenhuma referência à modelagem destes algoritmos através de diagramas tradicionais da engenharia

de software. Utilizou-se assim, os fluxogramas apresentados na Seção 2.3.3 para explicar a tradução

de uma linguagem estruturada em linguagem de montagem.

Para promover uma melhor compreensão do que foi desenvolvido, são mostradas ações

semânticas que foram usadas para tradução do Portugol para o Win32asm. Estas ações estão

descritas em alto nível, contendo chamadas de funções que realizam a geração do código

propriamente dito em um arquivo de saída (gcod, g_comando, g_condição), mas sem considerar

aspectos sintáticos da ferramenta selecionada para o desenvolvimento (YACC).

Para representar estes exemplos utiliza-se uma gramática resumida do Happy Portugol. O

subconjunto da linguagem portugol que foi implementado corresponde a gramática utilizada

atualmente pelo Happy Portugol e pode ser analisada no Anexo II. A Figura 27 mostra a gramática

resumida do Happy Portugol com os tokens em negrito.

50

Prog : programa id ; lista_dec lista_cmd lista_dec : lista_dec dec | dec dec : tipo id ; tipo : inteiro | real | caracter | logico lista_cmd : lista_cmd cmd | cmd rsenao : senao lista_cmdo | /* vazio */ cmd : se expr então lista_cmd rsenao fimse | enquanto expr faca lista_cmd fimenquanto | id <- expr expr : expr * expr | expr / expr | expr + expr | expr – expr | expr > expr | expr < expr | expr = expr | expr != expr | expr >= expr | expr <= expr | expr e expr | expr ou expr | não expr | id | valor valor : val_inteiro | val_real | val_caracter | val_string | val_lógico

Figura 27. Gramática resumida do Happy Portugol

3.1.1 Ações semânticas para geração de código para declaração de variáveis

A Tabela 9 mostra ações semânticas para geração de código para declaração de variáveis.

Tabela 9. Ações semânticas para geração de código para declaração de variáveis

Portugol Assembly Ações semânticas inteiro a; real b; caracter c; lógico d;

a byte 0 b word 0 c word 0 d byte 0

dec : tipo id ; { gcod (id, tipo, valor); } tipo : inteiro | real | caracter | lógico

3.1.2 Ações semânticas para geração de código para atribuições e expressões

A Tabela 10 mostra ações semânticas para geração de código para atribuições e expressões.

Tabela 10. Ações semânticas para geração de código para atribuições e expressões

Portugol Assembly Ações semânticas a <- 1; b <- a + 1; d <- a * b + c;

Mov a 1 Mov ebx,a Add ebx,1 Mov b,ebx Mov ebx, b Add ebx,c Mul ebx,a Mov d,ebx

Atribuições: cmd: id <- expr {gcod(“Mov”, id, valor);} Expressões: expr: expr * expr {gcod (“mul”, reg, id);} | expr + expr {gcod (“add”, reg, id);} | id {gcod (“Mov”, reg, id);}

51

3.1.3 Ações semânticas para geração de código para desvios condicionais simples

A Tabela 11 mostra ações semânticas para geração de código para desvio condicional

simples.

Tabela 11. Ações semânticas para geração de código para desvio condicional simples

Portugol Assembly Ações semânticas se x<0 então x <- 0; fimse

.IF x<0 Mov x,0 .ENDIF

cmd <- se expr { g_comando(.IF); g_condicao(expr); } então lista_cmd {gcod(“Mov”, id, valor);} fimse { g_comando(.ENDIF);}

3.1.4 Ações semânticas para geração de código para desvios condicionais compostos

A Tabela 12 mostra ações semânticas para geração de código para desvio condicional

composto.

Tabela 12. Ações semânticas para geração de código para desvio condicional composto

Portugol Assembly Ações semânticas se x<0 então x <- 0; senão x <- x + 1; fimse

.IF x<0 Mov x,0 .ELSE Mov ebx,x Add ebx,1 Mov x,ebx .ENDIF

cmd <- se expr { g_comando(.IF); g_condicao(expr); } então lista_cmd {gcod(“Mov”, id, valor);} senao lista_cmd { g_comando(.ELSEIF); {gcod (“Mov”, reg, id);} {gcod (“add”, reg, id);} {gcod(“Mov”, id, valor);} fimse { g_comando(.ENDIF);}

52

3.1.5 Ações semânticas para geração de código para laço de repetição

A Tabela 13 mostra ações semânticas para geração de código para laço de repetição.

Tabela 13. Ações semânticas para geração de código para laço de repetição

Portugol Assembly Ações semânticas enquanto x>0 faça x <- x - 1; fimenquanto

.WHILE x<0 Mov ebx,x Sub ebx,1 Mov x,ebx .ENDW

cmd <- enquanto expr { g_comando(.WHILE); g_condicao(expr); } faça lista_cmd {gcod (“Mov”, reg, id);} {gcod (“add”, reg, id);} {gcod(“Mov”, id, valor);} fimse { g_comando(.ENDW);}

3.1.6 Outros comandos disponíveis no Happy Portugol

Existem ainda outros comandos que o Happy Portugol disponibiliza. A Tabela 14 mostra

como os principais comandos de declarações, vetores, atribuições, entrada, saída, laço e desvios e

chamada de procedimentos do portugol puderam ser traduzidos para os comandos Win32asm.

53

Tabela 14. Tradução dos principais comandos portugol para Win32asm

Produção no Portugol Tradução para Win32asm Descrição Inteiro A,B LOCAL A :byte

LOCAL B :byte Declaração local das variáveis A e B

Real maior maior wdord 0 Declara a variável maior na seção .data

Character Texto Texto dword ? Declara a variável Texto na seção .data?

inteiroBuffer[128] LOCAL Buffer[128]:BYTE Define um vetor com 128 posições. A <- 1 Mov A,1 Atribui o valor 1 para variável A. A <- A+B mov ebx,A

Add B,ebx Soma A+B a atribui o resultado para A.

A <- A+B+C Mov ebx,C Add ebx,B Add A,ebx

Soma A+B+C a atribui o resultado para A.

Leia (A) Mov A,input(“Digite A”) Lê a variável A de uma console. Escreva (“oi”) Print chr$ (“oi”) Escreve oi em uma console. Escreva (A) Print str$(A) Escreve o valor da variável A em uma

console. Enquanto (A<=5) faca .WHILE (A<=5) O laço permanecerá enquanto a

variável A for menor ou igual a 5. para i <- 1 ate 50 passo 1

.WHILE i <=50 inc i

O comando para pode ser traduzido para o comando .WHILE.

Se (val>maior) então .IF val>ebx Desvio condicional, desvia se a variável val for maior que o conteúdo de ebx.

procedimento Mostra ( caracter msg )

Mostra proc msg:DWORD Definição do procedimento mostra

Mostra (Msg) invoke mostra, msg Chamada do procedimento Mostra

54

4 DESENVOLVIMENTO

De forma geral, a fase de desenvolvimento do gerador de código objeto para o Happy

Portugol dividiu-se em quatro etapas: (i) correção de bugs do Happy Portugol; (ii) implementação

do gerador de código; (iii) integração do gerador de código ao Happy Portugol; e (iv) Testes. Sendo

assim, as seções seguintes retratam as técnicas utilizadas em cada uma das etapas desta fase.

4.1 Correção de bugs do Happy Portugol

Ao longo dos três semestres em que a ferramenta vem sendo utilizada, alguns bugs de

implementação foram identificados e puderam ser corrigidos neste TCC, conforme segue:

• Execução da versão incorreta do executável gerado;

• Caracteres especiais travavam o analisador léxico;

• Cálculos com raiz quadrada não funcionavam;

• Uso de constantes no laço “para” não funcionavam;

• Havia restrição quanto ao uso de radiciação junto com potência; e

• Permitir resultado de uma divisão a uma variável do tipo inteiro;

4.2 Implementação do gerador de código

A etapa de implementação do gerador de código objeto para o Happy Portugol foi a mais

extensa das etapas. A implementação foi feita através da definição de ações semânticas que foram

integradas ao analisador sintático gerado pela ferramenta YACC, através de variáveis temporárias

para armazenamento de partes do código, do procedimento ger_cod e do procedimento ger_cod2

criados para montar o código Win32asm.

A primeira parte do código, o cabeçalho do arquivo Win32asm, foi montado numa variável

temporária, a segunda parte, o código realmente escrito em portugol traduzido para o Win32asm,

foi montado em outra variável temporária e ao final da análise do programa fonte e geração do

código as duas variáveis foram unidas num arquivo com o mesmo nome do arquivo escrito em

portugol, porém, com extensão .asm.

55

Para possibilitar a captação de informações como nome de variáveis e valores, o YACC

disponibiliza uma estrutura com o nome e o tipo de cada expressão que é reconhecida. Por exemplo,

na expressão cmdo: ID ATRIB exp, o YACC identifica cmdo com $$, ID com $1, ATRIB com

$2 e exp com $3, assim, pode-se ter a informação de que ID tem o nome de Var1, definido por

$1.nome e é de tipo inteiro definido por $1.tipo, o mesmo pode ser obtido para exp.

A fim de tornar possível a compreensão do que foi desenvolvido, serão mostradas e

comentadas as ações semânticas utilizadas para tradução do Portugol para o Win32asm.

4.2.1 Ações semânticas para geração de código para declaração de variáveis

A declaração de variáveis para o código Win32asm fica dentro da seção .data e para a

geração das declarações foi utilizada uma estrutura de dados, mostrada na Figura 28, que contém a

tabela de símbolos do algoritmo escrito em portugol. Esta estrutura contém o nome, o tipo, que

pode ser inteiro, real, caracter, lógico ou cadeia com valores 1, 2, 3, 4 e 5 respectivamente, o valor

inicial, caso haja, dos símbolos e o tipo de variável, que pode ser normal, vetor ou matriz com os

valores 0, 1 e 2 respectivamente. Esta estrutura é criada na declaração e definição das variáveis e a

partir dela são obtidos os dados necessários para geração do código.

typedef struct tab { char nome[256]; int tipo; char valorini[256]; int tipo2; struct tab *prox; }tab;

Figura 28. Estrutura da tabela de símbolos

A Tabela 15 mostra as ações semânticas para geração de código para declaração de

variáveis, onde as partes da gramática aparecem em negrito.

Tabela 15. Ações semânticas para geração de código para declaração de variáveis

Portugol Assembly Ações semânticas DEFINA N 5 real maior caracter nome

N dd 5 maior dd 0 nome db 0

1:dec_const : DEFINA ID_CONST valor 2: { if (busca_tab($2.nome) == NAO_ACHOU) 3: insere_tab ($2.nome, $3.tipo, $3.nome, 0); } 4:ident : ID { if (busca_tab($1.nome) == NAO_ACHOU) 5: insere_tab ($1.nome, $1.tipo,"0", 0);}

56

Na linha dois o nome do identificador (ID) é procurado na tabela de símbolos,caso não seja

encontrado, ele é inserido (linha 3) com o tipo do valor e seu nome (no caso o próprio lexema). Nas

linhas quatro e cinco, o mesmo é feito, porém para variáveis.

Após todo programa escrito em portugol ser lido e a tabela com os símbolos ser preenchida,

é chamado um procedimento com o nome gera_declaracoes, que lê a tabela de símbolos e a partir

das informações nela contidas gera o código. A Figura 29 mostra o procedimento para a geração de

código para declaração de variáveis.

void gera_declaracoes() { tab *pt = inicio; while (pt != NULL) { strcat (cabasm, pt->nome); if (pt->tipo==3 || pt->tipo==5){ strcat (cabasm, " db "); strcat (cabasm, pt->valorini); if (pt->tipo2==1){ strcat (cabasm," dup(?)"); } } else { strcat (cabasm, " dd "); strcat (cabasm, pt->valorini); if (pt->tipo2==1){ strcat (cabasm," dup(?)"); } } strcat (cabasm, "\n"); pt=pt->prox; } strcat (cabasm, "wwaaiitt dd 0"); strcat (cabasm, "\n"); strcat (cabasm, ".code\n\n"); strcat (cabasm, "inicio:\n"); }

Figura 29. Procedimento para a geração de código para declaração de variáveis.

4.2.2 Ações semânticas para geração de código para atribuições e expressões

A Tabela 16 mostra ações semânticas para geração de código para atribuições e expressões,

onde as partes da gramática aparecem em negrito.

57

Tabela 16. Ações semânticas para geração de código para atribuições e expressões

Portugol Assembly Ações semânticas a <- 1 a <- b; b <- a + 1; c <- a + b;

mov a,1 mov ebx,b mov a,ebx mov eax,a add eax,1 mov b,eax mov eax,a add eax,b mov c,eax

Atribuições: 1:cmdo: ID ATRIB exp { if (busca_tab($3.nome) != 2:NAO_ACHOU { 3: ger_cod("mov","ebx",$3.nome,",");} 4: ger_cod("mov",$1.nome,"ebx",","); 5: else { 6: ger_cod("mov",$1.nome,$3.nome,",");} } 7:Explessões: 8:exp: expr + expr { ger_cod("mov","eax",$1.nome,","); 9: ger_cod("add","eax",$3.nome,","); 10: strcpy ($$.nome,"eax\n");}

Na primeira linha o nome da expressão exp é procurado na tabela de símbolos. Caso seja

encontrado, considera-se que é uma variável, então, primeiro o valor da segunda variável é

atribuído ao registrador ebx(linha 3), depois esse registrador é atribuído a primeira variável(linha 4),

isto porque não é possível atribuir diretamente uma variável a outra. Caso não seja uma variável é

feita uma atribuição direta de um literal para uma variável(linha 6). Na linha oito quando o

analisador reconhece uma expressão de soma, o valor da primeira variável é atribuído ao registrador

eax, sendo feita a adição da segunda variável a este mesmo registrador(linha 9) e é dado o nome da

operação como eax(linha 10) para posteriormente, quando o analisador reconhecer a expressão

cmdo: ID ATRIB exp o nome de exp ou $3.nome seja eax e ele seja usado para terminar a

operação (linha 3).

4.2.3 Ações semânticas para geração de código para leitura de dados do console

A Tabela 17 mostra ações semânticas para geração de código para leitura de dados do

console, onde as partes da gramática aparecem em negrito.

58

Tabela 17. Ações semânticas para geração de código para leitura de dados do console

Portugol Assembly Ações semânticas leia (a) leia(b,c)

mov a,sval(input()) mov c,sval(input()) mov c,sval(input())

1:cmdo: LEIA '(' lista_id2 ')' 2:lista_id2: lista_id2 ',' ident2 3:{ger_cod("mov",$3.nome,"sval(input())",",");} 4:| ident2 {ger_cod("mov",$1.nome,"sval(input())",",");}

Quando o analisador reconhece a expressão na linha dois, que é uma lista de variáveis a

serem lidas do console, é atribuído a cada variável o valor retornado da função input(linha 3), que

capta valores do console. O mesmo ocorre na linha quatro, porém para uma só variável.

4.2.4 Ações semânticas para geração de código para escrita de dados no console

A Tabela 18 mostra ações semânticas para geração de código para escrita de dados no

console, onde as partes da gramática aparecem em negrito.

Tabela 18. Ações semânticas para geração de código para escrita de dados no console

Portugol Assembly Ações semânticas escreva(a) escreva("O valor de a eh", a)

print str$( a) print chr$(13,10) print chr$( "O valor de a eh") print str$( a) print chr$(13,10)

1:cmdo: ESCREVA '(' lista_saida ')' 2:{ strcat (saidaasm, 2:"\nprint chr$(13,10)\n");} 3:lista_saida: ident2 { 4: ger_cod("print str$(",$1.nome,")","");} 5: ',' lista_saida 6: | valor {ger_cod("print chr$(",$1.nome,")","");} 7: ',' lista_saida 8: | ident2 {ger_cod("print str$(",$1.nome,")","");} 9: | valor { if ($1.tipo == TIPO_CARACTER || $1.tipo 10:== TIPO_CADEIA) { 11: ger_cod("print chr$(",$1.nome,")","");} 12: else { 13: ger_cod("print str$(",$1.nome,")",""); } 14: }

Quando é reconhecida a expressão da linha três, que é uma lista de dados que serão

mostrados no console, é utilizada a função print para mostrar cada uma das variáveis da lista(linha

6), porém há uma diferença na chamada da função, assim, caso o valor a ser mostrado for numérico

usa-se str$ e caso seja caracter usa-se chr$. O mesmo ocorre nas linhas oito, nove, onze e treze,

porém para uma só variável. Para finalizar a impressão quando a expressão da linha um é

reconhecida, é gerado código para pular uma linha(linha 2).

59

4.2.5 Ações semânticas para geração de código para desvios condicionais simples

A Tabela 19 mostra ações semânticas para geração de código para desvio condicional

simples, onde as partes da gramática aparecem em negrito.

Tabela 19. Ações semânticas para geração de código para desvio condicional simples

Portugol Assembly Ações semânticas se (x>a) então x <- 0; fimse

Mov ebx a .if x>ebx Mov x,0 .endif

1:exp: exp MAIOR exp { 2: strcpy (tempcond,"\nmov "); 3: strcat (tempcond,"ebx,"); 4: strcat (tempcond,$3.nome); 5: strcat (tempcond,"\n"); 6: strcpy (tempcond2, $1.nome); 7: strcat (tempcond2, " > "); 8: strcat (tempcond2,"ebx\n");} 9:cmdo : SE '(' exp ')' { 10: ger_cod2(tempcond); 11: ger_cod(".if","","",""); 12: ger_cod2(tempcond2); } 13: ENTAO lista_cmdo rsenao FIMSE 14:{ger_cod(".endif","","","");}

Como nos desvios condicionais é muito comum o uso de operadores como maior, menor

igual, entre outros, quando essas expressões são reconhecidas são geradas duas variáveis

temporárias para a condição das expressões(linha 2 a 8). Quando a expressão de desvio é

reconhecida é utilizado o procedimento ger_cod2(linha 10), depois o procedimento ger_cód para

gerar o desvio(linha 11) e em seguida novamente o procedimento ger_cod2(linha 12) para terminar

a condição. E, por fim é gerado o comando para finalizar o desvio(linha 14).

4.2.6 Ações semânticas para geração de código para laço de repetição “para”

A Tabela 20 mostra ações semânticas para geração de código para laço de repetição “para”,

onde as partes da gramática aparecem em negrito.

60

Tabela 20. Ações semânticas para geração de código para laço de repetição “para”

Portugol Assembly Ações semânticas para i<-1 ate N passo 1 i2<-i2+1 fimpara

mov i,1 mov ebx,N .while i <= ebx push ebx mov ebx,1 add i,ebx mov eax,i2 add eax,1 mov i2,eax pop ebx .endw

1:cmdo: PARA ID ATRIB exp {if (busca_tab($2.nome) != 2:NAO_ACHOU { 3:ger_cod("mov","ebx",$5.nome,","); 4:ger_cod("mov",$2.nome,"ebx",",");} 5:else { 6: ger_cod("mov",$2.nome,$5.nome,",");} 7: } 8:ATE exp {ger_cod("mov","ebx",$8.nome,","); 9: ger_cod(".while",$2.nome,"ebx"," <= "); 10: ger_cod("push","ebx","","");} 11:PASSO exp {ger_cod("mov","ebx",$11.nome,","); 12: ger_cod("add",$2.nome,"ebx",",");} 13:lista_cmdo FIMPARA {ger_cod("pop","ebx","",""); 14: ger_cod(".endw","","","");} 15:}

Na primeira linha o nome da expressão exp é procurado na tabela de símbolos. Caso seja

encontrado, considera-se que é uma variável, então, primeiro o valor da segunda variável é

atribuído ao registrador ebx(linha 3), depois esse registrador é atribuído a primeira variável(linha 4).

Caso não seja uma variável é feita uma atribuição direta de um literal para uma variável(linha 6).

Na linha oito quando é encontrado o limite do laço é carregado para o registrador ebx o valor do

limite. Para saber quando sair do laço, é comparado o valor deste registrador com a variável que

condicionará a saída do laço(linha 9). Neste caso i. Então é empilhado o valor de ebx com o

comando push(linha 10), para que este não se perca nos demais comandos. Na linha 11, quando é

encontrado o valor de incremento para a variável que controla a saída do laço, é atribuído o nome

ou valor desse incremento ao registrador ebx e incrementado a variável de controle com o valor de

ebx(linha 12). Quando é encontrado o final do laço, é desempilhado com o comando pop o valor de

ebx(linha 13) para que este possa novamente ser comparado ao inicio do laço(linha 9) e é gerado o

comando para fim do laço(linha 14)

4.3 Integração do Win32asm ao Happy Portugol

A integração o Happy Portugol ao Win32asm foi feita através da interface que é

disponibilizada pelo Happy Portugol, onde o usuário escreve seu programa em portugol e então

compila este programa. Ao fazer isso, o portugol é traduzido para a linguagem C++ que é mostrada

61

em uma aba chamada C++ e as rotinas desenvolvidas para a geração de código, realizam a tradução

do portugol para o código objeto Win32asm que é mostrado numa outra aba chamada Win32asm.

Depois de feitas as conversões o aluno pode escolher em testar seu programa através do

código C++ ou através do código Win32asm. Para isso, basta no Happy Portugol o aluno estar com

o foco na aba C++ para testar o programa gerado em C++, através do compilador turbo C 1.01 ou

com o foco na aba Win32asm para testar o código Win32asm gerado, através do montador MASM.

Com esta integração os alunos que utilizarão a ferramenta terão a disposição a visualização

de uma nova tecnologia(Win32asm), e poderão ver como os comandos escritos em uma linguagem

de alto nível se transformam em comandos de baixo nível. Além disso, com a exposição desta nova

tecnologia contribui-se para a inclusão deste tópico como conteúdo de ensino para a disciplina de

Compiladores, possibilitando aos alunos de Algoritmos uma ferramenta mais rica em recursos para

apoiar a aprendizagem da lógica de programação.

A Figura 30 mostra a nova interface que o Happy Portugol disponibiliza para a criação dos

programas.

62

Figura 30. Nova interface que o Happy Portugol disponibiliza para a criação dos programas

Para melhor ilustrar o que foi desenvolvido, a Figura 31 mostra de forma abstrata as

implementações realizadas neste trabalho.

63

Figura 31. Implementações realizadas sobre a ferramenta Happy Portugol

4.4 Testes

Os testes foram executados ao longo de todo o desenvolvimento, pois, conforme eram

implementados comandos do portugol eram feitos testes para validação do código Win32asm

gerado, e como teste final de integração do Happy Portugol ao Win32asm, foram testados os

exemplos disponíveis pela própria instalação do Happy Portugol, sendo que eles funcionaram

adequadamente, com exceção dos que continham matrizes e a função sorteia(). Para melhor

demonstrar os resultados dos testes, serão mostradas tabelas com alguns programas escritos em

portudol e seu respectivos código Win32asm.

64

A Tabela 21 mostra um exemplo simples de um programa que escreve “helloworld” na tela.

Tabela 21. Exemplo de programa que escreve“helloworld” na tela.

Portugol Win32asm programa hello

inicio

limpa()

escreva

("helloworld")

aguarda()

fim

.486

.model flat, stdcall

option casemap :none

include \masm32\include\windows.inc

include \masm32\macros\macros.asm

include \masm32\include\masm32.inc

include \masm32\include\gdi32.inc

include \masm32\include\user32.inc

include \masm32\include\kernel32.inc

includelib \masm32\lib\masm32.lib

includelib \masm32\lib\gdi32.lib

includelib \masm32\lib\user32.lib

includelib \masm32\lib\kernel32.lib

.data

wwaaiitt dd 0

.code

inicio:

cls

print chr$( "helloworld")

print chr$(13,10)

mov wwaaiitt,input("Aguardando, tecle Enter para

continuar")

exit

end inicio

A Tabela 22 mostra um exemplo de um programa que calcula a média entre três notas e diz

se o aluno foi aprovado ou não.

65

Tabela 22. Exemplo de programa calcula a média e diz se o aluno foi aprovado ou não.

Portugol Win32asm programa media

declaracoes

logico passou

real n1, n2, n3, media

inicio

limpa()

escreva ("Informe as tres medias ")

escreva (" ")

leia (n1,n2,n3)

media <- (n1 + n2 + n3) / 3

se (media >= 6) entao

passou <- verdadeiro

senao

passou <- falso

fimse

se (passou) entao

escreva("sua media eh: ", media,

" voce esta aprovado")

senao

escreva("sua media eh: ", media,

" voce esta reprovado")

fimse

aguarda()

fim

.486

.model flat, stdcall

option casemap :none

.data

wwaaiitt dd 0

falso dd 0

verdadeiro dd 1

media dd 0

n3 dd 0

n2 dd 0

n1 dd 0

passou dd 0

.code

inicio:

cls

print chr$( "Informe as tres medias ")

print chr$(13,10)

print chr$( " ")

print chr$(13,10)

mov n1,sval(input())

mov n2,sval(input())

mov n3,sval(input())

mov eax,n1

add eax,n2

mov eax,eax

add eax,n3

mov edx,0

mov eax,eax

mov ecx,3

idiv ecx

mov ebx,eax

mov media,ebx

mov ebx,6

.if media >= ebx

mov ebx,verdadeiro

mov passou,ebx

.else

mov ebx,falso

mov passou,ebx

.endif

mov ebx,verdadeiro

.if passou == ebx

print chr$( "sua media eh: ")

print str$( media)

print chr$( " voce esta aprovado")

print chr$(13,10)

.else

print chr$( "sua media eh: ")

print str$( media)

print chr$( " voce esta reprovado")

print chr$(13,10)

.endif

mov wwaaiitt,input("Aguardando, tecle

Enter para continuar")

exit

end inicio

66

A Tabela 23 mostra um exemplo de um programa que informa o maior de cinco valores

digitados pelo aluno.

Tabela 23. Exemplo de programa que informa o maior de cinco valores digitados pelo aluno.

Portugol Win32asm programa maior

declaracoes

real valor, maior

inteiro cont

inicio

limpa()

escreva ("Este programa descobre o

maior de cinco valores digitados")

escreva (" ")

cont <- 1

maior <- 0

enquanto (cont <= 5) faca

escreva ("Informe o ", cont, "o

numero: ")

leia (valor)

se (valor > maior) entao

maior <- valor

fimse

cont <- cont + 1

fimenquanto

escreva ("O maior valor eh: ",

maior)

aguarda ()

fim

.486

.model flat, stdcall

option casemap :none

.data

wwaaiitt dd 0

cont dd 0

maior dd 0

valor dd 0

.code

inicio:

cls

print chr$( "Este programa descobre o

maior de cinco valores digitados")

print chr$(13,10)

print chr$( " ")

print chr$(13,10)

mov ebx,1

mov cont,ebx

mov ebx,0

mov maior,ebx

mov ebx,5

.while cont <= ebx

push ebx

print chr$( "Informe o ")

print str$( cont)

print chr$( "o numero: ")

print chr$(13,10)

mov valor,sval(input())

mov ebx,maior

.if valor > ebx

mov ebx,valor

mov maior,ebx

.endif

mov eax,cont

add eax,1

mov ebx,eax

mov cont,ebx

pop ebx

.endw

print chr$( "O maior valor eh: ")

print str$( maior)

print chr$(13,10)

mov wwaaiitt,input("Aguardando, tecle

Enter para continuar")

exit

end inicio

67

4.5 Limitações e restrições

A funcionalidade de execução dos algoritmos passo a passo não foi implementada por não

ter sido identificada uma solução compatível com a estrutura do gerador de código escrito. Desta

forma, os programas escritos poderão ser executados, mas não depurados.

O montador MASM é uma ferramenta que deve estar presente para a construção do

executável a ser testado, de forma que isto cria uma dependência com este montador.

68

5 CONCLUSÕES

Ao final deste trabalho pode-se afirmar que foi compreendido o funcionamento de um

gerador de código, conhecimento este, necessário para o desenvolvimento do projeto. Foram

definidos e apresentados os comandos da linguagem portugol implementados. Foi conhecido e

utilizado o conjunto de instruções Win32asm na implementação do gerador de código utilizando o

compilador C++ Builder e foi selecionada a ferramenta MASM para montagem e link-edição do

programa executável.

Foram também realizados testes de funcionamento do gerador de código e integração com o

Happy Portugol utilizando alguns exemplos disponíveis pela própria instalação do Happy Portugol.

Sendo que estes testes apresentaram sucesso, viabilizando assim o uso do Happy Portugol.

Também foi alterada a gramática do portugol, incluindo-se o token “REPITA”, visando a

correção de uma inconsistência entre os tokens “ENQUANDO FACA” e “FACA ENQUANTO”.

Foram ainda, efetuadas correções de alguns bugs identificados ao longo dos três semestres de uso

do Happy Portugol.

Desta forma, considera-se que os objetivos estabelecidos para este trabalho foram

contemplados.

Assim os alunos dos primeiros períodos poderão utilizar a ferramenta Happy Portugol para

aprender noções básicas de construção de programas, desenvolver a lógica algorítmica e se

habituarem a um ambiente de programação. Deve-se considerar ainda que, com a aprendizagem,

domínio e disponibilização de uma nova linguagem ou tecnologia, está se contribuindo para o

ensino dos alunos e para a melhoria das disciplinas de compiladores e para o curso de Ciência da

Computação de maneira geral, uma vez que abrangeu-se os conceitos e características para geração

do código Win32asm, um conjunto de instruções até então pouco explorado nas disciplinas de

compiladores.

5.1 Trabalhos Futuros

Embora os objetivos para este trabalho tenham sido atingidos, é possível ainda que

melhorias na ferramenta Happy Portugol e na geração de código possam vir a ser feitas, tais como:

69

• Permitir mudar a cor de texto e fundo;

• Incluir suporte para estruturas;

• Incluir suporte para subrotinas;

• Melhorar as mensagens nos erros de compilação;

• Tornar o help sensível ao contexto;

• Ampliar os níveis de undo-redo;

• Permitir a execução do programa passo-a-passo.

REFERÊNCIAS BIBLIOGRÁFICAS

AHO, A. V., SETHI,R., ULLMAN, J. D. Compiladores: princípios, técnicas e ferramentas. Rio de Janeiro: Guanabara Koogan, 1995.

CRESPO, R. G. Processadores de Linguagem: da concepção à implementação. Lisboa, Portugal: IST Press, 1998.

GESSER, Carlos Eduardo. GALS: Gerador de analisadores léxicos e sintáticos. 2002. 150f. Trabalho de Conclusão de Curso (Bacharelado em Ciência da Computação)–Centro Tecnológico, Universidade Federal de Santa Catarina, Florianópolis, 2003.

ICZELION. Iczelion's Win32 Assembly Homepage. Disponível em: <http://www.win32asm.cjb.net/>. Acesso em: 17 out. 2005.

MICROSOFT. Windows API Reference. Disponível em: <http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winprog/winprog/windows_api_reference.asp>. Acesso em: 29 set. 2005.

PAULA FILHO, Wilson de Pádua. Engenharia de software: fundamentos, métodos e padrões. Rio de Janeiro: LTC, 2001.

PRICE, A. M. A., TOSCANI, S. S. Implementação de Linguagens de Programação: Compiladores. Porto Alegre: Sagra Luzzatto, 2001.

LOUDEN, K. C. Compiladores: princípios e práticas. São Paulo: Thomson Pioneira, 2004.

71

ANEXOS

I MNEMÔNICOS UTILIZADOS NA PROGRAMAÇÃO WIN32ASM

AAA - Ascii Adjust for Addition

AAD - Ascii Adjust for Division

AAM - Ascii Adjust for Multiplication

AAS - Ascii Adjust for Subtraction

ADC - Add With Carry

ADD - Arithmetic Addition

AND - Logical And

ARPL - Adjusted Requested Privilege Level of Selector (286+ PM)

BOUND - Array Index Bound Check (80188+)

BSF - Bit Scan Forward (386+)

BSR - Bit Scan Reverse (386+)

BSWAP - Byte Swap (486+)

BT - Bit Test (386+)

BTC - Bit Test with Compliment (386+)

BTR - Bit Test with Reset (386+)

BTS - Bit Test and Set (386+)

CALL - Procedure Call

CBW - Convert Byte to Word

CDQ - Convert Double to Quad (386+)

CLC - Clear Carry

CLD - Clear Direction Flag

CLI - Clear Interrupt Flag (disable)

CLTS - Clear Task Switched Flag (286+ privileged)

CMC - Complement Carry Flag

73

CMP - Compare

CMPS - Compare String (Byte, Word or Doubleword)

CMPXCHG - Compare and Exchange

CWD - Convert Word to Doubleword

CWDE - Convert Word to Extended Doubleword (386+)

DAA - Decimal Adjust for Addition

DAS - Decimal Adjust for Subtraction

DEC - Decrement

DIV - Divide

ENTER - Make Stack Frame (80188+)

ESC - Escape

HLT - Halt CPU

IDIV - Signed Integer Division

IMUL - Signed Multiply

IN - Input Byte or Word From Port

INC - Increment

INS - Input String from Port (80188+)

INT - Interrupt

INTO - Interrupt on Overflow

INVD - Invalidate Cache (486+)

INVLPG - Invalidate Translation Look-Aside Buffer Entry (486+)

IRET/IRETD - Interrupt Return

JA - Jump if Above

JAE - Jump if Above or Equal

JB - Jump if Below

JBE - Jump if Below or Equal

JC - Jump if Carry

74

JCXZ/JECXZ - Jump if Register (E)CX is Zero

JE - Jump if Equal

JG - Jump if Greater (signed)

JGE - Jump if Greater or Equal (signed)

JL - Jump if Less (signed)

JLE - Jump if Less or Equal (signed)

JMP - Unconditional Jumpunconditional

JNA - Jump if Not Above

JNAE Jump if Not Above or Equal

JNB - Jump if Not Below

JNBE Jump if Not Below or Equal

JNC - Jump if Not Carry

JNE - Jump if Not Equal

JNG - Jump if Not Greater (signed)

JNGE - Jump if Not Greater or Equal (signed)

JNL - Jump if Not Less (signed)

JNLE - Jump if Not Less or Equal (signed)

JNO - Jump if Not Overflow (signed)

JNP - Jump if No Parity

JNS - Jump if Not Signed (signed)

JNZ - Jump if Not Zero

JO - Jump if Overflow (signed)

JP - Jump if Parity

JPE - Jump if Parity Even

JPO - Jump if Parity Odd

JS - Jump if Signed (signed)

JZ - Jump if Zero

75

LAHF - Load Register AH From Flags

LAR - Load Access Rights (286+ protected)

LDS - Load Pointer Using DS

LEA - Load Effective Address

LEAVE - Restore Stack for Procedure Exit (80188+)

LES - Load Pointer Using ES

LFS - Load Pointer Using FS (386+)

LGDT - Load Global Descriptor Table (286+ privileged)

LIDT - Load Interrupt Descriptor Table (286+ privileged)

LGS - Load Pointer Using GS (386+)

LLDT - Load Local Descriptor Table (286+ privileged)

LMSW - Load Machine Status Word (286+ privileged)

LOCK - Lock Bus

LODS - Load String (Byte, Word or Double)

LOOP - Decrement CX and Loop if CX Not Zero

LOOPE/LOOPZ - Loop While Equal / Loop While Zero

LOOPNZ/LOOPNE - Loop While Not Zero / Loop While Not Equal

LSL - Load Segment Limit (286+ protected)

LSS - Load Pointer Using SS (386+)

LTR - Load Task Register (286+ privileged)

MOV - Move Byte or Word

MOVS - Move String (Byte or Word)

MOVSX - Move with Sign Extend (386+)

MOVZX - Move with Zero Extend (386+)

MUL - Unsigned Multiply

NEG - Two's Complement Negation

NOP - No Operation (90h)

76

NOT - One's Compliment Negation (Logical NOT)

OR - Inclusive Logical OR

OUT - Output Data to Port

OUTS - Output String to Port (80188+)

POP - Pop Word off Stack

POPA/POPAD - Pop All Registers onto Stack (80188+)

POPF/POPFD - Pop Flags off Stack

PUSH - Push Word onto Stack

PUSHA/PUSHAD - Push All Registers onto Stack (80188+)

PUSHF/PUSHFD - Push Flags onto Stack

RCL - Rotate Through Carry Left

RCR - Rotate Through Carry Right

REP - Repeat String Operation

REPE/REPZ - Repeat Equal / Repeat Zero

REPNE/REPNZ - Repeat Not Equal / Repeat Not Zero

RET/RETF - Return From Procedure

ROL - Rotate Left

ROR - Rotate Right

SAHF - Store AH Register into FLAGS

SAL/SHL - Shift Arithmetic Left / Shift Logical Left

SAR - Shift Arithmetic Right

SBB - Subtract with Borrow/Carry

SCAS - Scan String (Byte, Word or Doubleword)

SETAE/SETNB - Set if Above or Equal / Set if Not Below (386+)

SETB/SETNAE - Set if Below / Set if Not Above or Equal (386+)

SETBE/SETNA - Set if Below or Equal / Set if Not Above (386+)

SETE/SETZ - Set if Equal / Set if Zero (386+)

77

SETNE/SETNZ - Set if Not Equal / Set if Not Zero (386+)

SETL/SETNGE - Set if Less / Set if Not Greater or Equal (386+)

SETGE/SETNL - Set if Greater or Equal / Set if Not Less (386+)

SETLE/SETNG - Set if Less or Equal / Set if Not greater or Equal

SETG/SETNLE - Set if Greater / Set if Not Less or Equal (386+)

SETS - Set if Signed (386+)

SETNS - Set if Not Signed (386+)

SETC - Set if Carry (386+)

SETNC - Set if Not Carry (386+)

SETO - Set if Overflow (386+)

SETNO - Set if Not Overflow (386+)

SETP/SETPE - Set if Parity / Set if Parity Even (386+)

SETNP/SETPO - Set if No Parity / Set if Parity Odd (386+)

SGDT - Store Global Descriptor Table (286+ privileged)

SIDT - Store Interrupt Descriptor Table (286+ privileged)

SHL - Shift Logical Left

SHR - Shift Logical Right

SHLD/SHRD - Double Precision Shift (386+)

SLDT - Store Local Descriptor Table (286+ privileged)

SMSW - Store Machine Status Word (286+ privileged)

STC - Set Carry

STD - Set Direction Flag

STI - Set Interrupt Flag (Enable Interrupts)

STOS - Store String (Byte, Word or Doubleword)

STR - Store Task Register (286+ privileged)

SUB - Subtract

TEST - Test For Bit Pattern

78

VERR - Verify Read (286+ protected)

VERW - Verify Write (286+ protected)

WAIT/FWAIT - Event Wait

WBINVD - Write-Back and Invalidate Cache (486+)

XCHG - Exchange

XLAT/XLATB - Translate

XOR - Exclusive OR

79

II GRAMÁTICA DO PORTUGOL

programa: PROGRAMA ID dec INICIO lista_cmdo FIM ; dec : DECLARACOES lista_decl | /* vazio */ ; lista_decl: lista_decl decl | decl ; decl : dec_const | dec_tipo | dec_var ; dec_const : DEFINA ID_CONST valor ; valor : VALOR_INTEIRO | VALOR_REAL | VALOR_CARACTER | VALOR_LOGICO | VALOR_CADEIA | funcoes ; funcoes : SORTEIA '(' exp ')' | TEMTECLA '(' ')' | LETECLA '(' ')' ; dec_tipo: DEFINATIPO tipo ident | DEFINATIPO ESTRUTURA ID lista_dec_var FIMESTRUTURA ; tipo : INTEIRO | REAL | CARACTER | LOGICO | CADEIA ; ident : ID | ID '[' ID_CONST ']' | ID '[' ID_CONST ']' '[' ID_CONST ']' | ID '[' VALOR_INTEIRO ']' | ID '[' VALOR_INTEIRO ']' '[' VALOR_INTEIRO ']' ; lista_dec_var : lista_dec_var dec_var | dec_var ; dec_var : tipo lista_var ; lista_var : lista_var ',' ident | ident ; lista_cmdo : lista_cmdo cmdo | cmdo ; rsenao : SENAO lista_cmdo | /* vazio */ ; cmdo : SE '(' exp ')' ENTAO lista_cmdo rsenao FIMSE | ENQUANTO '(' exp ')' FACA FIMENQUANTO | REPITA lista_cmdo ENQUANTO '(' exp ')' | PARA ID ATRIB ATE exp PASSO exp lista_cmdo FIMPARA | LEIA '(' lista_id2 ')' | ESCREVA '(' lista_saida ')' | ID ATRIB exp | ID '[' exp ']' ATRIB exp | ID '[' exp ']' '[' exp ']' ATRIB exp | LIMPA '(' ')'

80

| POSXY '(' exp ',' exp ')' | AGUARDA '(' ')' | PAUSA '(' exp ')' ; lista_id2 : lista_id2 ',' ident2 | ident2 ; lista_saida : ident2 ',' lista_saida | valor ',' lista_saida | ident2 | valor ; exp: '-' exp %prec MENOS_UNARIO | exp POT exp | exp RAIZ exp | exp '*' exp | exp '/' exp | exp DIV exp | exp MOD exp | exp '+' exp | exp '-' exp | '(' exp ')' | valor | ID_CONST | ident2 | exp MAIOR exp | exp MENOR exp | exp MAIOR_IGUAL exp | exp MENOR_IGUAL exp | exp IGUAL exp | exp DIFERENTE exp | exp OU exp | exp E exp | NAO exp ; ident2: ID | ID '[' exp ']' | ID '[' exp ']' '[' exp ']' ;

81

III REGRAS TRADICIONAIS DE USO DE REGISTRADORES

• Usa-se EAX para passar dados para um procedimento e para retornar dados do

procedimento para o código que fez a chamada. As APIs do Windows também usam

EAX para retornar um valor para o chamador. EAX também deveria ser usado, na

medida do possível, para receber e para transferir dados de e para a memória porque são

ligeiramente mais rápidos que outros registradores;

• Usa-se o registrador EDX como um backup para EAX se o último estiver em uso.

• Usa-se o registrador ECX como contador. JECXZ é uma instrução especial que indica se

o valor de ECX é zero e a série de instruções LOOP, SCAS e MOVS usam ECX como

contador.

• Usa-se EBX para armazenar dados em geral ou para endereços de memória, por

exemplo: MOV EAX,[EBX] ou MOV [EBX],EDX.

• Usa-se ESI quando precisar ler a memória, por exemplo: MOV EAX,[ESI], e EDI

quando precisar escrever na memória, por exemplo: MOV [EDI],EAX.

• Usa-se qualquer um dos registradores como base ou registrador index em instruções de

memória complexas, por exemplo: MOV EAX,[MemPtr+ESI*4+ECX].

• Nunca se usa ESP para outra coisa que não seja um ponteiro da pilha, a não ser que o

programa não tenha absolutamente nenhuma atividade de pilha. Neste caso você pode-se

salvar o valor de ESP na memória e restaurá-lo antes de voltar para a rotina chamadora.

• Tradicionalmente o EBP é usado para endereçar dados locais na pilha em rotinas de

callback. O EBP pode ser usado como um registrador geral na programação Windows,

mas deve tomar muito cuidado se estiver usando frames de pilha ou dados locais. Isto

porque parâmetros de frames de pilha e dados locais são endereçados usando o valor

positivo ou negativo de EBP. Depois do EBP ter sido alterado, os parâmetros e os dados

locais não podem ser acessados até que o valor original de EBP tenha sido restaurado.

• Se for necessário usar os registradores comuns para armazenar uma informação de 64

bits use EDX:EAX, onde EDX guarda os bits mais significantes.

82

IV SINTAXE DO PORTUGUÊS ESTRUTURADO (PORTUGOL)

Sintaxe do português estruturado (Portugol) 1. Estrutura Geral de um Programa Sintaxe: programa <nome_do_programa> declaracoes <definição_de_constantes> <declarações_de_tipos_compostos> <declarações_de_variáveis> inicio <instruções_do_programa> fim 2. Definição de Constantes Sintaxe: defina <nome_da_constante> <valor> Exemplo: defina N 5 defina MAXIMO 30 // Usar letras Maiúsculas 3. Declaração de Tipos 3.1. Tipo Vetor Sintaxe: definatipo <tipo_basico> <nome>[tamanho] Exemplo: definatipo caracter endereco[50] definatipo inteiro notas[35] 3.2. Tipo Matriz Sintaxe: definatipo <tipo_basico> <nome>[linhas][colunas] Exemplo: definatipo caracter nome_alunos[35][30] definatipo inteiro tabuada[9][9] 3.3. Tipo Estrutura Sintaxe: definatipo estrutura <nome> <tipo> <nome> ... <tipo> <nome> fimestrutura Exemplo: definatipo estrutura pessoa caracter nome[30] inteiro idade caracter sexo real salario logico sindicato fimestrutura 4. Declaração de Variáveis 4.1 Tipos Básicos - inteiro, caracter, logico e real Sintaxe: <tipo> <nome_ou_lista_de_nomes> Exemplo: inteiro valor real valor, total caracter sexo, opcao logico achou 4.2 Vetores Sintaxe: <tipo> <nome>[tamanho] Exemplo: inteiro valor[10] caracter nome[30] 4.3 Matrizes Sintaxe: <tipo> <nome>[linhas][colunas]

83

Exemplo: inteiro mat[10][20] real tabela[5][5] 5. Operadores Símbolos: Lógicos: E, OU, NAO Relacionais: >, <, >=, <=, =, != Aritméticos: +, -, *, /, ^(potência), :: (raiz), DIV, MOD Exemplo: (3^2) > (4::2) // três ao quadrado maior que raiz quadrada de quatro (2^3) = (512::3) // dois ao cubo é igual a raiz cúbica de 512 6. Instruções do Programa 6.1. Atribuição Sintaxe: <nome_da_variável> <- <valor ou variável ou expressão> Exemplo: a <- 5 b <- a b <- a-b 6.2. Entrada de Dados Sintaxe: leia (<variável_ou_lista>) Exemplo: leia (a) leia (a,b,c) 6.3. Saída de Dados Sintaxe: escreva (<literal_ou_variável_ou_lista>) Exemplos: escreva ("Digite um Valor") escreva (a) escreva (a,b,c) 6.4. Desvio Condicional 6.4.1 Desvio Condicional Simples Sintaxe: se (<condicao>) entao <instruções> fimse Exemplo: se (x>5) entao x <- 7 fimse 6.4.2 Desvio Condicional Composto Sintaxe: se (<condição>) entao <instruções> senao <instruções> fimse Exemplo: se (a=b) entao a <- 1 senao a <- 2 fimse 6.4.3 Estrutura Escolha Sintaxe: escolha (<variavel>) caso <valor_constante> : <instrucoes> caso <valor_constante > : <instrucoes> outrocaso : <instrucoes> fimescolha Exemplo: escolha (dia) caso 1: escreva ("Domingo") caso 7: escreva ("Sábado") outrocaso : Escreva "Dia de útil"

84

fimescolha 6.5. Laços de Repetição 6.5.1. Com Teste Lógico no Início Sintaxe: enquanto (<condição>) faca <instruções> fimenquanto Exemplo: enquanto (x < 5) faca x <- x + 1 fimenquanto 6.5.2. Com Teste Lógico no Final Sintaxe: repita <instruções> enquanto (<condição>) Exemplo: repita x <- x + 1 enquanto (x < 5) 6.5.3. Com Variável de Controle Sintaxe: para <variavel><-<valor_inicial> ate <valor_final> passo <valor> <instruções> fimpara Exemplo: para i <- 1 ate 50 passo 1 escreva (i) fimpara 7. Subrotinas 7.1. Procedimentos Sintaxe: procedimento <nome> (<tipo><parametro_1>;...; <tipo><parametro_n>) declaracoes <declarações_de_variáveis_locais> inicio <instruções> fim Exemplo: procedimento bom_dia ( caracter nome[30] ) declaracoes inteiro i inicio escreva ("Bom Dia") para i <- 1 ate 30 passo 1 escreva (nome[i]) fimpara fim 7.2. Funções Sintaxe: <tipo> funcao <nome> (<tipo><parametro_1>;...;<tipo><parametro_n>) declaracoes <declarações_de_variáveis_locais> inicio <instruções> retorna (<variável ou valor ou expressão>) fim Exemplo: inteiro funcao fatorial (inteiro n) declaracoes inteiro i, fat inicio fat <- 1 para i <- 1 ate n passo 1 fat <- fat * i fimpara

85

retorna (fat) fim 7.3. Passagem de Parâmetros 7.3.1. Por Valor Sintaxe: <subrotina> (<tipo> <nome>) Exemplo: inteiro funcao dobro ( inteiro val ) procedimento dobro ( inteiro val ) 7.3.2. Por Referência Sintaxe: <subrotina> (<tipo> *<nome>) Exemplo: inteiro funcao dobrar ( inteiro *val ) procedimento dobrar ( inteiro *val )