Reading and writing arrays in files
by Jon Ripley, May 2011
Alternative method added by Steve Drain, Oct 2013
BBC BASIC for Windows does not provide a simple method of writing arrays to a file and reading them back again, doing so traditionally requires looping through the array and saving each separate item individually.
The PROC_ReadArray and PROC_WriteArray routines below provide equivalents to PRINT# and INPUT# for arrays and are faster than the traditional method in most cases.
PROC_WriteArray(fileHandle%, ^array()) PROC_ReadArray(fileHandle%, ^array())
When reading and writing arrays you must prefix the array variable name with the address of operator, the ^ symbol, this allows us to pass all types of array to a single routine rather than requiring a separate routine for each.
PROC_WriteArray writes the contents of an array to the file specified by fileHandle%.
DIM array(9, 9, 9, 9) REM Initialise array fileHandle% = OPENOUT("data.dat") PROC_WriteArray(fileHandle%, ^array()) CLOSE#fileHandle%
PROC_ReadArray reads data from the file specified by fileHandle% and stores it in the specified array.
DIM array(1000, 10) fileHandle% = OPENIN("data.dat") PROC_ReadArray(fileHandle%, ^array()) CLOSE#fileHandle%
For more information about PROC_SwapMemory see swapping the contents of two areas of memory.
The full code for PROC_ReadArray, PROC_WriteArray and PROC_SwapMemory is as follows:
DEF PROC_WriteArray(file%, parr%):LOCAL write%:write%=TRUE DEF PROC_ReadArray(file%, parr%):LOCAL write% LOCAL i%, tele%, esize%, temp% IF NOT (parr%?-1 = 0 AND parr%?-2 = 40) THEN ERROR 6, "Type mismatch: Bad array pointer" ENDPROC ENDIF tele%=1 FOR i%=0 TO ?!parr%-1 tele%*=!((!parr%+1)+(4*i%)) NEXT i% CASE parr%?-3 OF WHEN ASC"%":esize%=4 WHEN ASC"#":esize%=8 WHEN ASC"$":esize%=6 WHEN ASC"&":esize%=1 OTHERWISE:esize%=5 ENDCASE PTR#file%=PTR#file% IF parr%?-3 <> ASC"$" THEN IF write% THEN SYS "WriteFile", @hfile%(file%), !parr%+1+?!parr%*4, tele%*esize%, ^temp%, 0 ELSE SYS "ReadFile", @hfile%(file%), !parr%+1+?!parr%*4, tele%*esize%, ^temp%, 0 ENDIF ELSE LOCAL temp$() DIM temp$(tele%-1) PROC_SwapMemory(!^temp$()+1+?!^temp$()*4, !parr%+1+?!parr%*4, tele%*esize%) IF write% THEN FOR i%=0 TO tele%-1:PRINT#file%, temp$(i%):NEXT i% ELSE FOR i%=0 TO tele%-1:INPUT#file%, temp$(i%):NEXT i% ENDIF PROC_SwapMemory(!^temp$()+1+?!^temp$()*4, !parr%+1+?!parr%*4, tele%*esize%) ENDIF ENDPROC REM PROC_SwapMemory(addr1%, addr2%, size%) REM Suggested by Richard Russell DEF PROC_SwapMemory(B%, D%, C%) PRIVATE memswap IF memswap=0 THEN LOCAL P% DIM memswap 10 P%=memswap [OPT 2 mov al,[ebx] xchg al,[edx] mov [ebx],al inc ebx inc edx loop memswap ret ] ENDIF CALL memswap ENDPROC
Thanks to Richard Russell for the SwapMemory function.
Alternative method
This method writes information about the type of array and its size in the file and checks that it it the same when reading. It does not require the SwapMemory assembler code.
The API is the same as the routines above, except, when reading:
- the number of elements in the target array must be equal to the file array, but the array can be a different shape
- a string target array must be empty
DEF PROC_WriteArray(file%,pntr%) LOCAL type%,size%,numb%,temp% PROC_ArrayData BPUT#file%,type% REM write the number of elements as a word BPUT#file%,numb% BPUT#file%,numb%>>8 BPUT#file%,numb%>>16 BPUT#file%,numb%>>24 REM flush the buffer PTR#file%=PTR#file% IF type%=ASC"$" THEN FOR pntr%=pntr% TO pntr%+numb%*size%-1 STEP size% BPUT#file%,pntr%?4 BPUT#file%,pntr%?5 size%=pntr%?4+(pntr%?5<<8) PTR#file%=PTR#file% SYS"WriteFile", @hfile%(file%),pntr%,size%,^temp%,0 NEXT pntr% ELSE SYS"WriteFile", @hfile%(file%),pntr%,numb%*size%,^temp%,0 ENDIF ENDPROC DEF PROC_ReadArray(file%,pntr%) LOCAL type%,size%,numb%,temp% PROC_ArrayData IF type%<>BGET#file% THEN ERROR 6,"Wrong array type" REM read the number of elements as a word temp%=BGET#file% temp%+=BGET#file%<<8 temp%+=BGET#file%<<16 temp%+=BGET#file%<<24 IF numb%<>temp% THEN ERROR 6,"Wrong array size" REM flush the buffer PTR#file%=PTR#file% IF type%=ASC"$" THEN FOR pntr%=pntr% TO pntr%+numb%*size%-1 STEP size% IF pntr%?4 OR pntr%?5 THEN ERROR 6,"String array not empty" pntr%?4=BGET#file% pntr%?5=BGET#file% size%=pntr%?4+(pntr%?5<<8) DIM !pntr% size% PTR#file%=PTR#file% SYS"ReadFile", @hfile%(file%),pntr%,size%,^temp%,0 NEXT pntr% ELSE SYS"ReadFile",@hfile%(file%),pntr%,numb%*size%,^temp%,0 ENDIF ENDPROC DEF PROC_ArrayData IF pntr%?-2<>ASC"(" THEN ERROR 6,"Not an array" type%=pntr%?-3 CASE type% OF WHEN ASC"%":size%=4 WHEN ASC"#":size%=8 WHEN ASC"$":size%=6 WHEN ASC"&":size%=1 OTHERWISE:type%=ASC"|":size%=5 ENDCASE pntr%=!pntr% numb%=1 FOR temp%=pntr%+1 TO pntr%+?pntr%*4 STEP 4 numb%*=!temp% NEXT temp% pntr%=temp% ENDPROC