Lettertype

Products: Lettertype
Developer(s): Max Sieder
Date: 1983
Type: Cassette
Platform(s): TS 1000

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

VariableDimensionsPurpose
F$(M)10 × 30Address line 1
G$(M)10 × 30Address line 2
E$(M)10 × 30Address line 3
S$(M)10 × 30Address line 4
J$(M)10 × 20Salutation per recipient
C$(N)40 × 160Letter paragraphs
H$30 charsSubject line
D$10 charsDate string
I$15 charsDeclared but unused
A$160 charsWord-wrap working buffer
B$40 charsSingle-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.

Content

Appears On

Related Products

Related Articles

Related Content

Image Gallery

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 
 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

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

Scroll to Top