--- title: "Zed Text" id: 57443 type: "computer_media" slug: "zed-text" url: "http://localhost/computer_media/zed-text/" markdown_url: "http://localhost/computer_media/zed-text.md" published_at: "2024-10-05T13:49:08+00:00" modified_at: "2026-04-03T07:58:29+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/244_ZT.png" excerpt: "A complete text editor hidden inside a single-string variable — with cursor movement, justification, paging, and tape save, all driven by a letter-based macro interpreter." 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/" genre: - name: "Word Processor" slug: "word-processor" taxonomy: "genre" url: "http://localhost/type/word-processor/" media_contents: - id: 56736 title: "Timex Sinclair Public Domain Library Tape 1005" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1005/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/10/244_ZT.png" media_type_tags: "Word Processor" --- # Zed Text 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 `N` repetitions of `W$` at position `CI`, at the start, or at the end of `T$` depending on whether `CI` is 0, equal to `LEN T$`, or in between. - **Cursor (C):** Moves `CI` by `N` characters; 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 `N` characters from position `CI` using four conditional substring cases; then calls the cursor routine with `N=-1` to 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` / `SLOW` mode 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 `N` times by resetting `D` and `I`. - Line 4030 contains `SAVE "1024\4"` — the inverse-digit escape `\4` in the filename acts as an auto-run marker for the ZX81 tape loader. ### Bugs and Anomalies - Line 80: `RAND F(12)=3000` should be `LET 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 of `T$` and later re-`DIM`s `H$`; however, `V$(I2)` at line 2860 treats the previously assigned string `V$` as an array, which will work on a ZX81 only if `V$` is exactly 32 characters — the logic is fragile for short documents. - The cursor blink loop (lines 2310–2340) uses `J=CODE T$(CI)+128` to compute the inverse character, relying on the ZX81 inverse-video bit (bit 7). If `CI` points beyond the end of `T$`, this will cause an error. ## 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 1000 REM %M%A%C%R%O% %D%E%C%O%D%E 1010 LET D=0 1020 LET I=1 1025 FAST 1030 INPUT M$ 1040 LET L=LEN M$ 1050 IF M$(L)<>"""" THEN LET M$=M$+"""" 1060 LET L=LEN M$ 1070 LET A=CODE M$(I) 1080 IF A>=38 AND A<=63 THEN GOTO 1110 1090 LET I=I+1 1100 GOTO 1070 1110 LET I0=I 1120 LET K$=M$(I) 1130 IF I0=D+1 THEN LET N=0 1140 IF I0<>D+1 THEN LET N=VAL M$(D+1 TO I0-1) 1150 LET I=I+1 1160 IF M$(I)<>"""" THEN GOTO 1150 1180 LET D=I 1190 LET W$=M$(I0+1 TO D-1) 1200 GOSUB F(CODE K$-37) 1210 IF I=L THEN GOTO 1000 1220 LET I=I+1 1230 GOTO 1070 2000 REM %A%P%P%E%N%D 2010 IF N=0 THEN LET N=1 2020 LET U$="" 2030 FOR Q=1 TO N 2040 LET U$=U$+W$ 2050 NEXT Q 2060 IF CI=0 THEN GOTO 2110 2070 IF CI=LEN T$ THEN GOTO 2130 2080 IF CI>0 AND CILEN T$ THEN LET CI=LEN T$ 2240 IF CI<=0 THEN GOTO 2360 2250 LET PN=INT ((CI-1)/640) 2255 IF PN<>CPN-1 THEN RETURN 2260 LET CP=CI-640*PN 2270 LET LN=INT ((CP-1)/32) 2280 LET CN=CP-32*LN-1 2290 LET J=CODE T$(CI)+128 2300 IF J>=256 THEN LET J=J-256 2310 FOR Y=1 TO 10 2320 PRINT AT LN,CN;T$(CI) 2330 PRINT AT LN,CN;CHR$ J 2340 NEXT Y 2350 RETURN 2360 LET CI=640*(CPN-1) 2370 RETURN 2380 LET CI=LEN T$ 2390 RETURN 2500 REM %D%E%L%E%T%E 2510 IF N=0 THEN LET N=1 2520 LET L0=LEN T$ 2530 IF CI<=1 AND N>=L0 THEN LET T$="" 2540 IF CI<=1 AND N1 AND CI+N-1>=L0 THEN LET T$=T$( TO CI-1) 2560 IF CI>1 AND CI+N-1LEN T$ THEN RETURN 2830 LET V$=T$(CI+31) 2835 IF CI+32>LEN T$ THEN RETURN 2840 IF T$(CI+32)=" " THEN LET T$=T$( TO CI+31)+T$(CI+33 TO ) 2850 LET I2=32 2860 IF V$(I2)=" " THEN GOTO 2920 2870 LET I2=I2-1 2880 IF I2=0 THEN GOTO 2900 2890 GOTO 2860 2900 LET CI=CI+32 2910 GOTO 2820 2920 DIM H$(32) 2930 LET H$=V$( TO I2) 2940 LET T$=T$( TO CI-1)+H$+T$(CI+I2 TO ) 2950 LET CI=CI+32 2960 GOTO 2820 3000 REM %K% %A%N%D% %L 3005 CLS 3010 IF W$="" THEN LET N=1 3015 IF W$<>"" THEN LET N=VAL W$ 3020 LIST N 3025 RETURN 3100 REM %M%E%M%O%R%Y 3110 CLS 3120 PRINT AT 5,0;"SPACE FOR TEXT IS "; 3130 PRINT PEEK 16386+256*PEEK 16387-PEEK 16412-256*PEEK 16413; 3140 PRINT " BYTES" 3150 RETURN 3300 REM %P%R%I%N%T 3310 CLS 3315 SLOW 3320 IF N=0 THEN LET N=CPN 3330 LET CPN=N 3340 LET H=640*N 3350 IF LEN T$