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