Curso de Basic
Arquivos


  A memória RAM do MSX é volátil, ou seja, quando desligamos o micro, os dados contidos nessa memória são perdidos. Para preservarmos os dados de uma aplicação nossa, devemos salvar os dados em disco ou fita cassete.
  O comando utilizado para gerenciar a entrada e saída (E/S) de dados em disco é o OPEN.

  Sintaxe:
OPEN nome_do_arquivo FOR INPUT/OUTPUT/APPEND AS#n
  Onde:
  O funcionamento do OPEN do MSX é semelhante ao fopen do C ou o assign do Pascal:
OPEN
' Operações de leitura e gravação
CLOSE


  Modo Texto x Modo Binário

  Na realidade, todo arquivo é binário. Entretanto, o que muda é como vamos interpretar dos dados contidos nesse arquivo.
  No modo texto, cada byte ou caractere é um código ASCII que representa uma letra, número ou símbolo.
  Vejamos o seguinte arquivo de exemplo chamado "texto.txt" em um editor hexadecimal:
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------+--------------
0000   | 4D 61 72 4D 53 58    | MarMSX

  Até mesmo quando temos números no formato texto, sua representação em bytes é um código ASCII. Por exemplo, o número 25 no formato texto ficaria:
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------+--------------
0000   | 32 35                | 25
  A informação relevante está na parte do "Texto", ou seja, o valor 25 é encontrado nessa parte e não na parte dos "Bytes".

  No modo binário, os bytes representam o valor diretamente. Por exemplo, o número 25 (&H19) no formato binário ficaria:
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------+--------------
0000   | 19                   | .

  A quantidade de bytes demandada para representar um valor irá depender do tipo de variável que foi definida. No MSX temos:   Por exemplo, o valor 25 (&H19) armazenado em uma variável do tipo inteiro seria representado em um arquivo assim:
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------+--------------
0000   | 19 00                | ..
  Observe que agora a informação relevante está na parte dos "Bytes", ou seja, encontramos o valor 25 nessa parte.


  Os Comandos PRINT#n, INPUT#n e LINE INPUT#n

  Os três comandos funcionam a partir de um arquivo aberto por OPEN e operam no modo texto.


  PRINT#n

  O comando PRINT#n converte uma string, um valor ou uma expressão para o formato texto e grava no arquivo n. O funcionamento é igual ao do comando PRINT, onde:
  INPUT#n

  O comando INPUT#n lê um texto do arquivo até encontrar uma vírgula ou o return, ou seja, eles servem de caracteres separadores de dados. Para o texto:
Rua das Flores, 123,Moca
  Ao lermos:
INPUT #1,A$
INPUT #1,B$
INPUT #1,C$
  Teríamos:
A$ = "Rua das Flores"
B$ = " 123"
C$ = "Moca"


  LINE INPUT#n

  O comando LINE INPUT#n lê uma linha inteira (máximo 254 caracteres) de um arquivo no formato texto.

  Usando o mesmo exemplo anterior, teríamos:
Rua das Flores, 123,Moca
  Ao lermos:
LINE INPUT #1,A$
  Teríamos:
A$ = "Rua das Flores, 123,Moca"


  Lendo e gravando dados

  O exemplo a seguir cria um arquivo de texto, escreve duas mensagens e grava.
10 OPEN"MENSAGEM.TXT" FOR OUTPUT AS#1
20 PRINT#1, "O MSX possui 16 cores"
30 PRINT#1, "O MSX possui 3 canais de som"
40 CLOSE 1
  Ao abrirmos o arquivo gerado "mensagem.txt", veremos o seguinte formato:

  O MSX possui 16 cores
  O MSX possui 3 canais de som

  Substituíndo a linha 20 por
20 PRINT#1, "O MSX possui 16 cores";
  O texto resultante seria:

  O MSX possui 16 coresO MSX possui 3 canais de som

  Agora, vamos ler o primeiro arquivo "mensagem.txt" gerado e imprimir as linhas.
10 OPEN"MENSAGEM.TXT" FOR INPUT AS#1
20 LINE INPUT#1, A$
30 PRINT A$
40 IF NOT EOF(1) THEN 20
50 CLOSE 1
  Saída:
  O MSX possui 16 cores
  O MSX possui 3 canais de som

  O comando EOF testa se o fim do arquivo foi atingido. O arquivo de texto do MSX possui o caractere de código ASCII igual a &H1A ao final do texto, indicando o fim do arquivo.

  Nesse próximo exemplo, iremos ler a informação de 3 alunos e salvar em disco.
10 OPEN"ALUNOS.TXT" FOR OUTPUT AS#1
20 FOR F=1 TO 3
30 PRINT"Aluno "+STR$(F)+":"
40 INPUT"Nome ";N$
50 INPUT"Idade ";I
60 INPUT"Curso ";C$
70 PRINT #1, N$ + "," + STR$(I) + "," + C$
80 NEXT F
90 CLOSE 1
  Um exemplo de arquivo "alunos.txt" gerado para esse programa é:

  Patrícia, 21,Direito
  Camila, 22,Direito
  Daniel, 20,Geografia

  O programa para recuperar os dados dos alunos no disco e imprimir na tela é:
10 OPEN"ALUNOS.TXT" FOR INPUT AS#1
20 F=1
30 PRINT"Aluno "+STR$(F)+":"
40 INPUT #1,N$
50 INPUT #1,I$
60 INPUT #1,C$
70 PRINT "Nome: ";N$
80 PRINT "Idade: ";I$
90 PRINT "Curso: ";C$
100 F=F+1:PRINT
110 IF NOT EOF(1) THEN 30
120 CLOSE 1
  Como a idade está armazenada no formato texto, a variável "I" para recuperar o dado será string. No caso desse programa, como só desejamos imprimir o valor, nenhuma conversão é necessária.

  A saída correspondente ao arquivo "alunos.txt" para esse programa é:

  Aluno 1:
  Nome: Patrícia
  Idade: 21
  Curso: Direito

  Aluno 2:
  Nome: Camila
  Idade: 22
  Curso: Direito

  Aluno 3:
  Nome: Daniel
  Idade: 20
  Curso: Geografia

  Quando quisermos gravar os dados de uma tabela criada através de matrizes, podemos utilizar o caractere vírgula "," para delimitar as colunas no texto do arquivo. Ex:
10 DIM T(3,2)
20 ' Popula a tabela
30 T(1,1)=1 : T(1,2)=2
40 T(2,1)=3 : T(2,2)=4
50 T(3,1)=5 : T(3,2)=6
60 ' Salva a tabela
70 OPEN"tabela.txt" FOR OUTPUT AS#1
80 FOR I=1 TO 3
90 FOR J=1 TO 2
100 PRINT#1, T(I,J);
110 IF J<2 THEN PRINT#1,",";
120 NEXT J
130 PRINT #1,
140 NEXTI
150 CLOSE 1

  Arquivo texto gerado:
  1 , 2
  3 , 4
  5 , 6

  O programa que recupera os dados da tabela salva é o seguinte:
10 DIM T(3,2)
20 OPEN"tabela.txt" FOR INPUT AS#1
30 FOR I=1 TO 3
40 FOR J=1 TO 2
50 INPUT #1, A$
60 T(I,J) = VAL(A$)
70 PRINT "T(";I;",";J;")=";T(I,J)
80 NEXT J,I
  Saída:
  T( 1 , 1 )= 1
  T( 1 , 2 )= 2
  T( 2 , 1 )= 3
  T( 2 , 2 )= 4
  T( 3 , 1 )= 5
  T( 3 , 2 )= 6


  O Comando INPUT$(c, #n)

  Este comando permite determinar o número de caracteres a ser lido por vez, onde c é a quantidade de caracteres a ser lido e n o número do arquivo aberto.
  Com este comando, podemos ler um arquivo caractere a caractere. Veja o exemplo a seguir, que lê os 7 primeiros bytes de um arquivo binário:
10 OPEN "arquivo.dat" FOR INPUT AS#1
20 FOR I=1 TO 7
30 V = ASC(INPUT$(1,#1))
40 PRINT HEX$(V);" ";
50 NEXT I
60 CLOSE 1
  Um exemplo de saída para o "arquivo.dat":
  FE 6 80 7 80 6 80

  Os arquivos binários não possuem o caractere de terminação "&H1A" como os arquivos de texto. Nesse caso, não podemos utilizar o comando EOF para testar o fim de arquivo.
  Para resolver este problema, usamos o comando LOF(n) para obter o tamanho do arquivo em bytes, e poder determinar seu fim. O valor n corresponde ao número do arquivo aberto.

  Utilizando o artigo dessa página sobre funções, vamos formatar a saída dos bytes no exemplo anterior:
10 DEF FN FRMT$(N$) = RIGHT$("0"+N$,2)
20 OPEN "arquivo.dat" FOR INPUT AS#1
30 FOR I=1 TO 7
40 V = ASC(INPUT$(1,#1))
50 PRINT FN FRMT$(HEX$(V));" ";
60 NEXT I
70 CLOSE 1
  Saída:
  FE 06 80 07 80 06 80


  Arquivos Híbridos - MKI$, MKS$, MKD$ / CVI, CVS, CVD

  Um arquivo híbrido é aquele que poderá ter ao mesmo tempo dados no formato texto e no formato binário.

  Foi visto anteriormente, que há duas maneiras de armazenar um dado numérico: como string ou como bytes diretamente.
  O armazenamento de um número como string em um arquivo texto é simples. Basta utilizar a função STR$ para converter de número para string. Ex:
PRINT #1, STR$(I)

  Já o armazenamento de número como binário em um arquivo texto requer um truque, que é criar uma string com os códigos ASCII iguais ao valor desse número (vide a seção de formato binário).
  O Disk-Basic (novos comandos do Basic para disquetes) implementa 6 funções que realizam esse truque para nós. São elas: MKI$, MKS$, MKD$, CVI, CVS e CVD.

  As seguintes funções convertem de número para string:   Exemplo:
PRINT #1, MKI$(I)

  As seguintes funções convertem de string para número:   Exemplo:
I = CVI(I$)

  O exemplo a seguir irá ilustrar como funciona um arquivo híbrido.
10 OPEN "estoque.dat" FOR OUTPUT AS#1
20 INPUT"Peça";P$
30 INPUT"Quantidade";Q
40 PRINT #1, P$; ","; MKI$(Q)
50 CLOSE 1
  Saída:
  Peça? Parafuso
  Quantidade? 4

  O nome da peça foi salvo como string, já a quantidade de peças foi salvo como um número inteiro de 2 bytes. Observe o arquivo "estoque.dat" no editor hexadecimal, onde o nome da peça está destacado em verde e a quantidade em azul:
Offset | Bytes em hexadecimal       | Texto gerado
-------+----------------------------+--------------
0000   | 50 61 72 61 66 75 73 6F 2C | Parafuso,
0009   | 04 00 0D 0A 1A             | .....

  É evidente que temos que usar a conversão no sentido inverso para obter a quantidade de peças:
INPUT #1, Q$
Q = CVI(Q$)


  Os Comandos FIELD, PUT e GET

  Esse modelo de gravação permite formatar os dados de saída, baseado na idéia de registros de um banco de dados. Além disso, o acesso a cada registro é aleatório, ou seja, eu posso acessar e modificar qualquer registro, bastando para isto, informar o número do registro.
  Há uma pequena diferença no modo de abertura de um arquivo aqui, em relação aos outros modos que utilizam o comando OPEN. Para esse tipo, um arquivo é aberto genericamente, ou seja, para leitura e gravação ao mesmo tempo. Isto é feito através da omissão dos termos "FOR INPUT/OUTPUT/APPEND". Ex:
OPEN "arquivo.txt" AS #1 LEN=20
  Outra novidade é a presença da opção LEN, que irá indicar o comprimento total de cada registro.


  FIELD

  O comando FIELD tem como objetivo modelar o registro da tabela de dados, indicando quais os campos existentes e o tamanho de cada um. Ele cria na memória o registro que irá receber ou enviar dados ao disco, através dos comandos GET ou PUT.

  Sintaxe:
FIELD #n, c1 AS variável_string_1, c2 AS variável_string_2, etc
  Onde "c" é o comprimento de cada campo criado no registro e "variável" é a variável de acesso a um determinado campo do registro.

  Exemplo: vamos criar um registro para gravar informações sobre um aluno:
FIELD #1, 20 AS N$, 2 AS I$

  O comando FIELD acima cria um registro com 2 campos: o primeiro para inserir o nome do aluno (N$), com comprimento igual a 20 caracteres, e o segundo para inserir a idade (I$), com comprimento igual a 2 caracteres. Veja o diagrama abaixo:
+--------------------+--+
|          N$        |I$|
+--------------------+--+
|                    |  |
+--------------------+--+

  Quando o registro for preenchido, a informação estará pronta para ser salva de uma só vez no disco, através da instrução PUT.


  Acesso ao registro - LSET e RSET

  O registro criado pelo comando FIELD deve ser modificado através dos comandos LSET e RSET. A diferença entre ambos, é que o LSET alinha o texto à esquerda do campo, enquanto que o RSET alinha o texto à direita.
  Seguindo o exemplo anterior, o comando:
LSET N$="MarMSX"
  irá preencher o registro da seguinte maneira:
+--------------------+--+
|          N$        |I$|
+--------------------+--+
|MarMSX              |  |
+--------------------+--+

  Já o comando:
RSET N$="MarMSX"
  irá preencher o registro da seguinte maneira:
+--------------------+--+
|          N$        |I$|
+--------------------+--+
|              MarMSX|  |
+--------------------+--+

  Para ler um campo do registro, basta acessar diretamente a variável relativa a esse campo. Ex:
PRINT N$
  Saída:
  MarMSX


  PUT #n, r

  Este comando grava em disco o conteúdo de um registro "r", criado através do comando FIELD e preenchido por LSET ou RSET.

  Vejamos o seguinte exemplo de como cadastrar alunos.
10 OPEN "alunos.dat" AS #1 LEN=22
20 FIELD #1, 20 AS N$, 2 AS I$
30 R=1
40 INPUT"Nome";NO$
50 IF NO$ = "" THEN CLOSE 1: END
60 INPUT"Idade";ID$
70 LSET N$ = NO$
80 LSET I$ = ID$
90 PUT #1,R
100 R=R+1
110 NO$="":GOTO 40
  Obs: Tecle "enter" sem digitar qualquer nome, para terminar o cadastro de alunos.

  Saída:
  Nome? Fabiana
  Idade? 20
  Nome? Diego
  Idade? 19
  Nome? Valentina
  Idade? 20
  Nome?
  Ok

  O arquivo "alunos.dat" gerado é:
Fabiana             20Diego               19Valentina           20

  Os registros estão divididos da seguinte maneira:
Fabiana             20Diego               19Valentina           20
  Azul: Registro 1
  Verde: Registro 2
  Amarelo: Registro 3

  Obs: Uma vez que o acesso é aleatório, podemos, por exemplo, alterar somente o registro 2 sem que os registros 1 e 3 sejam afetados.


  GET #n, r

  Este comando lê do disco o conteúdo de um registro "r", criado através do comando FIELD.
  O número de registros pode ser calculado, dividindo-se o tamanho do arquivo pelo tamanho de um registro.
  Vamos criar um programa para ler os dados dos alunos:
10 OPEN "alunos.dat" AS #1 LEN=22
20 FIELD #1, 20 AS N$, 2 AS I$
30 TR = LOF(1) / 22
40 PRINT"Total de registros:";TR
50 FOR R=1 TO TR
60 GET #1,R
70 PRINT
80 PRINT"Aluno";R
90 PRINT"Nome: " + N$
100 PRINT"Idade: " + I$
110 NEXT R
120 CLOSE 1
  Saída:
  Total de registros: 3

  Aluno 1
  Nome: Fabiana
  Idade: 20

  Aluno 2
  Nome: Diego
  Idade: 19

  Aluno 3
  Nome: Valentina
  Idade: 20


  Gravação de Dados em Formato Binário

  Podemos também gravar uma área de memória, o conteudo de variáveis ou de matrizes como um arquivo binário.


  Modo 1: usando o PRINT# e INPUT$

  O comando PRINT converte uma variável numérica em uma string. Entretanto, em um arquivo binário, necessitamos gravar o valor de uma variável numérica diretamente como um byte e não como um texto.
  Para resolver essa questão, temos que gravar a informação byte a byte, utilizando o seguinte "truque": vamos gerar um caractere, cujo código ASCII é o valor que desejamos armazenar na área dos bytes. O comando CHR$ obtém um caractere a partir do código ASCII fornecido, resolvendo o nosso problema.
  Por exemplo, para gravar o valor 25 no formato binário, usamos o comando PRINT # assim:
PRINT #1, CHR$(25)
  Resultado:
Offset | Bytes em hexadecimal | Texto gerado
-------+----------------------+--------------
0000   | 19                   | . 
  Observe que o valor 25 (&H19) está no lado dos bytes e não do texto.

  Uma vez entendida esta questão, podemos passar para o programa a seguir, que irá gravar um trecho de memória compreendido entre &HC000 a &HC00A no fomato binário.
10 OPEN "memoria.dat" FOR OUTPUT AS#1
20 FOR P=&HC000 TO &HC00A
30 PRINT #1, CHR$(PEEK(P));
40 NEXT P
50 CLOSE 1
  Obs: devemos colocar o sinal de ponto e vírgula ao final do comando PRINT #. Do contrário, a cada caractere gravado serão acrescidos dois bytes sinalizando o "fim de linha". O caractere de fim de arquivo, com o código ASCII igual a "&H1A", é colocado ao final do arquivo.

  O programa para ler o arquivo de memória gerado é:
10 OPEN "memoria.dat" FOR INPUT AS#1
20 FOR P=1 TO LOF(1)-1
30 V = ASC(INPUT$(1,#1))
40 PRINT HEX$(V);" ";
50 NEXT P
60 CLOSE 1


  Modo 2: usando o BSAVE e BLOAD

  No modo anterior, a operação de leitura / gravação é feita byte a byte, o que a torna excessivamente lenta.
  Os comandos BSAVE e BLOAD realizam operação de E/S por bloco, tornando esse processo bem mais rápido.

  Sintaxes:
BSAVE nome_do_arquivo, endereço_inicial, endereço_final[, endereço_de_execução][, S]
BLOAD nome_do_arquivo[, deslocamento][, R][, S]

  Exs:
BSAVE"memoria.dat", &HC000, &HC00A
BLOAD"memoria.dat"

  Obs:

  Lendo e gravando variáveis e matrizes

  Podemos gravar dados de variáveis ou matrizes, consultando diretamente a memória. Para isso, devemos saber a localização do dado e seu respectivo tamanho.
  No exemplo a seguir, vamos definir a variável "A" como inteira, modificar seu conteúdo e depois salvar.
10 DEFINT A
20 A = 6
30 P = VARPTR(A)
40 BSAVE"a.dat", P, P+1
  Lembre-se que as variáveis são, por padrão, do tipo precisão dupla. Por essa razão, redefinimos "A" como uma variável do tipo inteiro. Uma variável do tipo inteiro ocupa exatamente 2 bytes.
  O comando VARPTR obtém o endereço inicial de memória onde está localizado o dado da variável.

  Para recuperar a informação salva da variável, precisamos primeiramente definir novamente a variável "A" como inteira e inicializá-la com um valor qualquer.
  Quando criarmos novamente essa variável, ela poderá estar em um endereço diferente do que foi gravado pelo programa anterior.
  Conforme dito na seção anterior, não podemos determinar diretamente o endereço de memória que iremos carregar o arquivo. Nesse caso, vamos ler primeiro o arquivo, de forma a obter o endereço inicial do cabeçalho. Depois, é só calcular o deslocamento baseado nesse endereço.
10 DEFINT A
20 A=0
30 OPEN "a.dat" FOR INPUT AS#1
40 E = ASC(INPUT$(1,#1)) : ' Pula 0xFE
50 E = CVI(INPUT$(2,#1))
60 CLOSE 1
70 BLOAD"a.dat",VARPTR(A)-E
80 PRINT A
  Saída:
  6

  Obviamente, não vale a pena utilizar este recurso para gravar apenas uma variável. Foi colocado aqui apenas para ilustrar a idéia.
  Entretanto, este método é recomendado para salvar um bloco grande de dados, como, por exemplo, uma matriz.
  O exemplo a seguir mostra como salvar uma matriz em um arquivo binário.
10 DEFINT T
20 DIM T(2,1)
30 T(0,0)=1 : T(1,0)=2 : T(2,0)=3
40 T(0,1)=4 : T(1,1)=5 : T(2,1)=6
60 P = VARPTR(T(0,0))
70 BSAVE"tabela.dat", P, P+(3*2*2)-1
  O endereço inicial dos dados está na primeira célula da tabela. Os dados estão dispostos de forma contígua em memória e possuem o tamanho definido. Assim, o cálculo do tamanho dos dados é:

  tamanho = largura x altura x tamanho_do_dado

  Para recuperar os dados da tabela, vamos utilizar o seguinte programa:
10 DEFINT T
20 DIM T(2,1)
30 OPEN "tabela.dat" FOR INPUT AS#1
40 E = ASC(INPUT$(1,#1)) : ' Pula 0xFE
50 E = CVI(INPUT$(2,#1))
60 CLOSE 1
70 BLOAD"tabela.dat",VARPTR(T(0,0))-E
80 FOR I=0 TO 2
90 FOR J=0 TO 1
100 PRINT T(I,J);
110 NEXT J : PRINT : NEXT I
  Saída:
  1 4
  2 5
  3 6



  Referências:

  [1] - Drive Interface DDX - Manual de instruções.


MARMSX/CURSOS/Basic