En los inicios de la informática doméstica apareció un programa que permitió que tanto usuarios particulares como profesionales pudieran crear de una forma sencilla bases de datos relacionales y aplicaciones que permitían el acceso a estos datos.
Este programa lo lanzó la empresa Ashton-Tate con el nombre de dBASE II, a pesar de que nunca existió una versión I, pensando que esto le daba una imagen de producto más consolidado.
Rápidamente el programa fue un éxito y se traslado a muchos equipos basados en sistemas operativos CP/M y MS-DOS.
COMANDOS DISPONIBLES
Disponemos de una línea de comandos, indicada con el carácter “.”, donde vamos introduciendo los distintos comandos que queremos ejecutar.
Esta versión admite 16 comandos del dBASE II original. Son los necesarios para definir y manipular bases de datos, pero se ha prescindido de los índices y la parte de programación.
CREATE
Este comando nos permite crear una base de datos. Al ejecutarlo, entramos en modo de diseño, y se han de introducir los campos, de uno en uno y separados por coma, con el siguiente formato: nombre, tipo, longitud, decimales.
Hay 2 tipos de datos:
N - Numérico.
C - Alfanumérico.
Esta versión nos permite definir hasta 20 campos por base de datos, y los campos pueden tener un nombre de hasta 10 caracteres.
Los campos alfanuméricos admiten hasta 25 caracteres, en lugar de los 254 del original, y los numéricos hasta 10 dígitos.
Solo hay que especificar los decimales en los campos que los precisen.
Si hay algún error nos avisa, y nos permite volver a introducir los valores del campo.
Forma de uso:
CREATE EJEMPLO
USE
Abre la base de datos indicada. Para ello es preciso poner en marcha el casete y esperar a que se cargue en memoria.
Si ya hay una base de datos activa se pedirá si se quiere volcar el contenido a una cinta de casete, antes de abrir la nueva.
Forma de uso:
USE EJEMPLO
APPEND
Permite introducir un nuevo registro al final de la base de datos activa.
En la pantalla aparecen los distintos campos y el espacio para introducir los valores.
Forma de uso:
APPEND
INSERT
Inserta un registro a continuación del registro activo.
En la pantalla aparecen los distintos campos y el espacio para introducir los valores.
Forma de uso:
INSERT
EDIT
Este comando permite editar el registro indicado.
Forma de uso:
EDIT 5
DELETE
Marca como borrados registros de la base de datos activa. Estos registros siguen visibles, pero aparece un “*” a continuación del número de registro al hacer un LIST.
Forma de uso:
DELETE : Marca el registro activo.
DELETE ALL : Marca todos los registros de la base de datos.
DELETE NEXT 5 : Marca 5 registros a partir del registro activo.
DELETE RECORD 5 : Marca el registro número 5.
RECALL
Este comando desmarca registros marcados como borrados.
Forma de uso:
RECALL : Desmarca el registro activo.
RECALL ALL : Desmarca todos los registros de la base de datos.
RECALL NEXT 5 : Desmarca 5 registros a partir del registro activo.
RECALL RECORD 5 : Desmarca el registro número 5.
SET DELETED
Activa o desactiva la visualización y acceso a los registros marcados como borrados.
Forma de uso:
SET DELETED ON : Los registros marcados no son visibles ni accesibles.
SET DELETED OFF : Los registros marcados están visibles y accesibles.
COUNT
Muestra el número total de registros de la base de datos activa, incluyendo los marcados como borrados.
Forma de uso:
COUNT
PACK
Este comando elimina todos los registros marcados como borrados, actualiza el contador de registros y reduce el tamaño que ocupa la base de datos.
Posteriormente no hay posibilidad de recuperar los registros borrados.
Forma de uso:
PACK
LIST
Lista los registros o estructura de la base de datos por pantalla, entre otras funciones especiales.
Forma de uso:
LIST : Muestra todos los registros de la base de datos.
LIST ALL : Igual que LIST.
LIST NEXT 5 : Muestra 5 registros a partir del registro activo.
LIST RECORD 5 : Muestra el registro número 5.
LIST STRUCT: Muestra la estructura de la base de datos.
DISPLAY
Este comando es "casi" idéntico a LIST y funciona con los mismos parámetros.
Forma de uso:
DISPLAY : Muestra el registro activo.
DISPLAY ALL : Igual que LIST.
DISPLAY NEXT 5 : Muestra 5 registros a partir del registro activo.
DISPLAY RECORD 5 : Muestra el registro número 5.
GO
Posiciona el puntero en el número de registro indicado. Permite los parámetros TOP y BOTTOM, para ir al primer o último registro de la base de datos.
Forma de uso:
GO 5 : Nos posicionamos en el registro 5.
GO TOP : Nos posicionamos en el primer registro.
GO BOTTOM : Nos posicionamos en el último registro.
SKIP
Mueve el puntero del registro activo en función del número indicado.
Forma de uso:
SKIP 2 : Avanzamos dos registros a partir del registro activo.
SKIP -2 : Retrocedemos dos registros a partir del registro activo.
SORT ON
Este comando genera un fichero de base de datos con los registros ordenados por el campo indicado. Los registros quedan renumerados en función del nuevo orden. La base de datos original queda inalterada.
A diferencia del original, solo permite ordenar por un único campo, ya sea numérico o alfanumérico.
Si se desea que el orden sea descendente, se debe usar DESCENDING a continuación del nombre del fichero.
Forma de uso:
SORT ON campo TO basedatos
SORT ON campo TO basedatos DESCENDING
QUIT
Sale de dBASE II y vuelve al sistema operativo. Este comando, previa confirmación, vuelca el contenido de la memoria en la cinta de casete. Abandonar el programa de otra forma provocará la perdida de los datos.
Descargar dBASE II en formato TAP:
¿COMO FUNCIONA?
Este proyecto se ha organizado para que ocupe lo mínimo y funcione lo más rápido posible. El resultado es un programa de unos 8 KB, que deja unos 36 KB libres para datos.
Variables principales
Para ganar velocidad me he limitado a usar variables con nombres de una única letra, excepto con la "E".
C$() : Matriz donde se almacenan los comandos disponibles.
D$ : Nombre de la base de datos activa.
L$ : Línea de entrada de comandos.
P$() : Matriz de los parámetros introducidos.
R : Número de registros en la base de datos.
S$() : Matriz con todos los registros de la base de datos.
T : Número de campos de la base de datos.
U : Visualizar (1) o Ocultar (0) los registros marcados como borrados.
V : Número de registro activo.
W$() : Matriz de campos durante la edición de un registro.
X$ : Fecha de entrada a dBASE II.
Y$ : Fecha última actualización de la base de datos.
E1$() : Matriz de Nombres de los campos.
E2$() : Matriz de Tipos de los campos.
E3$() : Matriz de Tamaño de los campos.
E4$() : Matriz de Decimales de los campos numéricos.
El resto de las variables pueden tener distintos usos.
EL LISTADO
Código: Seleccionar todo
' Punteros de Memoria de Variables y Matrices
1 GRAB:DOKE#9C,10000:DOKE#9E,10000:DOKE#A0,10000:DOKE#A2,99
' DATA con los Comandos y Saltos a rutinas de Comandos
2 DATA"CREATE","USE","QUIT","APPEND","INSERT","EDIT","DELETE","SET"
3 DATA "RECALL","COUNT","PACK","LIST","GO","SKIP","DISPLAY","SORT"
4 DATA 4200,4600,5000,2100,2000,2200,3600,3500
5 DATA 3700,3400,3900,2800,3200,3300,2800,4000
6 X$=" "
' Declaración de matrices
10 DIMJ(16),C$(16),P$(6),E1$(20),E2$(20),E3$(20),E4$(20),N$(20),S$(512)
100 GOSUB 9000
' Entrada de Comandos
200 PRINT". ";:GOSUB1500:IFL$=""THEN200ELSEB$=" ":N=6:GOSUB1000:I=0
230 FORF=1TO16:IFP$(1)=C$(F)THENI=F:F=16
250 NEXT:IFI=0THEN1200
270 IFD$<>""ORI<4THEN280
275 PRINT"No database in use, enter filename:":GOSUB1500:IFL$=""THEN200
276 N=2:GOSUB1000:P$(2)=P$(1):GOTO4605
280 IFI>3ANDI<7THENPRINTCHR$(17)
285 IFI>0ANDI<17THENGOTOJ(I)ELSE200
' Separar Valores
1000 I=1:L$=L$+" ":FORF=1TON:P$(F)="":NEXT
1010 FORF=1TOLEN(L$)
1020 A$=MID$(L$,F,1):IFA$=B$ANDI<NTHENI=I+1ELSEP$(I)=P$(I)+A$
1030 NEXT:RETURN
' Formatear Número
1100 PRINTRIGHT$("00000"+MID$(STR$(N),2),J);:RETURN
' Imprimir Errores
1200 PRINT"*** Unknown command":PRINTP$(1):GOTO200
' Rutina de INPUT alternativa
1500 L$=""
1505 GETB$:F=ASC(B$):IFF>31ANDF<127THEN1530
1510 IFF=13THENPRINT:RETURN
1515 REM IFF<32ORF>127THEN1505
1516 IFF<32ANDF<>3THEN1505
1520 IFL$=""THEN1505ELSEL$=LEFT$(L$,LEN(L$)-1):PRINTB$;:GOTO1505
1530 IFLEN(L$)<35THENL$=L$+B$:PRINTB$;
1540 GOTO1505
' Comando INSERT
2000 FORF=R+1TOV+1STEP-1:S$(F)=S$(F-1):NEXT
' Comando APPEND
2100 IFP$(1)="APPEND"THENV=R+1ELSEV=V+1
2110 R=R+1:S$(V)=" "
2120 FORF=1TOT:S$(V)=S$(V)+" "+MID$(X$,1,VAL(E3$(F))):NEXT:GOTO2240
' Comando EDIT
2200 IFR=0THEN200
2210 N=VAL(P$(2)):IFN=0THENPRINT"Enter record # : ";:GOSUB1500:N=VAL(L$)
2220 IFN>RTHENPRINT"Record out of range":GOTO200
2230 IFN=0THENV=RELSEV=N
2240 CLS:PRINT"RECORD # ";:J=5:N=V:GOSUB1100:PRINT:J=3
2250 FORF=1TOT:W$(F)=MID$(S$(V),J,VAL(E3$(F))):J=J+VAL(E3$(F))+1
2260 PRINTE1$(F);TAB(12);"| ";LEFT$(W$(F),25);"|":NEXT:F=0:J=1
2300 IFF=0THENA$="_"+W$(J)ELSEA$=LEFT$(W$(J),F)+"_"+MID$(W$(J),F+1)
2310 A$=LEFT$(A$,VAL(E3$(J))+1):PLOT13,J,A$
2320 GETA$:IFA$<>CHR$(27)THEN2360
2330 IFP$(1)="APPEND"THENV=V-1:R=R-1
2350 CLS:PRINTCHR$(17):GOTO200
2360 K=ASC(A$):IFK<32ORK>126THEN2410
2380 IFF=0THENW$(J)=A$+W$(J)ELSEW$(J)=LEFT$(W$(J),F)+A$+MID$(W$(J),F+1)
2390 W$(J)=LEFT$(W$(J),VAL(E3$(J))):F=F+1:GOTO2300
2410 IFK=11THENGOSUB2530:J=J-1:GOTO2460
2420 IFK=10ORK=13THENGOSUB2530:J=J+1:GOTO2460
2430 IFK=8ANDF>0THENF=F-1
2440 IFK=9ANDF<VAL(E3$(J))THENF=F+1
2450 IFK=127ANDF>0THENW$(J)=LEFT$(W$(J),F-1)+MID$(W$(J),F+1)+" ":F=F-1:GOTO2300
2460 IFJ=0THENJ=1ELSE2470
2465 IFV<2THEN2240ELSEGOSUB2500:V=V-1:GOTO2240
2470 IFJ<=TTHEN2300
2475 GOSUB2500:IFV<RTHENV=V+1:GOTO2240
2480 IFP$(1)="APPEND"THEN2100ELSECLS:PRINTCHR$(17):GOTO200
2500 S$(V)=LEFT$(S$(V),1):FORI=1TOT:S$(V)=S$(V)+" "+W$(I):NEXT:RETURN
2530 IFE2$(J)<>"N"THEN2560
2540 N=VAL(W$(J)):I=VAL(E4$(J)):IFI<>0THENF=10^I:N=INT(N*F)/F
2550 W$(J)=RIGHT$(" "+STR$(N),VAL(E3$(J)))
2560 F=0:PLOT13,J," "+W$(J):RETURN
' Comando LIST y DISPLAY
2800 IFLEFT$(P$(2),4)="STRU"THEN2910
2810 IFP$(2)="ALL"THENF=1:I=R:GOTO2850
2820 IFP$(2)="NEXT"THENF=V:I=F+VAL(P$(3))-1:GOTO2850
2830 IFP$(2)="RECORD"THENF=VAL(P$(3)):I=F:GOTO2850
2840 IFP$(1)="LIST"THENF=1:I=R
2850 IFI>RTHENI=R
2860 FORF=FTOI:A$=LEFT$(S$(F),1):IFA$="*"ANDU=0THEN2890
2880 J=5:N=F:GOSUB1100:PRINT" ";S$(F):PRINT:V=F
2890 NEXT:GOTO200
' Comando LIST STRUCT
2910 PRINT"Structure for file: ";D$
2915 PRINT"Number of records:";:J=5:N=R:GOSUB1100:PRINT
2920 PRINT"Date of last update: ";Y$:PRINT"Primary use database"
2930 PRINT "Fld Name Type Width Dec":I=0
2940 FORF=1TOT:J=3:N=F:GOSUB1100
2960 PRINTTAB(10);E1$(F);TAB(23);E2$(F);
2970 PRINTTAB(28);:J=3:N=VAL(E3$(F)):GOSUB1100
2980 IFVAL(E4$(F))<>0THENPRINTTAB(35);:J=3:N=VAL(E4$(F)):GOSUB1100
2990 PRINT:I=I+VAL(E3$(F)):NEXT
3010 PRINT"** Total **";TAB(26);:J=5:N=I:GOSUB1100:PRINT:GOTO200
' Comando GO
3200 IFP$(2)="TOP"THENV=1:GOTO3220
3210 IFP$(2)="BOTTOM"THENV=RELSEV=VAL(P$(2))
3220 IFV>RTHENV=R:GOTO3230
3225 IFV<1THENV=1
3230 PRINT"Record: ";:J=5:N=V:GOSUB1100:PRINT:GOTO200
' Comando SKIP
3300 N=VAL(P$(2)):IFN=0THENN=1
3310 V=V+N:GOTO200
' Comando COUNT
3400 PRINT"Count = ";:J=5:N=R:GOSUB1100:PRINT:GOTO200
3500 IFP$(2)="DELETED"ANDP$(3)="ON"THENU=1:GOTO200
3510 IFP$(2)="DELETED"ANDP$(3)="OFF"THENU=0:GOTO200
3520 GOTO1200
' Comando DELETE
3600 A$=" ":B$="*":GOTO3710
' Comando RECALL
3700 A$="*":B$=" "
3710 IFP$(2)="ALL"THENF=1:I=R:GOTO3750
3720 IFP$(2)="NEXT"THENF=V:I=F+VAL(P$(3))-1:GOTO3750
3730 IFP$(2)="RECORD"THENF=VAL(P$(3)):I=F:GOTO3750
3740 F=VAL(P$(2)):IFFTHEN1200ELSEF=V:I=F
3750 K=0:IFI>RTHENI=R
3760 FORF=FTOI
3770 IFLEFT$(S$(F),1)=A$THENS$(F)=B$+MID$(S$(F),2):K=K+1
3780 NEXT:J=5:N=K:GOSUB1100
3800 IFA$="*"THENPRINT" recall(s)"ELSEPRINT" deletion(s)"
3810 GOTO200
' Comando PACK
3900 I=0:FORF=1TOR:IFLEFT$(S$(F),1)=" "THENI=I+1:S$(I)=S$(F)
3930 NEXT:PRINT"Pack complete ";:J=5:N=I:GOSUB1100
3950 PRINT" records copied":R=I:V=I:GOTO200
' Comando SORT ON
4000 IFP$(2)<>"ON"ORP$(4)<>"TO"THEN1200
4010 A$=P$(3):D$=P$(5):N=0:J=2
4020 IFLEFT$(P$(6),4)="DESC"THENI=1ELSEI=0
4030 FORF=1TOT:IFA$=E1$(F)THENN=F:F=T+1
4040 J=J+VAL(E3$(F))+1:NEXT:IFN=0THEN1200ELSEN=VAL(E3$(N))
4080 FORF=1TOR:FORK=1TOR
4090 A$=MID$(S$(K),J,N):B$=MID$(S$(F),J,N):IFI=1THENL$=A$:A$=B$:B$=L$
4120 IFA$>B$THENL$=S$(F):S$(F)=S$(K):S$(K)=L$
4130 NEXTK,F:GOTO200
' Comando CREATE
4200 IFLEN(P$(2))<1ORLEN(P$(2))>8THEN1200
4210 IFD$<>""THENGOSUB5100ELSED$=P$(2)
4220 PRINT"Enter record structure as follows:"
4230 PRINT"Field Name, Type, Width, Decimals"
4240 PRINT" ";:J=3:N=T+1:GOSUB1100:PRINTTAB(11);:GOSUB1500
4260 IFL$=""THEN200
4270 B$=",":N=4:GOSUB1000:N=VAL(P$(3)):F=VAL(P$(4))
4290 IFLEN(P$(1))>10THEN4360
4300 IFP$(2)<>"C"ANDP$(2)<>"N"THEN4360
4310 IFP$(2)="C"AND(N<1ORN>25)OR(P$(2)="N"AND(N<1ORN>10))THEN4360
4320 IFP$(2)="N"ANDF>N-2ANDF<>0THEN4360
4330 T=T+1:E1$(T)=P$(1):E2$(T)=P$(2):E3$(T)=P$(3)
4340 IFP$(2)="C"THENE4$(T)="0"ELSEE4$(T)=P$(4)
4350 IFT=20THEN200ELSE4240
4360 PRINT"Bad type field":GOTO4240
' Comando USE
4600 IFD$<>""THENGOSUB5100
4605 D$=P$(2):CLOAD D$+".SYS":CLOAD D$+".DAT":GOTO200
' Comando QUIT (Grabar la base de datos)
5000 IFD$<>""THENGOSUB5100
5010 PRINT"*** End run dBASE II ***":PRINT:PRINT
5020 PRINT"Remember to back-up your data.":PRINT:END
5100 IFD$=""THENRETURN
5105 PRINT"Save data to tape? ";:GETA$:PRINTA$:IFA$="N"ORA$="n"THEN5115
5110 CSAVE D$+".SYS",A#9C,E#A3:CSAVE D$+".DAT",A10000,E46080
5115 T=0:R=0:V=0:U=1:D$=P$(2):RETURN
' Inicio de la Aplicación
9000 INK7:PAPER0:CLS:U=1:FORF=1TO16:READC$(F):NEXT:FORF=1TO16:READJ(F):NEXT
9005 FORF=46840TO46847:POKEF,255:NEXT
9010 PRINT"Enter today's date or return for none"
9020 PRINT" (MM/DD/YY) :";:GOSUB1500:Y$=L$:IFLEN(Y$)>8THENY$=LEFT$(Y$,8)
9030 PRINT:PRINT"*** dBASE II Ver Lite* 20 April 2015":PRINT
9040 PRINT"COPYRIGHT (c) SCAINET SOFT 2015"
9050 PRINT"AS AN UNPUBLISHED LICENSED PROPIETARY."
9060 PRINT"ALL RIGHTS RESERVED.":PRINT
9070 PRINT"dBASE II is a registered trademark and"
9080 PRINT"dBASE and ASHTON-TATE are trademarks":PRINT"of Ashton-Tate.":RETURN
EL PROYECTO
EL CP/M y sus aplicaciones no están disponibles en muchos de los equipos de 8 bits, y queda descartado en equipos que usan cintas de casete o no disponen del modo de 80 columnas o un microprocesador Z80, salvo excepciones o extrañas adaptaciones.
A partir de un hilo aparecido hace tiempo hablando sobre la posibilidad de hacer un CP/M botable de cinta de casete, y otro del Coleco ADAM de ron, me planteé la posibilidad de que si no se podía adaptar el CP/M, como mínimo hacerlo con sus aplicaciones.
Para ello, decidí hacer adaptaciones en Microsoft BASIC puro y duro, teniendo mucho cuidado que el código fuera muy reducido, con un buen rendimiento y sobretodo fácilmente adaptable a cualquier otro sistema.
Así nació el "dBASE II Lite", el primero de una serie de aplicaciones CP/M adaptadas.
Se puede decir que es un "pixel perfect" ya que tanto el aspecto como la funcionalidad disponible es idéntica al original en CP/M, pero se han reducido las funciones y se ha contemplado que tanto la cinta de casete o incluso el disquete pueda ser usado como medio de almacenamiento externo.
La primera versión fue desarrollada para PC y posteriormente se pasó al AMSTRAD CPC 464/464+ con soporte de cinta y modo 80 columnas. Esta tercera versión funciona en modo de 38 columnas y se ha reducido el código fuente a solo 152 líneas.
A partir de aquí, cualquiera lo puedo adaptar fácilmente a un DRAGON 32, SPECTRUM, BBC Micro, ALPHATRONIC o cualquier otro sistema.
LA VERSIÓN ORIC
La versión ORIC se ha adaptado muy fácilmente a partir del código fuente de la versión de AMSTRAD CPC, con la salvedad de que he tenido que pasarlo de 80 columnas a 38 columnas, ya que el ORIC funciona a 40 columnas pero las 2 de la izquierda son “bastante” especiales, y con un PRINT normal, inaccesibles.
La principal diferencia, que me ha provocado muchos dolores de cabeza, ha sido el tema de la grabación y carga de los datos.
Empieza la pesadilla...
El ORIC-1 no dispone de ninguna instrucción para gestionar la grabación de ficheros secuenciales o el volcado de matrices, y el ORIC ATMOS dispone de los comandos STORE y RECALL que se supone que lo hacen, pero o los emuladores no lo emulan bien o la complejidad del programa hace que no funcione bien. No lo he probado en un equipo original.
Usar STORE y RECALL habría excluido a los ORIC-1, cosa que no me gustaba, pero me habría facilitado mucho las cosas.
... pero investigando un poquito ...
Analizando las variables del sistema he podido descubrir unas cuantas direcciones interesantes, usadas por el sistema como punteros a direcciones de memoria:
#9C - #9D : Puntero al inicio de la zona de variables.
#9E - #9F : Puntero al inicio de la zona de matrices tipo “string”.
#A0 - #A1 : Puntero al final de la zona de matrices tipo “string”.
#A2 - #A3 : Puntero al siguiente espacio libre para variables tipo “strings”.
Examinando estas zonas de memoria he podido comprobar ciertas cosas:
- Las variables numéricas se guardan en la memoria entre las direcciones indicadas por #9C-#9D y #9E-#9F.
- Cada variable numérica ocupa 6 bytes, más un carácter por cada letra del nombre.
- Las matrices tipo “string” se guardan en la memoria entre las direcciones indicadas por #9E-#9F y #A0-#A1.
- En esta zona se guardan punteros de 2 bytes que apuntan a una zona comprendida entre las direcciones indicadas por #A2-#A3 y el RAMTOP, dirección máxima que puede alcanzar un programa en BASIC y sus variables.
- El valor de estos punteros puede ser alterado, pero deben tener un valor igual o superior al puntero anterior.
Como digo, el contenido de las variables tipo “string” se guardan en una posición de memoria entre #A2-#A3 y el RAMTOP, y la zona de la tabla de variables solo contiene los punteros a estas direcciones. Cada vez que una de estas variables cambia de contenido se copia entera en una nueva dirección a partir de la dirección indicada en #A2-#A3 y se decrementa el valor de este puntero en función de la longitud del string. Cuando este puntero apunta a una zona de memoria por debajo de la indicada por #A0-#A1, el sistema ejecuta una rutina que hace una especie de defragmentación de la zona de memoria donde se guarda el contenido de los strings. Esto hace que durante unos segundos el sistema se quede “parado”, y al terminar ajusta el contenido del puntero #A2-#A3.
Supongo que la zona donde se guarda el valor de los string se va decrementando porque si durante la ejecución del programa se van creando nuevas variables numéricas, se deberían ajustar todos los punteros y se podría sobrescribir información. De esta forma es más eficiente ya que solo debe desplazar hacia arriba la zona de los punteros, a la que no le afecta el cambio de situación en la memoria porque las direcciones de los contenidos no han cambiado.
... aparecen las posibles soluciones
Lo primero que hago es modificar los valores de los punteros, en la línea 1 del programa, para que exista una separación de unos 2 KB entre el final del programa en BASIC y la zona donde se guardan las variables. Las he puesto a partir de la dirección 10000, pero podría ser modificado. Con el GRAB de la línea 1, reduzco el espacio asignado a la memoria de vídeo, y esto nos deja unos 36 KB, que descontados la zona de punteros y demás, nos deja unos 32 KB para guardar la información de las bases de datos.
A continuación he definido las líneas DATA porque he visto que cuando haces una asignación de un string de un DATA a una variable, el puntero del string apunta al propio código BASIC y no a una zona de memoria entre #A2-#A3 y el RAMTOP. Más adelante explicaré que no hacerlo así daría problemas en el supuesto de realizar modificaciones en el programa BASIC.
Después de experimentar un buen rato encuentro varias posibles soluciones con un tema en común: se debe realizar un volcado de memoria en cinta.
He probado 3 métodos distintos y me he decidido por el que me ha funcionado mejor y me ha ofrecido más seguridad de que posteriormente se podrán recuperar los datos sin problemas. La diferencia es que en función del método, se graban más o menos bloques de distintos tamaños, que en el supuesto de usar un equipo real con cintas de casete tardará lo suyo…
1) Volcado simple
En el supuesto de que en el programa en BASIC no se realice ninguna modificación, se podría hacer un volcado mediante un CSAVE desde la dirección 10000 hasta la 46080, que es donde deja el RAMTOP el comando GRAB de la línea 1. Si las variables se definen antes y se guardan los valores de los punteros en las variables de sistema antes indicadas, al realizar un CLOAD del bloque de bytes guardado, el sistema quedará como estaba y el programa funcionará bien.
Este método graba un bloque de 36KB, y cualquier mínimo cambio en el programa BASIC provocará un cambio en los valores de los punteros del sistema, y al recuperar el bloque puede pasar que se sobrescriba el programa en BASIC, se pierda el valor de algunas variables, etc.
Visto esto, decidí descartarlo pero podría ser útil en otros casos.
2) Volcado seguro
Con este método se graban dos bloques de bytes: uno de 8 bytes con extensión “.SYS” que contiene los punteros a las zonas de variables.
A continuación guardo desde la dirección 10000 hasta la 46080 en un bloque con extensión “.DAT”. Decir que las extensiones no tienen ninguna importancia, excepto para diferenciar los ficheros generados.
El hacerlo de esta forma me permite poder realizar modificaciones en el programa BASIC, de forma que el bloque de bytes grabado tenga un buen margen de seguridad para poder seguir siendo cargado correctamente. Si las modificaciones son muy grandes y el programa BASIC supera la dirección 10000 puede pasar como con el método anterior.
Inicialmente las líneas con los DATA estaban al final del programa, como me gusta hacer siempre, pero me encontraba que al realizar cambios en el programa BASIC y cargar un bloque de bytes grabado previamente, los punteros apuntaban a posiciones incorrectas porque las direcciones de los textos del DATA habían cambiado. Al ponerlo al principio del programa he evitado este problema, pero si se modifica el contenido de las primeras líneas volvería a fallar al cargar un bloque grabado anteriormente, pero no uno grabado con la nueva versión del BASIC.
El problema de este método es que graba un pequeño bloque de 8 bytes, pero a continuación graba otro gran bloque de 36 KB, que en el emulador no se nota pero que en un equipo real nos llevará su tiempo.
Podemos reducir el tamaño de este bloque modificando el valor de los DOKE de la línea 1, ya que cuanto más grande, menos espacio para datos y menos tamaño del bloque a grabar. Para gustos...
Este es el método que he seleccionado porque me ha funcionado sin ningún problema.
3) Volcado óptimo
Este volcado es el mejor ya que el bloque de datos se reduce a la cantidad de datos reales a grabar. Pero por desgracia, a veces se pierde algún valor. Ignoro el motivo, pero seguro que no he tenido en cuenta algo...
Su funcionamiento consiste en forzar una compactación de la zona del contenido de las variables string, reduciendo el valor del puntero #A2-#A3 por debajo del contenido del puntero #A0-#1, y realizando un cambio en una variable tipo string. Después de la compactación, el puntero #A2-#A3 apunta a una zona libre más cercana a RAMTOP, y grabando el contenido desde ésta posición hasta la RAMTOP tenemos los datos guardados en un bloque de bytes con un tamaño más reducido.
A continuación se guarda otro bloque de unos 3 KB que contiene el bloque comprendido entre los punteros #9C-#9D y #A0-#A1, para poder posteriormente recuperar los punteros a la zona de strings.
Y para terminar se guarda un pequeño bloque de 8 bytes con la zona de las variables de sistemas, como en el caso anterior.
Es el tema más eficiente ya que el tamaño de los datos a grabar se reduce significativamente, pero como digo, “algo” no acaba de funcionar bien y lo descarté por inseguro hasta que encuentre el fallo.
Apuntes finales
He utilizado el método 2 porque funciona correctamente, pero hay un “pequeño” problema: restaurar la zona de variables completa y “a saco” provoca que perdamos el contenido de variables definidas antes de cargar el bloque con CLOAD. Por eso, y como podréis comprobar, en esta versión el tema de la fecha no funciona al cargar un fichero grabado previamente, porque se destruye su contenido y recupera el que tenía al realizarse la grabación. Con todo, lo he dejado porque respeta el original.
El tamaño de los bloques que comento es en función de esta aplicación, pero aplicado en otros programas variará en función de la cantidad de variables usadas y sus tamaños.
Y seguro que hay mejores soluciones, pero esta es la que he encontrado y excepto por el tema de la fecha, esta versión de dBASE II funciona perfectamente. Y con el emulador cargar y grabar datos es muy rápido !!!
PARA TERMINAR
Este programa ha sido un ejercicio estupendo para aprender un poco sobre como el sistema gestiona todo el tema de las variables. El tema es muy amplio y da para mucho más. Pero lo dejo para otra ocasión.
Si lo queréis usar:
1) Cargar el programa y haz RUN.
2) Puedes poner la fecha de entrada, si quieres.
3) Escribe: USE COMPUTER
4) Carga la base de datos “COMPUTER”, de ejemplo, que viene junto al programa.
5) Escribe: LIST para ver los registros.
6) Escribe: LIST STRUCT para ver la estructura de la base de datos.
7) Escribe: EDIT 5 para editar el registro número 5.
8) Escribe QUIT para terminar.
9) Da la opción de grabar los datos en cinta de casete. Pulsa “N” o “n” para no hacerlo.
Espero que os guste