--- title: "File Manager" id: 56940 type: "computer_media" slug: "file-manager" url: "http://localhost/computer_media/file-manager/" markdown_url: "http://localhost/computer_media/file-manager.md" published_at: "2024-10-02T01:08:33+00:00" modified_at: "2026-04-03T07:58:46+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/09/8FlMgr.png" excerpt: "A full flat-file database manager with add, edit, delete, search, sort, and tape-save — all driven by a single computed GOSUB dispatch." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" indiv: - name: "Russell King" slug: "russell-king" taxonomy: "indiv" url: "http://localhost/indiv/russell-king/" genre: - name: "Database" slug: "database" taxonomy: "genre" url: "http://localhost/type/database/" media_contents: - id: 56732 title: "Timex Sinclair Public Domain Library Tape 1001" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1001/" media_type: "Program" programmers: - name: "Russell King" slug: "russell-king" taxonomy: "indiv" url: "http://localhost/indiv/russell-king/" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/09/8FlMgr.png" article_media: - id: 7665 title: "A Small Wonder" type: "article" url: "http://localhost/article/a-small-wonder/" media_type_tags: "Database" --- File Manager is a general-purpose flat-file database program for the Timex/Sinclair 1000, published in Microcomputing Magazine (March 1983) by Russell King. It supports up to nine menu-driven operations: adding, changing, deleting, listing, searching, sorting, and saving records, plus field configuration and a field-listing view. Records are stored in a two-dimensional string array I$(M,J), with a companion array D(N1,2) holding the start and end character positions of each field within a record row, allowing variable-width fields packed into fixed-length strings. The bubble sort routine (lines 6170–6260) uses FAST/SLOW bracketing to speed up the inner loop, and the GOSUB dispatch at line 570 uses the arithmetic expression A*1000 to jump directly to the handler for the chosen menu option. *** ## Program Analysis ### Program Structure The program is organized into discrete subroutine blocks, each anchored at a round-thousands line number corresponding to a menu choice. The main menu occupies lines 200–580; each numbered option maps directly to a subroutine at `N*1000`: | Menu Option | Function | Entry Point | | --- | --- | --- | | 1 | Add Record | 1000 | | 2 | Change Record | 2000 | | 3 | Delete Record | 3000 | | 4 | List All Records | 4000 | | 5 | Search For A Record | 5000 | | 6 | Sort Records | 6000 | | 7 | Save Records To Tape | 7000 | | 8 | Set Up New List File | 8000 | | 9 | List Fields | 9000 | The computed dispatch at line 570 (`GOSUB A*1000`) is the central routing mechanism. Each subroutine ends with `RETURN` back to line 580, which loops to the main menu at line 210. ### Data Model The database is implemented entirely with string arrays. `I$(M,J)` is a two-dimensional array where `M` is the maximum number of records and `J` is the total character width of all fields combined. The companion two-dimensional numeric array `D(N1,2)` stores the start (`D(I,1)`) and end (`D(I,2)`) character positions of each field within a record row. Field names of up to `K` characters are stored in `N$(N1,K)`. This packing scheme lets the program access any field of any record with a slice expression such as `I$(N,D(I,1) TO D(I,2))`, which appears throughout the listing. Direct assignment to a slice (`INPUT I$(N,D(I,1) TO D(I,2))`) writes into the record in-place, which is a concise way to update a field without rebuilding the whole string. ### Setup Routine (lines 8000–8950) The setup routine prompts for a list name (`L$`), the number of fields (`N1`), a maximum description width (`K`, capped at 15), and then iterates over each field to collect its name and length. It builds the `D` array by accumulating a running offset `J`. After all fields are defined, it estimates the maximum number of records with the formula: `M = INT((8000 - N1*(31-L+12)) / J)` This heuristic attempts to compute available RAM headroom. The user may then choose a smaller value, and `DIM I$(M,J)` is executed to allocate the record array dynamically at runtime. ### Computed GOSUB Dispatch Line 570 uses `GOSUB A*1000`, evaluating the arithmetic expression at runtime to branch into the correct subroutine. This is a well-known idiom that replaces a chain of `IF … THEN GOTO` tests with a single line. The input is validated at line 520 to ensure `A` is in the range 1–9 before the dispatch executes. ### Bubble Sort (lines 6000–6950) The sort routine is a straightforward O(n²) bubble sort using two nested `FOR` loops over record indices `J` and `K`. The comparison is performed on the selected field slice of each record. When a swap is needed, the *entire* record string is exchanged via a temporary string `B$`: - `LET B$=I$(J)` - `LET I$(J)=I$(K)` - `LET I$(K)=B$` The entire sort loop is bracketed in `FAST`/`SLOW` to suppress display refresh during the potentially lengthy operation. ### Search Routine (lines 5000–5950) The search asks the user to select a field by answering Y/N to each field name in turn, then accepts a search string. The string is clipped to the field width if necessary. The comparison at line 5170 tests whether the entered string matches the beginning of the field content using a computed end position: `A = LEN(A$) - 1 + D(I,1)`, enabling prefix matching. A flag `Q` allows early exit if the user responds N to “Continue Search?”, and flag `F` tracks whether any match was found. ### Delete Routine (lines 3000–3950) Deletion shifts all records above the deleted index down by one position in a `FOR` loop (`LET I$(I)=I$(I+1)`), then decrements `N`. This is also bracketed in `FAST`/`SLOW`. If the record to be deleted is the last one (`A=N`), the shift loop is skipped entirely and only `N` is decremented. ### List and Print Routines (lines 4000–4950) The listing routine allows the user to select which fields to display. Selected fields are stored in the array `A(N1)` by index, and the outer loop iterates over records while the inner loop iterates over the selected fields. Both screen output and printer output (via `LPRINT`) paths are provided, chosen by the user entering S or P. ### Tape Save (lines 7000–7900) The save routine uses `SAVE L$` to save the entire program (including its variable arrays containing the data) under the list name as the filename. After saving, it jumps to line 100, which does not exist — this is an intentional technique to force a program restart or error exit after the save. ### Notable Techniques and Idioms - `GOSUB A*1000` — arithmetic expression as subroutine address, replacing a multi-branch IF chain. - Packed field slices in a 2-D string array with a separate offset table, emulating a record structure. - Direct `INPUT` into a string slice to update a field in place. - `FAST`/`SLOW` bracketing around sort, delete shift, and list-selection loops for performance. - Frequent `SCROLL` calls throughout, used instead of `CLS` to preserve context on the 1000’s small screen. - Line 9910 (`SAVE "1000%8"`) with inverse-video digit in the filename is an auto-run save of the program itself. ### Bugs and Anomalies - Line 3270 uses `N$(1)` (hardcoded index 1) instead of `N$(I)` when displaying the record to be deleted. This means only the first field name is shown for every field during delete confirmation, rather than the correct label for each field. - Line 1150 checks `A$(1)="Y"` after inputting `A$` at line 1140, but if the user enters an empty string, accessing `A$(1)` on a zero-length string will cause an error. The same pattern appears in several other routines (e.g., lines 2250, 3140, 3310). - At line 1030, if `N` exceeds `M` the code sets `N=M` and prints an error, but does not decrement `N` back; the pre-increment at line 1010 has already run, so the count becomes inconsistent. - The maximum-records formula at line 8380 is a rough heuristic and does not precisely reflect the ZX81/TS1000 memory model; it may over- or under-estimate available space depending on the RAMTOP setting. ## Source Code ``` 1 REM "FILE MANAGER" 2 REM 3 REM RUSSELL KING 4 REM MICROCOMPUTING MAGAZINE 5 REM MARCH 1983 6 REM 7 REM A LISTS PROGRAM FOR THE TIMEX/SINCLAIR 1000 8 REM 200 REM %M%A%I%N% %M%E%N%U% % % % % % % % % % % % % % 210 CLS 215 SLOW 220 PRINT TAB 7;"FILE MANAGER" 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 LIST 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 GOTO 560 530 CLS 540 PRINT "PLEASE CHOOSE 1-9" 550 GOTO 230 560 CLS 570 GOSUB A*1000 580 GOTO 210 1000 REM %A%D%D% %R%E%C%O%R%D%S% % % % % % % % % % % % 1010 LET N=N+1 1015 SCROLL 1020 PRINT "RECORD NUMBER ";N 1030 IF N<=M THEN GOTO 1060 1032 LET N=M 1035 SCROLL 1040 PRINT "NO MORE RECORDS CAN BE ADDED" 1050 GOTO 1920 1060 FOR I=1 TO N1 1070 SCROLL 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 SCROLL 1110 NEXT I 1120 SCROLL 1130 PRINT "CHANGE ANYTHING? "; 1140 INPUT A$ 1145 SCROLL 1150 IF A$(1)="Y" THEN GOTO 1060 1160 SCROLL 1170 PRINT "RECORD ";N;" ADDED" 1180 SCROLL 1900 PRINT "ADD MORE RECORDS?"; 1910 INPUT A$ 1920 SCROLL 1930 IF A$(1)="Y" THEN GOTO 1000 1950 RETURN 2000 REM %C%H%A%N%G%E% %R%E%C%O%R%D% % % % % % % % % % 2010 SCROLL 2011 SCROLL 2020 PRINT "TO CHANGE A RECORD, YOU MUST" 2030 SCROLL 2040 PRINT "ENTER THE RECORD NUMBER FOR" 2050 SCROLL 2060 PRINT "THAT RECORD. DO YOU WISH TO" 2070 SCROLL 2080 PRINT "SEARCH FOR THE RECORD NUMBER?"; 2090 INPUT A$ 2095 SCROLL 2100 IF A$(1)="Y" THEN GOSUB 5000 2110 CLS 2120 SCROLL 2130 PRINT "RECORD NUMBER TO CHANGE: "; 2140 INPUT A 2145 LET A=INT A 2150 SCROLL 2160 IF A>0 AND A<=N THEN GOTO 2200 2170 PRINT "INVALID RECORD NUMBER" 2180 GOTO 2900 2200 FOR I=1 TO N1 2210 SCROLL 2220 PRINT N$(I);I$(A,D(I,1) TO D(I,2)) 2230 SCROLL 2240 PRINT TAB 10;"CHANGE?"; 2250 INPUT A$ 2255 SCROLL 2260 IF A$(1)<>"Y" THEN GOTO 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 SCROLL 2300 NEXT I 2800 SCROLL 2810 PRINT "FINISHED WITH RECORD ";A 2900 SCROLL 2910 PRINT "CHANGE OTHER RECORDS?"; 2920 INPUT A$ 2930 SCROLL 2940 IF A$(1)="Y" THEN GOTO 2000 2950 RETURN 3000 REM %D%E%L%E%T%E% %A% %R%E%C%O%R%D% % % % % % % % 3010 IF N>0 THEN GOTO 3060 3020 SCROLL 3030 PRINT "NO RECORDS IN FILE" 3040 GOTO 3330 3060 SCROLL 3070 PRINT "ITEMS ARE DELETED BY RECORD" 3080 SCROLL 3090 PRINT "NUMBER. RECORD NUMBERS MAY" 3100 SCROLL 3110 PRINT "CHANGE AFTER AN ITEM IS DELETED" 3120 SCROLL 3130 PRINT "DO YOU WISH TO SEARCH FOR THE" 3132 SCROLL 3135 PRINT "RECORD?"; 3140 INPUT A$ 3150 SCROLL 3160 IF A$(1)="Y" THEN GOSUB 5000 3170 SCROLL 3180 PRINT "RECORD NUMBER TO DELETE:"; 3190 INPUT A 3195 LET A=INT A 3200 IF A>0 AND A<=N THEN GOTO 3250 3210 SCROLL 3220 PRINT "INVALID RECORD NUMBER" 3230 GOTO 3900 3250 FOR I=1 TO N1 3260 SCROLL 3270 PRINT N$(1);I$(A,D(I,1) TO D(I,2)) 3280 NEXT I 3290 SCROLL 3300 PRINT "DELETE THIS RECORD?"; 3310 INPUT A$ 3320 IF A$(1)="Y" THEN GOTO 3360 3330 SCROLL 3340 PRINT "DELETE CANCELLED" 3350 GOTO 3900 3360 IF A=N THEN GOTO 3450 3400 FAST 3405 FOR I=A TO N-1 3410 LET I$(I)=I$(I+1) 3420 NEXT I 3430 SLOW 3450 LET N=N-1 3460 SCROLL 3470 PRINT "RECORD DELETED" 3900 SCROLL 3910 PRINT "DELETE ANY OTHER RECORDS?"; 3920 INPUT A$ 3930 SCROLL 3940 IF A$(1)="Y" THEN GOTO 3010 3950 RETURN 4000 REM %L%I%S%T% %R%E%C%O%R%D%S% % % % % % % % % % % 4010 SCROLL 4015 LET A=0 4020 PRINT "PRINT ALL RECORD FIELDS?"; 4030 INPUT A$ 4040 IF A$(1)="Y" THEN LET A=1 4045 SCROLL 4050 FAST 4060 FOR I=1 TO N1 4070 LET A(I)=A 4075 IF A=1 THEN LET A(I)=I 4080 NEXT I 4090 SLOW 4095 LET J=N1 4100 IF A=1 THEN GOTO 4220 4110 SCROLL 4120 PRINT "ENTER Y FOR EACH FIELD" 4121 SCROLL 4123 PRINT "YOU WANT PRINTED:" 4125 LET J=0 4130 SCROLL 4140 FOR I=1 TO N1 4150 SCROLL 4160 PRINT N$(I);" ?"; 4170 INPUT A$ 4175 PRINT A$(1) 4180 IF A$(1)<>"Y" THEN GOTO 4210 4190 LET J=J+1 4200 LET A(J)=I 4210 NEXT I 4220 SCROLL 4230 PRINT "PRINT TO SCREEN OR PRINTER (S/P)" 4240 INPUT A$ 4250 IF A$(1)="P" THEN GOTO 4600 4300 SCROLL 4305 SCROLL 4310 PRINT TAB 5;L$;" LIST" 4320 SCROLL 4330 FOR I=1 TO N 4340 FOR K=1 TO J 4350 SCROLL 4360 PRINT N$(A(K));I$(I,D(A(K),1) TO D(A(K),2)) 4370 NEXT K 4380 SCROLL 4390 NEXT I 4400 GOTO 4900 4600 FAST 4610 LPRINT TAB 5;L$;" LIST" 4620 LPRINT 4630 FOR I=1 TO N 4640 FOR K=1 TO J 4650 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 4700 SLOW 4900 SCROLL 4910 PRINT "ANOTHER LIST?"; 4920 INPUT A$ 4930 IF A$(1)="Y" THEN GOTO 4000 4950 RETURN 5000 REM %S%E%A%R%C%H% %F%O%R% %R%E%C%O%R%D% % % % % % 5010 SCROLL 5020 PRINT "WHICH FIELD TO SEARCH ON:" 5030 FOR I=1 TO N1 5040 SCROLL 5050 PRINT N$(I);" ?"; 5060 INPUT A$ 5070 IF A$(1)="Y" THEN GOTO 5100 5080 NEXT I 5085 SCROLL 5090 PRINT "SELECTION CANCELLED" 5095 GOTO 5900 5100 SCROLL 5110 PRINT "ENTER THE SEARCH STRING"; 5120 INPUT A$ 5122 LET A=LEN A$ 5125 IF A<1 THEN GOTO 5110 5126 IF A<=(D(I,2)-D(I,1)+1) THEN GOTO 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 5150 FAST 5160 FOR J=1 TO N 5170 IF A$=I$(J,D(I,1) TO A) THEN GOSUB 5500 5180 IF Q=1 THEN GOTO 5200 5190 NEXT J 5200 SLOW 5210 SCROLL 5220 PRINT "SEARCH COMPLETE" 5230 SCROLL 5240 IF F=0 THEN PRINT "RECORD NOT FOUND" 5250 GOTO 5900 5500 SCROLL 5504 SCROLL 5505 PRINT "RECORD NUMBER ";J 5510 FOR K=1 TO N1 5520 SCROLL 5530 PRINT N$(K);I$(J,D(K,1) TO D(K,2)) 5540 NEXT K 5550 SCROLL 5560 PRINT "CONTINUE SEARCH?"; 5570 INPUT B$ 5580 IF B$(1)="N" THEN LET Q=1 5590 LET F=1 5600 RETURN 5900 SCROLL 5910 PRINT "ANOTHER SEARCH?"; 5920 INPUT A$ 5930 IF A$(1)="Y" THEN GOTO 5000 5950 RETURN 6000 REM %B%U%B%B%L%E% %S%O%R%T% %R%E%C%O%R%D%S% % % % 6010 SCROLL 6020 PRINT "SORTING TIME DEPENDS ON" 6021 SCROLL 6025 PRINT "NUMBER OF RECORDS" 6030 SCROLL 6040 PRINT "WHICH FIELD TO SORT BY:" 6050 SCROLL 6060 FOR I=1 TO N1 6070 SCROLL 6080 PRINT N$(I);" ?"; 6090 INPUT A$ 6100 IF A$(1)="Y" THEN GOTO 6150 6110 NEXT I 6120 SCROLL 6130 PRINT "SORT CANCELLED" 6140 GOTO 6900 6150 SCROLL 6160 FAST 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 GOTO 6250 6200 LET B$=I$(J) 6210 LET I$(J)=I$(K) 6220 LET I$(K)=B$ 6250 NEXT K 6260 NEXT J 6270 SLOW 6280 SCROLL 6290 PRINT "SORT COMPLETE" 6900 SCROLL 6910 PRINT "SORT ON ANOTHER FIELD?"; 6920 INPUT A$ 6925 SCROLL 6930 IF A$(1)="Y" THEN GOTO 6000 6950 RETURN 7000 REM %S%A%V%E% %P%R%O%G%R%A%M% %T%O% %T%A%P%E% % % 7010 SCROLL 7020 PRINT "PUT CASSETTE IN TAPE RECORDER" 7030 SCROLL 7040 PRINT "BEGIN RECORDING, THEN" 7042 SCROLL 7045 PRINT "PRESS ANY KEY TO SAVE LIST"; 7050 IF INKEY$="" THEN GOTO 7050 7060 SCROLL 7070 SAVE L$ 7900 GOTO 100 8000 REM %S%E%T% %U%P% %N%E%W% %L%I%S%T% % % % % % % % 8010 SCROLL 8020 PRINT "WHAT LIST NAME TO USE?"; 8030 INPUT L$ 8040 SCROLL 8050 PRINT "HOW MANY RECORD FIELDS?"; 8060 INPUT N1 8061 PRINT N1 8070 SCROLL 8080 LET K=15 8090 PRINT "MAXIMUM NUMBER OF CHARACTERS IN" 8100 SCROLL 8110 PRINT "DESCRIPTION IS (0-15)?"; 8120 INPUT K 8130 IF K>15 THEN LET K=15 8135 IF K<0 THEN LET K=0 8140 SCROLL 8150 LET L=31-K 8160 PRINT "MAXIMUM SIZE OF DATA FIELD" 8170 SCROLL 8180 PRINT "IS ";L;" CHARACTERS" 8182 DIM N$(N1,K) 8184 DIM A(N1) 8186 DIM D(N1,2) 8190 SCROLL 8195 SCROLL 8200 PRINT "NOW ENTER THE FIELD DESCRIPTIONS" 8210 SCROLL 8220 PRINT "AND FIELD LENGTHS:"; 8225 LET J=1 8230 SCROLL 8240 FOR I=1 TO N1 8250 SCROLL 8255 LET A$="" 8260 PRINT "FIELD ";I;":"; 8270 INPUT A$ 8272 PRINT A$ 8275 LET A$=A$+" " 8280 LET N$(I)=A$ 8290 SCROLL 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 A 8320 LET D(I,1)=J 8330 LET J=J+A 8335 LET D(I,2)=J-1 8340 SCROLL 8350 NEXT I 8360 LET J=J-1 8370 LET N=0 8380 LET M=INT ((8000-N1*(31-L+12))/J) 8390 SCROLL 8400 PRINT "MAXIMUM NUMBER OF RECORDS" 8410 SCROLL 8420 PRINT "POSSIBLE IS ABOUT ";M 8430 SCROLL 8435 PRINT "HOW MANY DO YOU WANT?"; 8437 INPUT A 8439 LET A=INT A 8440 IF A>0 AND A