Saving Data Matrices With Your SYM-1
George Wells
This article describes a machine-language program that enables BASIC data matrices to be saved on cassette tape and loaded back into the computer at a later time. There have already been several attempts to perform this function, but most of them suffer in one or more of the following ways:
- They will not allow the BASIC program to be modified.
- They will not allow the BASIC data to be loaded into a different program.
- They will not allow selected data to be saved on tape.
- They will not allow string data to be saved on tape.
- They are clumsy to use, requiring PEEKing and POKEing.
The program described here overcomes all of these problems. It will run only on a SYM-1 with the new MONITOR 1.1 ROM and will require extensive modification to enable it to run on other machines.
Functional Description Of Program
After the machine-language program is in memory and BASIC is running, all that is required to save a matrix is a statement of the form:
MATRIX (1,2,3) = USR (SAV,ID)
Where MATRIX (1,2,3) is any kind of matrix (numeric, integer or string) of any size with any number of dimensions, SAV is a previously defined variable pointing to the SAVE.MAT machine language program and ID is a variable in the range of 0 to 127 which is the tape ID file number. This statement can be entered as a direct command after a program has been run or it can be used anywhere in a program, even in a loop. If you want to save the entire MATRIX, then use the same subscripts that were used to DIMension and the MATRIX. You can save a portion of the MATRIX by making the last subscript a smaller number than its DIMension. SAV and ID cannot be matrix variables.
To load a matrix back into the computer use a similar statement of the form:
MATRIX (1,2,3) = USR (LOA,ID)
Where LOA is a previously defined simple variable pointing to the LOAD.MAT machine-language program and the other variables are the same ones used to save the MATRIX.
If you have implemented a second cassette control for your SYM-1 (see MICRO 18:5) then the proper cassette will be turned on for a LOAd or a SAVe operation and you can write programs that in effect handle a very large data base by partitioning it into smaller chunks that are read in from one recorder, operated on and read out to the other recorder automatically.
Description Of Program Implementation
Step 1: Deposit and Verify the OBJECT LISTING. If you have only 4K of RAM, do this at $0EE6 and then change all of the 1F's to 0F's. This can be done easily with .M 1F,EE6-FFF (CR) followed by 11 sets of 0FG (no (CR)'s). Do another verify (.V EE6-FFF) which should give a checksum of 8747.
Step 2: Jump to BASIC (J 0) and use 7910 for the size or 3814 if you have 4K of RAM.
Step 3: Enter a program such as:
100 SAV = &“1F07” (or SAV = & “0F07” for 4K)
110 POKE 42544,10: REM LONG TAPE DELAY
120 FOR I = 0 TO 10
130 A%(I) = I
140 A(I) = SQR(I)
150 A$(I) = CHR$(I + 65)
160 NEXT I
150 A%(10) = USR(SAV,1)
180 A(10) = USR(SAV,2)
190 A$(10) = USR(SAV,3)
Step 4: Rewind a tape and start it in record mode.
Step 5: RUN the program.
Step 6: When BASIC responds with OK, type NEW and enter a second program such as:
100 LOA = & “1EE6” (or LOA = &“0EE6” for 4K)
110 A%(10) = USR(LOA,1)
120 A(10) = USR(LOA,2)
130 A$(10) = USR (LOA,3)
140FOR I = 0 TO 10
150 PRINT A%(I), A(I), A$(I)
160 NEXT I
Step 7: Rewind the tape and start it in play mode.
Step 8: RUN the program. After all the matrices are read in from tape they will be printed on the terminal.
Step 9: In case the computer has trouble reading a file, rewind the tape and restart it in play mode. If you want to abort the tape read process, hold the BREAK key on the terminal down until the tape stops. You can CONTinue from this point if you want to; however, the matrix that couldn't be read in will be cleared to zeroes or nulls.
Description Of Program Operation
The program stores two copies of each file on tape to provide an automatic back-up in case of error during loading. Between files, the tape delay is set to its minimum possible value (about 1.5 seconds) and set back to its default value (about 6 seconds) at the end of the routine. You can change these values if you have special require-ments. Also, you can set the tape delay to a larger value at the beginning of tape operations to automatically move the tape off its leader, as in the first sample program above at line 110.
For numeric and integer matrices, the elements themselves are stored directly on tape with no manipulation since they are already in contiguous order. The start and stop addresses are determined from “foot-prints” on page zero left over from the normal matrix interpretation done by BASIC.
For string matrices the procedure is considerably more complex. About one half of the total code is involved in the special requirements of strings since they are not stored together in one place. The best we can do is rearrange things until all the string information is contained in two separate files which can be stored on tape. The first of these files consists of three bytes per string. The first byte is the length of the string and the last two are a pointer or address to where the ASCII characters making up the string are stored. This first file is already in contiguous order but before it can be used the second file must be created since the first file contains pointers to the second file. The second file is created by going through all the string pointers (in reverse order) and copying each string into unused memory space using two of the BASIC interpreter routines which leave the strings themselves in one continuous block. When the first file is stored it is given an ID greater than 127 (most significant bit is set) so the load routine can distinguish it from the second file.
The routines determine when a string matrix is being operated on by pulling six bytes off the stack and branching on the condition of a zero which indicates a non-string matrix. These six bytes plus one more are normally used to pass pertinent information to the calling routine. However, as they are used here, we don't want the calling routine to store the returned value in the specified matrix, so we simply discard the seven stack bytes. In fact, under normal conditions, it is not possible to specify a string variable in a USR statement, but by pulling seven bytes off the stack, we avoid the type mismatch (TM) error test (which fortunately is done after returning from USR) and return one level deeper which causes the BASIC interpreter to go on to the next statement.
If an attempt is made to store a string matrix that consists entirely of null strings, then only the first file is stored since there will be nothing in the second file. Special tests are made in both the save and load routines to handle this case. Also, it is necessary to use the end of the last non-null string as the end of the second file since to use the pointer of a null string would result in a meaningless tape stop address. Special tests are used to perform this function.
It is a good idea to eliminate DATA statements from a program after they are used to initialize a matrix that is stored on tape since they will only take up memory in future runs. If during the ordering of strings the memory is actually used up, an OM (out of memory) error will occur.
No record is kept on the tape of the name of the matrix or its size and no tests are performed to verify these things. However, if the file does not have the correct number of bytes in it, it will fail the normal tape error tests. The intention is to provide a means for a program or operator to save and retrieve data conveniently, but the task of remembering the size of the matrix or portion of matrix remains with the program or operator. Unlike the usual BASIC command for LOADing programs, if you don't know how big the matrix is that is on a tape, it can be very difficult to load it, so be organized in your use of these routines and they will serve you well.
Assembly Listing:
0010 ID .DE $6F COPY OF TAPE ID
0020 MAT.START .DE $A8 POINTER TO FIRST ELEMENT OF MATRIX
0030 MAT.STARTC .DE $70 COPY OF *MAT.START
0040 MAT.CUR.EL .DE $99 POINTER TO MATRIX CURRENT ELEMENT
0050 MAT.ENDC .DE $6D COPY OF END OF MATRIX FOR ABORT
0060 EL.SIZE .DE $78 NUMBER OF BYTES PER ELEMENT
0070 CUR.STRING .DE $BF CURRENT STRING TO BE TRANSFERRED
0080 NEW.STR.PN .DE $83 POINTER TO NEW STRING
0090 NEW.STRING .DE $D2A9 GET LOCATION OF STRING OF LENGTH=A
0100 XFR.STRING .DE $D42F TRANSFER STRING TO NEW LOCATION
0110 ACCESS .DE $8B86 SYSTEM MONITOR RAM UNPROTECT
0120 INJISV .DE $8392 CARRY SET MEANS BREAK
0130 F1 .DE $8723 MIDDLE OF MONITOR FILL ROUTINE
0140 DIG .DE $A400 FIRST DIGIT OF DISPLAY
0150 P1 .DE $A64E TAPE ID PARAMETER
0160 P2 .DE $A64C TAPE START ADDRESS
0170 P3 .DE $A64A TAPE STOP ADDRESS + 1
0180 TAPDEL .DE $A630 TAPE DELAY LOCATION
0190 TAPE.DELAY .DE 4 TAPE DELAY DEFAULT VALUE
0200 J.SAVET .DE $C6 BASIC JUMP VECTOR TO SAVE TAPE
0210 J.LOADT .DE $C9 BASIC JUMP VECTOR TO LOAD TAPE
0220 .BA $1EE6
0230 .OS
0240
1EE6- 20 2E 1F 0250 LOAD.MAT JSR INIT.PARMS INITIALIZE TAPE PARAMETERS
1EE9- 68 0260 PLA FOR FIRST FILE
1EEA- 68 0270 PLA
1EEB- 68 0280 PLA THROW AWAY 7 STACK BYTES
1EEC- 68 0290 PLA 6 NOW, 1 LATER
IEED- 68 0300 PLA
1EEE- 68 0310 PLA
1EEF- F0 0B 0320 BEQ LOAD.MAT6 BRANCH IF NOT STRING MATRIX
1EF1- 20 C9 1F 0330 JSR LOADT.HS GET STRING POINTER FILE
0340 (ID MUST BE > 127)
1EF4- 20 5F 1F 0350 JSR ORD.STRING RESERVE STRING FREE SPACE
1EF7- F0 0C 0360 BEQ LOAD.MAT7 BRANCH IF ALL STRINGS NULL
1EF9- 20 9E 1F 0370 JSR STR.PARMS SET UP STRING PARAMETERS
1EFC- A5 6F 0380 LOAD.MAT6 LDA *ID ID MUST BE < 128
1EFE- 29 7F 0390 AND #$7F
1F00- 85 6F 0400 STA *ID
1F02- 20 C9 1F 0410 JSR LOADT.HS GET LAST FILE
1F05- 68 0420 LOAD.MAT7 PLA THROW AWAY LAST STACK BYTE
1F06- 60 0430 RTS RETURN TO BASIC
0440
1F07- 20 2E 1F 0450 SAVE.MAT JSR INIT.PARMS INITIALIZE TAPE PARAMETERS
1F0A- 68 0460 PLA FOR FIRST FILE
1F0B- 68 0470 PLA
1F0C- 68 0480 PLA THROW AWAY 7 STACK BYTES
1F0D- 68 0490 PLA 6 NOW, 1 LATER
0F0E- 68 0500 PLA
1F0F- 68 0510 PLA
1F10- F0 0B 0520 BEQ SAVE.MAT6 BRANCH IF NOT STRING MATRIX
1F12- 20 5F 1F 0530 JSR ORD.STRING PUT MATRIX STRINGS IN ORDER
1F15- F0 0D 0540 BEQ SAVE.MAT7 BRANCH IF ALL STRINGS NULL
1F17- 20 BC 1F 0550 JSR SAVET.2.HS SAVE 2 COPIES OF POINTERS
0560 WITH ID > 127
1F1A- 20 9E 1F 0570 JSR STR.PARMS SETUP STRING PARAMETERS
1F1D- A5 6F 0580 SAVE.MAT6 LDA *ID MAKE ID < 128
1F1F- 29 7F 0590 AND #$7F
1F21- 8D 4E A6 0600 STA P1
1F24- 20 BC 1F 0610 SAVE.MAT7 JSR SAVET.2.HS SAVE 2 COPIES OF LAST FILE
1F27- A9 04 0620 LDA #TAPE.DELAY RESTORE TAPE DELAY DEFAULT
1F29- 8D 30 A6 0630 STA TAPDEL
1F2C- 68 0640 PLA THROW AWAY LAST STACK BYTE
1F2D- 60 0650 RTS
0660
1F2E- 20 86 8B 0670 INIT.PARMS JSR ACCESS
1F31- 98 0680 TYA PASS 1D TO PARM 1 BUT
1F32- 09 80 0690 ORA #$80 MAKE ID > 127
1F34- 8D 4E A6 0700 STA P1
1F37- 85 6F 0710 STA *ID ALSO SAVE COPY FOR STRINGS
1F39- A5 A8 0720 LDA *MAT.START PASS MATRIX START TO PARM 2
1F3B- 8D 4C A6 0730 STA P2
1F3E- 85 70 0740 STA *MAT.STARTC ALSO SAVE COPY TO ORDER
1F40- A5 A9 0750 LDA *MAT.START+1 STRINGS
1F42- 8D 4D A6 0760 STA P2+1
1F45- 85 71 0770 STA *MAT.STARTC+1
1F47- A5 99 0780 LDA *MAT.CUR.EL CALCULATE PARM 3
1F49- 85 BF 0790 STA *CUR.STRING SAVE COPY TO ORDER STRINGS
1F4B- 18 0800 CLC
1F4C- 65 78 0810 ADC *EL.SIZE ADD ELEMENT SIZE TO INCLUDE
1F4E- 8D 4A A6 0820 STA P3 CURRENT ELEMENT
1F51- 85 6D 0830 STA *MAT.ENDC SAVE COPY FOR ABORT
1F53- A5 9A 0840 LDA *MAT.CUR.EL+1
1F55- 85 C0 0850 STA *CUR.STRING+1
1F57- 69 00 0860 ADC #0 PROPAGATE CARRY
1F59- 8D 4B A6 0870 STA P3+1
1F5C- 85 6E 0880 STA *MAT.ENDC+1
1F5E- 60 0890 RTS
0900
1F5F- A9 00 0910 ORD.STRING LDA #0 CLEAR NON-NULL STRING FLAG
1F61- 85 9A 0920 STA *MAT.CUR.EL+1
1F63- A0 00 0930 ORD.STR.1 LDY #0
1F65- B1 BF 0940 LDA (CUR.STRING),Y LENGTH OF CURRENT STRING
1EE6- 20 2E 1F 0250 LOAD.MAT JSR INIT.PARMS INITIALIZE TAPE PARAMETERS
1F67- F0 1C 0950 BEQ ORD.STR.3 BRANCH IF LENGTH = 0 (NULL)
1F69- A6 9A 0960 LDX *MAT.CUR.EL+1
1F6B- D0 08 0970 BNE ORD.STR.2 BRANCH IF NON-NULL STRING
0980 ALREADY FOUND
1F6D- A6 BF 0990 LDX *CUR.STRING COPY POINTER TO LAST NON-
1F6F- 86 99 1000 STX *MAT.CUR.EL NULL STRING
1F71- A6 C0 1010 LDX *CUR.STRING+1
1F73- 86 9A 1020 STX *MAT.CUR.EL+1
1F75- 20 A9 D2 1030 ORD.STR.2 JSR NEW.STRING GET NEW LOCATION FOR STRING
1F78- 20 2F D4 1040 JSR XFR.STRING TRANSFER TO NEW LOCATION
1F7B- C8 1050 INY Y = Y + 1
1F7C- A5 83 1060 LDA *NEW.STR.PN COPY NEW STRING POINTER
1F7E- 91 BF 1070 STA (CUR.STRING),Y
1F80- A5 84 1080 LDA *NEW.STR.PN+1
1F82- C8 1090 INY
1F83- 91 BF 1100 STA (CUR.STRING),Y
1F85- A5 BF 1110 ORD.SRT.3 LDA *CUR.STRING GET NEXT STRING POINTER
1F87- 38 1120 SEC (WORKING FROM LAST TO
1F88- E5 78 1130 SBC *EL.SIZE FIRST SO SUBTRACT)
1F8A- 85 BF 1140 STA *CUR.STRING
1F8C- A6 C0 1150 LDX *CUR.STRING+1
1F8E- B0 01 1160 BCS ORD.STR.4
1F90- CA 1170 DEX PROPAGATE BORROW
1F91- 86 C0 1180 ORD.STR.4 STX *CUR.STRING+1
1F93- E4 71 1190 CPX *MAT.STARTC+1 TEST FOR ALL STRINGS DONE
1F95- D0 CC 1200 BNE ORD.STR.1
1F97- C5 70 1210 CMP *MAT.STARTC
1F99- B0 C8 1220 BCS ORD.STR.1
1F9B- A5 9A 1230 LDA *MAT.CUR.EL+1 SET Z IF ALL STRINGS NULL
1F9D- 60 1240 RTS
1250
1F9E- A5 83 1260 STR.PARMS LDA *NEW.STR.PN START OF STRINGS TO PARM 2
1FA0- 8D 4C A6 1270 STA P2
1FA3- A5 84 1280 LDA *NEW.STR.PN+1
1FA5- 8D 4D A6 1290 STA P2+1
1FA8- A0 00 1300 LDY #0
1FAA- B1 99 1310 LDA (MAT.CUR.EL),Y END OF STRINGS TO PARM 3
1FAC- 18 1320 CLC
1FAD- C8 1330 INY
1FAE- 71 99 1340 ADC (MAT.CUR.EL),Y ADD LENGTH OF LAST NON-NULL
1FB0- 8D 4A A6 1350 STA P3 STRING TO ITS POINTER TO
1FB3- C8 1360 INY END OF STRINGS
1FB4- B1 99 1370 LDA (MAT.CUR.EL),Y
1FB6- 69 00 1380 ADC #0
1FB8- 8D 4B A6 1390 STA P3+1
1FBB- 60 1400 RTS
1410
1FBC- 20 C4 1F 1420 SAVET.2.HS JSR SAVET.HS SAVE 2 COPIES IN HI-SPEED
1FBF- A9 01 1430 LDA #1 MODE WITH MINIMUM DELAY
1FC1- 8D 30 A6 1440 STA TAPDEL BETWEEN THEM
1FC4- A0 80 1450 SAVET.HS LDY #$80
1FC6- 4C C6 00 1460 JMP J.SAVET JUMP THROUGH BASIC VECTOR
1470
1FC9- 20 92 93 1480 LOADT.HS JSR INJISV TEST FOR BREAK
1FCC- B0 14 1490 BCS ABORT
1FCE- A9 FF 1500 LDA #$FF GET ANY FILE FROM TAPE
1FD0- 8D 4E A6 1510 STA P1
1FD3- A0 80 1520 LDY #$80
1FD5- 20 C9 00 1530 JSR J.LOADT JUMP THROUGH BASIC VECTOR
1FD8- B0 EF 1540 BCS LOADT.HS REPEAT IF BAD LOAD
1FDA- AD 00 A4 1550 LDA DIG GET ID
1FDD- C5 6F 1560 CMP *ID
1FDF- D0 E8 1570 BNE LOADT.HS REPEAT IF WRONG ID
1FE1- 60 1580 RTS
1590
1FE2- 68 1600 ABORT PLA DISCARD EXTRA STACK BYTES
1FE3- 68 1610 PLA
1FE4- 68 1620 PLA
1FE5- A9 00 1630 LDA #0 CLEAR ABORTED MATRIX
1FE7- A6 70 1640 LDX *MAT.STARTC SET UP FOR MONITOR FILL
1FE9- A4 71 1650 LDY *MAT.STARTC+1
1FEB- 86 FE 1660 STX *$FE
1FED- 84 FF 1670 STY *$FF
1FEF- A4 6E 1680 LDY *MAT.ENDC+1
1FF1- A6 6D 1690 LDX *MAT.ENDC
1FF3- D0 01 1700 BNE ABORT.4
1FF5- 88 1710 DEY SUBTRACT ONE FROM END
1FF6- CA 1720 ABORT.4 DEX
1FF7- 8E 4A A6 1730 STX P3
1FFA- 8C 4B A6 1740 STY P3+1
1FFD- 4C 23 87 1750 JMP F1 FILL AND RETURN
1760 .EN