Desenvolvendo com OpenMSX

Acredito que toda, ou grande parte da comunidade MSX conheça o excelente emulador OpenMSX, principalmente os usuários de Linux e MacOSX.

Bom, confesso que depois de voltar a ativa no mundo MSX, eu utilizo uma máquina real em 100% do meu tempo desenvolvendo coisas, até porque não jogo no MSX há anos. Entretanto desde o último ano, não tenho conseguido tempo para estar à frente da minha máquina de desenvolvimento preferida, que é o meu MSX TurboR A1ST com 1MB de RAM, IDE Tecnobytes com 8GB de Compact Flash, Expansor de slots ACVS, placa de rede Obsonet Tecnobytes, dentre outras coisas que estão conectadas nessa máquina.

Desde o início dos anos 2000, senão me engano lá por 2002 ou 2003, quando a “batalha de emuladores” era mais intensa entre os RuMSX, ParaMSX, fMSX e seus mais diversos sabores, duas opções “definitivas” começaram a se fundamentar no meio da comunidade MSX mundial e hoje reinam absolutas em sua preferência.

Estou falando do BlueMSX e do OpenMSX.

BlueMSX LogoBlueMSX logo

Usei muito o BlueMSX, principalmente quando jogava no emulador, mas como desenvolvedor, percebi desde cedo que o OpenMSX se tratava de um projeto mais aberto e poderoso no que diz respeito a sua utilização no desenvolvimento de novos projetos, sejam de hardware ou de software, além do fato de seu projeto ser extremamente bem feito, com um código muito bem escrito e documentado, além de ser multiplataforma.

openmsxOpenMSX logo

OpenMSX como plataforma de desenvolvimento.

Além de já ter sido vastamente utilizado como plataforma para desenvolvimento de hardware, em projetos como o MSXARM, o OpenMSX é uma excelente plataforma de desenvolvimento de software, principalmente pelo fato do mesmo emular quase todos (ou todos) os dispositivos que podem ser conectados a um MSX real, como os famosos LaserDiscs, por exemplo.

Por esse motivo e também incentivado pela série que vem sendo escrita no site de Javier Lavanderia, sobre programação para MSX utilizando MSX C, eu decidi que era hora de testar o nível de compatibilidade do OpenMSX, bem como todas as capacidades desse emulador, no desenvolvimento de novos projetos para MSX.

OpenMSX console

O OpenMSX possui um console embutido, que você ativa toda vez que pressiona F10 e a partir daí, em paralelo com a emulação, é possível interagir com a máquina utilizando um conjunto extenso de comandos disponíveis no emulador, conforme pode ser visto nesse link aqui.

Inclusive, via comandos Tcl, você pode controlar o emulador utilizando uma interface através de alguns meios de comunicação disponíveis no emulador, como pipes, named pipes, a saída padrão (stdio), TCP Sockets e UNIX domain sockets, ou seja, é um emulador modular ao extremo, permitindo até que você escreva seu próprio launcher, caso não esteja satisfeito com o OpenMSX Catapult, que para mim poderia ter sua interface tão poderosa e até mais do que a do emulador BlueMSX por exemplo.

Abaixo segue um excelente vídeo demonstrando essa integração do OpenMSX com Tcl/Tk, através do console de Tcl embutido nesse emulador.

Integrando OpenMSX com Tcl

Preparando seu disco.

Apresentado o console do OpenMSX, agora vale a pena citar um comando muito especial e poderoso, o diskmanipulator.

Resumindamente, o diskmanipulator é capaz de criar imagens de disco bem como gerenciar seu conteúdo. As imagens criadas são compatíveis com FAT12 e FAT16 e poderão ser utilizadas no OpenMSX como floppy disks comuns e também como hard disks  IDE.

OpenMSXOpenMSX console

A página de instruções do diskmanipulator contém diversas explicações de como criar imagens para discos flexíveis de 720Kb, discos rígidos com partições de FAT12 e FAT16, bem como instruções de como importar dados para esses discos a partir do disco local da máquina host em que o OpenMSX está sendo executado.

Como o objetivo desse post é justamente apresentar o OpenMSX como uma plataforma real para desenvolvimento de softwares para MSX, então preparei um disco com duas partições, sendo a primeira FAT12 e outra FAT16, com diversos softwares específicos para o desenvolvimento de software no MSX. Segue abaixo a lista do que atualmente está contido no disco criado e pronto para usar:

DRIVE A: (FAT12)

MSXDOS2 operating system compatible

[DEV]     (Editors, compilers, assemblers and general purpose development tools)
—|———[M80] (Microsoft/ASCII – Z80 Assembler)
—|———[DEVPAC80] (HiSoft Z80 Assembler)
—|———[TP33] (MSX Computer club Turbo Pascal 3.3 compiler)
—|———[TP3] (Borland Turbo Pascal 3.00A compiler)
—|———[TED] (MSX2.0 and higher Text Editor)
—|———[APED] (MSX2.0 and higher Text Editor)
—|———[TOR] (MSX1&2 text editor)

DRIVE B: (FAT16)

[PROJECTS]   (Projects source code)
——-|—-[PASCAL] (Turbo Pascal compatible source code)
——-|———|
——-|———|——[PCX]  (Source code for PCX file format handling)
——-|———|——[INLASS] (Turbo Pascal Inline assembler)
——-|———|——[MKINL] (Another Turbo Pascal Inline assembler)
——-|———|——[PMLINK] (Another Turbo Pascal Inline assembler)
——-|———|——[INLINE] (Another Turbo Pascal Inline assembler)
——-|———|——GRAPH.INC (Graphical library for MSX)
——-|———|——PARSE.INC (Command line parameter parser)

Lembrando que essa é apenas uma configuração de disco inicial em que ainda estou trabalhando, portanto no futuro certamente serão adicionados a esse hard disk, os pacotes do MSXC (1 e 2), HiSoftC, dentre outras excelentes ferramentas destinadas ao desenvolvimento de software para o MSX.

Baixando e usando o hard disk

O harddisk citado acima, pode ser encontrado nessa URL aqui, e de fato é um compartilhamento de minha pasta do DropBox que deixo disponível à comunidade. Os arquivos dessa URL serão atualizados com o tempo, visando a adição de novos compiladores e ferramentas de desenvolvimento para a plataforma MSX.

Nesse mesmo link você encontrará dois arquivos, os quais descrevo abaixo:

dev_hd.dsk -A imagem de 64MB com as duas partições (A: FAT12 e B: FAT16) pronta para uso no OpenMSX;
dev_hd.tcl  – Script Tcl utilizado para dar boot no OpenMSX, usando a imagem de disco acima;
Passos para utilização do disco no OpenMSX:
  1. Baixe esses dois arquivos em qualquer diretório de sua máquina. O único ponto de atenção a ser tomado é que esses dois arquivos devem estar no mesmo diretório pois o script TCL <dev_hd.tcl>, busca pela imagem do hard disk no mesmo diretório em que o script está sendo executado;
  2. Abra o OpenMSX utilizando a sua configuração de máquina preferida;
  3. Com o OpenMSX rodando, pressione a tecla F10, para invocar o console Tcl do OpenMSX;
  4. Já na tela do console, digite o comando abaixo:
    source <caminho_onde_você_baixou_a_imagem_de_disco_e_o_arquivo_tcl/dev_hd.tcl>
  5. Pressione <ENTER>
  6. Divirta-se 🙂

Pronto, o seu OpenMSX irá reiniciar já fazendo boot com a imagem de hard disk que você baixou no link acima.

Isso é apenas uma pequenina amostra do potencial desse que, para mim, é o melhor e mais completo emulador da plataforma MSX, na atualidade.

Fiquem antenados nos novos updates da imagem de disco, que disponibilizei no link do Dropbox.

[]’s
PopolonY2k

Referência na internet

OpenMSX home
http://openmsx.org/

Linux (Wikipedia)
https://en.wikipedia.org/wiki/Linux

MacOSX (Wikipedia)
https://en.wikipedia.org/wiki/OS_X

RuMSX home
http://www.lexlechz.at/en/software/RuMSX.html

ParaMSX (MSX.org)
http://www.msx.org/news/emulation/en/paramsx-048b

fMSX home
http://fms.komkon.org/fMSX/

BlueMSX home
http://www.bluemsx.com/

MSXARM primeiro teste de hardware (PopolonY2k Rulezz)
http://www.popolony2k.com.br/?p=2074

LaserDisc (Wikipedia)
https://en.wikipedia.org/wiki/LaserDisc

Relearning MSX (Lavanderia.net)
http://www.lavandeira.net/relearning-msx/

OpenMSX console commands.
http://openmsx.org/manual/commands.html

Tcl Tk home
https://www.tcl.tk/

Controlling OpenMSX
http://openmsx.org/manual/openmsx-control.html

Pipe communication
http://www2.cs.uregina.ca/~hamilton/courses/330/notes/unix/pipes/pipes.html

Named Pipes (Wikipedia)
https://en.wikipedia.org/wiki/Named_pipe

Network socket (Wikipedia)
https://en.wikipedia.org/wiki/Network_socket

UNIX domain sockets
https://en.wikipedia.org/wiki/Unix_domain_socket

DiskManipulator (OpenMSX)
http://openmsx.org/manual/diskmanipulator.html

PopolonY2k’s OpenMSX development hard disk (DropBox sharing)
https://www.dropbox.com/sh/smo2dz1kwl6wvfm/AAAYfFbZGjoksmoIRE4TlwPGa?dl=0

DropBox website
http://www.dropbox.com

10 regras da NASA para desenvolver código seguro.

Recentemente postei nas comunidades GDMSX do FaceBook e do G+ um texto bem interessantante sobre algumas dicas da NASA de como desenvolver código seguro.

Isso é muito interessante principalmente para aqueles que desenvolvem código para sistemas/dispositivos que nunca pode se dar ao luxo de uma exceção não tratada ou um core dump, comuns no mundo UNIX ou os famosos GPF’s bem conhecidos para programadores do mundo Windows.

Alguns membros da comunidade GDMSX me pediram para traduzir, então eu decidi fazer uma tradução livre, com algumas considerações pessoais que estão marcadas no texto como “(*) Notas do tradutor” 🙂 .

Vamos ao texto.

10 regras da NASA para desenvolver código seguro.

NASA Computers

NASA Computers

A NASA tem escrito software de missão crítica para a exploração espacial por décadas e agora a organização está transformando esses guias de programação em um padrão de desenvolvimento de software da indústria.

O laboratório de propulsão a jato da NASA para softwares confiáveis (JPL), recentemente publicou um conjunto de guias de programação (code guidelines), “Potência de 10 – Regras para o desenvolvimento seguro de código crítico“. O autor do texto, o cientista chefe Gerard J. Holzmann, explicou que a quantidade de codificação existente é inconsistente e cheia de regras arbitrarias, raramente permitindo tarefas essenciais como verificação de conformidade de código baseado em ferramentas de checagem de código. Os guias existentes, ele disse, inundam os programadores com regras vagas, diminuindo a qualidade do código da maioria das aplicações críticas.

“Projetos mais sérios de desenvolvimento de software usam guias de codificação,” escreveu Holzmann. “Esses guias destinam-se a firmar as regras básicas para o qual o software está sendo escrito: como ele deveria ser estruturado e quais características da linguagem deveriam e não deveriam ser usadas. Curiosamente, existe um pouco de consenso sobre o que é um bom padrão de codificação.”

 Holzmann definiu 10 regras rígidas para o desenvolvimento de software, tendo em mente a segurança do código. As regras foram especificamente escritas levando em consideração a linguagem C (uma linguagem recomendada pela NASA devido a sua longa história no desenvolvimento de software crítico e seguro e também pelo extensivo suporte de ferramentas a essa linguagem como por exemplo analisadores de código, depuradores, ferramentas de testes, dentre outras), embora essas regras possam ser facilmente generalizadas para codificação em qualquer outra linguagem, principalmente as que tem forte similaridade com C no nível de estruturação, como é o caso de Pascal e suas variantes.

  1.  Restrinja toda a construção de seu código a um fluxo de controle muito simples. Não use declarações GOTO, construções com setjmp ou longjmp, ou recursividade direta ou indireta;

    (*) Nota do tradutor:
    Quanto ao GOTO não é preciso nem escrever muito a seu respeito pois um de seus “males”, que é o de deixar o código confuso através de saltos incondicionais, já são amplamente conhecidos ao longo dos anos. O mesmo acontece com a dupla setjmp/longjmp que causam o mesmo efeito do GOTO, mas infelizmente esse recurso ainda é muito defendido por programadores experientes de C que muitas vezes se recusam a abandoná-los.
    Quanto à recursividade direta ou indireta, realmente essas tornam o código mais complexo entretanto por diversas vezes o seu código pode se tornar mais eficaz do que utilizando métodos tradicionais para resolução de um problema. Um exemplo é o algoritmo de multiplicação reconhecidamente veloz conhecido como algoritmo de Karatsuba e que é recursivo.
    Eu particularmente sugiro que se utilize métodos recursivos pequenos e de baixa complexidade, pois dessa forma pode-se evitar possíveis pontos de falhas que são invisíveis na maioria das vezes em tempo de desenvolvimento, mas que são comuns em uma situação de missão crítica.
    Quando se optar por implementar métodos recursivos, tenha em mente que a cobertura de seus testes unitários deverá ser extremamente abrangente a ponto de se testar as mínimas situações de falha.

  2. Todos os loops devem ter um limite superior fixo. Isso deve ser trivial para uma ferramenta de checagem que prove, estaticamente, que um dado limite no número de iterações não pode ser excedido. Se o limite do loop não puder ser comprovado estaticamente, a regra é considerada violada. Caso os limites sejam violados, o método deverá disparar um assert a ser verificado pelo chamador.

    (*) Nota do tradutor:
    Essa regra pode ser checada através da implementação de testes unitários com os devidos cenários mapeados.

  3. Não use alocação dinâmica de memória após a inicialização.

    (*) Nota do tradutor
    Realmente alocação dinâmica em linguagens que não são gerenciadas por um Garbage Collector é algo crítico e que deve ser utilizado de maneira cuidadosa, principalmente para principiantes no desenvolvimento de software. No artigo original, o autor cita diversos dos problemas conhecidos em se utilizar alocação dinâmica como a não liberação de recursos corretamente, o que causam os famosos vazamentos de memória ou memory leaks, e também cita sobre o cuidado de não ultrapassar os limites da memória alocada, esse último sendo também um problema quando se está usando memória alocada estaticamente em C.
    Entretanto existem casos que a alocação dinâmica é necessária e não há como escapar, nesses casos o mais comum a se fazer é alocar o buffer desejado em um ponto único, fazer toda e qualquer referência a esse buffer através de ponteiros passados para as funções que irão manipulá-lo e por fim liberá-lo nesse mesmo ponto único em que foi alocado, de preferência. Dessa forma você restringe toda a construção de seu código a um fluxo de controle muito simples, que é exatamente o primeiro item sugerido por esse guideline.
    Muitos dos desenvolvedores de softwares novatos sequer tem conhecimento de problemas de fragmentação de memória, decorrentes do abuso excessivo de alocação e liberação dinâmica de memória, principalmente em dispositivos de missão crítica que geralmente são hardwares embarcados dedicados e com recursos limitadíssimos de memória e armazenamento.

  4. Nenhuma função deve ser tão extensa que não possa ser impressa em uma única folha de papel (em uma referência ao formato padrão com uma linha por instrução e uma linha por declaração). Tipicamente isso significa não mais do que 60 linhas de código por função.

    (*) Nota do tradutor
    Manter o código com poucas linhas por função é sempre uma excelente prática pois facilita na manutenção do mesmo, entretanto algumas vezes principalmente em se tratando de código que gerencia máquinas de estado complexas, é melhor que a função fique um pouco maior do que as 60 linhas sugeridas ao invés de quebrá-las em mais funções para que comporte as 60 linhas, pois se ganha em performance, uma vez que se reduz o custo das passagens de parâmetros entre funções, ainda mais em se tratando de passagem de parâmetros de grandes estruturas por valor, que nesse caso é também uma péssima prática e que deveria ser substituída por passagem por referência ou ponteiro. Em linguagens orientadas a objeto, a regra de 60 linhas por método (função) é mais plausível e fácil de se atingir por conta da capacidade de encapsulamento dessas linguagens.

  5. A quantidade de asserts por função deve ser de no mínimo duas por função. Asserts não devem causar nenhum efeito colateral no código e deveriam ser definidos como testes booleanos.

    (*) Nota do tradutor
    Linguagens com um rico pré-processador como são C e C++ são propensas a inserção de asserts que estarão ativos dependendo do modelo de compilação em que o  binário/executável foi gerado, com isso pode-se gerar compilações de software específicas para testes.

  6. Dados devem ser declarados no menor nível de escopo possível.

    (*) Nota do tradutor
    O uso de variáveis globais é algo que vem sendo desestimulado no decorrer dos anos, pois é reconhecido que manter os dados encapsulados em escopos cada vez menores é um grande facilitador na hora de se descobrir bugs ou realizar qualquer tipo de manutenção ou melhorias no código, então declarar variáveis específicas de cada escopo é uma boa prática de programação.

  7. Cada função chamadora deve checar o retorno de funções não void (que retornam valores) e a validade dos parâmetros devem ser checadas dentro de cada função.

    (*) Nota do tradutor
    Isso é algo óbvio……bom, pelo menos deveria ser 🙂 .

  8. Uso de pré-processador deve ser limitado a inclusão de arquivos header e definição simples de macros. Macros complexas como as recursivas e com listas de argumentos variáveis, devem ser evitadas.

    (*) Nota do tradutor No texto original o autor descreve que o uso de macros de compilação condicional é algo dúbio entretanto não pode ser sempre ignorado. Eu concordo plenamente com a ideia de que não pode ser ignorado, uma vez que sempre tivemos diversas plataformas diferentes e dominantes na história da computação moderna, e o uso do pré-processador para se ter suporte a compilação condicional é uma excelente técnica que tem possibilitado manter um código portátil entre as diversas plataformas que vão desde os diversos sistemas operacionais disponíveis em PC’s até aos diversos dispositivos mobile existentes hoje.

  9. O uso de ponteiros deve ser restrito. Especificamente, não mais do que um nível de deferenciação é permitido. Operações de deferenciação de ponteiro não podem estar escondidas em macros ou dentro de declarações typedef. Ponteiros de função não são permitidos.

    (*) Nota do tradutor.
    Talvez esse seja o mais polêmico item do guia. Operações com ponteiros são algumas vezes complexas dependendo do nível de deferenciação. São raros os casos em que é necessário o uso de ponteiros duplos ou triplos, entretanto pode ser evitado e sugiro que seja evitado.
    Quanto ao uso de macros para deixar a deferenciação mais “limpa e clara”, para mim isso tem um nome e se chama “armadilha”. Fuja de quaisquer construções que “escondam” a complexidade de alguma operação, e essa regra não deve ser considerada apenas nas operações de ponteiros.
    Entretanto há ressalvas quanto ao uso de typedefs na declaração de um tipo ponteiro,  como por exemplo as definições de ponteiros para funções amplamente utilizadas nas API’s do Windows e UNIXes em geral, nesse caso não há como escapar uma vez que esses sistemas operacionais fazem uso extensivo de callbacks para proporcionar respostas a eventos requeridos e necessários para a aplicação do usuário.
    Quanto a proibição de ponteiros para função, talvez para a maioria das aplicações comuns de desktop seu uso realmente não seja necessário, entretanto para aplicações de missão crítica embarcadas como as que utilizam algum kernel real-time, como por exemplo o uC de Jean Labrosse e que fazem extenso uso de ponteiros para função para implementar as diversas tasks da aplicação do usuário, o uso de ponteiro de função é algo bem comum.
    A construção de softwares kernel real-time é repleta de ponteiros de função, até mesmo para proporcionar uma abstração de tasks, timers dentre outras estruturas típicas desse tipo de software.

  10. Todo código deve ser compilável desde o primeiro dia de desenvolvimento, com todos warnings do compilador ativados. Todo código deve compilar com essa configuração, sem gerar nenhum warning ou erro. Todo código deve ser checado diariamente com pelo menos um – de preferência mais do que um – excelente analisador de código estático (o que ele cita como analisador estado-da-arte), e a análise deve passar sem warning algum.

    (*) Nota do tradutor
    Quanto ao detalhe dos warnings de compilação, acredito que já seja bem comum em grandes projetos, entretanto a parte do analisador “estado-da-arte”, não creio que sequer mercados corporativos em países avançados a utilizem, exceto empresas de altíssima tecnologia que dependam de que seus softwares funcionem 24×7 ou de forma embarcada em algum dispositivo.

 Holzmann incluiu diversos comentários no documento original para cada uma das regras descritas acima, mas a essência do documento é que quando as regras forem utilizadas em conjunto, garantam um fluxo claro e transparente que tornem fácil a construção, testes e análise de código amplamente aceitos como livres de falhas. A JPL tem desenvolvido softwares automatizados para missões espaciais como a Mars Curiosity and a Voyager, e o laboratório já está usando essas regras em uma base experimental para escrever software de missão crítica.

Holzmann acredita que seguir as regras da NASA minuciosamente, pode diminuir a carga sobre os desenvolvedores e levar a uma melhor segurança e clareza do código.

“Se as regras parecerem draconianas no inicio, tenha em mente que elas foram criadas para que se possa checar códigos onde a sua vida pode depender muito de que haja precisão: código utilizado para controlar o avião em que você voa, a usina nuclear a poucas milhas de onde você vive, as aeronaves que carregam os astronautas em órbita”, escreve ele.

“As regras agem como o cinto de segurança em seu carro: Inicialmente eles parecem ser um pouco desconfortáveis, mas depois de um tempo seu uso se torna natural e não usá-los se torna inimaginável.”

O texto acima foi traduzido do original que pode encontrado no link do site SDTimes abaixo:

http://sdtimes.com/nasas-10-rules-developing-safety-critical-code/

[]’s
PopolonY2k

MSX Sunrise-like IDE devices programming

Since I wrote my first post about IDE devices (Sunrise-like) on MSX computers, I’ve been thinking to write a new post about this subject.

Well, the MSX community already knows about my last efforts to develop disk tools for MSX computers and how I’m spending some energy to build a framework that will help the development of new software compatible with IDE (Sunrise-Like) devices.

In the last year I finished my first project using MSX IDE standard. The result was the creation of a disk editor known as MSXDUMP.

In conjunction with MSXDUMP, an important part of the framework has been developed. It is responsible to perform the access between the high level software layer and the low level hardware layer.

Of course this piece of my framework was possible thanks to the work done by Sunrise team, long time ago when they wrote the IDE BIOS containing all low level calls available to the software programmer.

Also it is possible to write software for Sunrise-like IDE devices using direct access through I/O ports dedicated to this device, but in this case the work would be very hard and unnecessary because the IDE BIOS routines cover everything needed by the device.

For this reason, this article won’t cover direct access.

A world without standard

In the 80’s the software industry was very primitive and the software developers seemed to be living in a wild jungle with several kinds of hardware to program and control. With different hardware standards those days were very hard for developers, considering the lack of software standardization in this primitive industry.

Everything was focused on hardware for the most part of existing standards found in the 80’s. The MSX was the first attempt, in the 8 bit world, to join the hardware with the software to act like the operating systems do today.

Then the concept of BIOS functions, generally introduced through a cartridge, was born with MSX computers and with it came the almost infinite expansibility of the MSX standard.

The Sunrise IDE BIOS

When I got my first Tecnobytes ATA/IDE interface, that was a Sunrise compatible interface manufactured in Brazil by Tecnobytes Classic Computers, for the first time I could understand the workings and limitations of this device.

Tecnobytes ATA IDE Sunrise-like interface

The big part of the device’s limitation is related to the software’s inherent lack for this device. This was the reason why I started to study Sunrise compatible device internals.

Fortunately the current Brazilian MSX community has been the best since MSX was introduced here in the 80’s and thanks to the internet we can find all the information needed to know most details about this device.

I want to cite and thank Ricardo Oazem of Tecnobytes Classic Computers who provided me all technical details including documents and source code about I/O ports and BIOS functions of Sunrise IDE.

All technical information found in this article is compatible with any Sunrise-like IDE devices, like, Sunrise, Tecnobytes and Carchano IDE.

IDE Sunrise-like interface by Carchano.

IDE Sunrise-like interface by Carchano.

Inside the BIOS

After a long period studying the source code of the Sunrise BIOS and the documents found on the Sunrise website, I finally understood how it works in details.

Functions versioning

Internally the IDE has some addresses dedicated to versioning and a IDE signature, like described below.

Const
      ctDefaultWrkspcPage     = 3;     { Default workspace slot page }
      ctIDENotFound           = 255;   { IDE not found }
      ctBIOSMajorVerAddr      = $7FB6; { BIOS major version address }
      ctBIOSMinorVerAddr      = $7FB7; { BIOS minor version address }
      ctBIOSRevisionAddr      = $7FB8; { BIOS revision version address }
      ctIDESignatureAddr      = $7F80; { IDE signature address }

(* Workspace BIOS Call Routines *)
      ctBIOSGetDriveFieldAddr = $7FBF; { Get drive field address }

The first operation to do is to discover what slot the IDE is connected to. This can be done using the address &H7F80 (IDE signature address) for each slot/sub-slot of MSX, looking for the bytes ‘I’ followed by ‘D’ and ‘#’, until you find it.

After discovering the slot that the IDE is connected to, you can get the BIOS version used by the interface. This is easily found at following addresses.

Const
      ctBIOSMajorVerAddr      = $7FB6; { BIOS major version address }
      ctBIOSMinorVerAddr      = $7FB7; { BIOS minor version address }
      ctBIOSRevisionAddr      = $7FB8; { BIOS revision version address }

Retrieving the BIOS version isn’t a mandatory step but is a good idea to check it. If you’re not sure about the existence of a feature provided by the BIOS of your device so this checking will be useful.

The Drive Field structure.

MSX computers are limited devices with low resources capacity, for this reason the MSX-DOS can handle just 8 simultaneous logical drives. The Sunrise IDE allocates 6 of these 8 logical drives, so the maximum capacity of simultaneous connected IDE devices is 6.

This allocated area for these 6 drives is a structure known as Drive Field. Each drive field contains the following structure below.

(*
 * Drive field definition. The size of drive fields is variable
 * and can change according BIOS version. The current rule is written
 * like below:
 * 8 for BIOS 1.9x and 2.xx;
 * > 8 for BIOS 3.xx and higher;
 * See idesys.txt for details
 *)
Type TDriveField = Record
  nDeviceCodeByte     : Byte;
  n24PartitionStart,                        { 24Bit absolute sector number }
  n24PartitionLenght  : TInt24;             { 24Bit sector (count - 1) }
  nAdditionalPartInfo,                      { Addition partition info }
  (* The two bytes below is reserved to BIOS 3.xx or higher *)
  nPartitionStart,                          { Partition start bit 24 to 31 }
  nPartitionLength    : Byte;               { Partition (lenght - 1) 24 to 31 }
End;

As you can see there are two bytes at the end of the structure above and in the “official” documentation of IDE, it says “RESERVED TO BIOS 3.xx OR HIGHER”. I suppose that these two bytes was put there for future use in FAT16 and FAT32 native support in the Sunrise IDE devices.

The WorkSpace

The Sunrise specification defines  an area that contains all the 6 DriveFields structures managed by the interface, plus another structure known as the DeviceInfoBytes, followed by an unused free space of 18 bytes.

This structure formed by 6 DriveFields + DeviceInfoBytes + Free space (18 bytes) is known as the IDE WorkSpace.

The IDE WorkSpace is filled at the machine start up and loads the first 6 partitions available to the IDE connected interface.

Const
(*
 * IDE interface Workspace allocate at boot process.
 * More details check idesys.txt file at this library
 * directory.
 *)
Type TIDEWorkspace = Record
  ptrDriveField      : Array[0..ctDriveFieldSize] Of PDriveField;
  ptrDeviceInfoBytes : PDeviceInfoBytes;
  ptrFreeSpace       : PFreeSpace;
End;

NOTE: It is possible to exchange the partition content pointed to a different DriveField just using the IDEPAR command line utility.
In a hypothetical situation where your drive letter B: points to the second partition in the disk, you could point this drive B: to another formatted partition higher than 6 crossing the limits imposed by the Sunrise interface.
This could be done using the IDEPAR utility, but it use is not covered by this article.

The DeviceInfoBytes structure

The DeviceInfoBytes structure is an important and useful area of the IDE WorkSpace because it contains some information about the physical device that is connected in the interface, like the number of heads from device master and slave, maximum of cylinders by sector on both, master and slave connected disks.

The structure is defined as shown below:

(*
 * Device info bytes definition.
 * 6 bytes for BIOS 1.9x and 2.xx.
 *)
Type TDeviceInfoBytes = Record
  nNumOfHeadsMaster,             { For ATA Devices }
  nNumOfHeadsSlave,              { For ATA Devices }
  nNumSectorsCylMaster,          { For ATA Devices }
  nNumSectorsCylSlave,           { For ATA Devices }
  nDeviceTypeMaster,
  nDeviceTypeSlave,
  nUndefined           : Byte;   { Undefined yet - don`t use them }
End;

Type PDeviceInfoBytes = ^TDeviceInfoBytes;  { TDeviceInfoBytes pointer type }

The new BIOS functions

The Sunrise interface introduces at least 4 new BIOS function calls that can be used by applications compatible with Sunrise devices.

ATAPI BIOS calls

The two first functions are listed below:

(* Sunrise-like IDE BIOS calls *)

Const     ctBIOSSelectATAPIDevice = $7FB9; { Select master or slave device }
          ctBIOSSendATAPIPacket   = $7FBC; { Send ATAPI to selected device }

These two functions are related to control ATA devices, like a CDROM, using the ATAPI command packets. An example is an application that commands the CDROM device to open the tray.

Sector I/O BIOS calls.

It is known that IDE devices can handle a mass storage with higher capacity than the old disk drives. In fact, old disk drives just can manage sectors from 0 (zero) to 65535 (the maximum value accepted by a 16 bit unsigned number).

Fortunately today we have some storage devices supporting megabytes, gigabytes and recently terabytes.

The Sunrise IDE interface brought the possibility of connecting with these higher capacity devices, but unfortunately the MSX-DOS BDOS functions and old softwares cannot handle the full capacity of these new devices.

Fortunately Sunrise already developed alternative I/O sector functions capable of managing devices that can handle a big amount of data.

Two new I/O functions were introduced into the IDE BIOS to do the sector’s handling using 24 bit unsigned numbers (0 to 16.777.215). These functions are listed below:

(* Sunrise-like IDE BIOS calls *)

Const     ctBIOSAbsSectorRead  = $7F89;  { Absolute sector read function  }
          ctBIOSAbsSectorWrite = $7F8C;  { Absolute sector write function }

These two functions should be used by applications that handle sector management instead the MSX-DOS BDOS functions ABSREAD (&H2F) and ABSWRIT (&H30), because the new functions support unsigned 24 bit sector’s handling instead the MSX-DOS old functions that support only 16 bit setor’s handling.

The Big Number library

The MSX computers can perform 16 bit operations directly and to use the new I/O functions provided by the BIOS, the programmers should provide a way for MSX perform 24 bit operations.

Fortunately I created a library (BigNumber) that can handle numbers of any size, since 8 bit, 16 bit, 24 bit, 32 bit to infinite bits.

This library can be found at OldSkoolTech and is fully written and compatible with Turbo Pascal 3 and has been used in my own softwares like the MSXDUMP 0.2, compatibles with Sunrise devices.

Well, I believe that this article could be an useful article to technicians and developers that are interested in creating new softwares to use the full capacity of Sunrise devices, so enjoy it !!

PopolonY2k

References

Análise da interface ATA IDE – Tecnobytes
http://www.popolony2k.com.br/?p=409

MSXDUMP v0.2 (final) liberado no SourceForge.net
http://www.popolony2k.com.br/?p=2125

Memory-mapped I/O ports
http://en.wikipedia.org/wiki/I/O_port

Tecnobytes Classic Computers
http://www.tecnobytes.com.br/

Sunrise for MSX home page
http://www.msx.ch/sunformsx/

MSX-DOS Wikipedia
http://en.wikipedia.org/wiki/MSX-DOS

Carchano MSX IDE interface
http://www.carchano.com.br/loja/

ATAPI packets (Wikipedia)
http://en.wikipedia.org/wiki/ATA_Packet_Interface

CP/M-BDOS (Wikipedia)
http://en.wikipedia.org/wiki/BDOS#BDOS

PopolonY2k’s BigNumber library at SourceForge.net
http://sourceforge.net/p/oldskooltech/code/HEAD/tree/msx/trunk/msxdos/pascal/bigint.pas

OldSkoolTech project (SourceForge.net)
http://sourceforge.net/projects/oldskooltech/

Turbo Pascal (Wikipedia)
http://en.wikipedia.org/wiki/Turbo_Pascal

Turbo Pascal Forever – Input/Output

Amigos, após um tempo sem posts, volto ao nosso curso de Pascal certo de que a fase inicial de fundamentação na linguagem já passou e que agora estamos aptos a avançar para tópicos de complexidade intermediária, ficando cada vez mais avançados.

Com base nisso vamos a uma das fundações mais importantes de qualquer linguagem  computacional e um dos pilares  da computação, que é a camada de I/O (Input/Output), ou em português, E/S (Entrada/Saída) de dados.

Tudo na computação envolve entrada e saída de dados, desde uma simples tecla digitada no teclado do computador, leitura/gravação de arquivos em um disco rígido, até a transferência de dados em grande escala através de dispositivos mais complexos de comunicação, como as placas de redes, seja com fio (Ethernet) ou sem fio (Wireless).

Por ser uma das principais fundações da computação, as linguagens computacionais implementam o conceito de E/S de forma bem abstrata na maioria dos casos, e principalmente contam com a possibilidade de futuras extensões a seu mecanismo de E/S padrão, visando o operações de E/S em novos dispositivos.

Feita a devida introdução, como de costume, vamos a….

 …E/S no Turbo Pascal. 

Os dispositivos de E/S mais comuns que utilizamos em  um computador, principalmente nos computadores antigos, é o teclado e o monitor de vídeo, sendo o teclado reconhecido com um dispositivo apenas de entrada de dados e o vídeo um dispositivo apenas de saída.

Os dispositivos de armazenamento (disquetes, hard disks, …) por sua vez, são dispositivos tanto de entrada quanto de saída, uma vez que é possível ler e escrever dados nos mesmos.

Como podemos ver, existem diversos dispositivos e se para cada um desses dispositivos padrão existisse uma forma proprietária para acessá-lo, teríamos uma verdadeira Torre de Babel na programação de novos dispositivos.

Felizmente os sistemas operacionais existem para abstrair uma série de dispositivos de tal forma que os mesmos sejam reconhecidos e acessados de maneira uniforme e transparente para o desenvolvedor, que por sua vez não precisa se preocupar com os detalhes internos do dispositivo em uso.

Felizmente tanto o MSX-DOS quanto o seu pai, o CP/M80, padronizaram alguns dispositivos de E/S, conhecidos como dispositivos lógicos. Abaixo segue a lista de dispositivos lógicos padronizados pelo CP/M e consequentemente o MSX-DOS:

CON: Console, ou a tela de texto do computador;
TRM: Terminal. Geralmente é redirecionado para o console ou CON;
LST: Dispositivo de listagem ou impressora;
AUX: Dispositivo de comunicação auxiliar. Também conhecido como COM:;
COM: Dispositivo de comunicação externo, comumente conhecido como COM1, ou dispositivo de comunicação externo, como uma porta serial RS-232;
USR: Dispositivo do usuário. Geralmente uma peça de software, conhecida como device driver, carregada pelo sistema operacional para responder às chamados ao dispositivo de hardware e/ou software a ser controlado. Podemos associa-lo aos modernos  device drivers, encontrados nos sistemas operacionais modernos, só que aqui em seu estado mais primitivo;
KBD: Dispositivo de teclado;

Cada um desses dispositivos lógicos está mapeado no Turbo Pascal a arquivos  pré-definidos, conforme descrito abaixo:

Input, associado ao dispositivo CON:
Output, associado ao dispositivo CON:
Con, associado ao dispositivo CON:
Trm, associado ao dispositivo TRM:
Kbd, associado ao dispositivo KBD:
Lst, associado ao dispositivo LST:
Aux, associado ao dispositivo AUX:
Usr, associado ao dispositivo USR:

Conhecendo agora os dispositivos padrão suportados pelo Turbo Pascal, vamos verificar a seguir como interagir com esses dispositivos, lendo ou escrevendo informações para os mesmos.

Rotinas padrão de E/S.

Como se trata de Entrada e Saída, o procedimento padrão implícito nessas operações é ler no caso de requisição de entrada de dados e escrever no caso de requisição de saída de dados. No Turbo Pascal essas operações são feitas ou para arquivos no FileSystem do sistema operacional, ou para qualquer um dos arquivos mapeados, conhecidos como file handlers, para os dispositivos, conforme descrito anteriormente.

Procedure Write e WriteLn

Para escrever uma informação em um determinado arquivo, ou dispositivo, utilizamos o comando Write e WriteLn, que já utilizamos bastante durante o tutorial. Segue abaixo a definição do mesmo:

Write( Var1, Var2, Var3, ...VarN );

Onde Var1, Var2, Var3, …VarN, são variáveis e/ou constantes a serem escritas no dispositivo padrão do Turbo Pascal, que no caso é o arquivo pré-definido, Con, conforme pode ser visto no código exemplo abaixo.

Program TestWrite;
Var     strText : String[50];

Begin
  strText := 'And this is a variable.';
  Write( 'This a constant for Write Procedure.', strText );
End.

Entretanto, como citamos anteriormente, é possível escrever em outros dispositivos diferentes do padrão (Con)  do comando Write, e para isso você deve especificar a variável do arquivo associada ao dispositivo desejado, como pode ser visto na definição abaixo:

Write( VarFile, Var1, Var2, Var3, ...VarN );

Onde VarFile é a variável associada ao arquivo/dispositivo em que se deseja escrever. Segue abaixo um exemplo onde ao invés de ter a saída direcionada para o dispositivo Con (Console), o mesmo é direcionado para a impressora conectada ao micro, lógicamente que só funcionará em computadores com a impressora conectada e senão estiver conectada, o seu programa ficará travado até que se pressione CTRL+STOP.

Program TestPrinter;
Var     strText : String[50];

Begin
  strText := 'And this is a variable.';
  Write( Lst, 'This a constant for Write Procedure.', strText );
End.

No caso acima, utilizamos o arquivo/dispositivo pré-mapeado do Turbo Pascal, Lst, que já está devidamente associada ao dispositivo LST: do sistema operacional MSX-DOS.

A procedure WriteLn tem sua sintaxe e uso idêntico a de Write, entretanto WriteLn, salta uma linha, inserindo um Carriage Return/LineFeed no final do dado escrito.

A procedure Read e ReadLn

Para ler dados de um arquivo/dispositivo temos os comandos Read e ReadLn, que utilizaremos bastante durante todo o tempo em que estivermos programando em Pascal e seus filhotes, como o Delphi. Segue abaixo a sintaxe do comando Read:

Read( Var1, Var2, ..., VarN );

Onde Var1, Var2, … VarN, são as variáveis que receberão os dados de entrada do arquivo/dispositivo em que se está fazendo a operação de leitura, no caso de Read, o dispositivo padrão é o Kdb, ou teclado (Keyboard).

O modo mais comum é utilizar Read/ReadLn para transferir dados do teclado, para uma variável em memória, como podemos ver no exemplo a seguir.

Program TestRead;
Var
      strData : String[255];

Begin
  Write( 'Please type a word' );
  Read( strData );
End;

No exemplo acima o programa aguarda uma entrada de uma string através do teclado, até que o usuário digite a tecla enter para finalizar a entrada.

Da mesma forma como nos comandos de escrita Write/WriteLn, os comandos Read/ReadLn aceitam uma entrada de um arquivo/dispositivo diferente do padrão estabelecido para o comando, conforme pode ser visto na definição abaixo:

Read( VarFile, Var1, Var2, Var3, ...VarN );

Onde VarFile é a variável associada ao arquivo/dispositivo em que se deseja realizar a leitura. Um arquivo/dispositivo padrão que pode ser utilizado para captura de dados é a porta AUX:, geralmente conhecida COM1: nos PC’s. Entretanto esse comando só retornará uma informação com sucesso, caso exista algum dispositivo conectado à porta auxiliar (COM) do computador.

Abaixo segue um exemplo utilizando o arquivo/dispositivo padrão, Kbd, que é o teclado, utilizado por padrão em Read/ReadLn, caso não seja especificado o arquivo/dispositivo a utilizar.

Program TestReadKbd;
Var
      strData : String[255];

Begin
  Write( 'Please type a word' );
  Read( Kbd, strData );
End;

Arquivos

Já que estamos falando de E/S e dispositivos, não podemos deixar de falar sobre E/S em  disco utilizando arquivos e as operações em arquivos consistem em 2 formas básicas. A primeira são os arquivos tipados e a segunda os arquivos sem tipo definido.

Para trabalhar com arquivos em Turbo Pascal, basta seguir a sequencia abaixo:

  1. Criar uma variável do tipo arquivo (file handle);
  2. Associar um  nome de arquivo a uma variável do tipo arquivo;
  3. Abrir o arquivo para escrita, leitura ou escrita/leitura;
  4. Ler dados do arquivo ou escrever dados para o arquivo, na sequencia desejada;
  5. Fechar o arquivo após o uso;

Primeiro de tudo devemos conhecer um tipo de dados importante na manipulação e gerenciamento de arquivos em Pascal, é o tipo File. Nos casos de arquivos tipados, podemos também definir um arquivo juntamente com o tipo de dados a ser manipulado nesse arquivo, conforme descrito abaixo:

Var
       fpIntegerFile : File Of Integer;
       fpRealFile    : File Of Real;
       fpStringFile  : File Of String[100];
       fpFileRecord  : File Of Record 
                                 strAddress : String[50];
                                 nNumber    : Integer;
                                 fSalary    : Real;
                               End;

Ao definir uma variável do tipo File, você está criando um file handle que será utilizado em todas as operações envolvendo arquivos e a primeira dessas operações é associar esse file handle a um arquivo no filesystem do sistema operacional, para isso utilizamos o comando Assign, cujo formato está descrito logo abaixo:

Assign( FileVar : File, strFileName : String );

Onde FileVar é uma variável do tipo File, podendo ser de qualquer um dos tipos especificados, conforme o exemplo anterior. strFileName é uma variável, ou constante do tipo String, que contém o nome do arquivo a ser aberto/criado para leitura/escrita;

Após associar o nome do arquivo à variável do tipo File, essa variável estará pronta para toda e qualquer operação subsequente a ser realizada com arquivos.

Abrindo arquivo para E/S.

A primeira operação que devemos fazer após associar um file handle a um arquivo no filesystem é abrir o arquivo para que possamos realizar qualquer operação de E/S no mesmo. Para isso Turbo Pascal conta com duas procedures que podem ser utilizadas para abrir um arquivo, que serão descritas a seguir.

Reset

A procedure Reset é utilizada para abrir um arquivo existente no filesystem e sua sintaxe é descrita abaixo:

Reset( FileVar : File );

Onde FileVar é a variável do tipo File, associada anteriormente a um arquivo através da procedure Assign, descrita anteriormente.

Rewrite

A procedure Rewrite é utilizada para abrir um arquivo independente se o mesmo existe no filesystem ou não. Caso o arquivo não exista o mesmo será criado.

Rewrite( FileVar : File );

Onde FileVar é a variável do tipo File, associada anteriormente a um arquivo através da procedure Assign, descrita anteriormente.

 Fechando arquivos

Bom, após todo esse processo de abertura de arquivos existe um processo importantíssimo a ser realizado ao final do uso do mesmo, que é o fechamento do arquivo previamente aberto, uma vez que as funções de E/S de Turbo Pascal, assim como as funções de E/S de ANSI C, são bufferizadas, ou seja, elas utilizam um buffer interno para cada leitura/escrita feita em um dispositivo, visando assim otimizar as operações de E/S, principalmente em dispositivos lentos.

Apesar do buffer interno proporcionar um ganho de performance nas operações de E/S, existe um problema, pois como tanto as operações de leitura quanto as de escrita são feitas em memória antes do buffer ser completado e despejado no dispositivo de armazenamento, no caso o disco rígido ou até mesmo um disquete.

Caso o desenvolvedor não instrua o programa a despejar esse buffer no disco, o Turbo Pascal irá fazê-lo “automagicamente“, quando o buffer for completado ou quando o arquivo for fechado.

Close

Fecha um arquivo especificado, despejando o buffer de E/S no dispositivo anteriormente aberto e invalidando o file handle para uso. Sua sintaxe é descrita abaixo:

Close( FileVar : File );

Onde FileVar é a variável do tipo File, associada anteriormente a um arquivo através da procedure Assign, descrita anteriormente.

Despejando o buffer de E/S.

Conforme descrito na procedure Close, as operações de E/S em arquivos de Turbo Pascal são bufferizadas e esse buffer é gerenciado “automagicamente” por Turbo Pascal.

Entretanto podemos forçar que o mesmo seja liberado no disco a qualquer momento e podemos fazer isso através da procedure Flush, cuja sintaxe está descrita abaixo:

Flush( FileVar : File );

Onde FileVar é a variável do tipo File, associada anteriormente a um arquivo através da procedure Assign, descrita anteriormente.

Abaixo um exemplo que demonstra o uso de todas as funções descritas acima:

(** Sample program to show the power 
  * of Turbo Pascal I/O procedures 
  * by PopolonY2k 2012.
  *)
Program Test_IO;
Var
       fpFile : File Of String[100];

Begin
  Assign( fpFile, 'temp.txt' ); { Assign  the file name to file variable }
  Rewrite( fpFile ); { Create a new file }
  WriteLn( fpFile, 'This is a Test' ); { Write text to file }
  Flush( fpFile ); { Force the buffer dump to disk }
  WriteLn( fpFile, 'Another text to file' );
  Close( fpFile ); { Close the file }
End.

Acesso aleatório ao arquivo.

Devemos conhecer que existe um ponteiro lógico que indica a posição de onde a próxima operação de leitura ou gravação será realizada, sendo que em cada operação de E/S realizada no arquivo, esse ponteiro lógico é movido ‘n’ bytes para frente, principalmente nos exemplos descritos até aqui, onde trabalhamos com arquivos sequenciais.

Sabendo disso, devemos conhecer também que existe uma forma, em Turbo Pascal, para manipulação desse ponteiro lógico, o que nos permite navegar pelo arquivo de maneira aleatória, não necessitando assim varrer um arquivo inteiro em busca de uma informação, e assim podemos trabalhar de maneira mais eficiente e veloz para os casos onde sabemos a posição do registro a ser lido ou escrito.

Para manipular a posição do ponteiro do arquivo, Turbo Pascal possui a procedure descrita abaixo.

Procedure Seek( fpFile : File; nRec : Integer );

A procedure Seek recebe um parâmetro do tipo File, que contém o file handle para o arquivo aberto através de Reset ou Rewrite e também aceita um valor inteiro que descreve  a nova posição do ponteiro no arquivo. Lembrando que a posição inicial do ponteiro no arquivo é sempre zero.

Essa posição não é o número de bytes a mover no arquivo e sim o número de registros do arquivo e sua posição em bytes varia de acordo com o tipo do arquivo. Abaixo um exemplo do uso de Seek.

(** This is a sample to show how to make a random access
  * to file, using Seek.
  * by PopolonY2k 2012.
  *)
Program Test_Seek;
Type TMyData = Record
  strName : String[50];
  nAge    : Integer;
End;

Var
       fpFile : File Of TMyData;
       rec    : TMyData;

Begin
  { The first step is write some data to file }
  Assign( fpFile, 'Data.dat' );  { Create the data file }
  Rewrite( fpFile );

  rec.strName := 'PopolonY2k';
  rec.nAge    := 60;
  Write( fpFile, rec );          { Write the first record }
  rec.strName := 'AphroditeY2k';
  rec.nAge    := 40;
  Write( fpFile, rec );          { Write the second record }
  rec.strName := 'HudnosY2k';
  rec.nAge    := 1000;
  Write( fpFile, rec );          { Write the third record }
  Close( fpFile );               { Close the file }

  { Now we start to read the file written previously }

  Assign( fpFile, 'Data.dat' );  { Open the data file }
  Reset( fpFile );

  { Our file has 3 record and the first record is in position 0 zero }
  { Then we start to read the mid record }
  Seek( fpFile, 1 );
  Read( fpFile, rec );
  WriteLn( 'Name -> ', rec.strName );
  WriteLn( 'Age  -> ', rec.nAge );

  { Now reading the last record }
  Seek( fpFile, 2 );
  Read( fpFile, rec );
  WriteLn( 'Name -> ', rec.strName );
  WriteLn( 'Age  -> ', rec.nAge );

  { Now reading the first record }
  Seek( fpFile, 0 );
  Read( fpFile, rec );
  WriteLn( 'Name -> ', rec.strName );
  WriteLn( 'Age  -> ', rec.nAge );
  Close( fpFile );
End.

Aquivos Texto.

Arquivos texto são arquivos cujo conteúdo é formado basicamente por caracteres imprimíveis, sendo compostos por diversas linhas de caracteres imprimíveis separados por um terminador de linha, que no caso do MSX-DOS é o caractere de controle carriage-return seguido de line-feed, sendo o final de arquivo delimitado por um caractere especial representado pelo código ASCII de número 26 (1A em hexadecimal), também representado pela combinação de teclas CTRL+Z.

Abaixo segue a descrição de alguns comandos importantes para a manipulação de arquivos em modo texto:

Function EOF( fpFile : File) : Boolean.

Essa função é especifica para o uso em arquivos no modo texto e sua finalidade é reconhecer se o final de arquivo foi alcançado ou não. O nome dessa função vem da abreviação de End Of File.

Conforme pode ser visto no protótipo da função acima, EOF recebe um parâmetro que é um file handle para um arquivo texto anteriormente aberto por Reset ou Rewrite. A função testa se arquivo chegou ao seu fim e retorna True caso o teste seja satisfeito e Falso caso contrário.

Function EOLn( fpFile : File ) : Boolean;

Assim como EOF, EOLn aceita um file handle, previamente aberto através de Reset ou Rewrite. A função testa se a ultima operação de leitura feita através da procedure Read, atingiu o final da linha delimitado por Carriage-return/Line-feed. O nome da função vem da abreviação de End Of Line.

Abaixo temos um exemplo demonstrando o uso das funções de manipulação de arquivo texto.

(** Sample code to show the Turbo Pascal text files related
  * procedures and functions;
  * This sample copy a complete input file to an output file specified
  * by the user;
  * by PopolonY2k 2012;
  *) 
Program Test_TextFiles;

Type TString = String[255];

Var
      fpFileIn,
      fpFileOut  : File Of TString;
      strLine,
      strFileIn,
      strFileOut : TString;

Begin
  WriteLn( 'Please type the input file name' );
  ReadLn( strFileIn );
  WriteLn( 'Please type the output file name' );
  ReadLn( strFileOut );

  Assign( fpFileIn, strFileIn );   { Assign the input and output   }
  Assign( fpFileOut, strFileOut ); { file names to the File handle }
  Reset( fpFileIn );               { variables                     }
  Rewrite( fpFileOut );

  {
    Now the we enter in loop reading the input file until reach the
    end of input file and writing the output to the output file
    previously opened for writing.
  }
  While( Not EOF( fpFileIn ) )  Do  { EOF function sample }
  Begin
    { 
      Here another loop to read the content for each line of input
      file, until the end of line.
    }
    While( Not EOLn( fpFileIn ) ) Do
    Begin
      Read( fpFileIn, strLine );    { Read content from input file }
      Write( fpFileOut, strLine );  { Write content to output file }
    End;
    WriteLn( fpFileOut ); { After read a complete line. We must to }
                          { add a CR/LF at the end of line for the }
                          { output file                            }
  End;
  Close( fpFileIn );      { Close the input and }
  Close( fpFileOut );     { the output files    }
End.

Arquivos sem tipo definido.

Já passeamos pelas funções de E/S de Turbo Pascal, para manipulação de arquivos tipados incluindo arquivos texto, entretanto não podemos deixar de citar que Turbo Pascal é uma linguagem poderosa no nível de que você tenha pleno controle da máquinam o que possibilita a criação de programas e bibliotecas de uso geral, incluindo a capacidadede manipulação de arquivos independente do tipo, ou seja, podemos manipular e gerenciar arquivos binários diversos com extrema facilidade devido a esse conjunto de funções  que veremos à seguir.

As procedures padrão para manipulação de arquivos não tipados são canais de E/S de baixo nível que usam acesso direto a arquivos no disco. Esse acesso é feito através de um registro, ou buffer, que tem um tamanho lógico de 128 bytes.

Esse tamanho virtual pode ser comparado aos setores de um arquivo de um sistema operacional, como no MSX-DOS que é de 512 bytes.

Vamos ao que interessa que são as rotinas de baixo nível para acesso direto a arquivos do Turbo Pascal, que descrevo abaixo.

Procedure BlockRead( fpFile : File; 
                     Var Buffer; 
                     nBytes : Integer );
{ Variation of the same procedure }
Procedure BlockRead( fpFile : File; 
                     Var Buffer; 
                     nBytes : Integer; 
                     Var nResult : Integer );

A procedure BlockRead é uma poderosa rotina para leitura de grandes blocos de dados de qualquer arquivo em qualquer formato. Como podemos ver, a rotina aceita um file handle que especifica o arquivo aberto anteriormente através de Reset ou Rewrite, uma variável buffer que irá receber o conteúdo da leitura feita por BlockRead, uma variável que especifica o número de bytes a ler do arquivo e também uma variável referência (vide passagem por referência no segundo post), que irá receber o número de registros lidos na ultima operação.

O buffer pode ter qualquer tamanho e apontar para qualquer área de memória que esteja disponível no computador. O parâmetro inteiro representado por nBytes, deve especificar o tamanho, em bytes, do buffer a ser carregado na operação de leitura e nResult irá retornar o número de registros de 128 bytes realmente lidos, ou seja se o seu buffer for de 256 bytes, nResult terá o valor 2 após a leitura de BlockRead.

Após a ultima operação de BlockRead, a posição do ponteiro do arquivo será  movida nBytes para frente no arquivo, ou até a posição final do arquivo, caso a mesma seja atingida.

Procedure BlockWrite( fpFile : File;
                      Var Buffer; 
                      nRecs : Integer );
{ Variation of the same procedure }
Procedure BlockWrite( fpFile : File;
                      Var Buffer; 
                      nRecs : Integer; 
                      Var nResult : Integer );

A procedure BlockWrite tem basicamente a mesma estrutura de BlockRead porém com função inversa, ou seja, ao invés de ler, a mesma grava dados de um buffer em memória para o disco. Da mesma forma que BlockRead, a rotina aceita um file handle que especifica o arquivo aberto anteriormente através de Reset ou Rewrite, uma variável buffer que contém a informação a ser escrita por BlockWrite, uma variável que especifica o número de registros a ser escrito e também uma variável referência, que irá receber o número de registros gravados na ultima operação de escrita.

buffer pode ter qualquer tamanho, ou apontar para qualquer área de memória, desde que estejam disponíveis para o computador. O parâmetro inteiro representado por nRecs, deve especificar o tamanho, em registros de 128 bytes, do buffer a ser gravado na operação de escrita e nResult irá retornar o número de registros de 128 bytes realmente escritos, ou seja se o seu buffer for de 256 bytesnResult terá o valor 2 após a operação de escrita feita por BlockWrite.

Após a ultima operação de BlockWrite, a posição do ponteiro do arquivo será  movida nBytes para frente no arquivo.

Abaixo segue um exemplo que demonstra o uso das rotinas de baixo nível, não tipadas, de Turbo Pascal.

(** Sample code to show the Turbo Pascal direct access low level
  * related procedures and functions;
  * This sample copy a complete input file to an output file specified
  * by the user;
  * by PopolonY2k 2012;
  *) 
Program Test_DirectIO;

Type TString = String[255];
Const ctMaxBuffer : Integer = 1024; { Uses 1024 bytes copy buffer }

Var
      fpFileIn,
      fpFileOut  : File;
      strLine,
      strFileIn,
      strFileOut : TString;
      nRecs     : Integer;
      aBuffer   : Array[0..ctMaxBuffer] Of Byte;

Begin
  WriteLn( 'Please type the input file name' );
  ReadLn( strFileIn );
  WriteLn( 'Please type the output file name' );
  ReadLn( strFileOut );

  Assign( fpFileIn, strFileIn );   { Assign the input and output   }
  Assign( fpFileOut, strFileOut ); { file names to the File handle }
  Reset( fpFileIn );               { variables                     }
  Rewrite( fpFileOut );

  { 
    Start reading the input file until reach the end of file, when
    the BlockRead return zero for the number of records loaded.
  }
  Repeat
    BlockRead( fpFileIn, aBuffer, SizeOf( aBuffer ), nRecs );
    BlockWrite( fpFileOut, aBuffer, nRecs );
  Until( nRecs = 0 );

  Close( fpFileIn );
  Close( fpFileOut );
End.

Error handling

Bom, até agora fizemos o caminho perfeito onde problemas não acontecem, discos não tem sua capacidade máxima atingida, erros de escrita e leitura não existem, proteção contra gravação é lenda, enfim, tudo é maravilhoso no mundo de Pokémon.

Porém no mundo real, todos os problemas acima e muitos outros mais, podem acontecer e irão acontecer pois Murphy nos acompanha a cada segundo e fará com que cada um dos problemas possíveis aconteça. Por isso devemos trabalhar nossos softwares buscando prever a maioria dos problemas ou pelo menos tratar a maioria das excessões possíveis de acontecer durante o ciclo de execução do software.

O Turbo Pascal possui um mecanismo de controle do comportamento de run-time conhecida como Diretivas de Compilação, que falaremos em outra oportunidade dedicada exclusivamente a Diretivas de Compilação. Entre os exemplos de diretivas de compilação está a que controla a checagem de acesso indevido a um índice de um array, outra que controla a checagem de compatibilidade entre parâmetros String, e entre as diversas diretivas existentes temos uma em especial para controle da checagem de erro de E/S que possibilita ao programador desabilitar a checagem automática de erros de E/S pelo Turbo Pascal, transferindo assim a responsabilidade para o programador sobre o tratamento de erros de E/S.

As diretivas de compilação são ativadas através de comentários (*$ *) ou {$ } sendo que a diretiva inicia com o caractere $ seguida pela representação da diretiva desejada. Algumas diretivas podem ser ativadas ou desativadas através do + (ativa) ou – (desativa) e tem escopo local ou global.

No caso da diretiva de checagem e erro de E/S, a diretiva é a {$I} (I/O checking) que tem seu estado default ativado {$I+}, ou seja, qualquer erro de E/S que aconteça em seu programa, o Turbo Pascal automagicamente irá adicionar um código de checagem de erro para cada operação de E/S e caso ocorra um erro de E/S, o sistema operacional será acionado informando o erro ocorrido, o que muitas vezes chega a ser um inconveniente pois as mensagens apresentadas pelo sistema operacional nem sempre são “esteticamente” conveninentes ao usuário final. Quem não se lembra das mensagens do MSDOS/MSX-DOS, (A)bort, (R)etry, (I)gnore ????

Pois bem, para contornar inconvenientes como esse e dar maior poder ao desenvolvedor para controlar as excessões de E/S, utilizamos a diretiva {$I}, que é uma diretiva local, ou seja, podemos ativar e desativá-la a qualquer momento durante o fluxo de execução do software, conforme pode ser visto no programa abaixo.

(** Sample code to show the Turbo Pascal directive for I/O control
  * by the programmer;
  * This sample copy a complete input file to an output file specified
  * by the user;
  * by PopolonY2k 2012;
  *) 
Program Test_IO_Compiler_Directive;

Type TString = String[255];
Const ctMaxBuffer : Integer = 1024; { Uses 1024 bytes copy buffer }

Var
      fpFileIn,
      fpFileOut  : File;
      strLine,
      strFileIn,
      strFileOut : TString;
      nRecs     : Integer;
      aBuffer   : Array[0..ctMaxBuffer] Of Byte;

Begin
  WriteLn( 'Please type the input file name' );
  ReadLn( strFileIn );
  WriteLn( 'Please type the output file name' );
  ReadLn( strFileOut );

  Assign( fpFileIn, strFileIn );   { Assign the input and output   }
  Assign( fpFileOut, strFileOut ); { file names to the File handle }
  {$I-}
  Reset( fpFileIn );               { variables                     }
  {$I+}

  If( IOResult <> 0 )  Then
  Begin
    WriteLn( 'Error to open the input file' );
    Halt;
  End
  Else
  Begin
    {$I-}
    Rewrite( fpFileOut );
    {$I+}
    If( IOResult <> 0 )  Then
    Begin
      WriteLn( 'Error to open the output file' );
      Halt;
    End;
  End;

  { 
    Start reading the input file until reach the end of file, when
    the BlockRead return zero for the number of records loaded.
  }
  Repeat
    BlockRead( fpFileIn, aBuffer, SizeOf( aBuffer ), nRecs );
    BlockWrite( fpFileOut, aBuffer, nRecs );
  Until( nRecs = 0 );

  Close( fpFileIn );
  Close( fpFileOut );
End.

No código acima podemos ver que {$I-} e {$I+} delimitam o escopo de onde inicia e onde termina a checagem de erro de E/S, sendo que após isso chamamos a função IOResult que nos retorna o ultimo erro ocorrido na ultima operação de E/S, sendo que qualquer valor diferente de zero sinaliza o código do ultimo erro ocorrido na ultima operação de E/S.

Dessa forma podemos fazer um controle minucioso sobre cada operação de E/S, retornando erros mais detalhados ou até permitindo que o software tome decisões sobre a melhor ação de contorno a ser executada para se resolver a excessão.

Finalizando o esse post percebemos que Turbo Pascal possui um rico conjunto de rotinas para manipulação de E/S, permitindo assim que softwares modernos e com alto grau de controle sejam construídos para plataformas que suportam esse poderoso compilador, o que é verdadeiro para a plataformas baseadas em CP/M80, como o caso do MSX.

Agradecimento

Aproveito para deixar aqui meu agradecimento a Nirvardo Cavalcante, membro atuante na comunidade de MSX nacional, pelo farto material sobre Turbo Pascal enviado a mim, incluindo código fonte.

Obrigado e, sim, em breve faremos aquele universal music player em Pascal. 🙂

[]’s
PopolonY2k

Referência na internet

Wireless network (Wikipedia)
http://en.wikipedia.org/wiki/Wireless_network

Ethernet (Wikipedia)
http://en.wikipedia.org/wiki/Ethernet

MSX-DOS (Wikipedia)
http://en.wikipedia.org/wiki/MSX-DOS

RS-232 (Wikipedia)
http://en.wikipedia.org/wiki/Rs232

Device Driver (Wikipedia)
http://en.wikipedia.org/wiki/Device_driver

Torre de Babel (Wikipedia)
http://pt.wikipedia.org/wiki/Torre_de_Babel

Caracteres imprimíveis (Wikipedia)
http://pt.wikipedia.org/wiki/ASCII#Caracteres_imprim.C3.ADveis

Caracteres não imprimíveis, ou caracteres de controle (Wikipedia)
http://pt.wikipedia.org/wiki/ASCII#Caracteres_n.C3.A3o_imprim.C3.ADveis

ANSI C (Wikipedia)
http://en.wikipedia.org/wiki/ANSI_C

Protótipo de função (Wikipedia)
http://pt.wikipedia.org/wiki/Prot%C3%B3tipo_de_fun%C3%A7%C3%A3o

Turbo Pascal Forever – Begin (PopolonY2k Rulezz)
http://www.popolony2k.com.br/?p=1694

Pokemón ( Wikipedia)
http://pt.wikipedia.org/wiki/Pok%C3%A9mon

Lei de Murphy (Wikipedia)
http://pt.wikipedia.org/wiki/Lei_de_Murphy

Turbo Pascal compiler directives (David I Embarcadero Blog)
http://blogs.embarcadero.com/davidi/2008/11/16/39118/

Turbo Pascal Forever – Statements

Antes de continuar o artigo sobre programação Pascal para plataformas old skool, primeiramente quero agradecer principalmente aos membros da comunidade nacional de MSX pelo feedback e apoio a essa série que está sendo desenvolvida e também à comunidade internacional de desenvolvedores Pascal, representados pelas novas gerações da linguagem, através do Delphi e Lazarus/FreePascal.

Below some individual thanks to community members by the support to this tutorial.

  • Werner Kai, active member of the brazilian and international MSX scenary, thanks for the Pascal documentation, source code and software compilation, sent to me, specially by the HiSoft Pascal information for MSX.
  • Embarcadero Development RAD Tools and Eliazer Kosciuk (Klax) on Twitter, thanks for the support to this tutorial, retwitting my Twitter messages.

Vamos ao que realmente interessa que é  mão-na-massa, hands on, ….

Statements

Um Statement em sua tradução livre é definido como uma declaração mas prefiro defini-lo tecnicamente da mesma forma como faz o manual original da versão 3 do Turbo Pascal de 1983~86 e que descrevo abaixo.

Tradução do manual original da Borland
Turbo Pascal – The Ultimate Pascal Development Environment – Borland 1983~1986

Um statement define a ação a ser executada pelo programa (ou sub-programa, ou sub-rotina) como uma sequencia de statements; cada uma especificando uma parte da ação.

Nesse sentido Pascal é uma linguagem de programação sequencial, ou seja, cada statement é executado sequencialmente um por vez, nunca simultâneamente. Um statement é encapsulado pelas palavras reservadas begin e end e dentro delas, statements são separadas por ponto e virgula (;).

Statements podem ser simples ou estruturados.

Claro que muita coisa mudou de lá pra cá, onde a tecnologia de hardware e a tecnologia de software evoluíram muito permitindo que tenhamos hoje um certo paralelismo, possibilitando a execução de statements simultaneamente, seja através de threads ou até mesmo paralelismo entre os diversos núcleos do processador, entretanto o conceito de execução sequencial de cada statement, em um módulo do programa, ou sub-programa, ainda é o mesmo até nas versões mais modernas de Pascal (Delphi, FreePascal, …), bem como na maioria das linguagens de alto e médio nível como Java e C.

Outro fator importante sobre os statements é que os mesmos podem ser categorizados como simples  e estruturados.

Statements simples.

Entre os statements simples podemos citar o de atribuição, procedure e o statement vazio.

Segue abaixo os casos de statements simples.

Statement de atribuição.

O statement de atribuição é um simples comando de atribuição a uma variável, conforme pode ser visto no código fonte do exemplo a seguir:

nValue   := 1000;  { Integer assignment statement }
strValue := 'This is a string assignment statement';
dValue   := 10.45; { Real assignment statement }
bValue   := ( 1 = 2 ); { Boolean assignment statement }
bEof     := EOF;{ Boolean assignment statement from function EOF }

Procedure statement

Uma procedure statement tem a função de executar uma sub-rotina definida pelo usuário ou até mesmo uma sub-rotina builtin do Turbo Pascal.

WriteLn( 'This is a builtin procedure call statement' );
ClrScr;  { Another builtin Turbo Pascal procedure statement }
ClrEOL;  { Another builtin Turbo Pascal procedure statement }

Statement vazio

Apesar de soar estranho, é possível ter um statement vazio, ou seja, que não executa nada. Segue abaixo alguns usos de statements vazios.

Begin End.        { Empty block }
While (True) Do;  { Infinite loop }
Repeat Until KeyPressed; { Wait for any key to be pressed }

Statements estruturados

Statements estruturados são construções compostas por outros statements, chamados de statements compostos, que podem ser executados em sequência, condicionalmente ou repetidamente.

Segue abaixo os statements estruturados.

Statements compostos

Um Statement composto é definido como um conjunto de outros statements simples ou compostos que estão sempre agrupados em blocos, no caso de Pascal um bloco é definido através das palavras reservadas begin e end, especificando assim que os statements serão executados na sequência em que eles foram escritos, sendo assim podemos afirmar que o próprio bloco principal de um programa em Pascal é um statement composto.

Abaixo alguns exemplos de statements compostos:

Program Test;
 
 Var bTest   : Boolean;
     strTemp : String[255];
 
{ Main block - In fact the main block is the major compound statement }
Begin
  If( bTest = True ) Then
    Begin { This is a compound statement }
      strTemp := 'This is a compound statement test';
      WriteLn( strTemp );
    End
  Else { This is another compound statement }
    Begin
      strTemp := 'Another compound statement test';
      WriteLn( strTemp );
    End;
End. { End of main block and the main compound statement }

Statements condicionais

É reconhecido que os computadores são máquinas extremamente lógicas e nada mais lógico que as linguagens de programação tenham ricos conjuntos de operações e possibilidades lógicas.

Turbo Pascal possui um conjunto moderno de statements dedicados a lógica condicional, possibilitando assim uma estruturação clara de grandes sistemas desenvolvidos na linguagem.

IF Statement

Assim como a maioria das linguagens de médio e alto nível, Pascal possui um statement dedicado a lógica condicional com duas opções de execução dependendo do resultado booleano de seu comparador.

A estrutura do statement IF é a seguinte:

IF boolean_expression THEN  { Statement executed for true condition }
ELSE  { Statement to be executed for false condition - optional };

Onde boolean_expression é uma expressão lógica, ou booleana, a ser executada e caso a mesma tenha um resultado verdadeiro (True) o statement após o THEN será executado, caso contrário será executado o statement após o ELSE.  Lembrando que o ELSE é opcional, ou seja, se o seu teste condicional não necessita executar nada para casos cuja expressão booleana testada não seja verdadeira (False), a condição do ELSE poderá ser suprimida do statement IF.

Vejamos alguns exemplos práticos de utilização do statement IF.

Program Test_IF;
Var
      bValue1,
      bValue2   : Boolean;
      nValue    : Integer;
 
Begin
  bValue1 := True;
  bValue2 := False;
  nValue  := 100;
 
  { If statement checking boolean values }
  If( bValue1 = bValue2 )  Then
    WriteLn( 'The boolean values are equals' )
  Else
    WriteLn( 'The boolean values are different' );
 
  { If statement checking integer values }
  If( nValue = 1000 )  Then
    WriteLn( 'The integer value is equal the constant value' )
  Else
    WriteLn( 'The integer value is different of constant value' );
 
  { If statement with only one execution condition }
  If( nValue = 100 ) Then
    WriteLn( 'The integer value is equal the constant value' );
End.

Uma observação muito importante é quanto a utilização do ; (ponto-e-virgula) no final do statement IF. Nunca devemos nos esquecer de que o (ponto-e-virgula) é um finalizador de statements, ou seja, deve estar presente no final de cada statement, seja simples ou composto.

No caso do IF, ele poderá estar apenas no fim do statement completo, ou seja, caso tenhamos um IF com as condição do ELSE além do THEN implementada, o ; (ponto-e-virgula) só poderá estar ao final do ELSE, uma vez que ele finaliza o statement IF nesse caso. Mas caso tenhamos apenas a condição do THEN implementada o ; (ponto-e-virgula) deverá estar após o statement seguinte ao THEN.

Abaixo alguns exemplos do uso do (ponto-e-virgula) finalizando o statement IF.

Program TEST_IF_2;
Begin
  { If statement with optional ELSE }
  If( 1 = 2 )  Then
    WriteLn( 'Equals' );
 
  If( 1 = 1 )  Then
    WriteLn( 'Equals' )
  Else
    WriteLn( 'Not equals' );
End.

Outra observação é quanto ao uso combinado de um statement IF com statements compostos, como pode ser visto no exemplo abaixo.

Program TEST_IF_With_Compound_Statements;
Begin
  If( True )  Then
    Begin  { Start of the first compound statement }
      WriteLn( 'This is the first' );
      WriteLn( 'compound statement' );
    End
  Else
    Begin  { Start of second compound statement }
      WriteLn( 'This is the second' );
      WriteLn( 'compound statement' );
    End;
End.

CASE Statement

Outra estrutura condicional presente em Pascal, bem como na maioria das linguagens modernas, é o statement CASE que possibilita a comparação de uma expressão com diversas constantes de comparação, podendo ter assim especificadas diversas condições de execução de acordo com o valor da expressão de entrada.

A estrutura do statement CASE é a seguinte:

CASE expression OF
  constant1 : statement;
  constant2 : statament;
  .
  .
  .
  constantN : statament;
  ELSE statement { Optional };
END;

Onde expression é a expressão a ser comparada com cada constante especificada por  constant1, constant2, …., constantN sendo que caso uma dessas constantes satisfaça a expressão, o statement  associado à constante, após o : (dois pontos) será executado. Caso nenhuma das opções seja satisfeita, o statement após o ELSE será executado. Lembrando que assim como o ELSE do statement IF, o ELSE do statement CASE é opcional.

Abaixo temos um exemplo da utilização do CASE.

Program TEST_Case;
Var
      nValue : Integer;
 
Begin
  WriteLn( 'Digit a number between 0-3' );
  Read( nValue );
 
  Case ( nValue ) Of
    0 : WriteLn( 'The number is 0' );
    1 : WriteLn( 'The number is 1' );
    2 : WriteLn( 'The number is 2' );
    3 : WriteLn( 'The number is 3' );
    Else
      WriteLn( 'Number is not between 0-3' );
  End;
End.

Da mesma forma como no statement IF, podemos combinar o statement CASE com statements compostos, conforme pode ser visto no exemplo abaixo.

Program TEST_Case2;
Var
      nValue : Integer;
 
Begin
  WriteLn( 'Digit a number between 0-1' );
  Read( nValue );
 
  Case ( nValue ) Of
    0 : Begin   { The first compound statement in CASE statement }
          WriteLn( 'The number is 0' );
          ReadLn;
        End;
    1 : Begin   { The second compound statement in CASE statement }
          WriteLn( 'The number is 1' );
          ReadLn;
        End;
  End;
End.

Statements de repetição.

Uma das estruturas interessantes e poderosas de Pascal são os  statements de repetição que de fato são estruturas responsáveis pela execução cíclica de um ou mais statements até que uma condição booleana seja satisfeita, ou um contador tenha seu limite máximo atingido.

Em Pascal temos 3 statements de repetição distintos:

  • Statement FOR;
  • Statement WHILE;
  • Statement REPEAT UNTIL;

Statement FOR.

statement FOR é uma estrutura de repetição capaz de executar statements simples ou compostos por um determinado número de vezes tanto em ordem crescente quanto em ordem decrescente. É muito útil quando se conhece antecipadamente o limite máximo de repetições que se deseja executar.

A estrutura do statement FOR é a seguinte:

FOR count_variable := initial_value TO maximum_value DO execution_statement;

Onde count_variable é uma variável inteira a ser utilizada como contador e seu valor inicial será representado por initial_value, sendo incrementada em uma unidade, onde o statament representado por execution_statement será executado a cada iteração até que o limite de contagem, representado por maximum_value, seja atingido.

A segunda variação do statement FOR é a seguinte:

FOR count_variable := initial_value DOWNTO minimum_value DO execution_statement;

Sendo que a diferença entre essa última e a anterior é que ao invés de um contador crescente, temos um contador decrescente, representado pela palavra DOWNTO.

Segue abaixo alguns exemplos de repetição utilizando o statement FOR.

Program Test_Simple_FOR;
  Const   ctMaxCount : Integer = 2000; { Maximum for loop counter }
  Var     nCount     : Integer;
 
  Begin
    For nCount := 0 To ctMaxCount Do  { This is a simple FOR statement }
      WriteLn( 'Counting [', nCount, ']' );
  End.

Outro exemplo do statement FOR, executando um statement composto.

Program Test_Compound_FOR;
  Const   ctMaxCount : Integer = 2000; { Maximum for loop counter }
  Var     nCount     : Integer;
 
  Begin
    For nCount := 0 To ctMaxCount Do  { This is a compound FOR statement }
      Begin
        WriteLn( 'Counting [' );
        WriteLn( nCount );
        WriteLn( ']' );
      End;
  End.

Abaixo temos um exemplo do statement FOR com um contador decrescente.

Program Test_Decreasing_FOR;
  Const   ctMinCount : Integer = 10; { Minimum limit for the FOR loop counter }
  Var     nCount     : Integer;
 
  Begin
    { This is a decreasing FOR statament }
    For nCount := 1000 DownTo ctMinCount Do
      WriteLn( 'Counting [', nCount, ']' );
  End.

Statement WHILE.

Assim como o statement FOR, o statement WHILE é capaz de executar repetições de  statements simples ou compostos. Entretanto a diferença primordial entre o statement FOR e o statement WHILE é que esse último é capaz de executar a repetição até que uma condição booleana seja satisfeita, podendo ser essa simples ou mais complexa.

A estrutura do statement WHILE é a seguinte:

WHILE boolean_condition DO execution_statement;

Onde boolean_condition é a condição booleana a ser satisfeita e execution_statement o statement a ser executado até que a condição boolean_condition seja satisfeita.

Vamos a alguns exemplos do uso do statement de repetição, WHILE.

Program Test_Simple_WHILE;
Const
         ctMaxCount : Integer = 1000;
Var
         nCount     : Integer;
 
Begin
  nCount := 0;
 
  { Increase the counter variable, nCount, while nCount is ‹ 1000 }
  While( nCount ‹ ctMaxCount ) Do
    nCount := nCount + 1;
End.

Abaixo temos outro exemplo utilizando o statement WHILE, executando um statement composto onde a condição de parada é uma combinação booleana complexa.

Program Test_Complex_WHILE;
Const
         ctMaxCount : Integer = 1000;
Var
         nCount     : Integer;
         bExit      : Boolean;
 
Begin
  nCount := 0;
  bExit  := False;
 
  { This is a While with a more complex boolean checking exit }
  While( ( bExit = False ) And ( nCount ‹ ctMaxCount ) ) Do
    Begin
      If( nCount = 100 )  Then
        bExit := True;
 
      nCount := nCount + 1;
    End;
End.

Statement REPEAT UNTIL;

Por ultimo vamos descrever o statement de repetição, REPEAT UNTIL, que assim como os statements FOR e WHILE, é capaz de executar statements simples e compostos e assim como o statement WHILE, é capaz de executar a repetição até que uma condição booleana seja satisfeita.

A grande diferença do statement REPEAT UNTIL para o WHILE é que o REPEAT UNTIL tem a checagem da condição de parada do loop executada ao fim de uma primeira iteração no loop de execução do statement, ou seja, teremos pelo menos uma execução do statement ou conjunto de statements do REPEAT UNTIL.

A estrutura do statement REPEAT UNTIL é a seguinte:

REPEAT execution_statement UNTIL boolean_condition;

Onde execution_statement é o statement ou conjunto de statements a ser executados até que a condição boolean_condition seja satisfeita.

Segue abaixo um exemplo do statement REPEAT UNTIL em ação.

Program Test_REPEAT_Until;
Const    ctMaxCount = 1000;
Var      nCount : Integer;
         bExit  : Boolean;
Begin
  nCount := 0;
  bExit  := False;
 
  Repeat
    If( nCount = 100 )  Then
      bExit := True;
 
    nCount := nCount + 1;
  Until( ( bExit = True ) And ( nCount = ctMaxCount ) );
End.

Como podemos ver, a linguagem Pascal é rica de estruturas que facilitam e possibilitam o desenvolvimento de software de forma organizada e altamente estruturada, capacitando assim a construção de grandes sistemas escaláveis e de mais fácil manutenção.

Certamente daqui a 15 anos outro desenvolvedor poderá ler um código feito por você em Pascal e compreendê-lo com extrema facilidade devido a estrutura organizada, nativa de Pascal.

Por enquanto vou ficando por aqui deixando mais esse material para que possam se divertir até o post seguinte. 🙂

[]’s
PopolonY2k 

Referência na internet

Embarcadero Delphi
http://www.embarcadero.com/br/products/delphi

Lazarus
http://www.lazarus.freepascal.org/

FreePascal
http://www.freepascal.org/

Embarcadero RAD Tools Dev team (Twitter)
http://twitter.com/#!/RADtools

Eliazer Kosciuk – Klax (Twitter)
http://twitter.com/#!/eliazerk

Twitter
http://twitter.com/#!/RADtools