Back to the index of Compute II

COMPUTE II ISSUE 3 / AUGUST/SEPTEMBER 1980 / PAGE 36

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:

  1. They will not allow the BASIC program to be modified.
  2. They will not allow the BASIC data to be loaded into a different program.
  3. They will not allow selected data to be saved on tape.
  4. They will not allow string data to be saved on tape.
  5. 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