Merge 1

Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Business

With File 1, support for mail merging letters.

This program implements a mail-merge system that loads a letter template and a data file from tape, then merges field values from the data file into the letter before printing or displaying the result. The letter template uses special byte markers (131 for field delimiters, 143 for end-of-letter, 144 for a special character, 94 for field references) stored in memory starting at address 38000, while the merged file is assembled at address 32500. A 14-byte machine code routine is POKEd into address 60000 and executed via RANDOMIZE USR to perform fast block memory copies using the Z80’s LDIR instruction (opcode 237, 176), with source and destination addresses patched at runtime by reading the system variables for the RANDOMIZE seed. The program detects whether a printer is attached by reading port 251 (the keyboard/printer port) and supports paginated output with configurable lines-per-page.


Program Analysis

Program Structure

The program is organized into several functional blocks:

  • Lines 10–11: Initialization — POKEs a 14-byte machine code routine into address 60000 and jumps to the main menu at line 1000.
  • Lines 20–23: Subroutine for printing a slice of b$ and tracking line/page counters (x1, x4).
  • Line 30: Machine code launcher — patches source and destination addresses into the routine at 60000 and executes it via RANDOMIZE USR 60000.
  • Lines 70–72: Input validation subroutine — pads z$ to exactly 10 characters and prompts for confirmation.
  • Lines 1000–1130: Setup phase — loads the letter template and the data file from tape, measures their lengths, and detects printer presence.
  • Lines 1140–1240: Field-scanning loop — scans the letter at 38000 for field delimiter bytes (131), builds an array b() of field start addresses, then walks the template at 61000 to assemble the merged output at 32500.
  • Lines 2000–2050: Field substitution — when a caret (ASCII 94) is encountered in the template, reads a numeric field index, looks up the corresponding data in b(), and copies it into the output buffer.
  • Lines 3000–3450: Output/rendering engine — reads the assembled output from 32500 and prints it word-wrapped, handling special control bytes for page breaks (139) and form feeds (134), with optional printer copy via COPY.
  • Line 5000: End-of-list prompt — offers another copy pass.
  • Line 6000: SAVE line to preserve the program.

Machine Code Routine

Line 10 POKEs 14 bytes into address 60000. The DATA at line 11 decodes as follows:

OffsetByte(s)Z80 MnemonicNotes
001 00 00LD BC, 0BC = byte count (patched at runtime, offsets 1–2)
321 00 00LD HL, 0HL = source address (patched at offsets 4–5)
654LD D, H
75DLD E, LDE = HL (destination = source initially, then patched)
821 00 00LD HL, 0HL = destination address (patched at offsets 9–10)
11ED B0LDIRBlock copy BC bytes from HL to DE
13C9RET

The routine is a standard Z80 LDIR block copy. The patching mechanism in line 30 is indirect: RANDOMIZE v stores v in the system variable at addresses 23670–23671 (the last RANDOMIZE value), and the program reads those bytes back with PEEK q / PEEK r to write the low and high bytes of each 16-bit address into the machine code. This avoids any direct address arithmetic in BASIC.

Memory Map

AddressPurpose
32500Output buffer — merged, ready-to-print letter
37997Number of fields (byte) in data file header
37998–37999Length of loaded CODE block (low/high byte)
38000Letter template loaded from tape
60000–60013Machine code LDIR routine
61000Data file loaded from tape
23670–23671System variable: last RANDOMIZE seed (used for address patching)

Key BASIC Idioms and Techniques

  • RANDOMIZE for address passing: Using RANDOMIZE to store a value in a known system variable location and immediately PEEKing it back is an efficient way to split a 16-bit integer into low/high bytes without using INT or MOD arithmetic.
  • Word-wrap in lines 3060–3110: The renderer scans forward up to 32 characters, tracks the last space position, and breaks there, implementing a rudimentary word-wrap without string slicing beyond what the BASIC b$(TO y-x) in line 20 provides.
  • Field array b(): Rather than storing field content, b() stores the memory addresses of each field’s start within the data file at 38000, enabling direct PEEK-based copying in line 2030 without intermediate string allocation.
  • Printer detection (line 1040): Reads port 251 (IN 251) to test keyboard row bits — value 58 indicates the printer is present/selected, value 126 the normal keyboard state. The expression using (1 AND ...) and (0 AND ...) produces a boolean 0/1 result for p.
  • Pad-to-10 loop (lines 70–71): Trims silently if too long, then pads with spaces in a tight self-referencing GO TO loop until z$ is exactly 10 characters, matching the tape filename length requirement.

Special Control Bytes in Templates

  • 131 — Field delimiter in the data record; marks boundaries between fields.
  • 139 — Paragraph/section break marker in the letter template (triggers top-of-column spacing).
  • 143 — End-of-letter sentinel; written at the tail of the loaded CODE block (line 1030) and tested in line 1140 to detect end of records.
  • 144 — Special character in the output stream (tested at line 3030); causes the renderer to fall through to the word-wrap block rather than treating it as a line-break control.
  • 134 — Form-feed / page-eject marker (line 3300); triggers a full COPY and advances the record pointer.
  • 94 (caret ^) — Field substitution marker in the template; introduces a numeric field index to be looked up in b().

Bugs and Anomalies

  • Line 3110: The line ends with LET x=x-1: followed by a bare colon — this trailing colon is a no-op but syntactically harmless.
  • Line 3220: Entirely commented out with REM, yet the code before it (lines 3210–3219) still performs partial spacing logic; the intent of the REM’d double-line spacing is unclear and may represent an unfinished edit.
  • Line 1040: The second term (0 AND IN 251=126) always evaluates to 0 regardless of the port reading, so p can only ever be 0 or 1 based solely on the first term. The second term appears to be a leftover from a more complex detection attempt.
  • Line 70: If LEN z$ > 10, b$ is set to " " (a space), but the condition check at line 1010 tests b$<>"c", so an over-length name silently re-prompts — this is functional but the logic is indirect.

Content

Appears On

One of a series of library tapes. Programs on these tapes were renamed to a number series. This tape contained

Related Products

Related Articles

Related Content

Image Gallery

Merge 1

Source Code

   10 LET q=23670: LET r=23671: RESTORE : FOR j=60000 TO 60013: READ k: POKE j,k: NEXT j: GO TO 1000
   11 DATA 1,0,0,33,0,0,84,93,33,0,0,237,176,201
   20 PRINT b$( TO y-x): LET x=y+1
   21 IF x1=22 AND p=1 THEN COPY : CLS : LET x1=0
   22 IF x1=22 AND p=0 THEN INPUT c$: CLS : LET x1=0
   23 LET x1=x1+1: LET x4=x4+1: RETURN 
   30 RANDOMIZE v1: POKE 60001,PEEK q: POKE 60002,PEEK r: RANDOMIZE v2: POKE 60004,PEEK q: POKE 60005,PEEK r: RANDOMIZE v3: POKE 60009,PEEK q: POKE 60010,PEEK r: RANDOMIZE USR 60000: RETURN 
   70 IF LEN z$>10 THEN LET b$=" "
   71 IF LEN z$<>10 THEN LET z$=z$+" ": GO TO 71
   72 INPUT "Key C if OK ";b$: RETURN 
 1000 CLS : PRINT TAB 10;"MERGE 1"
 1010 PRINT ''"Type the letter name ": INPUT z$: PRINT ''z$: GO SUB 70: IF b$<>"c" OR z$="" THEN GO TO 1
 1020 PRINT ''"Start the tape": LOAD z$CODE : CLS 
 1030 LET c1=PEEK 37998+256*PEEK 37999: POKE 37999+c1,143: IF c1>4500 THEN PRINT "Letter too long": PAUSE 250: GO TO 1
 1040 LET p=(1 AND IN 251=58)+(0 AND IN 251=126)
 1060 LET x3=80: IF p=1 THEN PRINT "Printer ON. How many lines/page?": INPUT x3
 1070 LET v1=c1: LET v2=61000: LET v3=38000: GO SUB 30
 1100 PRINT "Type the file name ": INPUT z$: PRINT ''z$: GO SUB 70: IF b$<>"c" OR z$="" THEN GO TO 1070
 1110 PRINT ''"Start the tape": LOAD z$CODE : CLS 
 1120 LET c5=PEEK 37998+256*PEEK 37999: LET a=PEEK 37997
 1130 LET z=38000
 1140 CLS : PRINT FLASH 1;"Building the next letter": DIM b(a+1): IF PEEK z=143 THEN GO TO 5000
 1150 LET b(1)=z: FOR j=1 TO a
 1160 IF PEEK z=131 THEN LET b(j+1)=z+1: GO TO 1180
 1170 LET z=z+1: GO TO 1160
 1180 LET z=z+1: NEXT j
 1190 PRINT "Fields found"
 1200 LET w=61000: LET x=32500
 1210 IF PEEK w=94 THEN GO TO 2000
 1220 POKE x,PEEK w: LET x=x+1: LET w=w+1: IF w=61000+c1 THEN CLS : GO SUB 3000: LET w=61000: LET z=z+1: GO TO 1140
 1240 GO TO 1210
 2000 LET w=w+1: LET b$=""
 2010 IF PEEK w=94 OR PEEK w=44 THEN GO TO 2030
 2020 LET b$=b$+CHR$ PEEK w: LET w=w+1: GO TO 2010
 2030 LET f=VAL b$: FOR j=b(f) TO b(f+1)-2: POKE x,PEEK j: LET x=x+1: NEXT j
 2040 IF PEEK w=94 THEN LET w=w+1: LET x=x-1: GO TO 1210
 2050 GO TO 2000
 3000 LET x=32500
 3010 LET x1=1: LET x2=x1: LET x4=x1
 3020 LET y=31
 3030 LET f=PEEK x: IF f=144 THEN GO TO 3050
 3035 IF f=42 THEN GO TO 4000
 3040 IF f>127 THEN LET x=x+1: IF f<>131 THEN GO TO 3200
 3050 IF INKEY$="z" THEN GO TO 1
 3060 LET f=0: LET b$="": FOR j=x TO x+y: LET b$=b$+CHR$ PEEK j: IF (PEEK j>127 AND PEEK j<>144) AND f=0 THEN LET f=j
 3070 NEXT j: LET y=j: IF f>0 THEN LET y=f
 3080 LET f=0: FOR j=x TO y: IF PEEK j=32 THEN LET f=1: LET j=y
 3090 NEXT j: IF f=0 THEN GO TO 3110
 3100 IF PEEK y<>32 AND PEEK y<128 THEN LET y=y-1: GO TO 3100
 3110 GO SUB 20: IF PEEK y>127 THEN LET x=x-1:
 3120 GO SUB 3400: GO TO 3020
 3200 IF f<>139 THEN GO TO 3300
 3210 LET k=x3-x4: IF k<3 THEN FOR j=1 TO k: PRINT : GO SUB 21: GO SUB 3400: NEXT j: GO TO 3230
 3220 REM PRINT : GO SUB 21: GO SUB 3400: PRINT : GO SUB 21: GO SUB 3400
 3230 PRINT TAB 5;: LET y=26: GO TO 3030
 3300 IF f<>134 THEN GO TO 3430
 3310 LET k=x3-x4: PRINT : GO SUB 21: FOR j=1 TO k: PRINT : GO SUB 21: NEXT j: GO SUB 3410
 3320 GO TO 3020
 3400 IF x4<x3 OR p=0 THEN RETURN 
 3410 FOR j=1 TO 5: PRINT : GO SUB 21: IF j=3 THEN PRINT TAB 15;x2: LET x2=x2+1: GO SUB 21
 3420 NEXT j: LET x4=1: RETURN 
 3430 IF p=0 THEN FOR j=1 TO 5: PRINT : NEXT j
 3440 IF p=1 THEN COPY 
 3450 LET z=z+1: GO TO 1140
 5000 CLS : PRINT AT 9,9;"End of list"'''"Key C for another copy": INPUT b$: IF b$="c" THEN GO TO 1120
 5999 STOP 
 6000 SAVE "Merge 1" LINE 1

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

People

No people associated with this content.

Scroll to Top