This program is a membership database manager for CATS, written in 1983 by Ned Beeler and revised in 1985 by Mark Fisher. It stores up to 214 member records across parallel string arrays covering name, street, city, state, ZIP, home phone, office phone, job, equipment, and a payment-status field. The main menu offers seven functions: adding records, searching and editing records with a cursor-driven update screen, listing to screen, printing mailing labels, printing a formatted roster, printing a ZIP-code-sorted list, and running a Shell sort on any chosen array variable. Payment currency is determined by comparing a stored date field against today’s date using a month-offset formula (VAL I$(TO 2)+12*VAL I$(4 TO 5)-12), and lapsed members receive an LPRINT-formatted “LAST ISSUE” renewal notice with a $18.00 dues amount. Printer control uses Epson-style escape sequences embedded in LPRINT statements, and two POKEs at addresses 64256 and 64260 configure printer hardware settings.
Program Analysis
Program Structure
The program is organized as a dispatch table centered on a main menu loop at line 1000–1499. Menu choices 1–7 are routed via the expression GO SUB VAL J$*500+G (line 1200), where G is a constant (likely 1000), mapping digit “1” to subroutine 1500, “2” to 2000 (search/update), “3” to 2500 (list), “4” to 3000 (label print), “5” to 3500 (roster print), “6” to 4000 (ZIP list), and “7” to 4500 (sort). Lines 0–14 and 50–96 form a field-entry subroutine bank, where GO SUB I inside a FOR I=O TO 13 loop dispatches to the matching line number for each field.
Key Constants and Variables
Several single-letter variables serve as constants throughout, established before the listing begins:
L= 1 (used as numeric constant one and loop start)O= 0 (used as zero in AT coordinates and loop bounds)D= 10 (column offset and field index)M= 1 (used in PRINT AT for membership number row)G= 1000 (base for menu dispatch GO SUB)W$= a separator/ruler string printed between sectionsX$holds the current sort key description displayed on the menuN= current number of records;K= last membership number assigned
Data Storage
Records are held in parallel string arrays, each dimensioned to at least 214 elements:
| Array | Field |
|---|---|
N$(J) | Name |
R$(J) | Street address |
C$(J) | City |
S$(J) | State |
Z$(J) | ZIP code |
H$(J) | Home phone |
P$(J) | Office phone |
O$(J) | Job/occupation |
E$(J) | Equipment owned |
D$(J) | Payment record (date in columns 1–5, status in column 10) |
The D$(J) array is multipurpose: columns 1–5 store the payment date (MO-YR format), column 6 onward stores the membership number as a string, and column 10 stores a single-character payment status code.
Payment Expiry Logic
At lines 1000 and 3010, the current date is converted to a month-offset integer using VAL I$(TO L+L)+12*VAL I$(4 TO 5)-12. In the label-print routine (line 3220–3225), each member’s stored payment date undergoes the same conversion and is compared to Q. Members with status "0" are silently skipped; those with status above "4" are treated as currently paid; those whose date offset does not exceed Q (i.e., paid within 12 months) are also skipped. All others receive an LPRINT-formatted renewal notice including Epson escape sequences for emphasis and a $18.00 dues reminder, and their status is set to "0".
Cursor-Driven Update Screen
The update/delete routine (lines 2100–2499) implements a simple full-screen record editor. A highlighted bar (FLASH, OVER) is drawn at row Q using W$. The user moves the bar with keys "6" (down) and "7" (up), checked via INKEY$ in a tight loop at line 2220. Row position Q maps to specific actions:
- Rows 0–13: dispatch
GO SUB Qto the matching field-entry subroutine - Row 15: forward to next record (
GO SUB O, i.e., GO SUB 0) - Rows 16–17: navigate forward/backward through records
- Row 19: delete with confirmation
- Row >19: return to menu
Bounds wrapping at line 2230 uses the expression Q+(Q<0)-(Q>20), adding or subtracting 1 when the cursor goes out of range — a common Sinclair BASIC boolean arithmetic idiom.
Shell Sort Implementation
Lines 4500–4999 implement a Shell (diminishing-increment) sort. The gap sequence is computed by halving Q repeatedly (Q=INT(Q/2)), with odd-gap enforcement at line 4530. The sort variable is user-selectable: choices “1” and “2” map to N$(J) and Z$(J) respectively, or the user may type any array expression directly. The comparison at line 4545 uses a remarkable runtime string construction technique:
IF VAL$ J$<=VAL$ (J$(TO 4)+"+Q"+J$(5 TO))
This builds a string like "N$(J+Q)" by splicing "+Q" into the sort-key expression string at position 5, then evaluates it with VAL$ — effectively an eval-based comparison that avoids hardcoding the sort field. When a swap is needed, all ten parallel arrays are swapped together using temporary variables (A$ through Y$).
Printer Control
All print routines use Epson-style escape codes embedded as string concatenation: CHR$ 27+"N" (normal pitch), CHR$ 27+"!" (condensed), CHR$ 27+"X..."+CHR$ 27+"Y..."+CHR$ 27+"Q..." for formatted renewal notices. The two POKEs at addresses 64256 and 64260 configure the TS2068’s printer interface port settings, switching between different printer modes across routines.
Initialization and Auto-Start
Line 5060 is the program’s auto-start entry point (used in the SAVE I$ LINE 5060 at line 5010). It sets system variables via POKEs (scroll count at 23561/23562, caps lock at 23658, and flags at 23609), configures the display with BORDER 0: PAPER 0: INK 7, and prompts for the date. The date-parsing loop at lines 5080–5110 finds the first non-digit character (the separator) and normalizes the date to a two-digit month, hyphen, two-digit year format stored in I$.
Notable Techniques and Anomalies
- The field-dispatch loop
FOR I=O TO 13: GO SUB I: NEXT Itargets line numbers 0–13 directly, keeping field routines at the very top of the program for fast lookup. - Lines
2,8, and11are bareRETURNstatements acting as stubs for unimplemented fields (family, and two phone-adjacent fields). - Lines
90–94are commented out withREM, preserving an earlier screen-clearing loop that was apparently superseded. - Line
9000–9050is a maintenance utility (with the print statement itself also REMmed out at line9010) that bulk-deletes records by callingGO SUB 2400in reverse order — reverse traversal is necessary to avoid index shifting during deletion. - Line
9500is a one-off data-migration snippet that replaces status code"Q"with"4"across all records. - At line
3570, the roster print routine usesIF D$(J,D)<"4" THEN NEXT J— placingNEXT Jinside anIFstatement, which is valid in Sinclair BASIC and skips non-current members without aGO TO. - Line
2275readsIF Q=15 THEN GO SUB O, callingGO SUB 0(the payment status subroutine at line 0) when the cursor is on the “UPDATE” row — the row numbering on screen and theQvalues do not align intuitively with the displayed menu rows.
Content
Source Code
0 LET D$(J,D)="5": PRINT AT L,D-L;D$(J,D): LET D$(J, TO 5)=I$: PRINT AT L,16;D$(J, TO 5): RETURN
1 INPUT "PAYMENT STATUS ";D$(J,D): PRINT AT L,D-L;D$(J,D): RETURN
2 RETURN
3 INPUT "NAME ";N$(J): PRINT AT 3,D;N$(J): RETURN
4 INPUT "STREET ";R$(J): PRINT AT 4,D;R$(J): RETURN
5 INPUT "CITY ";C$(J): PRINT AT 5,D;C$(J): RETURN
6 INPUT "STATE ";S$(J): PRINT AT 6,D;S$(J): RETURN
7 INPUT "ZIP CODE ";Z$(J): PRINT AT 7,D;Z$(J): RETURN
8 RETURN
9 INPUT "HOME PHONE ";H$(J): PRINT AT D-L,D;H$(J): RETURN
10 INPUT "OFFICE PHONE ";P$(J): PRINT AT D,D;P$(J): RETURN
11 RETURN
12 INPUT "JOB ";O$(J): PRINT AT 12,D;O$(J): RETURN
13 GO SUB 50: INPUT "EQUIPMENT";E$(J): PRINT AT 13,D;E$(J)
14 RETURN
50 PRINT AT 17,O;"A.TS1000 F.E-PROM K.FLOPPY "
52 PRINT "B.TS1500 G.16KRAM L.STRNGY "
54 PRINT "C.TS2068 H.32KRAM M. "
56 PRINT "D.PRINTER I.64KRAM N. "
58 PRINT "E.MODEM J.DISK O. "
62 RETURN
90 REM FOR I=16 TO D+D+L
92 REM PRINT AT I,O;" "
94 REM NEXT I
96 RETURN
100 PRINT "NAME :"';"STREET :"';"CITY :"';"STATE :"';"ZIP CODE :"
120 PRINT W$
130 PRINT "H-PHONE :"'"O-PHONE :"'"FAMILY :"'"JOB :"'"EQUIP. :"
140 PRINT W$
150 RETURN
999 STOP
1000 LET Q=VAL I$( TO L+L)+12*VAL I$(4 TO 5)-12
1006 POKE 64256,L: POKE 64260,O
1010 CLS
1020 PRINT ''"******* MEMBERSHIP FILES ******* by Ned Beeler 1983 revised by M. Fisher '85 "
1025 PRINT " TODAY'S DATE """;I$;""""
1030 PRINT '"1. ADD NEW NAME"''"2. OR """"; UPDATE/DELETE RECORD"''"3. LIST RECORDS"''
1050 PRINT "4. LPRINT LABELS"''"5. LPRINT ROSTER"''"6. LPRINT ZIP LIST "''
1075 PRINT "7. SORT: curr. by ";X$''"8. SAVE program"
1090 INPUT "SELECT YOUR CHOICE";J$
1095 CLS
1151 IF LEN J$<L THEN LET J$="2"
1160 IF CODE J$<49 OR CODE J$>58 THEN GO TO G
1200 GO SUB VAL J$*500+G
1499 GO TO G
1500 IF N>=214 THEN PRINT "FILE FULL": PAUSE D*D: RETURN
1505 LET N=N+L: LET K=K+L: LET J=N
1510 CLS
1520 PRINT "FILE NO. ";N,"MEMBSHP NO. ";K
1525 PRINT "----PAID ------------DATE -----"
1530 PRINT W$
1535 GO SUB D*D
1546 LET D$(N,6 TO D-L)=STR$ K: PRINT AT M,28;D$(N,6 TO D-L)
1550 FOR I=O TO 13: GO SUB I: NEXT I
1655 GO SUB D*D-D
1680 INPUT "IS ALL CORRECT (Y/N) ";J$
1700 IF J$="N" THEN GO SUB 2100
1720 CLS
1730 INPUT "ANY MORE NAMES? (Y/N) ";J$
1750 IF J$="Y" THEN GO TO 1500
1999 RETURN
2010 CLS
2020 INPUT "SEARCH NAME ? ";J$
2030 FOR J=L TO N
2040 IF J$=N$(J, TO LEN J$) THEN GO TO 2100
2050 NEXT J
2055 CLS
2060 INPUT " NO NAME-PRESS ENTER TO RETURN ";J$
2070 RETURN
2100 LET Q=15
2105 CLS
2110 PRINT "FILE NO. ";J;" MEMBERSHIP NO. ";D$(J,6 TO D-L)
2120 PRINT "PAID---- ";D$(J,D);" DATE ";D$(J, TO 5)
2125 PRINT W$
2130 GO SUB D*D
2150 PRINT AT 3,D;N$(J);AT 4,D;R$(J);AT 5,D;C$(J)
2160 PRINT AT 6,D;S$(J);AT 7,D;Z$(J)
2170 PRINT AT D-L,D;H$(J);AT D,D;P$(J)
2180 PRINT AT 12,D;O$(J);AT 13,D;E$(J)
2195 PRINT W$
2200 PRINT "UPDATE"'"FOREWARD"'"BACK"'" "'" DELETE "'" "'">--- RETURN TO MENU ---> OR (M)";
2210 PRINT #1;AT 0,0;"PUT CURSOR ON LINE TO BE CHANGEDPRESS <ENTER> WHEN READY"
2215 PRINT OVER L; FLASH L;AT Q,O;W$;
2216 PAUSE O
2217 PRINT OVER L; FLASH O;AT Q,O;W$
2220 LET Q=Q+(INKEY$="6")-(INKEY$="7")
2230 LET Q=Q+(Q<0)-(Q>20)
2240 IF INKEY$=CHR$ 13 THEN GO TO 2270
2245 IF INKEY$="M" THEN RETURN
2250 GO TO 2215
2275 IF Q=15 THEN GO SUB O
2280 IF Q=16 OR Q=17 THEN LET J=J+(Q=16 AND J<215)-(Q=17 AND J>1): GO TO 2105
2310 IF Q>19 THEN RETURN
2320 IF Q<14 THEN GO SUB Q
2380 IF Q=19 THEN INPUT "DELETE: ARE YOU SURE? (Y/N) ";J$: IF J$="Y" THEN GO TO 2400
2398 GO TO 2210
2400 FOR Z=J TO N
2402 LET N$(Z)=N$(Z+L)
2404 LET R$(Z)=R$(Z+L)
2406 LET C$(Z)=C$(Z+L)
2408 LET S$(Z)=S$(Z+L)
2410 LET H$(Z)=H$(Z+L)
2412 LET P$(Z)=P$(Z+L)
2414 LET Z$(Z)=Z$(Z+L)
2416 LET O$(Z)=O$(Z+L)
2420 LET E$(Z)=E$(Z+L)
2422 LET D$(Z)=D$(Z+L)
2430 NEXT Z
2440 LET N=N-L
2499 RETURN
2504 LET Z=0
2505 FOR J=L TO N
2510 PRINT J;" ";N$(J, TO 18);D$(J): LET Z=Z+1
2550 NEXT J
2555 PRINT Z
2560 PAUSE 0
2990 RETURN
3000 CLS
3010 LET Q=VAL I$( TO 2)+12*VAL I$(4 TO 5)-12
3100 POKE 64256,O: POKE 64260,D
3200 LPRINT CHR$ 27+"N TEST XXXXX"''''''
3205 PAUSE 0
3210 FOR J=L TO N
3220 IF D$(J,D)>"4" THEN LPRINT : GO TO 3230: REM current year's member
3221 IF D$(J,D)="0" THEN GO TO 3330: REM previously crossed off list
3222 IF VAL D$(J, TO L+L)+12*VAL D$(J,4 TO 5)>Q THEN LPRINT : GO TO 3230: REM less than 12 months since paid - still current
3225 LPRINT CHR$ 27+"XLAST ISSUE"+CHR$ 27+"Y-"+CHR$ 27+"Q$18.00; we need your support."+CHR$ 27+"N": LET D$(J,D)="0"
3230 LPRINT N$(J);" pd ";D$(J, TO 5);" ";D$(J,D)
3250 LPRINT R$(J)
3270 LPRINT C$(J);" ";
3280 LPRINT S$(J);" ";Z$(J)
3310 LPRINT ''
3330 NEXT J
3340 RETURN
3500 REM LPRINT "..."
3506 POKE 64256,O: POKE 64260,D
3507 LPRINT CHR$ 27+"N"+CHR$ 27+"!"
3510 LPRINT "CATS MEMBERSHIP, ";I$
3515 LPRINT "Name # Address H# O#"'
3520 LET LINE=3
3560 FOR J=L TO N
3566 IF LINE=57 THEN LET LINE=2: PAUSE 0: LPRINT CHR$ 27+"NName # Address H# O#"'
3569 IF N$(J,1)<>N$(J-1,1) THEN LET LINE=LINE+1: LPRINT
3570 IF D$(J,D)<"4" THEN NEXT J
3575 LET LINE=LINE+1
3580 LPRINT CHR$ 27;"N";
3590 LPRINT N$(J);CHR$ 27;"Q ";D$(J,6 TO );" ";
3600 LPRINT R$(J);" ";
3610 LPRINT C$(J);" ";
3620 LPRINT S$(J);" ";Z$(J);" ";H$(J);" ";P$(J)
3630 NEXT J
3640 RETURN
4000 LPRINT CHR$ 27+"N"
4002 POKE 64256,O: POKE 64260,D
4003 LPRINT CHR$ 27+"N"+CHR$ 27+"!"
4004 LET J$=Z$(L)
4010 LPRINT "CATS members, sorted by ZIP, ";I$
4020 FOR J=L TO N
4025 IF D$(J,10)<"3" THEN GO TO 4050
4028 IF J$( TO 3)<>Z$(J, TO 3) THEN LPRINT : LET LINE=LINE+1
4030 LPRINT N$(J);CHR$ 27+"Q"+C$(J)+CHR$ 27+"N"+S$(J);" ";Z$(J)
4040 LET J$=Z$(J)
4050 NEXT J
4100 RETURN
4500 CLS : PRINT "SORT BY:","(1) N$(J) NAME"'',"(2) Z$(J) ZIP"
4510 INPUT "SELECT SORT,OR ANY VAR";J$: PRINT ''"SORTED BY: ";J$
4512 IF J$="1" THEN LET J$="N$(J)"
4513 IF J$="2" THEN LET J$="Z$(J)"
4515 LET T=N
4520 LET Q=T
4525 LET Q=INT (Q/2): IF Q<L THEN LET X$=J$: RETURN
4530 IF Q/2=INT (Q/2) THEN LET Q=Q+1
4532 PRINT Q;" ";
4535 FOR I=L TO T-Q
4540 LET J=I
4545 IF VAL$ J$<=VAL$ (J$( TO 4)+"+Q"+J$(5 TO )) THEN GO TO 4750
4546 BEEP .02,50-Q
4550 LET A$=N$(J)
4552 LET B$=R$(J)
4554 LET G$=C$(J)
4556 LET X$=S$(J)
4558 LET K$=H$(J)
4560 LET L$=P$(J)
4562 LET M$=D$(J)
4564 LET Q$=O$(J)
4566 LET T$=E$(J)
4570 LET Y$=Z$(J)
4600 LET N$(J)=N$(J+Q)
4602 LET R$(J)=R$(J+Q)
4604 LET C$(J)=C$(J+Q)
4606 LET S$(J)=S$(J+Q)
4610 LET H$(J)=H$(J+Q)
4612 LET P$(J)=P$(J+Q)
4614 LET D$(J)=D$(J+Q)
4620 LET O$(J)=O$(J+Q)
4622 LET E$(J)=E$(J+Q)
4626 LET Z$(J)=Z$(J+Q)
4700 LET N$(J+Q)=A$
4702 LET R$(J+Q)=B$
4704 LET C$(J+Q)=G$
4706 LET S$(J+Q)=X$
4708 LET H$(J+Q)=K$
4710 LET P$(J+Q)=L$
4712 LET D$(J+Q)=M$
4714 LET O$(J+Q)=Q$
4716 LET E$(J+Q)=T$
4720 LET Z$(J+Q)=Y$
4730 LET J=J-Q
4740 IF J>0 THEN GO TO 4544
4750 NEXT I
4999 GO TO 4525
5000 CLS : PRINT "WILL BE SAVED AS; """;I$;""""
5010 SAVE I$ LINE 5060: PRINT ''"Rewind tape and VERIFY"
5020 VERIFY I$
5030 RETURN
5060 POKE 23561,30: POKE 23562,2: POKE 23658,8: POKE 23609,3: BORDER 0: PAPER 0: INK 7
5065 POKE 64260,0: FOR J=64263 TO 64265: POKE J,0: NEXT J
5070 INPUT "TODAY'S DATE? (MO-YR) ";I$
5080 FOR I=L TO 5: IF I$(I)<"0" THEN GO TO 5100
5090 NEXT I
5100 LET I$( TO L+L)=I$( TO I-L): LET I$(3 TO )="-"+I$(I+L TO )
5110 GO TO G
8996 STOP
9000 FOR J=N TO 196 STEP -1
9010 REM IF D$(J,10)<"3" THEN PRINT "OUT NO.";J,N$(J): GO SUB 2400
9020 GO SUB 2400
9050 NEXT J: STOP
9500 FOR J=L TO N: IF D$(J,10)="Q" THEN LET D$(J,10)="4":
9510 NEXT J
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
