UNIVERSIDADE DO VALE DO ITAJAÍ CENTRO DE CIÊNCIAS ...siaibib01.univali.br/pdf/Marcos Roberto...
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.
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 )