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.

code format="bb4w" PROC_WriteArray(fileHandle%, ^array) PROC_ReadArray(fileHandle%, ^array) code

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%.

code format="bb4w" DIM array(9, 9, 9, 9) REM Initialise array fileHandle% = OPENOUT("data.dat") PROC_WriteArray(fileHandle%, ^array) CLOSE#fileHandle% code


 * PROC_ReadArray** reads data from the file specified by fileHandle% and stores it in the specified array.

code format="bb4w" DIM array(1000, 10) fileHandle% = OPENIN("data.dat") PROC_ReadArray(fileHandle%, ^array) CLOSE#fileHandle% code

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:

code format="bb4w" 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

code

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

code format="bb4w" 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 code