Unifile III

This file is part of CATS Library Tape 8, and SINCUS Exchange Tape 102 - Utilities & Business. Download the collection to get this file.
Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Database

UNIFILE II is a general-purpose flat-file database manager that stores records in a large string buffer of 28,000 characters and maintains a sorted index using a binary search tree encoded in the string variable Y$. Records are composed of typed fields, where field types are named in a 50-element string array A$, and fields can reference a type by embedding a “#” marker followed by a type-code character. The system supports inserting, searching (normal, special substring, first-character, and multiple-item), amending, and deleting records, with deletion implemented by a “telescope” routine that physically compacts the buffer and updates all pointers. The program saves and verifies itself to tape, preserving both the data buffer and index on reload.


Program Analysis

Program Structure

The program is organized as a menu-driven dispatcher at lines 1000–1140, which branches to four main subsystems via GO SUB. Each subsystem is clearly delimited by REM banners.

LinesSubsystem
1000–1200Main menu, initialization, save/verify, stop
1210–1430File structure setup (DIM, pointer init)
1440–1650Normal data entry loop
1660–1910Binary-search insert into sorted index
1930–2160Amend/change an existing entry
2180–2740Search (normal, III, SSS, MMM modes)
2780–2900Utility subroutines: input, display
2920–3090Special (substring) search
3130–3390Telescope (delete + compact) routine
3400–3520Item-type name editor

Data Storage Model

All record data is stored sequentially in a single large string B$ dimensioned to 28,000 characters (line 1350). Each field within a record is a length-prefixed string: the first byte is CHR$ (LEN field), followed by the field content. Records are terminated by a field whose second byte is "*". The variable P tracks the next free position in B$.

The sorted index is kept in string Y$, which holds 2-byte big-endian pointers into B$ for each record. Record S starts at position 256*CODE Y$(2*S-1) + CODE Y$(2*S), exactly what FN A() computes.

Field types are held in A$(50,10), a 50-element fixed-width string array. A field ending with "#" followed by a type-code byte uses the type name from A$ as a label when displaying.

Key Function Definitions

  • DEF FN A() = 256*CODE Y$(2*S-1) + CODE Y$(2*S) — decodes the 2-byte big-endian pointer for sorted slot S into an absolute offset C into B$.
  • DEF FN A$() = B$(C TO C+CODE B$(C)-1) — returns the length-prefixed field string at position C, including the length byte.

Binary Search Insertion (Lines 1730–1910)

New records are inserted into the sorted index using a binary search. The algorithm computes POWER = INT(LN(N-1)/LN 2) to find the depth, then iterates from the most significant bit downward, adjusting S left or right depending on whether the new key T$ is greater or less than the existing entry at each midpoint. After locating the insertion point, the pointer array Y$ is expanded by splicing in two new bytes at position 2*S.

The use of LN to compute an integer logarithm base 2 is an idiom forced by the absence of a native integer-log function.

Telescope (Delete and Compact) Routine (Lines 3130–3390)

Deleting a record is a two-phase operation. First, the data bytes of the target record are physically overwritten by shifting all subsequent bytes in B$ down using a chunked copy loop with chunk size SHIFT=1000. Second, all 2-byte pointers in Y$ that pointed beyond the deleted record are decremented by C2 (the byte-length of the deleted record), and the index slot for the deleted entry is removed from Y$ by string slicing at line 3270.

The chunked copy (line 3210–3260) avoids the need to copy the entire tail of B$ in one operation, which could be slow or cause memory pressure. The final chunk is handled by adjusting SHIFT at line 3220.

Input Encoding Subroutine (Lines 2780–2800)

The GO SUB 2780 routine reads a raw input into Q$ and then prepends a length byte: Q$ = CHR$(LEN Q$ + 1) + Q$. This converts all user input into the same length-prefixed format used internally in B$, so the rest of the program can handle user input and stored data uniformly.

Lines 2785–2787 perform a two-pass check for trailing "#" followed by one or two decimal digits. If found, the digit string is converted via VAL to a CHR$ code, encoding a field type reference compactly as a single byte.

Search Modes

  • Normal: Sequential scan comparing full fields via FN A$().
  • III (first-character): Scans forward checking only B$(C+1) against the fifth character of S$.
  • SSS (special/substring): For each record, scans all byte positions within the record for a substring match against S$(5 TO ).
  • MMM (multiple): Accepts up to SEARCH items into array M$, then repeatedly calls the SSS search for each term, requiring all to match within the same record group.

Notable Techniques and Idioms

  • RANDOMIZE is used as a no-op separator after IF to allow a second statement on the same line without using AND or a colon — a common Spectrum BASIC idiom to chain conditions.
  • The sentinel record CHR$ 2 + CHR$ 255 at the start of B$ (line 1360) acts as an end-of-file marker; searches terminate when they encounter it.
  • The initial Y$ (line 1400) seeds two dummy pointer entries pointing to positions 0 and 3, bracketing the sentinel and providing stable boundary nodes for the binary search.
  • Record display (line 2860–2870) re-evaluates FN A$() multiple times in a single PRINT statement to extract different substrings — each call re-reads through C, which must not change during the line.

Bugs and Anomalies

  • Line 1190 contains a typo: "Press any ey to VERIFY" — the word “key” is missing the letter “k”.
  • Lines 2130–2160 appear unreachable in the change-entry subroutine, since all paths through lines 2050–2120 either loop back to line 2000 or return before reaching line 2130.
  • The FN A$() function is called multiple times in single expressions (e.g., lines 2860–2870), relying on C not being a side-effectful call — this is safe here but fragile if the function definition were changed.
  • The multiple-search loop at lines 2313–2318 has a subtle flow dependency: S1 is set once before the inner FOR K loop, but the SSS search modifies S; if any term fails to match (C4=0), the routine returns immediately rather than advancing to the next record, which may cause the outer loop to stall.
  • Line 3505 uses NEXT I inside an IF branch, which advances the outer FOR I loop from within the inner FOR J loop body — this is legal in Sinclair BASIC but unconventional and can skip the RETURN on line 3505, falling through correctly only because the loop exhaustion itself causes a return to the calling context.

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.
The workhorse tape — assemble Z80 code, manage databases, analyze statistics, cast I Ching hexagrams, balance your checkbook, and switch between four font styles. SINCUS Tape 102 turns the TS 2068 into a serious productivity machine.

Related Products

Related Articles

Related Content

Image Gallery

Source Code

  100 REM "UNIFILEII"
 1000 PAPER 7: CLS : BORDER 7: INK 6: PAPER 0: PRINT PAPER 2;"        UNIFILE                "
 1010 PRINT '"FUNCTIONS AVAILABLE:"
 1020 PRINT '"     1)SET UP NEW FILE"
 1030 PRINT '"     2)ENTER INFORMATION"
 1040 PRINT '"     3)SEARCH/DISPLAY/CHANGE"
 1045 PRINT '"     4)TYPE NAMES"
 1050 PRINT '"     5)STOP"
 1060 PRINT ''"PLEASE ENTER WHICH YOU REQUIRE."
 1070 INPUT Z$
 1080 CLS 
 1090 IF Z$="1" THEN GO SUB 1210
 1100 IF Z$="2" THEN GO SUB 1440
 1110 IF Z$="3" THEN GO SUB 2180
 1115 IF Z$="4" THEN GO SUB 3400
 1120 IF Z$="5" THEN GO SUB 1150
 1130 CLS 
 1140 GO TO 1000
 1150 PRINT AT 10,5; INK 7; PAPER 2;"FILING SYSTEM CLOSED"
 1160 BEEP 2,2
 1180 INPUT "Have you input new information  you wish to save? (Y/N)";Q$: IF Q$="N" THEN STOP 
 1190 SAVE "UNIFILE": PRINT '"Rewind tape, then Press any ey to VERIFY": PAUSE 0: VERIFY "UNIFILE": STOP 
 1200 REM ***********************
 1210 REM ENTRY STRUCTURE
 1220 REM ***********************
 1230 DIM A$(50,10)
 1350 DIM B$(28000)
 1360 LET B$(1 TO 4)=CHR$ 2+CHR$ 0+CHR$ 2+CHR$ 255
 1370 DEF FN A()=256*CODE Y$(2*S-1)+CODE Y$(2*S)
 1380 DEF FN A$()=B$(C TO C+CODE B$(C)-1)
 1390 LET P=5
 1400 LET Y$=CHR$ 0+CHR$ 1+CHR$ 0+CHR$ 3
 1410 LET N=2
 1420 RETURN 
 1430 REM ***********************
 1440 REM NORMAL INPUT
 1450 REM ***********************
 1460 LET R$=""
 1470 PRINT PAPER 2;"          ENTRIES               "
 1480 PRINT '"COMMANDS AVAILABLE:"
 1490 PRINT '">ENTER ITEM SPECIFIED"''">""ZZZ"" TO QUIT"
 1500 PRINT "********************************"
 1510 PRINT "FILE SIZE:";P-1;"/";LEN B$
 1540 GO SUB 2780
 1550 IF Q$(LEN Q$-1)="#" THEN PRINT A$(CODE Q$(LEN Q$));":";
 1580 PRINT Q$(2 TO LEN Q$-2*(Q$(LEN Q$-1)="#"))
 1590 IF LEN Q$>=4 THEN RANDOMIZE : IF Q$(2 TO 4)="ZZZ" THEN RETURN 
 1600 LET R$=R$+Q$
 1610 IF Q$(2)<>"*" THEN GO TO 1520
 1620 CLS 
 1630 GO SUB 1660
 1640 GO TO 1440
 1650 REM ***********************
 1660 REM PLACE DATA IN FILE
 1670 REM ***********************
 1680 IF P+LEN R$-1<LEN B$ THEN GO TO 1730
 1690 PRINT AT 14,10;"FILE NOW FULL"
 1700 PRINT ''"   Press any key to continue"
 1710 PAUSE 0
 1720 RETURN 
 1730 LET POWER=INT (LN (N-1)/LN 2)
 1740 LET S=2^POWER
 1750 LET T$=R$(2 TO CODE R$(1))
 1760 FOR K=POWER-1 TO 0 STEP -1
 1770 LET C=FN A()
 1780 LET U$=FN A$()(2 TO )
 1790 LET S=S+(2^K)*(T$>U$)-(2^K)*(T$<U$)
 1810 IF S>N-1 THEN LET S=N-1
 1820 IF S<2 THEN LET S=2
 1830 NEXT K
 1840 LET C=FN A()
 1850 LET U$=FN A$()(2 TO )
 1860 IF T$<U$ THEN LET S=S-1
 1870 LET B$(P TO P+LEN R$-1)=R$
 1880 LET N=N+1
 1890 LET Y$=Y$(1 TO 2*S)+CHR$ INT (P/256)+CHR$ (P-256*INT (P/256))+Y$(2*(S+1)-1 TO )
 1900 LET P=P+LEN R$
 1910 RETURN 
 1920 REM ***********************
 1930 REM CHANGE ENTRY
 1940 REM ***********************
 1950 LET S=S-1
 1960 LET C=FN A()
 1970 LET R$=""
 1980 PRINT "ENTRY ";S-1;":-"
 2000 IF FN A$()(2)="*" THEN LET R$=R$+CHR$ 2+"*": GO SUB 3130: GO SUB 1660: RETURN 
 2010 PRINT FN A$()(2 TO LEN FN A$()-1);
 2015 IF FN A$()(LEN FN A$()-1)="#" THEN PRINT CODE (FN A$()(LEN FN A$()))
 2017 IF FN A$()(LEN FN A$()-1)<>"#" THEN PRINT FN A$()(LEN FN A$())
 2020 PRINT AT 16,0; PAPER 2;"          AMEND                 "
 2030 PRINT "COMMANDS AVAILABLE:"
 2040 PRINT ">""ENTER"" LEAVES ITEM UNCHANGED"'">""ZZZ"" DELETES WHOLE ENTRY"'">CHANGED ITEM"'">NEW ITEM ENDING WITH ""*"""
 2050 GO SUB 2780
 2060 IF LEN Q$=1 OR Q$(LEN Q$)="*" THEN LET R$=R$+FN A$()
 2070 LET C=C+CODE B$(C)
 2080 CLS 
 2090 IF LEN Q$=1 THEN GO TO 2000
 2100 IF Q$(2 TO )="ZZZ" THEN GO SUB 3130: RETURN 
 2105 IF Q$(LEN Q$)="*" THEN LET Q$=Q$(2 TO LEN Q$-1): GO SUB 2785
 2110 LET R$=R$+Q$
 2120 GO TO 2000
 2130 GO SUB 3130
 2140 IF Q$(2 TO )="ZZZ" THEN RETURN 
 2150 GO SUB 1660
 2160 RETURN 
 2170 REM ***********************
 2180 REM SEARCH
 2190 REM ***********************
 2200 LET S=2
 2210 PRINT PAPER 2;"            SEARCH              "
 2220 PRINT ''"COMMANDS AVAILABLE:"
 2230 PRINT ">INPUT ITEM FOR NORMAL SEARCH"'">PRECEDE WITH ""SSS"" FOR"'" SPECIAL SEARCH"'">PRECEDE WITH ""III"" TO SEARCH"'" FOR FIRST CHARACTER OF ENTRY"'">""ENTER"" FOR FIRST ITEM ON FILE"
 2235 PRINT ">""MMM"" FOR MULTIPLE SEARCH"
 2240 PRINT "********************************"
 2250 PRINT ''"INPUT SEARCH ITEM:";
 2260 GO SUB 2780
 2270 PRINT Q$(2 TO )
 2280 LET S$=Q$
 2290 IF LEN S$=1 THEN GO TO 2510
 2300 LET C=FN A()
 2310 IF LEN S$<5 THEN GO TO 2430
 2311 IF S$(2 TO 4)<>"MMM" THEN GO TO 2320
 2312 PRINT "NUMBER OF SEARCH ITEMS? ";: INPUT SEARCH: PRINT SEARCH: DIM M$(SEARCH,10): FOR K=1 TO SEARCH: PRINT "SEARCH ITEM ";K;": ";: GO SUB 2780: PRINT Q$(2 TO ): LET M$(K)=Q$: NEXT K
 2313 LET S1=S
 2314 FOR K=1 TO SEARCH: LET S$="MMM"+M$(K,2 TO CODE M$(K,1)): GO SUB 2940: IF C4=0 THEN RETURN 
 2316 IF S=S1 THEN NEXT K: GO TO 2510
 2318 GO TO 2313
 2320 IF S$(2 TO 4)<>"III" THEN GO TO 2390
 2330 FOR I=S TO N
 2340 LET S=I
 2350 LET C=FN A()
 2360 IF B$(C+1)=S$(5) THEN GO TO 2510
 2370 NEXT I
 2380 RETURN 
 2390 IF S$(2 TO 4)<>"SSS" THEN GO TO 2430
 2400 GO SUB 2920
 2410 IF C4=1 THEN GO TO 2510
 2420 RETURN 
 2430 IF FN A$()=S$ THEN GO TO 2510
 2450 IF FN A$()=CHR$ 2+CHR$ 255 THEN RETURN 
 2460 LET C=C+CODE B$(C)
 2470 IF B$(C-1)<>"*" THEN GO TO 2430
 2480 LET S=S+1
 2490 LET C=FN A()
 2500 GO TO 2430
 2510 LET C=FN A()
 2520 LET C4=0
 2530 IF FN A$()=CHR$ 2+CHR$ 255 THEN RETURN 
 2540 CLS 
 2550 PRINT "ENTRY ";S-1;":-"
 2560 GO SUB 2850
 2570 LET S=S+1
 2580 PRINT AT 16,0; PAPER 2;"          SEARCH                "
 2590 PRINT "COMMANDS AVAILABLE:"
 2600 PRINT ">""ENTER"" TO DISPLAY NEXT ITEM"'">""ZZZ"" TO QUIT FUNCTION"'">""AAA"" TO AMEND"'">""CCC"" TO CONTINUE SEARCH"
 2610 INPUT P$
 2620 CLS 
 2625 IF LEN S$>=4 THEN RANDOMIZE : IF P$="CCC" AND S$(2 TO 4)="MMM" THEN GO TO 2313
 2630 IF P$="CCC" THEN GO TO 2300
 2640 IF P$="" THEN GO TO 2510
 2650 IF P$<>"AAA" THEN GO TO 2710
 2660 LET C=FN A()
 2670 CLS 
 2680 GO SUB 1930
 2710 IF P$="ZZZ" THEN RETURN 
 2720 IF P$="AAA" THEN RETURN 
 2730 CLS 
 2740 GO TO 2260
 2750 REM ***********************
 2760 REM FUNCTIONAL SUBROUTINES
 2770 REM ***********************
 2780 INPUT Q$
 2785 IF LEN Q$>1 THEN RANDOMIZE : IF Q$(LEN Q$-1)="#" THEN LET Q$=Q$( TO LEN Q$-1)+CHR$ (VAL Q$(LEN Q$))
 2787 IF LEN Q$>2 THEN RANDOMIZE : IF Q$(LEN Q$-2)="#" THEN LET Q$=Q$( TO LEN Q$-2)+CHR$ (VAL Q$(LEN Q$-1 TO ))
 2790 LET Q$=CHR$ (LEN Q$+1)+Q$
 2800 RETURN 
 2810 PRINT A$(I,2 TO CODE A$(I,1));":";
 2820 RETURN 
 2850 IF FN A$()(2 TO )="*" THEN RETURN 
 2860 IF FN A$()(LEN FN A$()-1)="#" THEN PRINT A$(CODE FN A$()(LEN FN A$()));":";
 2870 PRINT FN A$()(2 TO LEN FN A$()-2*(FN A$()(LEN FN A$()-1)="#"))
 2880 LET C=C+CODE B$(C)
 2890 GO TO 2850
 2900 RETURN 
 2910 REM ***********************
 2920 REM SPECIAL SEARCH
 2930 REM ***********************
 2940 LET C4=0
 2950 FOR H=S TO N-1
 2960 LET S=H
 2970 LET C=FN A()
 2980 LET C1=C
 3000 LET C1=C1+CODE B$(C1)
 3010 IF B$(C1-1)<>"*" THEN GO TO 3000
 3020 FOR J=C+1 TO C1-LEN S$+5
 3030 IF B$(J TO J+LEN S$-5)<>S$(5 TO ) THEN GO TO 3060
 3040 LET C4=1
 3050 RETURN 
 3060 NEXT J
 3070 NEXT H
 3080 LET C4=0
 3090 RETURN 
 3100 REM ***********************
 3110 REM TELESCOPE FILE
 3120 REM ***********************
 3130 LET C=FN A()
 3140 LET SHIFT=1000
 3150 LET C1=C
 3160 LET C3=C
 3180 LET C1=C1+CODE B$(C1)
 3190 IF B$(C1-1)<>"*" THEN GO TO 3180
 3200 LET C2=C1-C
 3210 FOR I=C1 TO LEN B$-1 STEP SHIFT
 3220 IF LEN B$-I+1<SHIFT THEN LET SHIFT=LEN B$-I+1
 3230 LET S$=B$(I TO I+SHIFT-1)
 3240 LET B$(C TO C+SHIFT-1)=S$
 3250 LET C=C+SHIFT
 3260 NEXT I
 3270 LET Y$=Y$(1 TO 2*(S-1))+Y$(2*(S+1)-1 TO )
 3280 FOR I=1 TO N-1
 3290 LET S=I
 3300 LET C=FN A()
 3310 IF C<=C3 THEN GO TO 3350
 3320 LET C=C-C2
 3330 LET Y$(2*I-1)=CHR$ INT (C/256)
 3340 LET Y$(2*I)=CHR$ (C-256*INT (C/256))
 3350 NEXT I
 3360 LET P=P-C2
 3370 LET N=N-1
 3380 RETURN 
 3400 REM ***********************
 3410 REM ITEM TYPES
 3420 REM ***********************
 3430 FOR I=1 TO 50 STEP 10
 3440 CLS : FOR J=I TO I+9
 3450 PRINT J;")";A$(J)
 3460 NEXT J
 3470 PRINT '''"COMMANDS:"
 3480 PRINT '">ZZZ=QUIT"
 3490 PRINT ">III=ITEM"
 3495 PRINT ">NNN=NEXT PAGE"
 3500 INPUT Q$: IF Q$="ZZZ" THEN RETURN 
 3505 IF Q$="NNN" THEN CLS : NEXT I: RETURN 
 3510 IF Q$="III" THEN INPUT "NUMBER? ";TYPE: INPUT "TYPE NAME? ";Q$: LET A$(TYPE)=Q$: CLS : GO TO 3440
 3520 GO TO 3500

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

People

No people associated with this content.

Scroll to Top