This program implements a general-purpose flat-file database manager allowing the user to define custom field names and lengths, then add, change, delete, list, search, sort, and save records to cassette tape. Records are stored in a two-dimensional string array `I$(M,J)`, where field boundaries are tracked in a `D(N1,2)` array holding start and end column positions for each field within each record row. The sort routine at line 6000 uses a selection sort (sometimes called a bubble sort in the listing’s own REM) operating directly on the string array slices. A dynamic memory calculation at line 8380 estimates the maximum number of records the available RAM can hold, and the `FREE` keyword is used at line 8433 to display current free memory to the user.
Program Analysis
Program Structure
The program is organized around a main menu dispatcher at lines 200–580. After collecting a numeric choice A (1–9), line 570 uses the computed GO SUB A*1000 to jump directly to each module. Each module occupies a thousand-line block and ends with RETURN, sending control back to the menu loop at line 580.
| Line block | Module |
|---|---|
| 1000–1950 | Add records |
| 2000–2950 | Change a record |
| 3000–3950 | Delete a record |
| 4000–4950 | List all records |
| 5000–5950 | Search for a record |
| 6000–6950 | Sort records |
| 7000–7900 | Save to tape |
| 8000–8950 | Set up new file |
| 9000–9900 | List field names |
Data Model
The schema is defined dynamically during “Set up new file” (module 8000). The user specifies the number of fields N1, a label width K, and per-field lengths. Field names are stored in N$(N1,K). The two-dimensional array D(N1,2) records the start (D(I,1)) and end (D(I,2)) column offsets within each record string. All record data is held in I$(M,J), where M is the user-requested record capacity and J is the total character width of one record (sum of all field lengths). Individual fields are accessed via string slicing: I$(N,D(I,1) TO D(I,2)).
Key BASIC Idioms and Techniques
- Computed GOSUB: Line 570 uses
GO SUB A*1000— an arithmetic expression as the target — to dispatch to any of the nine modules without a chain of IF/THEN tests. - String-slice assignment: Fields are written directly into sub-ranges of
I$(N,...)andI$(A,...)viaINPUT I$(N,D(I,1) TO D(I,2)), exploiting the language’s ability to assign to a string slice in place. - FREE keyword: Line 8433 uses
PRINT ~(theFREEkeyword on the TS2068) to display available memory alongside the calculated maximum record count. - PAPER attribute in PRINT: Several lines use inline
PAPER 5andPAPER 7within PRINT statements to highlight values on screen without changing the global attribute. - Memory estimation: Line 8380 computes
M=INT((27000-N1*(31-L+12))/J)to estimate how many records will fit in RAM, giving the user guidance before the finalDIM I$(M,J)at line 8450.
Search Module (5000–5950)
The search performs a linear scan over all N records (lines 5160–5190). The search string A$ is compared against the beginning of the chosen field using I$(J,D(I,1) TO A), where A is recalculated at line 5129 to be D(I,1)+LEN(A$)-1. This means the search is a leading-substring match rather than a full-field match. When a match is found, subroutine 5500 displays the record and asks whether to continue; setting Q=1 exits the loop early.
Sort Module (6000–6950)
The sort at lines 6170–6260 is described in its own REM as a bubble sort but is structurally a selection sort: the outer loop variable J advances from 1 to N-1, and the inner loop K runs from J to N, swapping I$(J) and I$(K) (full record strings) whenever the chosen field of record J is lexicographically greater than that of record K. Whole-record swaps via B$ preserve all field data.
List / Print Module (4000–4950)
The user may choose to print all fields or a subset. Selected field indices are stored in A(J) (reusing the A array dimensioned at line 8184). Output can go to screen or printer; however, the printer path has a structural bug: lines 4610–4690 contain FOR/NEXT loops and LPRINT statements but there is no GO TO 4610 or similar branch from line 4250. Line 4250 tests IF A$(1)="P" THEN GO TO 4600, but line 4600 does not exist in the listing — execution falls through to line 4610, skipping the outer FOR I=1 TO N loop header that would normally set up I, so LPRINT output would be malformed or cause an error.
Save Module (7000–7900)
Module 7000 saves data using SAVE L$ at line 7070, where L$ is the list name entered during setup. After saving, line 7900 uses GO TO 100 — a REM line — rather than RETURN. This unconditionally restarts the program from near the top (falling through to the main menu at line 210) instead of returning normally, which is an intentional design choice to reinitialize the menu display after a tape operation.
Bugs and Anomalies
- Printer path missing loop header:
GO TO 4600at line 4250 targets a non-existent line; execution resumes at 4610 inside what should be a doubleFORloop, but neitherFOR InorFOR Khas been established, making the LPRINT section non-functional. - Search boundary uses D(1,2) instead of D(I,2): Line 5126 reads
IF A<=(D(1,2)-D(I,1)+1); the first index should likely beI, not1, so field-width clamping is only correct when searching field 1. - Add record decrements then caps N: Line 1010 increments
Nbefore checking the limit at line 1030–1032, then setsN=Mon overflow. This means if the file is full,Nis incremented and immediately reset, leaving the count correct, but the logic flow is slightly fragile. - SAVE saves only data, not program:
SAVE L$without additional keywords saves the BASIC program itself, not the data arrays, so reloading would restore the program but not the records — a fundamental limitation of the save strategy.
Content
Source Code
10 REM "THIS IS A PROGRAM THAT WAS PRINTED IN THE MARCH ISSUE OF MICROCOMPUTING FOR THE ZX 1000. IT HAS BEEN CONVERTED FOR USE ON THE 2068"
100 REM A LISTS PROGRAM FOR THE 2068
200 REM MAIN MENU
210 CLS
215 BORDER 2: BRIGHT 1
220 PRINT TAB 7;"THE LISTS PROGRAM"
230 PRINT
240 PRINT "1..ADD RECORD"
250 PRINT
260 PRINT "2..CHANGE RECORD"
270 PRINT
280 PRINT "3..DELETE RECORD"
290 PRINT
300 PRINT "4..LIST ALL RECORDS"
310 PRINT
320 PRINT "5..SEARCH FOR A RECORD"
330 PRINT
340 PRINT "6..SORT RECORDS"
350 PRINT
360 PRINT "7..SAVE RECORDS TO TAPE"
370 PRINT
380 PRINT "8..SET UP NEW FILE"
390 PRINT
395 PRINT "9..LIST FIELDS"
400 PRINT
410 PRINT
500 PRINT "WHICH DO YOU WISH TO DO?";
510 INPUT A
515 LET A=INT A
520 IF A>0 AND A<10 THEN GO TO 560
530 CLS
540 PRINT "PLEASE CHOSE 1-9"
550 GO TO 230
560 CLS
570 GO SUB A*1000
580 GO TO 210
1000 REM ADD RECORDS
1010 LET N=N+1
1015 PRINT
1020 PRINT "RECORD NUMBER ";N
1030 IF N<=M THEN GO TO 1060
1032 LET N=M
1035 PRINT
1040 PRINT "NO MORE RECORDS CAN BE ADDED"
1050 GO TO 1920
1060 FOR I=1 TO N1
1070 PRINT
1080 PRINT N$(I);
1090 INPUT I$(N,D(I,1) TO D(I,2))
1095 PRINT I$(N,D(I,1) TO D(I,2));
1100 PRINT
1110 NEXT I
1120 PRINT
1130 PRINT "CHANGE ANYTHING?";
1140 INPUT A$
1145 PRINT
1150 IF A$(1)="Y" THEN GO TO 1060
1160 PRINT
1170 PRINT "RECORD ";N;" ADDED"
1180 PRINT
1900 PRINT "ADD MORE RECORDS?";
1910 INPUT A$
1920 PRINT
1930 IF A$(1)="Y" THEN CLS : GO TO 1000
1950 RETURN
2000 REM CHANGE A RECORD
2010 PRINT
2011 PRINT
2020 PRINT "TO CHANGE A RECORD, YOU MUST",,," ENTER THE RECORD NUMBER FOR",,," THAT RECORD. DO YOU WISH TO",,,"SEARCH FOR THE RECORD NUMBER?";
2090 INPUT A$
2095 PRINT
2100 IF A$(1)="Y" THEN GO SUB 5000
2110 CLS
2120 PRINT
2130 PRINT "RECORD NUMBER TO CHANGE: ";
2140 INPUT A
2145 LET A=INT A
2150 PRINT
2160 IF A>0 AND A<=N THEN GO TO 2200
2170 PRINT "INVALID RECORD NUMBER"
2180 GO TO 2900
2200 FOR I=1 TO N1
2210 PRINT
2220 PRINT N$(I);I$(A,D(I,1) TO D(I,2))
2230 PRINT
2240 PRINT TAB 10;"CHANGE?";
2250 INPUT A$
2255 PRINT
2260 IF A$(1)<>"Y" THEN GO TO 2300
2270 PRINT N$(I);
2280 INPUT I$(A,D(I,1) TO D(I,2))
2285 PRINT I$(A,D(I,1) TO D(I,2))
2290 PRINT
2300 NEXT I
2800 PRINT
2810 PRINT "FINISHED WITH RECORD ";A
2900 PRINT
2910 PRINT "CHANGE OTHER RECORDS?";
2920 INPUT A$
2930 PRINT
2940 IF A$(1)="Y" THEN GO TO 2000
2950 RETURN
3000 REM DELETE A RECORD
3010 IF N>0 THEN GO TO 3060
3020 PRINT
3030 PRINT "NO RECORDS IN FILE"
3040 GO TO 3330
3060 PRINT
3070 PRINT "ITEMS ARE DELETED BY RECORD"
3080 PRINT
3090 PRINT "NUMBER. RECORD NUMBERS MAY",,,"CHANGE AFTER ITEM IS DELETED"
3120 PRINT
3130 PRINT "DO YOU WISH TO SEARCH FOR THE",,,"RECORD?";
3140 INPUT A$
3150 PRINT
3160 IF A$(1)="Y" THEN GO SUB 5000
3170 PRINT
3180 PRINT "RECORD NUMBER TO DELETE:";
3190 INPUT A
3195 LET A=INT A
3200 IF A>0 AND A<=N THEN GO TO 3250
3210 PRINT
3220 PRINT "INVALID RECORD NUMBER"
3230 GO TO 3900
3250 FOR I=1 TO N1
3260 PRINT
3270 PRINT N$(I);I$(A,D(I,1) TO D(I,2))
3280 NEXT I
3290 PRINT
3300 PRINT "DELETE THIS RECORD?";
3310 INPUT A$
3320 IF A$(1)="Y" THEN GO TO 3360
3330 PRINT
3340 PRINT "DELETE CANCELLED"
3350 GO TO 3900
3360 IF A=N THEN GO TO 3450
3405 FOR I=A TO N-1
3410 LET I$(I)=I$(I+1)
3420 NEXT I
3450 LET N=N-1
3460 PRINT
3470 PRINT "RECORD DELETED"
3900 PRINT
3910 PRINT "DELETE ANY OTHER RECORDS?";
3920 INPUT A$
3930 PRINT
3940 IF A$(1)="Y" THEN GO TO 3010
3950 RETURN
4000 REM LIST RECORDS
4010 PRINT
4015 LET A=0
4020 PRINT "PRINT ALL RECORD FIELDS?";
4030 INPUT A$
4040 IF A$(1)="Y" THEN LET A=1
4045 PRINT
4060 FOR I=1 TO N1
4070 LET A(I)=A
4075 IF A=1 THEN LET A(I)=I
4080 NEXT I
4095 LET J=N1
4100 IF A=1 THEN GO TO 4220
4110 PRINT
4120 PRINT "ENTER Y FOR EACH FIELD",,,"YOU WANT PRINTED:"
4125 LET J=0
4130 PRINT
4140 FOR I=1 TO N1
4150 PRINT
4160 PRINT N$(I);" ?";
4170 INPUT A$
4175 PRINT A$(1)
4180 IF A$(1)<>"Y" THEN GO TO 4210
4190 LET J=J+1
4200 LET A(J)=I
4210 NEXT I
4220 PRINT
4230 PRINT "PRINT TO SCREEN OR PRINTER(S/P)"
4240 INPUT A$
4250 IF A$(1)="P" THEN GO TO 4600
4300 PRINT
4310 PRINT TAB 10; PAPER 5;" ";L$;" "; "LIST"
4330 FOR I=1 TO N
4340 FOR K=1 TO J
4350 PRINT
4360 PRINT N$(A(K));I$(I,D(A(K),1) TO D(A(K),2))
4370 NEXT K
4390 NEXT I
4400 GO TO 4900
4610 LPRINT TAB 5;L$;" LIST"
4620 LPRINT
4660 LPRINT N$(A(K));I$(I,D(A(K),1) TO D(A(K),2))
4670 NEXT K
4680 LPRINT
4690 NEXT I
4900 PRINT
4910 PRINT "ANOTHER LIST?";
4920 INPUT A$
4930 IF A$(1)="Y" THEN GO TO 4000
4950 RETURN
5000 REM SEARCH FOR RECORD
5010 PRINT
5020 PRINT "WHICH FIELD TO SEARCH ON:"
5030 FOR I=1 TO N1
5040 PRINT
5050 PRINT N$(I);" ?"
5060 INPUT A$
5070 IF A$(1)="Y" THEN GO TO 5100
5080 NEXT I
5085 PRINT
5090 PRINT "SELECTION CANCELLED"
5095 GO TO 5900
5100 PRINT
5110 PRINT "ENTER THE SEARCH STRING";
5120 INPUT A$
5122 LET A=LEN A$
5125 IF A<1 THEN GO TO 5110
5126 IF A<=(D(1,2)-D(I,1)+1) THEN GO TO 5129
5127 LET A=(D(I,2)-D(I,1)+1)
5128 LET A$=A$(1 TO A)
5129 LET A=A-1+D(I,1)
5130 LET F=0
5140 LET Q=0
5160 FOR J=1 TO N
5170 IF A$=I$(J,D(I,1) TO A) THEN GO SUB 5500
5180 IF Q=1 THEN GO TO 5200
5190 NEXT J
5210 PRINT
5220 PRINT "SEARCH COMPLETE"
5230 PRINT
5240 IF F=0 THEN PRINT "RECORD NOT FOUND"
5250 GO TO 5900
5500 PRINT
5505 PRINT "RECORD NUMBER ";J
5510 FOR K=1 TO N1
5520 PRINT
5530 PRINT N$(K);I$(J,D(K,1) TO D(K,2))
5540 NEXT K
5550 PRINT
5560 PRINT "CONTINUE SEARCH?";
5570 INPUT B$
5580 IF B$(1)="N" THEN LET Q=1
5590 LET F=1
5600 RETURN
5900 PRINT
5910 PRINT "ANOTHER SEARCH?";
5920 INPUT A$
5930 IF A$(1)="Y" THEN GO TO 5000
5950 RETURN
6000 REM BUBBLE SORT RECORDS
6010 PRINT
6020 PRINT "SORTING TIME DEPENDS ON ",,,"NUMBER OF RECORDS"
6030 PRINT
6040 PRINT "WHICH FIELD TO SORT BY:"
6050 PRINT
6060 FOR I=1 TO N1
6070 PRINT
6080 PRINT N$(I);" ?";
6090 INPUT A$
6100 IF A$(1)="Y" THEN GO TO 6150
6110 NEXT I
6120 PRINT
6130 PRINT "SORT CANCELLED"
6140 GO TO 6900
6150 PRINT
6170 FOR J=1 TO N-1
6180 FOR K=J TO N
6190 IF I$(J,D(I,1) TO D(I,2))<=I$(K,D(I,1) TO D(I,2)) THEN GO TO 6250
6200 LET B$=I$(J)
6210 LET I$(J)=I$(K)
6220 LET I$(K)=B$
6250 NEXT K
6260 NEXT J
6280 PRINT
6290 PRINT "SORT COMPLETE"
6900 PRINT
6910 PRINT "SORT ON ANOTHER FIELD?";
6920 INPUT A$
6925 PRINT
6930 IF A$(1)="Y" THEN GO TO 6000
6950 RETURN
7000 REM SAVE PROGRAM TO TAPE
7010 PRINT
7020 PRINT "PUT CASSETTE IN TAPE RECORDER",,,"BEGIN RECORDING, THEN",,,"PRESS ANY KEY TO SAVE LIST";
7050 IF INKEY$="" THEN GO TO 7050
7060 PRINT
7070 SAVE L$
7900 GO TO 100
8000 REM SET UP NEW LIST
8020 PRINT "WHAT LIST NAME TO USE?";
8030 INPUT L$
8035 PRINT TAB 10; PAPER 5;"**";L$;"**"
8040 PRINT
8045 PRINT "HOW MANY RECORD FIELDS ? ": INPUT N1
8050 PRINT TAB 10; PAPER 5;" ";N1;" "
8070 PRINT
8080 LET K=15
8090 PRINT "ENTER MAX. NUMBER OF CHARACTERS IN DESCRIPTIONS +1 (LIMIT 15)";
8120 INPUT K
8125 PRINT TAB 10; PAPER 5;" ";K;" "
8130 IF K>15 THEN LET K=15
8135 IF K<0 THEN LET K=0
8140 PRINT
8150 LET L=31-K
8160 PRINT "MAXIMUM SIZE OF DATA FIELD IS";TAB 10; PAPER 5;" ";L;" "; PAPER 7;" CHARACTERS"
8182 DIM N$(N1,K)
8184 DIM A(N1)
8186 DIM D(N1,2)
8190 PRINT
8195 PRINT
8200 PRINT "NOW ENTER FIELD DESCRIPTIONS","AND FIELD LENGTHS:";
8225 LET J=1
8230 PRINT
8240 FOR I=1 TO N1
8250 PRINT
8255 LET A$=""
8260 PRINT "FIELD ";I;":";
8270 INPUT A$
8272 PRINT TAB 15; PAPER 5;" ";A$;" "
8275 LET A$=A$+" "
8280 LET N$(I)=A$
8290 PRINT
8300 PRINT "FIELD LENGTH (1-";L;"):";
8310 INPUT A
8312 IF A<1 THEN LET A=1
8315 IF A>L THEN LET A=L
8317 PRINT TAB 25; PAPER 5;" ";A;" "
8320 LET D(I,1)=J
8330 LET J=J+A
8335 LET D(I,2)=J-1
8340 PRINT
8350 NEXT I
8360 LET J=J-1
8370 LET N=0
8380 LET M=INT ((27000-N1*(31-L+12))/J)
8390 PRINT
8400 PRINT "MAXIMUM NUMBER OF RECORDS",,,"POSSIBLE IS ABOUT ";M
8430 PRINT
8432 PRINT "MEMORY FREE ";
8433 PRINT ~
8434 PRINT
8435 PRINT "HOW MANY RECORDS DO YOU WANT?";
8437 INPUT A
8439 LET A=INT A
8440 IF A>0 AND A<M THEN LET M=A
8450 DIM I$(M,J)
8900 PRINT
8910 PRINT L$;" LIST SET UP"
8920 PRINT
8930 PRINT "PRESS ANY KEY TO RETURN TO MENU"
8940 IF INKEY$="" THEN GO TO 8940
8950 RETURN
9000 REM LIST FIELDS
9020 PRINT
9030 PRINT TAB 5;L$;" LIST FIELDS"
9040 PRINT
9050 FOR I=1 TO N1
9060 PRINT
9065 PRINT
9070 PRINT N$(I)
9080 NEXT I
9090 PRINT
9100 PRINT "PRESS ANY KEY TO RETURN TO MENU";
9105 PRINT
9110 IF INKEY$="" THEN GO TO 9110
9900 RETURN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
