Z-Mail is a mailing-list manager from Zebra Systems for its FDD disk drive system on the Timex/Sinclair 2068 (loading screen: “copyright © 1985 Zebra Systems, Inc.”). A list holds up to 1,000 records of 128 characters, kept in a random-access disk file with a separate name and ZIP code index. You page through records with the arrow keys, and edit them in an on-screen editor with Modify, Insert, Delete, Append, Goto, Sort, Print and Quit commands. The list can be sorted by name or ZIP code, searched, and printed as mailing labels or as a summary through an AERCO, Tasman B, Tasman C or A&J printer interface, or the drive’s own RS232 port.
Z-Mail came on the Zebra Bonus Disk, side B, volume ZMAIL. The download has the disk image and every file as a TAP: the loader start, the BASIC program zmail.bas, its machine code zmail.obj (63000, 1300 bytes) and the files of a demo list. The demo list’s records, and a copy of an index and a record left in the program’s saved variables, have been blanked. The program uses TOS disk commands throughout, so it runs only with the drive, for example in FUSE with the FDD3000 interface (fdd3000e). It has not been tested.
Program Analysis
Files
A list called name uses four files: name.dat, the 128-byte records, opened with OPEN #*1;n$+".dat";r;128; name.ix1, saved from s$() (1,000 × 15: surname and ZIP for sorting and searching); name.ix2, saved from i$() (1,000 × 2: the physical record number of each entry as two bytes); and name.ptr, saved from p() (current, last and physical record counters). Records are read and written with INPUT *#1;r$;AT n and PRINT *#1;r$;AT n.
Structure
- Lines 100–216: open an old list or create a new one, loading the three index files with
LOAD *… DATA. - Lines 300–565: printer interface choice and the FDD-RS232 parameter settings.
- Lines 1000–1744: record display and a character editor with cursor movement, insert and delete.
- Lines 2000–2900: main loop and the record commands.
- Lines 3000–3500: name extraction, sorting, and label and summary printing.
Notable Techniques
- The physical record number is stored as two characters in
i$(), so the index can be sorted without moving records in the data file. - Sorting and searching have both BASIC and machine code versions (
RANDOMIZE USR 63004and63001). - Errors from the disk system are trapped by POKEing system variable 23729 and testing 23728 after each disk command.
Potential Issues
- The routine at line 3002 that rebuilds the name index opens
"mlist.dat"instead of the current list’s file. - Line 565 POKEs 23279, where the matching lines use 23729; this appears to be a typo.
Source Code
1 REM !VAL \~!CODE \ RETURN ( RETURN ( THEN '. THEN AND *U\>"PEEK #( IF RESTORE !LEN \N!@ RESTORE !VAL \~ NEW o>& THEN % THEN gSTR$ RESTORE !VAL \~ LLIST RETURN 8 RETURN "TAB \ LLIST RESTORE ~= OR RETURN a8paSTR$ X COPY OR ao> THEN % THEN gSTR$ RESTORE !ATTR \~ LLIST RETURN (r CAT >2AT \ RESTORE ~W!ATTR \~ RETURN z({*TAB \ OR STR$ r#s+:AT \ RETURN (% RETURN x RETURN RESTORE !CODE \ THEN F LLIST OR LPRINT PI GO SUB BSTR$ LPRINT PI GO SUB BSTR$ "TAB \ LLIST #:AT \=2AT \ POINT !CODE \ THEN F*TAB \(PI GO SUB BA..PI GO SUB B\ RETURN #"TAB \ LLIST #>"PEEK >= DATA :! THEN ? THEN THEN - THEN ? THEN THEN ,T]!ATTR \~ RETURN ( FLASH OR RESTORE ~ VERIFY *TAB \r#s+"TAB \ LLIST #STR$ *TAB \ RETURN PI GO SUB B"TAB \ LLIST #>"PEEK >=bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
2 DEF FN a()=PEEK 23635+256*PEEK 23636+5
9 REM Int Editor Parms
10 LET l=5: LET c=0: OVER 1
11 LET rs=0
12 LET f=6: REM flash period
14 LET rm=31: REM right margin
16 LET lm=0: REM left margin
18 LET bm=8: REM bottom margin
20 LET tm=4: REM top margin
30 LET rd=10: REM repeat delay
40 RETURN
100 REM Start
101 FOR n=7 TO 20: PRINT AT n,0;" ": NEXT n
102 INPUT "New or Old file (n or o)?";k$
110 IF k$="o" THEN INPUT "Name of old data file?";n$
111 IF k$="n" THEN INPUT "Name of new data file?";n$
112 IF n$="" THEN GO TO 102
113 IF k$="n" THEN GO TO 170
114 PRINT AT 21,0; FLASH 1; PAPER 5;"data disk in drive'A' then ENTER"
116 PAUSE 4e4
117 PRINT AT 21,0; FLASH 1; PAPER 5;" files loading "
118 LET erret=102: POKE 23729,255
120 LOAD *n$+".ix1" DATA s$()
121 IF PEEK 23728<>0 THEN GO TO 4000
128 LOAD *n$+".ix2" DATA i$()
129 IF PEEK 23728<>0 THEN GO TO 4000
130 LOAD *n$+".ptr" DATA p()
132 IF PEEK 23728<>0 THEN GO TO 4000
133 LET CURrec=p(1): LET LSTrec=p(2): LET PHYrec=p(3)
140 OPEN #*1;n$+".dat";r;128
150 GO TO 2000
170 REM new file
172 PRINT AT 21,0; FLASH 1; PAPER 5;"data disk in drive'A' then ENTER": PAUSE 4e4: PRINT AT 21,0; PAPER 5;" "
174 POKE 23728,9: POKE 23729,10: POKE 23723,2
176 RANDOMIZE USR FN a()
178 REM "please wait"
180 PRINT AT 21,0; FLASH 1; PAPER 5;" intilization in progress "
182 LET erret=102: POKE 23729,255: POKE 23728,0
183 DIM s$(1000,15)
184 IF PEEK 23728<>0 THEN GO TO 4000
185 DIM i$(1000,2)
186 IF PEEK 23728<>0 THEN GO TO 4000
187 DIM p(3)
188 IF PEEK 23728<>0 THEN GO TO 4000
189 FOR n=1 TO 1000
190 LET hi=INT (n/256)
192 LET lo=n-(hi*256)
194 LET i$(n,1)=CHR$ hi
196 LET i$(n,2)=CHR$ lo
198 NEXT n
199 REM RANDOMIZE USR 63007: REM MC int-index
200 DIM *n$+".dat"
201 IF PEEK 23728<>0 THEN GO TO 4000
202 SAVE *n$+".ix1" DATA s$()
203 IF PEEK 23728<>0 THEN GO TO 4000
204 SAVE *n$+".ix2" DATA i$()
205 IF PEEK 23728<>0 THEN GO TO 4000
206 LET CURrec=1
208 LET LSTrec=1
210 LET PHYrec=1
211 LET sflg=0
212 OPEN #*1;n$+".dat";r;128
214 LET r$=" "
216 PRINT *#1;r$;AT 1
220 GO TO 2000
310 PRINT AT 8,0;" Which printer interface"," do you use?"
315 PRINT AT 12,0;" 1 = Aerco"," 2 = Tasman-b"," 3 = Tasman-c"," 4 = A & J"," 5 = FDD-RS232"
320 PRINT AT 21,0; INVERSE 1;" press no# to select "
330 LET k$=INKEY$: IF k$="" THEN GO TO 330
335 IF k$<>"1" AND k$<>"2" AND k$<>"3" AND k$<>"4" AND k$<>"5" THEN GO TO 330
340 LET If=CODE k$-48
345 RETURN
500 REM Cofiguration Mods
530 IF IF=1 THEN RANDOMIZE USR 63013
540 IF IF=2 THEN RANDOMIZE USR 63016
545 IF IF=3 THEN RANDOMIZE USR 63019
560 IF IF=4 THEN RANDOMIZE USR 63022
565 IF IF=5 THEN POKE 23729,255: CLOSE #*3: POKE 23279,0: CLS : PRINT AT 0,0; INVERSE 1;" FDD-RS232 PARAMETER SETTINGS ": FORMAT *":ch_a": OPEN #*3;":ch_a";o;1: RANDOMIZE USR 63025: CLS
570 RETURN
1000 REM PRNT_REC
1002 PRINT AT 20,0;"Record #";CURrec;" "
1010 PRINT AT 5,0;r$(1 TO 32)
1020 PRINT AT 6,0;r$(33 TO 64)
1030 PRINT AT 7,0;r$(65 TO 96)
1040 PRINT AT 8,0;r$(97 TO )
1050 RETURN
1100 REM DRW_WND
1110 PAPER 5: BORDER 5: CLS
1120 POKE 23728,0: POKE 23729,10
1130 POKE 23723,3
1140 RANDOMIZE USR FN a()
1150 REM "Z-MAIL"
1155 POKE 23728,0: POKE 23729,0: PAPER 7
1160 PRINT AT 5,0;" ": GO SUB 1161: RETURN
1161 PRINT AT 11,0; PAPER 5;"_________ Control Keys _________"
1162 PRINT AT 13,0; PAPER 5;" Modify Insert Delete Append "
1164 PRINT AT 15,0; PAPER 5;" Goto Quit Sort Print "
1166 PRINT AT 17,0; PAPER 5;" ^up/dwn arrows to view records"
1170 RETURN
1498 GO SUB 3200: REM EDITOR
1499 LET is=0: LET flctr=f
1502 LET flctr=flctr-1
1504 IF flctr=0 THEN PRINT AT l,c;" ": LET is=(is=0): LET flctr=f
1560 LET k=CODE INKEY$
1562 IF k=0 THEN LET k=1
1564 IF z(k)=1500 THEN GO TO 1500
1566 IF is=0 THEN GO TO z(k)
1568 PRINT AT l,c;" ": LET is=0
1570 GO TO z(k)
1595 BEEP .005,10
1596 LET cnt=rd: IF INKEY$<>"" AND rs=1 THEN LET flctr=0: GO TO 1504
1597 LET cnt=cnt-1: LET rs=1: IF cnt=0 THEN GO TO 1499
1598 IF INKEY$<>"" THEN GO TO 1597
1599 LET rs=0: LET flctr=0: GO TO 1504
1600 REM Print Char
1620 PRINT AT l,c; OVER 0;CHR$ k; OVER 1
1622 LET r$(((l-5)*32)+c+1)=CHR$ k
1632 GO SUB 1685
1634 GO TO 1595
1650 REM Cmove
1660 GO SUB ((k-8)*5)+1680
1670 GO TO 1595
1680 REM Left
1682 LET c=c-1
1683 IF c=lm-1 THEN LET c=lm
1684 RETURN
1685 REM right
1686 LET c=c+1
1687 IF c=rm+1 THEN LET c=rm
1689 RETURN
1690 REM Down
1691 LET l=l+1: IF l=bm+1 THEN LET l=bm
1692 RETURN
1695 REM Up
1696 LET l=l-1: IF l=tm-1 THEN LET l=tm
1697 RETURN
1700 REM Next Ln
1704 LET l=l+1: IF l>bm THEN LET l=bm
1705 LET c=lm
1706 GO TO 1595
1740 REM Delete
1742 GO SUB 1680
1744 PRINT AT l,c; OVER 0;" "; OVER 1
1746 LET r$(((l-5)*32)+c+1)=" "
1748 GO TO 1595
2000 REM MAIN LOOP
2013 GO SUB 1100: REM drw window
2020 LET PHYrec=CODE I$(CURrec,1)*256+CODE I$(CURrec,2)
2026 INPUT *#1;r$;AT PHYrec
2030 REM DIS_REC
2050 GO SUB 1000: REM prnt rec
2060 REM RD_KEY
2070 LET k=CODE INKEY$
2072 IF k=71 THEN GO TO 2800
2074 IF k=81 THEN GO TO 2700
2076 IF k=83 THEN GO TO 3100
2078 IF k=80 THEN GO TO 3300
2080 IF k=10 THEN GO TO 2200
2090 IF k=11 THEN GO TO 2300
2100 IF k=68 THEN GO TO 2600
2110 IF k=77 THEN GO TO 2400
2120 IF k=73 THEN GO TO 2900
2130 IF k=65 THEN GO TO 2500
2140 GO TO 2060
2200 REM LST_REC
2210 IF CURrec=1 THEN BEEP .05,20: PRINT AT 21,0; PAPER 5; FLASH 1;" bottom of file ": PAUSE 100: PRINT AT 21,0; PAPER 5;" ": GO TO 2060
2220 LET CURrec=CURrec-1
2222 BEEP .01,0
2240 GO TO 2020
2300 REM NXT_REC
2310 IF CURrec=LSTrec THEN BEEP .05,20: PRINT AT 21,0; PAPER 5; FLASH 1;" end of file ": PAUSE 100: PRINT AT 21,0; PAPER 5;" ": GO TO 2060
2320 LET CURrec=CURrec+1
2322 BEEP .01,0
2350 GO TO 2020
2400 REM MOD_REC
2410 LET z(226)=2432
2420 GO SUB 10: REM int edt parm
2430 GO TO 1498: REM editor
2432 OVER 0: GO SUB 1161
2440 LET PHYrec=CODE I$(CURrec,1)*256+CODE I$(CURrec,2)
2450 PRINT *#1;r$;AT PHYrec
2452 GO SUB 3029: REM parse name
2454 LET s$(CURrec)=r$(n+1 TO n+10)+r$(97 TO 102)
2460 GO TO 2025
2500 REM ADD_REC
2510 IF CURrec=1000 THEN BEEP .05,20: PRINT AT 21,0; PAPER 5; FLASH 1;" file full ": PAUSE 100: PRINT AT 21,0; PAPER 5;" ": GO TO 2060
2518 LET CURrec=LSTrec+1
2520 LET LSTrec=LSTrec+1
2521 IF CURrec=2 AND sflg=0 THEN LET CURrec=1: LET sflg=1: LET LSTrec=LSTrec-1
2526 LET z(226)=2432
2530 LET r$=" "
2550 GO SUB 1000: REM prnt rec
2560 GO SUB 10: REM int edit prm
2570 GO TO 1498: REM ret via 2432
2600 REM DEL_REC
2601 INPUT BRIGHT 1;"Delete record?(y or n)";k$
2602 IF k$<>"y" THEN GO TO 2060
2604 IF LSTrec=1 THEN GO TO 2020
2610 LET t$=I$(CURrec)
2621 IF CURrec=1 THEN BEEP .05,20: PRINT AT 21,0; PAPER 5; FLASH 1;" last record ": PAUSE 100: PRINT AT 21,0; PAPER 5;" ": GO TO 2060
2630 IF CURrec=LSTrec THEN LET CURrec=CURrec-1: GO TO 2670
2640 FOR n=CURrec+1 TO LSTrec
2650 LET I$(n-1)=I$(n)
2652 LET s$(n-1)=s$(n)
2660 NEXT n
2670 LET I$(LSTrec)=t$
2680 LET LSTrec=LSTrec-1
2690 GO TO 2020
2700 REM Quit
2701 INPUT BRIGHT 1;"Quit program?(y or n)";k$
2702 IF k$<>"y" THEN GO TO 2060
2710 PRINT AT 21,0; FLASH 1; PAPER 5;"data disk in drive'A' then ENTER": PAUSE 4e4: PRINT AT 21,0; PAPER 5;" "
2711 CLOSE #*1: LET erret=2700: POKE 23729,255
2712 SAVE *n$+".ix1" DATA s$()n
2713 IF PEEK 23728<>0 THEN GO TO 4000
2714 SAVE *n$+".ix2" DATA i$()n
2715 IF PEEK 23728<>0 THEN GO TO 4000
2716 LET p(1)=CURrec: LET p(2)=LSTrec: LET p(3)=PHYrec
2717 SAVE *n$+".ptr" DATA p()n
2718 IF PEEK 23728<>0 THEN GO TO 4000
2720 CLS : NEW
2800 REM Goto record
2810 INPUT BRIGHT 1;"Record to goto?";k$
2811 IF k$="" THEN GO TO 2020
2812 ON ERR GO TO 2850
2814 LET k=INT (VAL k$)
2816 ON ERR RESET
2821 IF k>LSTrec OR k<1 THEN BEEP .05,20: PRINT AT 21,0; PAPER 5; FLASH 1;" record not in file ": PAUSE 100: PRINT AT 21,0; PAPER 5;" ": GO TO 2810
2830 LET CURrec=k
2835 PRINT AT 21,0; PAPER 5;" "
2840 GO TO 2020
2850 ON ERR RESET : PRINT AT 21,0; FLASH 1; PAPER 5;" search in progress "
2851 IF tog=1 THEN GO TO 2860
2852 FOR k=1 TO LSTrec
2854 IF s$(k,1 TO LEN k$)=k$ THEN GO TO 2830
2856 NEXT k
2857 LET k=CURrec
2858 BEEP .05,20: PRINT AT 21,0; PAPER 5; FLASH 1;" record not found ": PAUSE 120: GO TO 2830
2859 REM mc version
2860 POKE 63029,INT ((LSTrec+1)/256)
2861 LET buf=63035: POKE 63028,(LSTrec+1)-((INT ((LSTrec+1)/256))*256)
2862 FOR n=1 TO LEN k$
2863 POKE buf,CODE k$(n)
2864 LET buf=buf+1
2865 NEXT n
2866 POKE buf,254
2867 RANDOMIZE USR 63001: REM srch
2870 LET n=PEEK 63033+256*PEEK 63034
2880 IF n=0 THEN GO TO 2857
2882 LET k=n: GO TO 2830
2900 REM INS_REC
2902 IF LSTrec=1 THEN GO TO 2025
2910 LET t$=I$(LSTrec+1)
2920 FOR n=LSTrec TO CURrec STEP -1
2930 LET I$(n+1)=I$(n)
2932 LET s$(n+1)=s$(n)
2940 NEXT n
2950 LET I$(CURrec)=t$
2952 LET s$(CURrec)=" "
2970 GO TO 2520
3000 REM Pull all LST names
3002 OPEN #*1;"mlist.dat";r;128
3010 FOR m=1 TO LSTrec
3021 LET PHYrec=CODE I$(m,1)*256+CODE I$(m,2)
3022 INPUT *#1;r$;AT PHYrec
3024 GO SUB 3030
3026 GO TO 3080
3029 REM parse name
3030 LET x=0
3031 FOR n=32 TO 1 STEP -1
3040 IF r$(n)<>" " THEN LET x=1
3050 IF r$(n)=" " AND x=1 THEN RETURN
3060 NEXT n
3062 RETURN
3063 REM end of rtn
3080 LET s$(m)=r$(n+1 TO )
3090 NEXT m
3092 RETURN
3100 REM Sort alpha/num
3102 INPUT BRIGHT 1;"Sort name or zip?(n or z)";k$
3103 IF k$="" THEN GO TO 2020
3104 IF k$<>"n" AND k$<>"z" THEN GO TO 3102
3106 PRINT AT 21,0; FLASH 1; PAPER 5;" sort in progress "
3108 IF tog=1 THEN GO TO 3167
3110 FOR m=1 TO LSTrec-1
3120 FOR n=m+1 TO LSTrec
3130 IF k$="n" AND s$(m, TO 10)<=s$(n, TO 10) THEN GO TO 3163
3132 IF k$="z" AND s$(m,11 TO )<=s$(n,11 TO ) THEN GO TO 3163
3140 LET t$=I$(n)+s$(n)
3150 LET s$(n)=s$(m)
3152 LET I$(n)=I$(m)
3160 LET s$(m)=t$(3 TO )
3162 LET I$(m)=t$(1 TO 2)
3163 NEXT n
3164 NEXT m
3165 PRINT AT 21,0; PAPER 5;" "
3166 GO TO 2020
3167 REM mc version
3168 IF k$="n" THEN POKE 63030,0
3169 IF k$="z" THEN POKE 63030,1
3170 POKE 63029,INT (LSTrec/256)
3171 POKE 63028,LSTrec-((INT (LSTrec/256))*256)
3174 RANDOMIZE USR 63004: REM sort
3176 GO TO 3165
3200 REM edit contrl keys
3201 OVER 0
3210 PRINT AT 11,0; PAPER 5;"______ Editor Control Keys ____"
3220 PRINT AT 13,0; PAPER 5;"STOP=exit editor ENTER=next line"
3230 PRINT AT 15,0; PAPER 5;"CS/0=delete char ^=arrows move"
3240 PRINT AT 17,0; PAPER 5;" "
3245 OVER 1
3250 RETURN
3299 REM Print function
3300 GO TO 3600
3301 LET str=1: LET fin=LSTrec
3302 INPUT BRIGHT 1;"labels or summary?(l or s)";k$: IF k$="" THEN GO TO 3300
3303 IF k$<>"l" AND k$<>"s" THEN GO TO 3302
3304 IF k$="l" THEN GO SUB 3500
3305 IF k$="s" THEN GO SUB 3402
3306 IF k$="" THEN GO TO 2060
3309 ON ERR GO TO 3310
3310 INPUT BRIGHT 1;"Print from Rec#?(1)";k$
3320 IF k$="" THEN GO TO 3340
3330 LET str=VAL k$
3340 ON ERR GO TO 3342
3342 INPUT BRIGHT 1;"Print to Rec#?(LSTrec)";k$
3350 IF k$="" THEN GO TO 3370
3360 LET fin=VAL k$: IF fin>1000 THEN LET fin=1000
3370 ON ERR RESET
3372 PRINT AT 21,0; INVERSE 1; PAPER 5;" press ENTER to abort printing "
3380 FOR n=str TO fin
3385 LET PHYrec=CODE I$(n,1)*256+CODE I$(n,2)
3390 INPUT *#1;r$;AT PHYrec
3391 LET CURrec=n: GO SUB 1000
3392 LPRINT t$;r$(1 TO 32);
3393 LPRINT t$;r$(33 TO 64);
3394 LPRINT t$;r$(65 TO 96);
3396 LPRINT t$;r$(97 TO end);: IF pf=1 THEN LPRINT
3397 IF pf=0 THEN FOR m=1 TO space: LPRINT : NEXT m
3398 PAUSE 60: IF INKEY$<>"" THEN GO TO 3400
3399 NEXT N
3400 PRINT AT 21,0; PAPER 5;" ": IF INKEY$<>"" THEN GO TO 3400
3401 GO TO 2060
3402 REM print sumarry
3410 POKE 63000,128: RANDOMIZE USR 63010
3420 POKE 23728,1: LPRINT CHR$ 15;: POKE 23728,0
3425 LET pf=1: LET end=128
3426 LET t$=CHR$ 17+CHR$ 5
3430 RETURN
3432 ON ERR GO TO 3432: ON ERR RESET : RETURN
3500 REM print labels
3510 POKE 63000,80: RANDOMIZE USR 63010
3520 POKE 23728,1: LPRINT CHR$ 27;CHR$ 64;: POKE 23728,0
3525 LET pf=0: LET end=103
3526 LET t$=CHR$ 13
3527 FOR n=1 TO lftmrg
3528 LET t$=t$+" "
3529 NEXT n
3530 RETURN
3600 REM IF customize
3610 INPUT BRIGHT 1;"Configure interface?(y or n)";k$
3612 IF k$="" THEN GO TO 2060
3620 IF k$<>"y" THEN GO TO 3301
3630 CLS
3640 GO SUB 300: REM IF menu
3642 GO SUB 500: REM do custom
3650 GO SUB 1100: REM drw wnd
3660 GO SUB 1000: REM prnt rec
3680 GO TO 3301
4000 REM disk error
4010 CLS
4020 LET k=PEEK 23728
4030 PRINT AT 10,8; FLASH 1;" disk error: ";k;" "
4040 PRINT AT 13,4;"press ENTER to try again"
4050 PAUSE 4e4
4055 CLS
4060 GO TO erret
9900 REM
9901 OPEN #*1;"mlist.dat";I;128
9910 INPUT *#1;a$
9920 PRINT a$;
9930 GO TO 9910
9995 LPRINT " Record Index ": FOR n=1 TO LSTrec+900
9996 LET PHYrec=CODE I$(n,1)*256+CODE I$(n,2)
9997 LPRINT n;" ";s$(n);"/";" PHYrec=";PHYrec: IF n=LSTrec THEN LPRINT "-------------------------------"
9998 NEXT n
9999 LET m=PEEK 63002+256*PEEK 63003: FOR n=m TO 64000: PRINT n;" ";CHR$ PEEK n: NEXT nNote: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
