--- title: "Record Formatter" id: 57272 type: "computer_media" slug: "record-formatter" url: "http://localhost/computer_media/record-formatter/" markdown_url: "http://localhost/computer_media/record-formatter.md" published_at: "2024-10-04T07:55:21+00:00" modified_at: "2026-04-03T07:58:38+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/160_RF.png" excerpt: "A full-featured 64-field database manager with custom machine-code tape I/O routines lets you define, enter, edit, display, and print structured records entirely from BASIC." 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: "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: "Tim Ward" slug: "tim-ward" taxonomy: "indiv" url: "http://localhost/indiv/tim-ward/" genre: - name: "Database" slug: "database" taxonomy: "genre" url: "http://localhost/type/database/" media_contents: - id: 56734 title: "Timex Sinclair Public Domain Library Tape 1003" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1003/" media_type: "Program" programmers: - name: "Tim Ward" slug: "tim-ward" taxonomy: "indiv" url: "http://localhost/indiv/tim-ward/" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/10/160_RF.png" media_type_tags: "Database" --- Record Formatter Ver. 1 is a ZX81/TS1000 database management program that allows users to define up to 64 custom fields, then enter, display, edit, and print records stored in a 1,459-character string array. Tape I/O is handled by two machine-code routines embedded in the line-10 REM statement: one at USR 16514 writes records to tape and one at USR 16549 reads them back, called via RAND USR. The program uses a packed record layout where each field’s start and end positions are tracked in parallel numeric arrays A() and B(), allowing substring slicing of the master string A$(1). A directory-printing routine compresses data by stripping redundant spaces before sending output to the printer via LPRINT. *** ## Program Analysis ### Program Structure The program is organized into clearly labeled sections using inverse-text REM statements as headers. Execution begins at line 10 (which contains machine code) and proceeds through initialization before entering the main menu loop. 1. Lines 10–20: Machine code in REM; program identity label 2. Lines 30–80: Array and variable initialization 3. Lines 100–180: Field definition loop (up to 64 fields) 4. Lines 190–300: Print record layout to printer 5. Lines 310–550: Main menu and dispatch 6. Lines 560–760: Enter new record subroutine 7. Lines 770–1140: Change a record subroutine 8. Lines 1150–1460: Display a record subroutine 9. Lines 1470–1680: Print a record subroutine 10. Lines 1690–2010: Print data tape directory subroutine 11. Lines 2020–2110: Copy data tape subroutine 12. Lines 2120–2200: Copy program subroutine 13. Lines 2210–2270: End program subroutine 14. Lines 2280–2400: Read record from tape subroutine 15. Lines 2410–2540: Write record to tape subroutine 16. Lines 2550–2590: Input checker subroutine (Y/N/M) 17. Lines 2600–2610: SAVE and restart ### Machine Code Routines Two machine-code routines are embedded in the REM statement at line 10. They are invoked via `RAND USR 16514` (write record) and `RAND USR 16549` (read record). The ZX81 system variables place the start of the BASIC program at address 16509 (407Fh); the REM data begins at offset 5 (after the line number, length, and REM token), so the routines land at 16514 and 16549 respectively. Disassembly of the REM bytes reveals two distinct routines: | Offset | Address | Bytes | Mnemonic | Notes | | --- | --- | --- | --- | --- | | 0 | 16514 | CD E7 02 | CALL 02E7h | ZX81 ROM: slow-speed display/sync entry | | 3 | 16517 | 06 0E | LD B,0Eh | Loop counter = 14 | | 5 | 16519 | 21 00 FF | LD HL,FF00h | Start of candidate address range | | 8 | 16522 | 2B | DEC HL | Step HL down | | 9 | 16523 | 7C | LD A,H | | | 10 | 16524 | B5 | OR L | Check HL=0 | | 11 | 16525 | 20 FB | JR NZ,-5 | Loop until zero | | 13 | 16527 | 10 F6 | DJNZ -10 | Outer timing loop | | 15 | 16529 | 2A 10 40 | LD HL,(4010h) | Load A$ descriptor pointer | | 18 | 16532 | 23 | INC HL | | | 19 | 16533 | 4E | LD C,(HL) | Low byte of length | | 20 | 16534 | 23 | INC HL | | | 21 | 16535 | 46 | LD B,(HL) | High byte of length | | 22 | 16536 | 23 | INC HL | | | 23 | 16537 | C5 | PUSH BC | Save byte count | | 24 | 16538 | 5E | LD E,(HL) | Data address low | | 25 | 16539 | CD 1F 03 | CALL 031Fh | ROM: output byte to tape | | 28 | 16542 | C1 | POP BC | | | 29 | 16543 | 0B | DEC BC | | | 30 | 16544 | 78 | LD A,B | | | 31 | 16545 | B1 | OR C | Check BC=0 | | 32 | 16546 | 20 F4 | JR NZ,-12 | Write loop | | 34 | 16548 | C9 | RET | End of write routine | | 35 | 16549 | CD E7 02 | CALL 02E7h | Read routine starts here | | 38 | 16552 | 2A 10 40 | LD HL,(4010h) | Load A$ descriptor | | … | … | 1E 08 DB FE D3 FF… | (tape read loop) | Reads bytes from EAR port, reconstructs A$ | The read routine polls the EAR port (`DB FE` = `IN A,(FEh)`) and outputs to `FFh` (`D3 FF` = `OUT (FFh),A`), using rotate and bit-test instructions to decode the Manchester-like tape signal. The length of data transferred is derived from the A$ array descriptor at address 4010h, so the routines always read/write exactly 1,459 bytes — the full record. ### Data Layout The master record is held in `A$(1)`, a single 1,459-character string (the first and only row of the 1×1459 DIM). Field positions are pre-computed during setup: - `A(X)` — start position of field X within A$(1) - `B(X)` — end position of field X within A$(1) - `C(X)` — declared length of field X - `B$(X)` — 32-character field title (DIM B$(64,32)) Field data is read and written using ZX81 substring slicing: `A$(1,A(X) TO B(X))`. A temporary single-row string `C$(1,C(X))` is re-DIMmed for each field during entry and editing to enforce the correct field width. ### Menu and Navigation The main menu at line 310 uses a single-keypress idiom: `INKEY$` is tested in a tight loop at line 450, then captured at line 460 and dispatched via a cascade of `IF … THEN GOSUB` / `GOTO` statements. The reusable input checker subroutine at line 2550 accepts only `Y`, `N`, or `M` (Menu return), providing consistent three-way navigation throughout. ### Key BASIC Idioms - **RAND USR** — used to call machine-code routines without storing a return value, discarding the USR result safely. - **Re-DIM inside loop** — `DIM C$(1,C(X))` at lines 630 and 1010 re-allocates C$ each iteration to match the current field’s length, ensuring INPUT fills exactly the right number of characters. - **VAL-style line targets** — line numbers in `GOTO`/`GOSUB` are written with leading zeros (e.g., `GOTO 0450`, `GOSUB 0560`), a common ZX81 style that has no runtime effect but aids readability. - **FAST/SLOW bracketing** — printer output blocks (lines 190–300 and 1610–1670) are wrapped in `FAST`/`SLOW` to disable the display interrupt and speed up LPRINT operations. ### Directory Space-Compression Routine The tape directory printer (lines 1930–1970) implements a simple run-length space compressor: it builds `D$` by copying non-space characters from `C$(1)` and inserting a single space only when the *next* character is a space, collapsing multiple spaces into one. This produces compact, human-readable directory entries from fixed-width fields. ### Notable Bugs and Anomalies - **Line 1000 unreachable check** — `IF I$="N" AND X=65 THEN GOTO 1130` follows `IF I$="N" THEN NEXT X` at line 990. If `I$="N"`, control jumps to NEXT X and never reaches line 1000; the X=65 boundary check is therefore dead code. The loop termination after all 64 fields are processed falls through to line 1130 naturally via the FOR/NEXT mechanism. - **Field count vs. tape block size** — The program defines up to 64 fields but the directory loop at line 1880 is hard-coded to `FOR X=1 TO 35`, implying the data tape is expected to hold exactly 35 records. There is no run-time check that the total declared field lengths do not exceed 1,459. - **Line 1430 double-read of INKEY$** — After the wait loop at line 1420 confirms a key is pressed, line 1430 calls `INKEY$` again. On a fast machine this could return an empty string if the key was released between the two polls, potentially failing to detect the M keypress. - **Line 2610** — `GOTO 300` targets line 300 (`SLOW`), not the menu at line 310, meaning after a SAVE the machine briefly executes SLOW before falling into the menu — harmless but slightly inconsistent with the direct `GOTO 0310` used elsewhere. ## Source Code ``` 10 REM CDE726E210FF2B7CB520FB10F62A1040234E234623C55ECD1F3C1B78B120F4C9CDE722A1040234E234623C51E8DBFED3FF1730F9E9461ADDBFE17CB797938F510F5204FE5630E43FCB161D20DEC1B78B1C818D3 20 REM %P%R%O%G%R%A%M% %S%E%T% %U%P% % % % % % % % % 30 DIM A$(1,1459) 40 DIM B$(64,32) 50 DIM A(64) 60 DIM B(64) 70 DIM C(64) 80 LET M=310 90 LET W=1 100 FOR X=1 TO 64 110 PRINT AT 12,0;"ENTER FIELD TITLE (32 POS)" 120 INPUT B$(X) 130 PRINT AT 12,0;"ENTER LENGTH OF FIELD " 140 INPUT C(X) 150 LET A(X)=W 160 LET B(X)=W+C(X)-1 170 LET W=W+C(X) 180 NEXT X 190 FAST 200 LPRINT "RECORD LAYOUT" 210 LPRINT 220 LPRINT "FIELD NAME" 230 LPRINT "ST POS END POS LENGTH FLD NBR." 240 LPRINT "--------------------------------" 250 FOR X=1 TO 64 260 LPRINT B$(X) 270 LPRINT A(X);TAB 8;B(X);TAB 17;C(X);TAB 25;X 280 LPRINT 290 NEXT X 300 SLOW 310 REM %M%E%N%U% % % % % % % % % % % % % % % % % % % 320 CLS 330 PRINT TAB 5;"RECORD FORMATTER VER.1" 340 PRINT TAB 3;"COPYRIGHT 1985 TIM L. WARD",,, 350 PRINT "DO YOU WISH TO..",,, 360 PRINT "1)...ENTER A NEW RECORD?",,, 370 PRINT "2)...CHANGE A RECORD?",,, 380 PRINT "3)...DISPLAY A RECORD?",,, 390 PRINT "4)...PRINT A RECORD?",,, 400 PRINT "5)...PRINT DATA TAPE DIRECTORY?",,, 410 PRINT "6)...COPY DATA TAPE?",,, 420 PRINT "7)...COPY PROGRAM?",,, 430 PRINT "8)...END PROGRAM?",,, 440 PRINT "ENTER NBR. OF SELECTION" 450 IF INKEY$="" THEN GOTO 0450 460 LET I$=INKEY$ 470 IF I$="1" THEN GOSUB 0560 480 IF I$="2" THEN GOSUB 0770 490 IF I$="3" THEN GOSUB 1150 500 IF I$="4" THEN GOSUB 1470 510 IF I$="5" THEN GOSUB 1690 520 IF I$="6" THEN GOSUB 2020 530 IF I$="7" THEN GOTO 2120 540 IF I$="8" THEN GOSUB 2210 550 GOTO 0310 560 REM %E%N%T%E%R% %A% %N%E%W% %R%E%C%O%R%D% % % % % 570 CLS 580 PRINT ,,,,,,"DO YOU WISH TO ADD A NEW RCD?" 590 PRINT TAB 11;"(Y, N, OR M)" 600 GOSUB 2550 610 IF I$<>"Y" THEN RETURN 620 FOR X=1 TO 64 630 DIM C$(1,C(X)) 640 CLS 650 PRINT AT 0,6;"%E%N%T%E%R% %A% %N%E%W% %R%E%C%O%R%D" 660 PRINT AT 2,0;"PLEASE ENTER INDIVIDUALS:" 670 PRINT AT 4,0;B$(X) 680 INPUT C$(1) 690 PRINT AT 6,0;C$(1) 700 PRINT "IS THIS CORRECT? (Y OR N)" 710 GOSUB 2550 720 IF I$<>"Y" THEN GOTO 0640 730 LET A$(1,A(X) TO B(X))=C$(1) 740 NEXT X 750 GOSUB 2410 760 RETURN 770 REM %C%H%A%N%G%E% %A% %R%E%C%O%R%D% % % % % % % % 780 CLS 790 PRINT AT 0,8;"%C%H%A%N%G%E% %A% %R%E%C%O%R%D" 800 PRINT AT 2,0;"DO YOU WISH TO CHANGE THIS RCD?" 810 PRINT AT 4,11;"(Y, N, OR M)",,, 820 FOR X=1 TO 5 830 PRINT B$(X) 840 PRINT A$(1,A(X) TO B(X)) 850 NEXT X 860 GOSUB 2550 870 IF I$="M" THEN RETURN 880 IF I$="Y" THEN GOTO 0920 890 GOSUB 2410 900 GOSUB 2280 910 GOTO 0770 920 FOR X=1 TO 64 930 CLS 940 PRINT AT 0,8;"%C%H%A%N%G%E% %A% %R%E%C%O%R%D" 950 PRINT AT 2,0;"CHANGE THIS FIELD? (Y OR N)" 960 PRINT AT 4,0;B$(X) 970 PRINT A$(1,A(X) TO B(X)) 980 GOSUB 2550 990 IF I$="N" THEN NEXT X 1000 IF I$="N" AND X=65 THEN GOTO 1130 1010 DIM C$(1,C(X)) 1020 CLS 1030 PRINT AT 0,8;"%C%H%A%N%G%E% %A% %R%E%C%O%R%D" 1040 PRINT AT 2,0;"PLEASE ENTER INDIVIDUALS:" 1050 PRINT AT 4,0;B$(X) 1060 INPUT C$(1) 1070 PRINT AT 6,0;C$(1) 1080 PRINT "IS THIS CORRECT? (Y OR N)" 1090 GOSUB 2550 1100 IF I$<>"Y" THEN GOTO 1020 1110 LET A$(1,A(X) TO B(X))=C$(1) 1120 NEXT X 1130 GOSUB 2410 1140 RETURN 1150 REM %D%I%S%P%L%A%Y% %A% %R%E%C%O%R%D% % % % % % % 1160 CLS 1170 PRINT AT 0,8;"%D%I%S%P%L%A%Y% %A% %R%E%C%O%R%D" 1180 PRINT AT 2,0;"DO YOU WISH TO DISPLAY THIS RCD?" 1190 PRINT AT 4,11;"(Y, N, OR M)" 1200 FOR X=1 TO 5 1210 PRINT B$(X) 1220 PRINT A$(1,A(X) TO B(X)) 1230 NEXT X 1240 GOSUB 2550 1250 IF I$="M" THEN RETURN 1260 IF I$="Y" THEN GOTO 1290 1270 GOSUB 2280 1280 GOTO 1150 1290 CLS 1300 FOR X=1 TO 64 1310 PRINT AT 0,8;"%D%I%S%P%L%A%Y% %A% %R%E%C%O%R%D" 1320 PRINT AT 2,0;"RECORD DISPLAYED IS FOR..." 1330 PRINT AT 4,0;"GEN/CODE NBR. ";A$(1, TO 5) 1340 PRINT AT 5,0;A$(1,6 TO 20) 1350 PRINT AT 6,0;A$(1,21 TO 35) 1360 PRINT AT 7,0;A$(1,36 TO 50) 1370 PRINT AT 8,0;A$(1,51 TO 65) 1380 PRINT AT 15,0;"%P%R%E%S%S% %E%N%T%E%R% %F%O%R% %N%E%X%T% %F%I%E%L%D% % % % % % " 1390 PRINT AT 16,0;"%P%R%E%S%S% %"%M%"% %T%O% %R%E%T%U%R%N% %T%O% % %M%E%N%U% % % % " 1400 PRINT AT 10,0;B$(X) 1410 PRINT AT 12,0;A$(1,A(X) TO B(X)) 1420 IF INKEY$="" THEN GOTO 1420 1430 IF INKEY$="M" THEN RETURN 1440 PRINT AT 12,0;" " 1450 NEXT X 1460 RETURN 1470 REM %P%R%I%N%T% %A% %R%E%C%O%R%D% % % % % % % % % 1480 CLS 1490 PRINT AT 0,8;"%P%R%I%N%T% %A% %R%E%C%O%R%D" 1500 PRINT AT 2,0;"DO YOU WISH TO PRINT THIS RCD?" 1510 PRINT AT 4,11;"(Y, N, OR M)",,, 1520 FOR X=1 TO 5 1530 PRINT B$(X) 1540 PRINT A$(1,A(X) TO B(X)) 1550 NEXT X 1560 GOSUB 2550 1570 IF I$="Y" THEN GOTO 1610 1580 IF I$="M" THEN RETURN 1590 GOSUB 2280 1600 GOTO 1470 1610 FAST 1620 FOR X=1 TO 64 1630 LPRINT B$(X) 1640 LPRINT A$(1,A(X) TO B(X)) 1650 LPRINT 1660 NEXT X 1670 SLOW 1680 RETURN 1690 REM %P%R%I%N%T% %D%/%T% %D%I%R%E%C%T%O%R%Y% % % % 1700 CLS 1710 PRINT AT 0,5;"%D%A%T%A% %T%A%P%E% %D%I%R%E%C%T%O%R%Y" 1720 PRINT AT 4,0;"IS THE PRINTER ON ? (Y, N, OR M)" 1730 GOSUB 2550 1740 IF I$="M" THEN RETURN 1750 IF I$="Y" THEN GOTO 1790 1760 PRINT AT 12,0;"PLEASE TURN THE PRINTER ON" 1770 PRINT AT 14,0;"PRESS ENTER WHEN PRINTER IS ON" 1780 INPUT I$ 1790 CLS 1800 PRINT AT 0,5;"%D%A%T%A% %T%A%P%E% %D%I%R%E%C%T%O%R%Y" 1810 PRINT AT 4,0;"PLACE OLD DATA TAPE IN TAPE DECK" 1820 PRINT AT 6,0;"PRESS ""PLAY"" ON TAPE DECK" 1830 PRINT AT 8,0;"PRESS ""ENTER"" ON COMPUTER" 1840 INPUT I$ 1850 LPRINT "% % % % % % % DATA TAPE DIRECTORY% % % % % % ",, 1860 LPRINT "GEN/" 1870 LPRINT "CODE FIRST/LAST NAME",,, 1880 FOR X=1 TO 35 1890 RAND USR 16549 1900 DIM C$(1,36) 1910 LET C$(1)=A$(1,1 TO 5)+" "+A$(1,6 TO 20)+A$(1,51 TO 65) 1920 LET D$="" 1930 FOR Y=1 TO 36 1940 IF C$(1,Y)=" " THEN GOTO 1970 1950 LET D$=D$+C$(1,Y) 1960 IF C$(1,Y+1)=" " THEN LET D$=D$+" " 1970 NEXT Y 1980 LPRINT D$ 1990 LPRINT 2000 NEXT X 2010 RETURN 2020 REM %C%O%P%Y% %D%A%T%A% %T%A%P%E% % % % % % % % % 2030 CLS 2040 PRINT ,,,,,,"HOW MANY RECORDS DO YOU WISH TO",,,"COPY? (1 TO 35)" 2050 INPUT Z 2060 IF Z>35 THEN GOTO 2020 2070 FOR X=1 TO Z 2080 GOSUB 2280 2090 GOSUB 2410 2100 NEXT X 2110 RETURN 2120 REM %C%O%P%Y% %P%R%O%G%R%A%M% % % % % % % % % % % 2130 CLS 2140 PRINT ,,,,,,"PLACE BLANK PROGRAM TAPE IN TAPE",,"DECK, PRESS ""PLAY/RECORD"" ON",,,"TAPE DECK, PRESS ""ENTER"" ON",,,"COMPUTER." 2150 INPUT I$ 2160 CLS 2170 SAVE "FORMATTE%R" 2180 PRINT ,,,,,,"PRESS ""STOP"" ON TAPE DECK",,,"PRESS ""ENTER"" ON COMPUTER." 2190 INPUT I$ 2200 GOTO 0310 2210 REM %E%N%D% %P%R%O%G%R%A%M% % % % % % % % % % % % 2220 CLS 2230 PRINT ,,,,,,"ARE YOU SURE YOU WANT TO QUIT?" 2240 PRINT TAB 11;"(Y, N, OR M)" 2250 GOSUB 2550 2260 IF I$="Y" THEN NEW 2270 RETURN 2280 REM %R%E%A%D% %R%E%C%O%R%D% %O%N% %T%A%P%E% % % % 2290 CLS 2300 PRINT AT 2,0;"PLACE OLD DATA TAPE IN TAPE DECK" 2310 PRINT AT 4,0;"PRESS ""PLAY"" ON TAPE DECK" 2320 PRINT AT 6,0;"PRESS ""ENTER"" ON COMPUTER" 2330 INPUT Z$ 2340 RAND USR 16549 2350 CLS 2360 SLOW 2370 PRINT AT 12,0;"PRESS ""STOP"" ON TAPE DECK" 2380 PRINT AT 14,0;"PRESS ""ENTER"" ON COMPUTER" 2390 INPUT Z$ 2400 RETURN 2410 REM %W%R%I%T%E% %R%E%C%O%R%D% %O%N% %T%A%P%E% % % 2420 CLS 2430 PRINT AT 2,0;"PLACE NEW DATA TAPE IN TAPE DECK" 2440 PRINT AT 4,0;"PRESS ""PLAY/RECORD"" ON TAPE" 2450 PRINT "DECK" 2460 PRINT AT 7,0;"PRESS ""ENTER"" ON COMPUTER" 2470 INPUT Z$ 2480 RAND USR 16514 2490 CLS 2500 SLOW 2510 PRINT AT 12,0;"PRESS ""STOP"" ON TAPE DECK" 2520 PRINT AT 14,0;"PRESS ""ENTER"" ON COMPUTER" 2530 INPUT Z$ 2540 RETURN 2550 REM %I%N%P%U%T% %C%H%E%C%K%E%R% %(%Y% %O%R% %N%)% 2560 IF INKEY$="" THEN GOTO 2550 2570 LET I$=INKEY$ 2580 IF I$<>"Y" AND I$<>"N" AND I$<>"M" THEN GOTO 2550 2590 RETURN 2600 SAVE "1016%0" 2610 GOTO 300 ```