This program implements a full-featured text editor for the ZX81/TS1000, storing document text in a single string variable T$ and operating through a macro-command interpreter. Commands are entered as letter-prefixed macro strings where the letter selects a GOSUB target via a dispatch table array F(26) indexed by character code, allowing one-letter command codes (A=Append, C=Cursor, D=Delete, J=Justify, M=Memory, P=Print, R=Reset, S=Save, T=Test, X=Execute, Z=Stop) to route to the appropriate subroutine. The editor supports multi-page documents (640 characters per page), cursor movement with blinking via inverse-video toggling, word-wrap justification, and repeat-execution of macros via the X command. Text is stored and manipulated entirely as substring operations on T$, with memory usage reported by PEEKing the ZX81 system variables for RAMTOP and the display file pointer.
Program Analysis
Program Structure
The program is organized into an initialization block, a central macro-decode loop, and a suite of command subroutines. Initialization (lines 10–235) populates the dispatch table, then jumps to the title-screen/menu renderer at line 3670 before entering the macro interpreter at line 1000.
| Line range | Purpose |
|---|---|
| 10–150 | Build dispatch table F(26) |
| 200–235 | Initialize editor state variables, jump to splash screen |
| 1000–1230 | Macro decode loop |
| 2000–2140 | A – Append text |
| 2200–2390 | C – Cursor movement and display |
| 2500–2590 | D – Delete |
| 2800–2960 | J – Justify (word-wrap) |
| 3000–3025 | K – LIST (mapped but labelled K & L in menu) |
| 3100–3150 | M – Memory test |
| 3300–3390 | P – Print page |
| 3500–3520 | R – Reset (RUN) |
| 3600–3665 | S – Save to tape |
| 3670–3710 | Splash screen / menu, then enter decode loop |
| 3800–3820 | T – Test (load sample text) |
| 3900–3970 | X – Multiple/repeat execution |
| 4000–4040 | Z – Stop |
Dispatch Table via F(26)
Lines 10–150 initialize the array F(26) with line numbers corresponding to each command subroutine. The macro decoder (line 1200) calls GOSUB F(CODE K$-37), where K$ is the command letter. Since CODE "A" on the ZX81 is 38, subtracting 37 gives index 1 for A, 3 for C, 4 for D, and so on up to 26 for Z. Any letter without an entry in F has a default value of 0, causing a harmless GOSUB 0 to a non-existent line — a known ZX81 no-op technique.
Note that line 80 reads RAND F(12)=3000 rather than LET F(12)=3000. This is a typo or corruption; RANDOMIZE with a conditional expression will silently evaluate as a numeric statement and set the random seed, so F(12) (the L command) is never actually assigned 3000 and remains 0.
Macro Decode Loop
The loop at lines 1000–1230 accepts a line of input in M$, then scans for characters whose CODE falls in the range 38–63 (the letters A–Z on the ZX81 character set). The letter found becomes the command key K$; the digits before it become the numeric repeat count N; the characters between the letter and the next quote become the string argument W$. After the GOSUB dispatch, the parser resumes scanning for the next command in the same input string, allowing multiple commands per input line.
Text Storage and Editing
The entire document is held in the string T$. The current insert/cursor position is tracked by the integer CI. Pages are 640 characters wide (20 lines × 32 columns). Substring slicing with T$(x TO y) handles all insertions, deletions, and page extractions.
- Append (A): Inserts
Nrepetitions ofW$at positionCI, at the start, or at the end ofT$depending on whetherCIis 0, equal toLEN T$, or in between. - Cursor (C): Moves
CIbyNcharacters; special arguments"0"and"1"jump to page start or end of text. Cursor blinking at lines 2310–2340 loops 10 times printing the normal then inverse character. - Delete (D): Removes
Ncharacters from positionCIusing four conditional substring cases; then calls the cursor routine withN=-1to refresh display. - Justify (J): Scans 32-character blocks looking for a space near position 32 to use as a word-wrap break, padding into a fixed-length
DIM H$(32)to normalize the line.
Memory Reporting
The M command (lines 3100–3150) reports free space for text by reading ZX81 system variables directly: PEEK 16386 + 256*PEEK 16387 gives RAMTOP, and PEEK 16412 + 256*PEEK 16413 gives the current stack/string area pointer, with the difference being available bytes.
Notable Techniques
FAST/SLOWmode switching around INPUT and PRINT operations to balance display stability with execution speed.GOSUB F(CODE K$-37)— a compact computed GOSUB that replaces a long IF/THEN chain for 26 possible commands.- The splash screen at lines 3680–3685 uses ZX81 block-graphic escape sequences to draw a decorative banner.
- Line 55 places
F(9)=3670, pointing the I command (index 9: letter I = CODE 46, 46−37=9) back to the splash/menu screen, implementing a “show menu” function. - The X (multiple execution) command at lines 3900–3970 re-enters the decode loop at line 1040, bypassing the INPUT, to replay the current macro
Ntimes by resettingDandI. - Line 4030 contains
SAVE "1024"— the inverse-digit escapein the filename acts as an auto-run marker for the ZX81 tape loader.
Bugs and Anomalies
- Line 80:
RAND F(12)=3000should beLET F(12)=3000. The L command (index 12) is therefore never registered, so the K/L listing command is inaccessible via the dispatch table despite having its subroutine at line 3000. - The Justify routine (lines 2820–2960) references variable
V$as both a 32-character slice ofT$and later re-DIMsH$; however,V$(I2)at line 2860 treats the previously assigned stringV$as an array, which will work on a ZX81 only ifV$is exactly 32 characters — the logic is fragile for short documents. - The cursor blink loop (lines 2310–2340) uses
J=CODE T$(CI)+128to compute the inverse character, relying on the ZX81 inverse-video bit (bit 7). IfCIpoints beyond the end ofT$, this will cause an error.
Content
Source Code
10 DIM F(26)
20 LET F(1)=2000
30 LET F(3)=2200
40 LET F(4)=2500
50 LET F(6)=2600
55 LET F(9)=3670
60 LET F(10)=2800
70 LET F(11)=3000
80 RAND F(12)=3000
90 LET F(13)=3100
100 LET F(16)=3300
110 LET F(18)=3500
120 LET F(19)=3600
130 LET F(20)=3800
140 LET F(24)=3900
150 LET F(26)=4000
200 LET T$=""
210 LET CI=0
220 LET CPN=1
230 LET XC=0
235 GOTO 3670
\n1000 REM %M%A%C%R%O% %D%E%C%O%D%E
\n1010 LET D=0
\n1020 LET I=1
\n1025 FAST
\n1030 INPUT M$
\n1040 LET L=LEN M$
\n1050 IF M$(L)<>"""" THEN LET M$=M$+""""
\n1060 LET L=LEN M$
\n1070 LET A=CODE M$(I)
\n1080 IF A>=38 AND A<=63 THEN GOTO 1110
\n1090 LET I=I+1
\n1100 GOTO 1070
\n1110 LET I0=I
\n1120 LET K$=M$(I)
\n1130 IF I0=D+1 THEN LET N=0
\n1140 IF I0<>D+1 THEN LET N=VAL M$(D+1 TO I0-1)
\n1150 LET I=I+1
\n1160 IF M$(I)<>"""" THEN GOTO 1150
\n1180 LET D=I
\n1190 LET W$=M$(I0+1 TO D-1)
\n1200 GOSUB F(CODE K$-37)
\n1210 IF I=L THEN GOTO 1000
\n1220 LET I=I+1
\n1230 GOTO 1070
\n2000 REM %A%P%P%E%N%D
\n2010 IF N=0 THEN LET N=1
\n2020 LET U$=""
\n2030 FOR Q=1 TO N
\n2040 LET U$=U$+W$
\n2050 NEXT Q
\n2060 IF CI=0 THEN GOTO 2110
\n2070 IF CI=LEN T$ THEN GOTO 2130
\n2080 IF CI>0 AND CI<LEN T$ THEN LET T$=T$( TO CI)+U$+T$(CI+1 TO )
\n2090 LET CI=CI+LEN U$
\n2100 RETURN
\n2110 LET T$=U$+T$
\n2120 GOTO 2090
\n2130 LET T$=T$+U$
\n2140 GOTO 2090
\n2200 REM %C%U%R%S%O%R
\n2210 IF W$="0" THEN GOTO 2360
\n2215 IF W$="1" THEN GOTO 2380
\n2220 LET CI=CI+N
\n2230 IF CI>LEN T$ THEN LET CI=LEN T$
\n2240 IF CI<=0 THEN GOTO 2360
\n2250 LET PN=INT ((CI-1)/640)
\n2255 IF PN<>CPN-1 THEN RETURN
\n2260 LET CP=CI-640*PN
\n2270 LET LN=INT ((CP-1)/32)
\n2280 LET CN=CP-32*LN-1
\n2290 LET J=CODE T$(CI)+128
\n2300 IF J>=256 THEN LET J=J-256
\n2310 FOR Y=1 TO 10
\n2320 PRINT AT LN,CN;T$(CI)
\n2330 PRINT AT LN,CN;CHR$ J
\n2340 NEXT Y
\n2350 RETURN
\n2360 LET CI=640*(CPN-1)
\n2370 RETURN
\n2380 LET CI=LEN T$
\n2390 RETURN
\n2500 REM %D%E%L%E%T%E
\n2510 IF N=0 THEN LET N=1
\n2520 LET L0=LEN T$
\n2530 IF CI<=1 AND N>=L0 THEN LET T$=""
\n2540 IF CI<=1 AND N<L0 THEN LET T$=T$(N+1 TO )
\n2550 IF CI>1 AND CI+N-1>=L0 THEN LET T$=T$( TO CI-1)
\n2560 IF CI>1 AND CI+N-1<L0 THEN LET T$=T$( TO CI-1)+T$(CI+N TO )
\n2570 LET N=-1
\n2580 GOSUB 2200
\n2590 RETURN
\n2800 REM %J%U%S%T%I%F%Y
\n2810 LET CI=1
\n2820 IF CI+31>LEN T$ THEN RETURN
\n2830 LET V$=T$(CI+31)
\n2835 IF CI+32>LEN T$ THEN RETURN
\n2840 IF T$(CI+32)=" " THEN LET T$=T$( TO CI+31)+T$(CI+33 TO )
\n2850 LET I2=32
\n2860 IF V$(I2)=" " THEN GOTO 2920
\n2870 LET I2=I2-1
\n2880 IF I2=0 THEN GOTO 2900
\n2890 GOTO 2860
\n2900 LET CI=CI+32
\n2910 GOTO 2820
\n2920 DIM H$(32)
\n2930 LET H$=V$( TO I2)
\n2940 LET T$=T$( TO CI-1)+H$+T$(CI+I2 TO )
\n2950 LET CI=CI+32
\n2960 GOTO 2820
\n3000 REM %K% %A%N%D% %L
\n3005 CLS
\n3010 IF W$="" THEN LET N=1
\n3015 IF W$<>"" THEN LET N=VAL W$
\n3020 LIST N
\n3025 RETURN
\n3100 REM %M%E%M%O%R%Y
\n3110 CLS
\n3120 PRINT AT 5,0;"SPACE FOR TEXT IS ";
\n3130 PRINT PEEK 16386+256*PEEK 16387-PEEK 16412-256*PEEK 16413;
\n3140 PRINT " BYTES"
\n3150 RETURN
\n3300 REM %P%R%I%N%T
\n3310 CLS
\n3315 SLOW
\n3320 IF N=0 THEN LET N=CPN
\n3330 LET CPN=N
\n3340 LET H=640*N
\n3350 IF LEN T$<H THEN LET H=LEN T$
\n3360 PRINT T$(640*N-639 TO H)
\n3370 PRINT AT 21,0;"PAGE ";CPN
\n3375 LET N=0
\n3380 GOSUB 2200
\n3390 RETURN
\n3500 REM %R%E%S%E%T
\n3510 RUN
\n3520 RETURN
\n3600 REM %S%A%V%E
\n3610 CLS
\n3620 PRINT "%S%A%V%E"
\n3630 PRINT
\n3635 PRINT "INPUT PROGRAM NAME, DEFAULT NAME IS ""%T"""
\n3640 PRINT
\n3645 PRINT "START TAPE PLAYER"
\n3650 INPUT V$
\n3655 IF V$="" THEN LET V$="T"
\n3660 SAVE V$
\n3665 GOTO 10
\n3670 CLS
\n3680 PRINT "\##\##\##\## \ .\''\''\. \: \: \.'\''\'. \'. \.' \ : "
\n3681 PRINT " \,,\## \:.\..\..\.: \.'\''\'.\: \''\:'\''\''\ :\..\..\..\: \'.\.' \ '\':\''\''\' "
\n3682 PRINT " \,,\##\~~ \: \ .\ : \: \: \ .\ : \. \.'\'. \ : \. "
\n3683 PRINT "\,,\##\~~ \ '\..\..\' \'.\..\.'\: \ '\..\' \'.\..\.' \.' \'. \'.\.'"
\n3684 PRINT "\##\~~"
\n3685 PRINT "\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##\##"
\n3686 PRINT
\n3687 PRINT
\n3690 PRINT "A APPEND C CURSOR"
\n3691 PRINT
\n3692 PRINT "D DELETE I INFO (MENU)"
\n3693 PRINT
\n3694 PRINT "J JUSTIFY M MEMORY TEST"
\n3695 PRINT
\n3696 PRINT "P PRINT R RESET"
\n3697 PRINT
\n3698 PRINT "S SAVE T TEST"
\n3699 PRINT
\n3700 PRINT "X EXECUTE * N Z STOP"
\n3710 GOTO 1000
\n3800 REM %T%E%S%T
\n3810 LET T$="TO SEE A WORLD IN A GRAIN OF SAND AND A HEAVEN IN A WILD FLOWER, HOLD INFINITY IN THE PALM OF YOUR HAND AND ETERNITY IN AN HOUR."
\n3820 RETURN
\n3900 REM %M%U%L%T%I%P%L%E% %E%X%E%C%U%T%I%O%N% %X
\n3910 IF XC=N-1 OR N<=1 THEN GOTO 3960
\n3920 LET XC=XC+1
\n3930 LET D=0
\n3940 LET I=1
\n3950 GOTO 1040
\n3960 LET XC=0
\n3970 RETURN
\n4000 REM %S%T%O%P
\n4010 STOP
\n4020 CLEAR
\n4030 SAVE "1024%4"
\n4040 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
