LETTERTYPE is a mail-merge word processor that stores up to 10 recipient addresses and up to 40 paragraphs of letter body text, then prints personalised form letters and address labels via LPRINT. The program uses a menu-driven interface with a single numeric variable I routing control to distinct sections via chained IF/GOTO statements. A word-wrap routine at lines 1035–1085 (and its LPRINT mirror at 3135–3185) manually breaks lines at 32 characters, searching backwards for spaces or punctuation characters including CHR$ 11. Address data is held across six parallel string arrays (F$, G$, E$, S$, J$, and one date/subject string), and paragraphs support a two-space prefix convention to insert blank lines.
Program Analysis
Program Structure
The program is divided into clearly separated functional blocks, each reached by a numeric menu selection stored in I. After the main menu (lines 10–95), control jumps to one of seven subsystems:
- Lines 100–150 — Initialisation: DIM statements for all arrays, sentinel values set.
- Lines 160–243 — Address entry (new or additional).
- Lines 244–300 — Letter/paragraph input.
- Lines 800–1100 — Screen review of addresses and letter body.
- Lines 1200–1670 — Paragraph change/insert/delete editor.
- Lines 3002–3230 — LPRINT personalised letters to all recipients.
- Lines 4010–4055 — LPRINT address labels.
- Lines 5005–5020 — SAVE to tape.
A GOSUB at line 2005 is used to display a formatted address block, but the subroutine body begins at line 2227 — the intervening lines simply hold a STOP sentinel at 2000 and the RETURN is at 2235. This is a common technique to place subroutine code away from the main flow without a separate module system.
Initialisation and Sentinel Values
The expression NOT PI evaluates to 0 on this platform (since PI is non-zero and NOT of any non-zero number is 0). It is used throughout as a readable zero-initialiser: LET L=NOT PI (line 138), LET M=NOT PI (line 139). The address counter M is incremented before use at line 207, so the effective first address is index 1. The paragraph counter N starts at 1 (line 140) and O tracks the last paragraph written (line 145).
Lines 17 and 19 (LET M=L, LET N=O) appear before the arrays are dimensioned and before L and O have meaningful values. These lines are effectively no-ops on the first run but help restore working pointers when the menu is re-entered after editing, since L and O persist across menu returns.
Data Structures
| Variable | Dimensions | Purpose |
|---|---|---|
F$(M) | 10 × 30 | Address line 1 |
G$(M) | 10 × 30 | Address line 2 |
E$(M) | 10 × 30 | Address line 3 |
S$(M) | 10 × 30 | Address line 4 |
J$(M) | 10 × 20 | Salutation per recipient |
C$(N) | 40 × 160 | Letter paragraphs |
H$ | 30 chars | Subject line |
D$ | 10 chars | Date string |
I$ | 15 chars | Declared but unused |
A$ | 160 chars | Word-wrap working buffer |
B$ | 40 chars | Single-character test in word-wrap |
Arrays N(40), M(10), L(1), and O(1) are dimensioned numeric arrays, but L, M, N, and O are also used as plain numeric variables throughout the program. This dual use of identifiers as both simple variables and array names is legal in Sinclair BASIC as long as they are accessed with and without subscripts respectively, though it is potentially confusing.
Word-Wrap Algorithm
The word-wrap routine (lines 1035–1085 for screen, mirrored at 3135–3185 for LPRINT) operates on a 32-character line width. It works as follows:
- If
LEN A$≤ 32, print the whole string and loop back (lines 1020–1030). - Otherwise, start a backward search counter
Cfrom 0. - Check if position
33-Cis a space, or if position32-Cis a punctuation character (,.:;?orCHR$ 11). If so, break there. - If
Creaches 32 with no break found, force a break at position 32. - Print up to
32-Ccharacters, strip the leading space from the remainder, and repeat.
The comparison at line 1070 (A$( TO 32-C) <= " ", 32 spaces) is a check for whether the slice is all spaces — if so, the paragraph is effectively blank and control jumps to line 1087 (which does not exist; however the NEXT N at line 1090 is reached instead, continuing the loop). CHR$ 11 is the ZX81 cursor/delete character code, included as a break point to handle any control characters inadvertently entered.
Paragraph Editor
The paragraph change section (lines 1200–1670) supports navigation, correction, deletion, and insertion:
- Delete (option 3, lines 1550–1570): shifts all paragraphs from
Nonwards down by one using a FOR loop, then decrementsO. - Insert (option 4, lines 1580–1605): increments
O, shifts paragraphs upward fromOdown toNin reverse order (STEP -1), then falls through to the input prompt at line 1234. - The blank-line paragraph convention uses a two-space prefix in
C$(N); lines 1003–1004 (and 3102–3104) detect this and emit a blank line, then skip the word-wrap routine usingA$=C$(N)(3 TO )at line 1500/3500.
Notable Bugs and Anomalies
- Line 55 uses
IF I=NOT PI THEN GOTO 100to detect menu choice 0 (CLEAR).NOT PIequals 0, so this correctly routes to the initialisation block. - Line 280 is referenced by line 269 (
IF I=1 THEN GOTO 280) but does not exist; execution falls through to line 282. This is harmless as lines 282–300 are the intended continuation. - Line 1087 is referenced by the word-wrap guard (line 1070) but does not exist; execution falls to line 1090 (
NEXT N), which is the correct behaviour for an all-whitespace paragraph slice. - Line 2005 is the target of
GOSUB 2005calls (lines 227, 805) but this line is not present in the listing; execution will continue from the next available line, which is 2227. This is the intended subroutine body — a deliberate use of a non-existent target line number. - Line 1030 (
GOTO 1016) creates an infinite loop for a paragraph that is exactly 32 characters or shorter: it printsA$and jumps back to re-print it forever. The loop is only exited whenLEN A$=0causes a substring error or when the paragraph exhausts naturally — this is a latent bug if paragraphs are short. - The
LPRINT ATusage (lines 3005–3014) is non-standard; on the ZX81/TS1000LPRINT ATis supported and advances the print head by emitting spaces, so this produces correctly positioned output on a compatible printer. - Arrays
I$,N(40),M(10),L(1), andO(1)are dimensioned but never used as arrays in the program body — they consume memory without purpose.
SAVE Routine
The SAVE section (lines 5005–5020) prints an instruction asking the user to type CONT, then executes STOP. When the user resumes with CONTINUE, line 5015 saves the program. This is a common technique to allow the user to start the tape recorder before the SAVE actually begins.
Content
Source Code
5 REM "LETTERTYPE" 16-203, <C> M.SIEDER,1983
10 CLS
17 LET M=L
19 LET N=O
25 PRINT AT 2,5;"L E T T E R T Y P E"
27 PRINT
30 PRINT AT 4,0;"ENTER LEAD NO. FOR OPERATION"
32 PRINT AT 6,2;"0) CLEAR"
33 PRINT AT 7,2;"1) INPUT OF ADDRESSES"
34 PRINT AT 8,2;"2) INPUT OF LETTER"
35 PRINT AT 9,2;"3) REVIEW OF LETTER"
39 PRINT AT 10,2;"4) CHANGE PARAGRAPH"
40 PRINT AT 11,2;"5) LPRINT LETTERS"
41 PRINT AT 12,2;"6) LPRINT LABELS"
42 PRINT AT 13,2;"7) SAVE ON TAPE"
45 INPUT I
50 CLS
55 IF I=NOT PI THEN GOTO 100
60 IF I=1 THEN GOTO 160
65 IF I=2 THEN GOTO 244
70 IF I=3 THEN GOTO 800
75 IF I=4 THEN GOTO 1200
80 IF I=5 THEN GOTO 3000
85 IF I=6 THEN GOTO 4000
90 IF I=7 THEN GOTO 5000
95 IF I>7 THEN GOTO 10
105 DIM A$(160)
110 DIM B$(40)
115 DIM C$(40,160)
120 DIM D$(10)
122 DIM E$(10,30)
124 DIM F$(10,30)
126 DIM G$(10,30)
128 DIM H$(30)
130 DIM I$(15)
132 DIM J$(10,20)
135 DIM N(40)
137 DIM M(10)
138 LET L=NOT PI
139 LET M=NOT PI
140 LET N=1
141 DIM L(1)
142 DIM O(1)
145 LET O=1
146 DIM S$(10,30)
150 GOTO 10
160 PRINT AT 8,0;"ENTER ""1"" FOR NEW ADDRESSES";AT 9,6;"""2"" FOR ADDITIONAL ADDR."
163 INPUT I
164 CLS
165 IF I=1 THEN GOTO 190
167 IF I=2 THEN GOTO 206
168 IF I>2 THEN GOTO 10
190 LET L=NOT PI
191 LET M=NOT PI
202 PRINT AT 10,2;"ENTER DATE OF LETTER"
204 INPUT D$
206 CLS
207 LET M=M+1
208 PRINT AT 7,0;"NO.:";M;AT 8,0;"TYPE AND ENTER 1ST LINE OF ADDR."
209 INPUT F$(M)
210 CLS
211 PRINT AT 7,0;"NO.:";M;AT 8,0;"TYPE AND ENTER 2ND LINE OF ADDR."
212 INPUT G$(M)
213 CLS
214 PRINT AT 7,0;"NO.:";M;AT 8,0;"TYPE AND ENTER 3RD LINE OF ADDR."
215 INPUT E$(M)
216 CLS
217 PRINT AT 7,0;"NO.:";M;AT 8,0;"TYPE AND ENTER 4TH LINE OF ADDR."
218 INPUT S$(M)
219 CLS
224 PRINT AT 10,0;"TYPE AND ENTER SALUTATION:";M
225 INPUT J$(M)
226 CLS
227 GOSUB 2005
232 PRINT AT 19,10;"ENTER""0""FOR ERROR";AT 20,15;"""1""FOR NEXT ADDR.";AT 21,15;"""2""FOR RETURN"
234 INPUT I
236 CLS
237 IF I=0 THEN LET M=M-1
238 IF I=0 THEN GOTO 206
239 LET L=M
240 IF L=10 THEN GOTO 500
242 IF I=1 THEN GOTO 206
243 IF I>1 THEN GOTO 10
244 PRINT AT 8,2;"TYPE AND ENTER SUBJECT"
245 INPUT H$
247 CLS
250 IF N=1 THEN GOTO 252
251 PRINT AT 2,0;"LAST PARAGRAPH";AT 3,0;C$(N-1)
252 PRINT AT 10,0;"TYPE AND ENTER PARAGRAPH NO.:";N
255 PRINT AT 18,0;"% "
260 INPUT C$(N)
262 PRINT AT 19,10;"ENTER""0""FOR ERROR"
263 PRINT AT 20,15;"""1""FOR CONTINUE"
264 PRINT AT 21,15;"""2""FOR RETURN"
265 INPUT I
266 IF I=0 THEN GOTO 247
267 IF I>=1 THEN LET N=N+1
268 IF I>=1 THEN LET O=N
269 IF I=1 THEN GOTO 280
270 IF I>1 THEN GOTO 10
282 LET O=N
285 IF N=41 THEN GOTO 500
300 GOTO 247
500 PRINT AT 10,5;"FULL LETTER PAD"
505 PAUSE 50
510 GOTO 10
800 FOR M=1 TO L
801 PRINT AT 1,0;"REVIEW OF ADDRESS NO.: ";M
802 PRINT "--------------------------------"
805 GOSUB 2005
825 PAUSE 200
830 CLS
835 NEXT M
840 CLS
913 PRINT AT 6,22;D$
916 IF H$(1 TO 3)=" " THEN GOTO 918
917 PRINT AT 6,0;"RE.: ";H$
918 PRINT
920 PRINT J$(1)
925 PRINT
\n1000 FOR N=1 TO O
\n1001 PRINT
\n1002 PRINT "PARAGR. ";N
\n1003 IF C$(N)(1 TO 2)=" " THEN PRINT
\n1004 IF C$(N)(1 TO 2)=" " THEN GOTO 1500
\n1016 LET A$=C$(N)
\n1020 IF LEN A$>32 THEN GOTO 1035
\n1025 PRINT A$
\n1030 GOTO 1016
\n1035 LET C=0
\n1040 IF C=32 THEN GOTO 1065
\n1045 LET B$=A$(32-C)
\n1050 IF A$(33-C)=" " OR B$="," OR B$="." OR B$=":" OR B$=";" OR B$="?" OR B$=CHR$ 11 THEN GOTO 1070
\n1055 LET C=C+1
\n1060 GOTO 1040
\n1065 LET C=0
\n1070 IF A$( TO 32-C)<=" " THEN GOTO 1087
\n1072 PRINT A$( TO 32-C)
\n1075 LET A$=A$(33-C TO )
\n1080 IF A$(1)=" " THEN LET A$=A$(2 TO )
\n1085 GOTO 1020
\n1090 NEXT N
\n1092 FAST
\n1093 PRINT AT 18,10;"ENTER""0""CHANGE PARAGR.";AT 19,15;"""1""TO CONT.TYPING";AT 20,15;"""2""TO CONT.REVIEW";AT 21,15;"""3""TO RETURN"
\n1094 INPUT I
\n1095 CLS
\n1096 IF I=0 THEN GOTO 1207
\n1097 IF I=1 THEN LET N=N-1
\n1098 IF I=1 THEN GOTO 250
\n1099 IF I=2 THEN GOTO 1105
\n1100 IF I>2 THEN GOTO 10
\n1102 STOP
\n1140 GOTO 10
\n1207 CLS
\n1210 PRINT AT 10,2;"ENTER NO. OF PARAGRAPH YOU WISH TO CHANGE"
\n1211 INPUT N
\n1212 CLS
\n1213 IF N>O THEN GOTO 10
\n1214 PRINT AT 6,0;"PARAGR. ";N;": ";C$(N)
\n1215 PRINT AT 15,2;"ENTER""0""FOR PREV. PARAGR.";AT 16,7;"""1""FOR NEXT PARAGR.";AT 17,7;"""2""FOR CORRECTION";AT 18,7;"""3""DELETE PARAGR."
\n1216 PRINT AT 19,7;"""4""INSERT PARAGR.";AT 20,7;"""5""CONT.TO TYPE LETTER";AT 21,7;"""6""FOR RETURN"
\n1217 INPUT I
\n1218 IF I=0 THEN LET N=N-1
\n1219 IF I=1 THEN LET N=N+1
\n1220 IF I<=1 THEN GOTO 1212
\n1222 IF I=2 THEN GOTO 1232
\n1223 IF I=3 THEN GOTO 1550
\n1224 IF I=4 THEN GOTO 1580
\n1225 IF I=5 THEN LET N=O
\n1226 IF I=5 THEN CLS
\n1227 IF I=5 THEN GOTO 250
\n1228 IF I>5 THEN GOTO 10
\n1232 PRINT AT 15,2;" ";AT 16,2;" ";AT 17,7;" ";AT 18,7;" "
\n1233 PRINT AT 19,2;" ";AT 20,7;" ";AT 21,7;" "
\n1234 PRINT AT 12,2;"TYPE PARAGRAPH ";N;AT 18,0;"% "
\n1235 INPUT C$(N)
\n1240 CLS
\n1250 GOTO 1212
\n1500 LET A$=C$(N)(3 TO )
\n1510 GOTO 1020
\n1550 FOR N=N TO O
\n1555 LET C$(N)=C$(N+1)
\n1560 NEXT N
\n1565 LET O=O-1
\n1570 GOTO 1212
\n1580 LET O=O+1
\n1582 FOR N=O TO N STEP -1
\n1585 LET C$(N+1)=C$(N)
\n1590 NEXT N
\n1595 LET N=N+1
\n1600 CLS
\n1605 GOTO 1234
\n1670 GOTO 1212
\n2000 STOP
\n2227 PRINT AT 6,4;M;AT 8,4;F$(M)
\n2228 PRINT TAB 4;G$(M)
\n2229 PRINT TAB 4;E$(M)
\n2230 PRINT TAB 4;S$(M)
\n2231 PRINT TAB 4;J$(M)
\n2235 RETURN
\n3002 FOR M=1 TO L
\n3005 LPRINT AT 1,0;F$(M)
\n3010 LPRINT AT 2,0;G$(M)
\n3011 LPRINT AT 3,0;E$(M)
\n3012 LPRINT AT 4,0;S$(M)
\n3013 LPRINT
\n3014 LPRINT AT 6,22;D$
\n3015 LPRINT
\n3016 IF H$(1 TO 3)=" " THEN GOTO 3018
\n3017 LPRINT "RE.: ";H$
\n3018 LPRINT
\n3020 LPRINT J$(M)
\n3025 LPRINT
\n3100 FOR N=1 TO O
\n3102 IF C$(N)(1 TO 2)=" " THEN LPRINT
\n3104 IF C$(N)(1 TO 2)=" " THEN GOTO 3500
\n3116 LET A$=C$(N)
\n3120 IF LEN A$>32 THEN GOTO 3135
\n3125 LPRINT A$
\n3130 GOTO 3116
\n3135 LET C=0
\n3140 IF C=32 THEN GOTO 3165
\n3145 LET B$=A$(32-C)
\n3150 IF A$(33-C)=" " OR B$="," OR B$="." OR B$=":" OR B$=";" OR B$="?" OR B$=CHR$ 11 THEN GOTO 3170
\n3155 LET C=C+1
\n3160 GOTO 3140
\n3165 LET C=0
\n3170 IF A$( TO 32-C)<=" " THEN GOTO 3187
\n3172 LPRINT A$( TO 32-C)
\n3175 LET A$=A$(33-C TO )
\n3180 IF A$(1)=" " THEN LET A$=A$(2 TO )
\n3185 GOTO 3120
\n3190 NEXT N
\n3192 LPRINT
\n3193 LPRINT
\n3194 LPRINT
\n3195 LPRINT
\n3196 LPRINT
\n3205 IF M=L THEN GOTO 10
\n3220 NEXT M
\n3230 GOTO 10
\n3500 LET A$=C$(N)(3 TO )
\n3510 GOTO 3120
\n4010 CLS
\n4015 FOR M=1 TO L
\n4020 LPRINT F$(M)
\n4025 LPRINT G$(M)
\n4030 LPRINT E$(M)
\n4032 LPRINT S$(M)
\n4035 LPRINT
\n4040 LPRINT
\n4045 LPRINT
\n4050 IF M=L THEN GOTO 10
\n4055 NEXT M
\n5005 PRINT AT 10,0;"ENTER"" CONT "" TO SAVE ON TAPE"
\n5010 STOP
\n5015 SAVE "LETTERTYP%E"
\n5020 GOTO 10
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

