MULTIFILE+

This file is part of CATS Library Tape 8, and Timex Sinclair Public Domain Library Tape 2003. Download the collection to get this file.
Developer(s): Algis Gedris
Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Database

MULTIFILE+ is a multi-field string database manager that stores records in a three-dimensional string array, supporting initialization, data entry, browsing, searching, editing, deletion, sorting, and tape save/verify operations. Records are organized as variable-length fields packed into a single 3D array `a$(1,x,fieldstart TO fieldend)`, with cumulative field offsets tracked in the numeric array `n()`. The program implements an in-place Quicksort (lines 1880–2130) using explicit stack arrays `l()` and `h()` to avoid recursion, which BASIC does not support. A decorated menu screen is built using block graphic strings and INVERSE attributes, and an optional printer channel (channel 2) can be toggled on from the menu for printed output. The program saves itself back to tape with `SAVE y$ LINE 1180` so the title and current data are preserved together.

Based on the original MULTIFILE written for the ZX81 and marketed by BUG-BYTE. Modified to handle strings and has been enhanced to provide additional routines.


Program Analysis

Program Structure

The program is divided into functional regions separated by GO TO jumps from a central menu dispatcher at lines 1180–1410. Line 110 immediately jumps to 1180, which renders the initialization code at line 120 unreachable on first load — it is only entered via menu option 1. The overall flow is:

  1. Lines 1–2: Credit REMs.
  2. Line 110: Bootstrap jump to menu.
  3. Lines 120–400: Initialization — prompts for field headings, dimensions arrays, computes capacity.
  4. Lines 410–630: Record creation (data entry loop).
  5. Lines 640–860: Step/browse records, optional printer output.
  6. Lines 870–1110: Search — selects a heading, then scans all records for a matching field value.
  7. Lines 1120–1170: Change (modify) a record.
  8. Lines 1180–1410: Main menu display and dispatcher.
  9. Lines 1420–1560: Date entry and tape save/verify.
  10. Lines 1570–1580: Full reset and re-save of bare program.
  11. Lines 1600–1810: Record deletion with compaction.
  12. Lines 1820–1870: Sort setup — field selection and parameter computation.
  13. Lines 1880–2130: Quicksort implementation.
  14. Lines 2140, 2150, 9998: Diagnostic print loop, attribution REM, and final SAVE.

Data Storage Model

All record data is stored in the three-dimensional string array a$(1, recordIndex, characterPosition). The first dimension is fixed at 1, making a$ effectively a 2D structure: rows are records and columns are concatenated field characters. Field boundaries are tracked cumulatively in n(z): after initialization, n(z) holds the last character position of field z, so field z‘s data occupies a$(1, x, n(z-1)+1 TO n(z)). This packing avoids a separate array per field at the cost of fixed-width field allocation.

Capacity Calculation

Line 300 computes the maximum number of records as x = INT(FREE - 1000) / tot, where tot is the total characters per record and FREE is the available memory keyword. The 1000-byte safety margin is intended to leave headroom for program variables. The result is stored in both x (later used as a loop counter) and w (the permanent capacity ceiling checked throughout the program).

Quicksort Implementation

The sort routine (lines 1880–2130) is a non-recursive Quicksort using explicit stack arrays l(20) and h(20) for partition boundaries, since BASIC has no call stack for recursion. The stack pointer is ii, initialized to 2 at line 1910 (with l(1)=1 and h(1)=n as the first partition). The pivot is chosen as the last element of each partition (il=j=h(ii)). Swaps are performed via the temporary string k$, operating on entire record rows a$(1,i) and a$(1,j). Comparisons use the field slice a$(1,i,a TO b) where a and b are set by lines 1840–1850 depending on which field was selected for sorting.

Menu Screen Construction

The menu (lines 1220–1290) is drawn using pre-built block-graphic border strings x$, v$, and w$ defined at lines 150–170. The title y$ is displayed with INVERSE 1. Status fields show record count n/w, sort state p$, date d$, and printer state with conditional PAPER coloring (PAPER 2 for ON, PAPER 4 for OFF). The right and left borders are drawn by a FOR loop at line 1270 printing block-graphic characters at columns 0 and 31.

Printer Channel Handling

Menu option 9 sets chan=1 and returns to the menu display. When chan is set, browse (line 670) and search (line 940) open channel 2 to "p" (the printer) before outputting records. Line 750 closes the channel after each record display. This allows selective printer output without restructuring the display logic.

Tape Save Strategy

The program saves itself back to tape at line 1550 with SAVE y$ LINE 1180, where y$ is the user-supplied title (up to 10 characters). Because the array a$ containing the database records is part of the program’s variable area, this single SAVE preserves both the program code and all entered data. Line 1570 performs a full reset (CLEAR) and re-saves the bare program shell for distribution or reuse.

Title Storage via POKE

Lines 350–370 store the title string y$ into memory starting at address 26715 using POKE 26715+z-1, CODE y$(z). This address falls within the program area and is used to persist the title across saves — the value is read back by the SAVE y$ statement because y$ is updated from those POKEd bytes by line 1580 on restore. POKE 23658,8 at line 360 enables caps lock for the title input.

Record Deletion and Compaction

Delete (lines 1600–1810) shifts all records above the deleted index down by one position using LET a$(1,i)=a$(1,i+1) in a FOR loop (line 1740), then decrements n. If the deleted record is the last one, it simply clears that row (line 1730). The mo flag (modify mode) is set to 1 before calling the display subroutine at line 1660 so the record’s contents are shown before confirmation.

Notable Techniques and Idioms

  • PAUSE 0 followed by an INKEY$ poll loop is used as an efficient keypress wait at lines 590–630, 1300–1410, 1680–1720, 1770–1810.
  • Conditional string concatenation with AND is used in the menu (e.g., "ON " AND chan=1) to display status text conditionally without IF statements.
  • The mo (modify mode) flag is shared between the change and delete routines to reuse the data-entry loop at line 440 and the display subroutine at line 680.
  • VAL "number" is not present; all GO TO targets are literal line numbers.
  • Line 1820 uses a conditional string expression in the INPUT prompt: "not " AND p$<>"unsorted" appends “not” only when the data has already been sorted.

Bugs and Anomalies

  • Line 1910 initializes ii=2 but the Quicksort loop condition at line 1920 checks ii<=1 to return — since ii starts at 2, the first iteration always proceeds, which is the intended behavior. However, the stack is never explicitly cleared between sorts, so stale values in l() and h() from a previous sort could cause incorrect behavior on a second sort of a smaller dataset. In practice this is mitigated because l(1) and h(1) are always reset at lines 1890–1900.
  • Line 620 checks INKEY$<>"y" after line 610 has already matched "y"; since INKEY$ is sampled again, there is a race: if the key is released between lines 610 and 620, line 620’s condition is true and control falls through to 630, which re-polls. This is a common INKEY$ debounce pitfall.
  • Line 1410 is a dead fallthrough: after the cascading IF INKEY$=... checks at 1320–1400 cover all digits 1–9, line 1410 GO TO 1320 is unreachable in normal operation.
  • The CLOSE #2 at line 750 is called unconditionally even when chan=0 and no channel was opened; closing an unopened channel is harmless on this platform but is technically unnecessary.

Content

Appears On

The power-user's tape. Assemble and disassemble Z80 code, manage databases with Quicksort, trace BASIC program flow, or decode resistor color codes — Tape 8 is an essential toolkit for the serious TS 2068 programmer.
Where music meets machine code — hear pop ballads and Latin medleys rendered in three-voice AY chip harmony, manage records with Quicksort-powered databases, copy tapes, or blast through scrolling star fields.

Related Products

Related Articles

Related Content

Image Gallery

Source Code

    1 REM THIS PROGRAM WAS                DONATED BY TORONTO              TIMEX SINCLAIR USER'S           GROUP
    2 REM MODIFIED AND COPIED BY          ALGIS E. GEDRIS                 DECEMBER 20, 1986
  100 REM MULTIFILE+
  110 GO TO 1180
  120 LET free=~-1000: LET save=0: CLS 
  130 INPUT "How many string headings ?"'s
  140 LET t=0: LET g=1180
  150 LET x$="\:'\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\':"
  160 LET v$="\:.\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\.:"
  170 LET w$="\:                               \ :"
  180 IF save THEN RETURN 
  190 IF s<1 THEN GO TO 220
  200 DIM n$(s,12)
  210 DIM n(s)
  220 DIM l(20): DIM h(20)
  230 CLS 
  240 FOR z=1 TO s
  250 INPUT ("Name of string heading ";z)' LINE n$(z)
  260 INPUT "Maximum number of characters ?"'n(z)
  270 CLS 
  280 LET t=t+n(z): LET n(z)=t
  290 NEXT z
  300 LET tot=t: LET x=INT (free/tot): LET w=x
  310 PRINT #1;"There is room for ";x'"records."
  320 PAUSE 120
  330 IF s>0 THEN DIM a$(1,x,t)
  340 REM titling
  350 LET y$="          ": FOR z=1 TO LEN y$: POKE 26715+z-1,CODE y$(z): NEXT z
  360 POKE 23658,8: INPUT "Title ? (Max: 10 characters)"' LINE y$: POKE 23658,0
  370 FOR z=1 TO LEN y$: POKE 26715+z-1,CODE y$(z): NEXT z
  380 IF save THEN RETURN 
  390 LET d$="": LET n=0
  400 GO TO 1180
  410 CLS 
  420 LET n=n+1: LET x=n
  430 IF n>w THEN GO TO 1520
  440 IF mo THEN PRINT ;"Enter new data for each heading or press ENTER to leave as is.  "
  450 FOR z=1 TO s
  460 PRINT INVERSE 1;n$(z);"?  "
  470 IF mo AND z=1 THEN PRINT a$(z,x, TO n(1))
  480 IF mo AND z>1 THEN PRINT a$(1,x,n(z-1)+1 TO n(z))
  490 INPUT LINE z$
  500 IF z$="" THEN GO TO 550
  510 IF LEN z$>n(z) THEN LET z$=z$( TO n(z))
  520 IF z=1 THEN LET a$(z,x, TO n(1))=z$
  530 IF z>1 THEN LET a$(1,x,n(z-1)+1 TO n(z))=z$
  540 PRINT PAPER 2;"Changed to " AND mo;z$
  550 NEXT z
  560 PRINT #0;AT 0,0;"Record # ";x
  570 IF mo THEN PAUSE 120: RETURN 
  580 PRINT #0;AT 1,0; FLASH 1;" Another record ? Y/N"
  590 PAUSE 0
  600 IF INKEY$="" THEN GO TO 600
  610 IF INKEY$="y" THEN GO TO 410
  620 IF INKEY$<>"y" THEN GO TO 1180
  630 GO TO 590
  640 CLS 
  650 INPUT "Starting from what record # ?"'x
  660 CLS 
  670 IF chan THEN OPEN #2,"p"
  680 FOR z=1 TO s
  690 IF x=n+1 THEN CLS : PRINT AT 10,10;"Nothing found": PAUSE 120: GO TO 1180
  700 PRINT INVERSE 1;n$(z)
  710 IF z=1 THEN PRINT a$(1,x, TO n(1))
  720 IF z>1 THEN PRINT a$(1,x,n(z-1)+1 TO n(z))
  730 NEXT z
  740 PRINT #0;AT 0,0;"Record # ";x
  750 CLOSE #2
  760 IF mo THEN RETURN 
  770 PRINT #1;AT 1,0;"1=Continue; 2=Menu"
  780 LET u$=INKEY$
  790 IF u$="2" THEN GO TO 1180
  800 IF u$="1" AND j=1 THEN GO TO 860
  810 IF u$="1" THEN GO TO 830
  820 GO TO 780
  830 LET x=x+1
  840 IF x>w THEN GO TO 1180
  850 GO TO 660
  860 RETURN 
  870 CLS 
  880 LET j=1
  890 LET h$="Enter heading."
  900 LET e$="Enter search item."
  910 PRINT "Headings:"'': FOR z=1 TO s: PRINT TAB 3;n$(z): PRINT : NEXT z
  920 INPUT (h$)' LINE z$
  930 IF z$="" THEN GO TO 1180
  940 IF chan THEN OPEN #2,"p"
  950 LET y=1
  960 IF z$=n$(y, TO LEN z$) THEN GO TO 1030
  970 IF y=s THEN GO TO 1000
  980 LET y=y+1
  990 GO TO 960
 1000 PRINT #0;AT 0,5; FLASH 1;i$
 1010 PAUSE 120
 1020 GO TO 920
 1030 INPUT (e$)' LINE g$
 1040 LET g1=LEN g$
 1050 IF y=1 THEN GO TO 1460
 1060 LET x=1
 1070 IF g$=a$(1,x,n(y-1)+1 TO n(y-1)+g1) THEN GO SUB 660
 1080 IF x=n THEN CLS : PRINT AT 10,10;"Nothing found": PAUSE 120: GO TO 1180
 1090 IF x=w THEN GO TO 1180
 1100 LET x=x+1
 1110 GO TO 1070
 1120 CLS : REM change
 1130 INPUT "Enter record # to be changed"'x
 1140 LET mo=1
 1150 GO SUB 440
 1160 PRINT #0;AT 0,0; FLASH 1;" Changes complete "
 1170 PAUSE 120
 1180 LET chan=0: CLOSE #2: BORDER 0: PAPER 0: INK 9: CLS : LET g=1180
 1190 LET i$=" INVALID HEADING ! "
 1200 LET mo=0
 1210 POKE 23658,0
 1220 PRINT AT 1,12; INVERSE 1;y$
 1230 PRINT 'x$;w$;AT 4,13;" MENU "'w$;w$;AT 6,1;"Press...";AT 6,18; INVERSE 1;"Status";TAB 31
 1240 PRINT AT 8,0;" 1>> Initialize"'AT 9,0;" 2>> Create";AT 9,18; INVERSE 1;n;"/";w;TAB 31; INVERSE 0;AT 10,0;" 3>> Change";AT 11,0;" 4>> Search"
 1250 PRINT AT 12,0;" 5>> Step";AT 13,0;" 6>> Sort";AT 13,18; INVERSE 1;p$;TAB 31; INVERSE 0;AT 14,0;" 7>> Delete";AT 15,0;" 8>> Date/Save";AT 15,18; INVERSE 1;d$;TAB 31
 1260 PRINT " 9>> Print-out";AT 16,18; INVERSE 0; PAPER 2;"ON " AND chan=1; PAPER 4;"OFF" AND NOT chan
 1270 FOR i=7 TO 17: PRINT AT i,0;"\: ";AT i,31;"\ :": NEXT i: PRINT v$
 1280 PRINT 
 1290 LET j=0
 1300 PAUSE 0
 1310 IF INKEY$<"1" OR INKEY$>"9" THEN GO TO 1300
 1320 IF INKEY$="1" THEN GO TO 120
 1330 IF INKEY$="9" THEN LET chan=1: GO TO 1220
 1340 IF INKEY$="2" THEN GO TO 410
 1350 IF INKEY$="5" THEN GO TO 640
 1360 IF INKEY$="7" THEN GO TO 1600
 1370 IF INKEY$="4" THEN GO TO 870
 1380 IF INKEY$="6" THEN GO TO 1820
 1390 IF INKEY$="8" THEN GO TO 1420
 1400 IF INKEY$="3" THEN GO TO 1120
 1410 GO TO 1320
 1420 CLS 
 1430 DIM d$(6)
 1440 INPUT "Enter date (YYMMDD)"; LINE d$
 1450 GO TO 1550
 1460 LET x=1
 1470 IF g$=a$(1,x, TO g1) THEN GO SUB 660
 1480 IF x=n THEN CLS : PRINT AT 10,10;"Nothing found": PAUSE 120: GO TO 1180
 1490 IF x=w THEN GO TO 1180
 1500 LET x=x+1
 1510 GO TO 1470
 1520 CLS 
 1530 PRINT AT 10,10;"No room left !"
 1540 PAUSE 120
 1550 SAVE y$ LINE 1180: PRINT FLASH 1;"rewind for verify": VERIFY ""
 1560 GO TO 1180
 1570 CLEAR : LET p$="unsorted": LET save=1: GO SUB 150: GO SUB 350: LET g=1180: LET w=0: LET n=0: LET d$="": SAVE y$ LINE 1180: VERIFY "": LIST 
 1580 FOR z=1 TO LEN y$: POKE 26715+z-1,CODE y$(z): NEXT z: RETURN 
 1590 REM delete
 1600 CLS 
 1610 LET mo=1
 1620 INPUT "Record # ? (0=menu)"'x
 1630 IF x=0 THEN GO TO 1180
 1640 IF x>n THEN PRINT #0;" Invalid input ": PAUSE 60: GO TO 1620
 1650 PRINT "Record # ";x;" is:"''
 1660 GO SUB 680
 1670 PRINT #0;AT 0,0;"1=delete;2=menu"
 1680 PAUSE 0
 1690 IF INKEY$="" THEN GO TO 1690
 1700 IF INKEY$<>"1" THEN GO TO 1180
 1710 IF INKEY$="1" THEN PRINT #0;AT 0,0;"Deleting: Stand by....": GO TO 1730
 1720 GO TO 1680
 1730 IF x>n-1 THEN LET a$(1,x)="": GO TO 1745
 1740 FOR i=x TO n-1: LET a$(1,i)=a$(1,i+1): NEXT i
 1745 LET n=n-1
 1750 PAUSE 120
 1760 CLS : PRINT #1;"Record # ";x;" has been deleted": PRINT #0;"1=more deletions;2=menu"
 1770 PAUSE 0
 1780 IF INKEY$="" THEN GO TO 1780
 1790 IF INKEY$<>"1" THEN GO TO 1180
 1800 IF INKEY$="1" THEN GO TO 1600
 1810 GO TO 1770
 1820 CLS : FOR i=1 TO s: PRINT i,n$(i): NEXT i: INPUT ("choose sort type"'"not " AND p$<>"unsorted";p$ AND p$<>"unsorted")'st
 1830 IF st>s THEN CLS : PRINT "invalid input": PAUSE 60: CLS : GO TO 1820
 1840 IF st=1 THEN LET a=1: LET b=n(st)
 1850 IF st>1 THEN LET a=1+n(st-1): LET b=n(st)
 1860 CLS : PRINT FLASH 1;"Sorting..."'"by ";n$(st): LET p$=n$(st): GO SUB 1880
 1870 GO TO 1180
 1880 REM quicksort
 1890 LET l(1)=1
 1900 LET h(1)=n
 1910 LET ii=2
 1920 IF ii<=1 THEN RETURN 
 1930 IF l(ii)>=h(ii) THEN LET ii=ii-1
 1940 IF l(ii)>=h(ii) THEN GO TO 1920
 1950 LET i=l(ii)-1
 1960 LET j=h(ii)
 1970 LET il=j
 1980 IF i>=j THEN GO TO 2070
 1990 LET i=i+1
 2000 IF a$(1,i,a TO b)<a$(1,il,a TO b) THEN GO TO 1990
 2010 LET j=j-1
 2020 IF j>1 THEN IF a$(1,j,a TO b)>a$(1,il,a TO b) THEN GO TO 2010
 2030 IF i<j THEN LET k$=a$(1,i)
 2040 IF i<j THEN LET a$(1,i)=a$(1,j)
 2050 IF i<j THEN LET a$(1,j)=k$
 2060 GO TO 1980
 2070 LET j=h(ii): LET k$=a$(1,i): LET a$(1,i)=a$(1,j): LET a$(1,j)=k$
 2080 IF i-l(ii)<h(ii)-1 THEN LET l(ii+1)=l(ii): LET h(ii+1)=i-1: LET l(ii)=i+1: GO TO 2120
 2090 LET l(ii+1)=i+1
 2100 LET h(ii+1)=h(ii)
 2110 LET h(ii)=i-1
 2120 LET ii=ii+1
 2130 GO TO 1920
 2140 FOR i=1 TO n: PRINT a$(1,i): NEXT i
 2150 REM This program is based       on the original MULTIFILE       written for the ZX81 and        marketed by BUG-BYTE. It        has been modified to deal       only with strings and has       been enhanced to provide        additional routines:-           Delete, Sort, Printout.
 9998 SAVE "MULTIFILE" LINE 1

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

Scroll to Top