--- title: "Lettertype" id: 56695 type: "computer_media" slug: "lettertype" url: "http://localhost/computer_media/lettertype/" markdown_url: "http://localhost/computer_media/lettertype.md" published_at: "2024-09-22T23:31:42+00:00" modified_at: "2026-04-03T07:59:00+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/09/LT.png" excerpt: "A full mail-merge word processor squeezes form letters, address management, paragraph editing, and label printing into a single BASIC program — complete with hand-rolled word-wrap." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "M. Sieder" slug: "m-sieder" taxonomy: "post_tag" url: "http://localhost/tag/m-sieder/" - name: "TS 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" indiv: - name: "Max Sieder" slug: "max-sieder" taxonomy: "indiv" url: "http://localhost/indiv/max-sieder/" genre: - name: "Word Processor" slug: "word-processor" taxonomy: "genre" url: "http://localhost/type/word-processor/" media_type: "Cassette" programmers: - name: "Max Sieder" slug: "max-sieder" taxonomy: "indiv" url: "http://localhost/indiv/max-sieder/" download_url: "https://archive.org/download/timex-sinclair-software-archive/Lettertype%20%281983%29%28Seider%2C%20M.%20E.%29%28TS1000%29%28CA%29%28Cassette%29.zip" mediadate: "1983" producer_company: - id: 11060 title: "M. Sieder" type: "company" url: "http://localhost/company/m-sieder/" images: - url: "http://localhost/wp-content/uploads/2024/09/LT.png" - url: "http://localhost/wp-content/uploads/2024/09/Lettertype.jpg" related_products: - id: 56551 title: "Lettertype" type: "product" url: "http://localhost/product/lettertype/" media_type_tags: "Word Processor" --- 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: 1. **Lines 100–150** — Initialisation: DIM statements for all arrays, sentinel values set. 2. **Lines 160–243** — Address entry (new or additional). 3. **Lines 244–300** — Letter/paragraph input. 4. **Lines 800–1100** — Screen review of addresses and letter body. 5. **Lines 1200–1670** — Paragraph change/insert/delete editor. 6. **Lines 3002–3230** — LPRINT personalised letters to all recipients. 7. **Lines 4010–4055** — LPRINT address labels. 8. **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: 1. If `LEN A$` ≤ 32, print the whole string and loop back (lines 1020–1030). 2. Otherwise, start a backward search counter `C` from 0. 3. Check if position `33-C` is a space, or if position `32-C` is a punctuation character (`,``.``:``;``?` or `CHR$ 11`). If so, break there. 4. If `C` reaches 32 with no break found, force a break at position 32. 5. Print up to `32-C` characters, 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 `N` onwards down by one using a FOR loop, then decrements `O`. - **Insert** (option 4, lines 1580–1605): increments `O`, shifts paragraphs upward from `O` down to `N` in 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 using `A$=C$(N)(3 TO )` at line 1500/3500. ### Notable Bugs and Anomalies - Line 55 uses `IF I=NOT PI THEN GOTO 100` to detect menu choice 0 (CLEAR). `NOT PI` equals 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 2005` calls (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 prints `A$` and jumps back to re-print it forever. The loop is only exited when `LEN A$=0` causes a substring error or when the paragraph exhausts naturally — this is a latent bug if paragraphs are short. - The `LPRINT AT` usage (lines 3005–3014) is non-standard; on the ZX81/TS1000 `LPRINT AT` is 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)`, and `O(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. ## Source Code ``` 5 REM "LETTERTYPE" 16-203, 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 1000 FOR N=1 TO O 1001 PRINT 1002 PRINT "PARAGR. ";N 1003 IF C$(N)(1 TO 2)=" " THEN PRINT 1004 IF C$(N)(1 TO 2)=" " THEN GOTO 1500 1016 LET A$=C$(N) 1020 IF LEN A$>32 THEN GOTO 1035 1025 PRINT A$ 1030 GOTO 1016 1035 LET C=0 1040 IF C=32 THEN GOTO 1065 1045 LET B$=A$(32-C) 1050 IF A$(33-C)=" " OR B$="," OR B$="." OR B$=":" OR B$=";" OR B$="?" OR B$=CHR$ 11 THEN GOTO 1070 1055 LET C=C+1 1060 GOTO 1040 1065 LET C=0 1070 IF A$( TO 32-C)<=" " THEN GOTO 1087 1072 PRINT A$( TO 32-C) 1075 LET A$=A$(33-C TO ) 1080 IF A$(1)=" " THEN LET A$=A$(2 TO ) 1085 GOTO 1020 1090 NEXT N 1092 FAST 1093 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" 1094 INPUT I 1095 CLS 1096 IF I=0 THEN GOTO 1207 1097 IF I=1 THEN LET N=N-1 1098 IF I=1 THEN GOTO 250 1099 IF I=2 THEN GOTO 1105 1100 IF I>2 THEN GOTO 10 1102 STOP 1140 GOTO 10 1207 CLS 1210 PRINT AT 10,2;"ENTER NO. OF PARAGRAPH YOU WISH TO CHANGE" 1211 INPUT N 1212 CLS 1213 IF N>O THEN GOTO 10 1214 PRINT AT 6,0;"PARAGR. ";N;": ";C$(N) 1215 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." 1216 PRINT AT 19,7;"""4""INSERT PARAGR.";AT 20,7;"""5""CONT.TO TYPE LETTER";AT 21,7;"""6""FOR RETURN" 1217 INPUT I 1218 IF I=0 THEN LET N=N-1 1219 IF I=1 THEN LET N=N+1 1220 IF I<=1 THEN GOTO 1212 1222 IF I=2 THEN GOTO 1232 1223 IF I=3 THEN GOTO 1550 1224 IF I=4 THEN GOTO 1580 1225 IF I=5 THEN LET N=O 1226 IF I=5 THEN CLS 1227 IF I=5 THEN GOTO 250 1228 IF I>5 THEN GOTO 10 1232 PRINT AT 15,2;" ";AT 16,2;" ";AT 17,7;" ";AT 18,7;" " 1233 PRINT AT 19,2;" ";AT 20,7;" ";AT 21,7;" " 1234 PRINT AT 12,2;"TYPE PARAGRAPH ";N;AT 18,0;"% " 1235 INPUT C$(N) 1240 CLS 1250 GOTO 1212 1500 LET A$=C$(N)(3 TO ) 1510 GOTO 1020 1550 FOR N=N TO O 1555 LET C$(N)=C$(N+1) 1560 NEXT N 1565 LET O=O-1 1570 GOTO 1212 1580 LET O=O+1 1582 FOR N=O TO N STEP -1 1585 LET C$(N+1)=C$(N) 1590 NEXT N 1595 LET N=N+1 1600 CLS 1605 GOTO 1234 1670 GOTO 1212 2000 STOP 2227 PRINT AT 6,4;M;AT 8,4;F$(M) 2228 PRINT TAB 4;G$(M) 2229 PRINT TAB 4;E$(M) 2230 PRINT TAB 4;S$(M) 2231 PRINT TAB 4;J$(M) 2235 RETURN 3002 FOR M=1 TO L 3005 LPRINT AT 1,0;F$(M) 3010 LPRINT AT 2,0;G$(M) 3011 LPRINT AT 3,0;E$(M) 3012 LPRINT AT 4,0;S$(M) 3013 LPRINT 3014 LPRINT AT 6,22;D$ 3015 LPRINT 3016 IF H$(1 TO 3)=" " THEN GOTO 3018 3017 LPRINT "RE.: ";H$ 3018 LPRINT 3020 LPRINT J$(M) 3025 LPRINT 3100 FOR N=1 TO O 3102 IF C$(N)(1 TO 2)=" " THEN LPRINT 3104 IF C$(N)(1 TO 2)=" " THEN GOTO 3500 3116 LET A$=C$(N) 3120 IF LEN A$>32 THEN GOTO 3135 3125 LPRINT A$ 3130 GOTO 3116 3135 LET C=0 3140 IF C=32 THEN GOTO 3165 3145 LET B$=A$(32-C) 3150 IF A$(33-C)=" " OR B$="," OR B$="." OR B$=":" OR B$=";" OR B$="?" OR B$=CHR$ 11 THEN GOTO 3170 3155 LET C=C+1 3160 GOTO 3140 3165 LET C=0 3170 IF A$( TO 32-C)<=" " THEN GOTO 3187 3172 LPRINT A$( TO 32-C) 3175 LET A$=A$(33-C TO ) 3180 IF A$(1)=" " THEN LET A$=A$(2 TO ) 3185 GOTO 3120 3190 NEXT N 3192 LPRINT 3193 LPRINT 3194 LPRINT 3195 LPRINT 3196 LPRINT 3205 IF M=L THEN GOTO 10 3220 NEXT M 3230 GOTO 10 3500 LET A$=C$(N)(3 TO ) 3510 GOTO 3120 4010 CLS 4015 FOR M=1 TO L 4020 LPRINT F$(M) 4025 LPRINT G$(M) 4030 LPRINT E$(M) 4032 LPRINT S$(M) 4035 LPRINT 4040 LPRINT 4045 LPRINT 4050 IF M=L THEN GOTO 10 4055 NEXT M 5005 PRINT AT 10,0;"ENTER"" CONT "" TO SAVE ON TAPE" 5010 STOP 5015 SAVE "LETTERTYP%E" 5020 GOTO 10 ```